Skip to content

Commit 79033c2

Browse files
committed
internal/mod/modimports: new package
This encapsulates the functionality for walking all CUE files in a module, which will be used when finding out module dependencies in `cue mod tidy`. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Idc84401ea06ae0bcc26b6a3a15445701c9f52fb1 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1172704 TryBot-Result: CUEcueckoo <[email protected]> Reviewed-by: Daniel Martí <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 141925a commit 79033c2

File tree

4 files changed

+188
-0
lines changed

4 files changed

+188
-0
lines changed

internal/mod/modimports/modimports.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package modimports
2+
3+
import (
4+
"errors"
5+
"io/fs"
6+
"path"
7+
"strings"
8+
9+
"cuelang.org/go/cue/ast"
10+
"cuelang.org/go/cue/parser"
11+
"cuelang.org/go/internal/cueimports"
12+
)
13+
14+
type ModuleFile struct {
15+
// FilePath holds the path of the module file
16+
// relative to the root of the fs. This will be
17+
// valid even if there's an associated error.
18+
//
19+
// If there's an error, it might not a be CUE file.
20+
FilePath string
21+
22+
// Syntax includes only the portion of the file up to and including
23+
// the imports. It will be nil if there was an error reading the file.
24+
Syntax *ast.File
25+
}
26+
27+
// AllModuleFiles returns an iterator that produces all the CUE files inside the
28+
// module at the given root.
29+
func AllModuleFiles(fsys fs.FS, root string) func(func(ModuleFile, error) bool) {
30+
return func(yield func(ModuleFile, error) bool) {
31+
fs.WalkDir(fsys, root, func(fpath string, d fs.DirEntry, err error) (_err error) {
32+
if err != nil {
33+
if !yield(ModuleFile{
34+
FilePath: fpath,
35+
}, err) {
36+
return fs.SkipAll
37+
}
38+
return nil
39+
}
40+
if path.Base(fpath) == "cue.mod" {
41+
return fs.SkipDir
42+
}
43+
if d.IsDir() {
44+
if fpath == root {
45+
return nil
46+
}
47+
base := path.Base(fpath)
48+
if strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_") {
49+
return fs.SkipDir
50+
}
51+
_, err := fs.Stat(fsys, path.Join(fpath, "cue.mod"))
52+
if err == nil {
53+
// TODO is it enough to have a cue.mod directory
54+
// or should we look for cue.mod/module.cue too?
55+
return fs.SkipDir
56+
}
57+
if !errors.Is(err, fs.ErrNotExist) {
58+
// We haven't got a package file to produce with the
59+
// error here. Should we just ignore the error or produce
60+
// a ModuleFile with an empty path?
61+
yield(ModuleFile{}, err)
62+
return fs.SkipAll
63+
}
64+
return nil
65+
}
66+
if !strings.HasSuffix(fpath, ".cue") {
67+
return nil
68+
}
69+
if !yieldPackageFile(fsys, fpath, yield) {
70+
return fs.SkipAll
71+
}
72+
return nil
73+
})
74+
}
75+
}
76+
77+
func yieldPackageFile(fsys fs.FS, path string, yield func(ModuleFile, error) bool) bool {
78+
pf := ModuleFile{
79+
FilePath: path,
80+
}
81+
f, err := fsys.Open(path)
82+
if err != nil {
83+
return yield(pf, err)
84+
}
85+
defer f.Close()
86+
data, err := cueimports.Read(f)
87+
if err != nil {
88+
return yield(pf, err)
89+
}
90+
syntax, err := parser.ParseFile(path, data, parser.ParseComments)
91+
if err != nil {
92+
return yield(pf, err)
93+
}
94+
pf.Syntax = syntax
95+
return yield(pf, nil)
96+
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package modimports
2+
3+
import (
4+
"fmt"
5+
"io/fs"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
10+
"cuelang.org/go/internal/txtarfs"
11+
"github.com/go-quicktest/qt"
12+
"github.com/google/go-cmp/cmp"
13+
"golang.org/x/tools/txtar"
14+
)
15+
16+
func TestAllPackageFiles(t *testing.T) {
17+
files, err := filepath.Glob("testdata/*.txtar")
18+
qt.Assert(t, qt.IsNil(err))
19+
for _, f := range files {
20+
t.Run(f, func(t *testing.T) {
21+
ar, err := txtar.ParseFile(f)
22+
qt.Assert(t, qt.IsNil(err))
23+
tfs := txtarfs.FS(ar)
24+
want, err := fs.ReadFile(tfs, "want")
25+
qt.Assert(t, qt.IsNil(err))
26+
iter := AllModuleFiles(tfs, ".")
27+
var out strings.Builder
28+
iter(func(pf ModuleFile, err error) bool {
29+
out.WriteString(pf.FilePath)
30+
if err != nil {
31+
fmt.Fprintf(&out, ": error: %v\n", err)
32+
return true
33+
}
34+
for _, imp := range pf.Syntax.Imports {
35+
fmt.Fprintf(&out, " %s", imp.Path.Value)
36+
}
37+
out.WriteString("\n")
38+
return true
39+
})
40+
if diff := cmp.Diff(string(want), out.String()); diff != "" {
41+
t.Fatalf("unexpected results (-want +got):\n%s", diff)
42+
}
43+
})
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- want --
2+
x.cue: error: expected label or ':', found 'IDENT' contents
3+
-- x.cue --
4+
bogus contents
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
-- want --
2+
x.cue "dep1" "dep2" "dep3"
3+
y/y1.cue
4+
y/y2.cue
5+
y/z1.cue
6+
y/z2.cue
7+
-- cue.mod/module.cue --
8+
module: "example.com"
9+
10+
-- x.cue --
11+
package example
12+
13+
import (
14+
"dep1"
15+
"dep2"
16+
)
17+
import "dep3"
18+
19+
x: true
20+
-- y/y1.cue --
21+
package y
22+
23+
24+
-- y/y2.cue --
25+
package y
26+
27+
-- y/z1.cue --
28+
package z
29+
30+
-- y/z2.cue --
31+
package z
32+
33+
-- _omitted1/foo.cue --
34+
not even looked at
35+
36+
-- .omitted2/foo.cue --
37+
not looked at either
38+
39+
-- z/cue.mod/module.cue --
40+
module "other.com"
41+
42+
-- z/z.cue --
43+
package z

0 commit comments

Comments
 (0)