Skip to content

Commit 6741c90

Browse files
committed
interpreter/embed: allow glob to match explicit dot files
We don't want to ignore dot files when they're explicitly mentioned in a glob pattern, so use a slightly more sophisticated approach than just excluding all dot files. Fixes #3889. Signed-off-by: Roger Peppe <[email protected]> Change-Id: Iacf0cabb631b31c3d1277675a8cfac51793a6e31 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1213641 Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Paul Jolly <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 572a6cc commit 6741c90

File tree

3 files changed

+112
-35
lines changed

3 files changed

+112
-35
lines changed

cmd/cue/cmd/testdata/script/embed.txtar

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,11 @@ special: {
245245
dotFile: {
246246
z: 46
247247
}
248-
dotFileExplicit: {}
248+
dotFileExplicit: {
249+
"y/.test.json": {
250+
z: 46
251+
}
252+
}
249253
dotdotFile: {
250254
dotdot: true
251255
}
@@ -315,7 +319,7 @@ special: {
315319
// These are all valid.
316320
underscoreFile: z: 45
317321
dotFile: z: 46
318-
dotFileExplicit: {}
322+
dotFileExplicit: "y/.test.json": z: 46
319323
dotdotFile: dotdot: true
320324
underscoreDir: z: 47
321325
dotDir: z: 48

cue/interpreter/embed/embed.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,13 @@ func (c *compiler) processGlob(glob, scope string, schema adt.Value) (adt.Expr,
232232

233233
m := &adt.StructLit{}
234234

235-
matches, err := fs.Glob(c.fs, glob)
235+
matches, err := fsGlob(c.fs, glob)
236236
if err != nil {
237237
return nil, errors.Promote(err, "failed to match glob")
238238
}
239239

240240
dirs := make(map[string]string)
241241
for _, f := range matches {
242-
if c.isHidden(f) {
243-
// TODO: allow option for including hidden files?
244-
continue
245-
}
246242
// TODO: lots of stat calls happening in this MVP so another won't hurt.
247243
// We don't support '**' initially, and '*' only matches files, so skip
248244
// any directories.
@@ -293,10 +289,35 @@ func (c *compiler) clean(s string) (string, errors.Error) {
293289
return file, nil
294290
}
295291

296-
// isHidden checks if a file is hidden on Windows. We do not return an error
297-
// if the file does not exist and will check that elsewhere.
298-
func (c *compiler) isHidden(file string) bool {
299-
return strings.HasPrefix(file, ".") || strings.Contains(file, "/.")
292+
// fsGlob is like [fs.Glob] but only includes dot-prefixed files
293+
// when the dot is explictly present in an element.
294+
// TODO: add option for including dot files?
295+
func fsGlob(fsys fs.FS, pattern string) ([]string, error) {
296+
pattern = path.Clean(pattern)
297+
matches, err := fs.Glob(fsys, pattern)
298+
if err != nil {
299+
return nil, err
300+
}
301+
patElems := strings.Split(pattern, "/")
302+
included := func(m string) bool {
303+
for i, elem := range strings.Split(m, "/") {
304+
// Technically there should never be more elements in m than
305+
// there are in patElems, but be defensive and check bounds just in case.
306+
if strings.HasPrefix(elem, ".") && (i >= len(patElems) || !strings.HasPrefix(patElems[i], ".")) {
307+
return false
308+
}
309+
}
310+
return true
311+
}
312+
313+
i := 0
314+
for _, m := range matches {
315+
if included(m) {
316+
matches[i] = m
317+
i++
318+
}
319+
}
320+
return matches[:i], nil
300321
}
301322

302323
func (c *compiler) decodeFile(file, scope string, schema adt.Value) (adt.Expr, errors.Error) {

cue/interpreter/embed/embed_test.go

Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,94 @@
1414

1515
package embed
1616

17-
import "testing"
17+
import (
18+
"testing"
19+
"testing/fstest"
1820

19-
func TestIsHidden(t *testing.T) {
21+
"github.com/go-quicktest/qt"
22+
"github.com/google/go-cmp/cmp/cmpopts"
23+
)
24+
25+
func TestFSGlob(t *testing.T) {
2026
// These test cases are the same for both Unix and Windows.
2127
testCases := []struct {
22-
path string
23-
want bool
28+
testName string
29+
paths []string
30+
pattern string
31+
want []string
32+
wantError string
2433
}{{
25-
path: "",
26-
want: false,
27-
}, {
28-
path: "foo",
29-
want: false,
34+
testName: "EmptyDirectory",
35+
paths: []string{},
36+
pattern: "*/*.txt",
37+
want: nil,
3038
}, {
31-
path: ".foo",
32-
want: true,
39+
testName: "DirectoryWithSingleDotFile",
40+
paths: []string{
41+
"foo/bar",
42+
"foo/bar.txt",
43+
"foo/.hello.txt",
44+
},
45+
pattern: "*/*.txt",
46+
want: []string{
47+
"foo/bar.txt",
48+
},
3349
}, {
34-
path: "foo/bar",
35-
want: false,
50+
testName: "DotPrefixedDirectory",
51+
paths: []string{
52+
"foo/bar.txt",
53+
"foo/.hello.txt",
54+
"foo/baz.txt",
55+
".git/something.txt",
56+
},
57+
pattern: "*/*.txt",
58+
want: []string{
59+
"foo/bar.txt",
60+
"foo/baz.txt",
61+
},
3662
}, {
37-
path: "foo/.bar",
38-
want: true,
63+
testName: "DotPrefixedDirectoryWithExplicitDot",
64+
paths: []string{
65+
"foo/bar.txt",
66+
"foo/.hello.txt",
67+
"foo/baz.txt",
68+
".git/something.txt",
69+
".git/.otherthing.txt",
70+
},
71+
pattern: ".*/*.txt",
72+
want: []string{
73+
".git/something.txt",
74+
},
3975
}, {
40-
path: ".foo/bar",
41-
want: true,
76+
testName: "DotInCharacterClass",
77+
paths: []string{
78+
".foo",
79+
},
80+
pattern: "[.x]foo",
81+
want: nil,
4282
}, {
43-
path: "x/.foo/bar",
44-
want: true,
83+
testName: "SlashInCharacterClass",
84+
paths: []string{
85+
"fooxbar",
86+
"foo/bar",
87+
},
88+
pattern: "foo[/x]bar",
89+
wantError: `syntax error in pattern`,
4590
}}
46-
c := &compiler{dir: "/tmp"}
4791
for _, tc := range testCases {
48-
t.Run(tc.path, func(t *testing.T) {
49-
got := c.isHidden(tc.path)
50-
if got != tc.want {
51-
t.Errorf("isHidden(%q) = %t; want %t", tc.path, got, tc.want)
92+
t.Run(tc.testName, func(t *testing.T) {
93+
m := make(fstest.MapFS)
94+
for _, p := range tc.paths {
95+
m[p] = &fstest.MapFile{
96+
Mode: 0o666,
97+
}
98+
}
99+
got, err := fsGlob(m, tc.pattern)
100+
if tc.wantError != "" {
101+
qt.Assert(t, qt.ErrorMatches(err, tc.wantError))
102+
return
52103
}
104+
qt.Assert(t, qt.CmpEquals(got, tc.want, cmpopts.EquateEmpty()))
53105
})
54106
}
55107
}

0 commit comments

Comments
 (0)