Import upstream version 0.0.3
Debian Janitor
2 years ago
0 | # These are supported funding model platforms | |
1 | ||
2 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | |
3 | patreon: mattn # Replace with a single Patreon username | |
4 | open_collective: # Replace with a single Open Collective username | |
5 | ko_fi: # Replace with a single Ko-fi username | |
6 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | |
7 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | |
8 | liberapay: # Replace with a single Liberapay username | |
9 | issuehunt: # Replace with a single IssueHunt username | |
10 | otechie: # Replace with a single Otechie username | |
11 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] |
0 | name: Go | |
1 | ||
2 | on: | |
3 | push: | |
4 | branches: [ master ] | |
5 | pull_request: | |
6 | branches: [ master ] | |
7 | ||
8 | jobs: | |
9 | ||
10 | build: | |
11 | name: Build | |
12 | runs-on: ubuntu-latest | |
13 | steps: | |
14 | ||
15 | - name: Set up Go 1.x | |
16 | uses: actions/setup-go@v2 | |
17 | with: | |
18 | go-version: ^1.13 | |
19 | id: go | |
20 | ||
21 | - name: Check out code into the Go module directory | |
22 | uses: actions/checkout@v2 | |
23 | ||
24 | - name: Get dependencies | |
25 | run: | | |
26 | go get -v -t -d ./... | |
27 | if [ -f Gopkg.toml ]; then | |
28 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh | |
29 | dep ensure | |
30 | fi | |
31 | ||
32 | - name: Build | |
33 | run: go build -v . | |
34 | ||
35 | - name: Test | |
36 | run: go test -v . |
18 | 18 | "os" |
19 | 19 | "path/filepath" |
20 | 20 | "runtime" |
21 | "sync" | |
21 | 22 | ) |
22 | 23 | |
23 | 24 | // TraverseLink is a sentinel error for fastWalk, similar to filepath.SkipDir. |
38 | 39 | // sentinel error. It is the walkFn's responsibility to prevent |
39 | 40 | // fastWalk from going into symlink cycles. |
40 | 41 | func FastWalk(root string, walkFn func(path string, typ os.FileMode) error) error { |
42 | // Check if "root" is actually a file, not a directory. | |
43 | stat, err := os.Stat(root) | |
44 | if err != nil { | |
45 | return err | |
46 | } | |
47 | if !stat.IsDir() { | |
48 | // If it is, just directly pass it to walkFn and return. | |
49 | return walkFn(root, stat.Mode()) | |
50 | } | |
51 | ||
41 | 52 | // TODO(bradfitz): make numWorkers configurable? We used a |
42 | 53 | // minimum of 4 to give the kernel more info about multiple |
43 | 54 | // things we want, in hopes its I/O scheduling can take |
56 | 67 | // buffered for correctness & not leaking goroutines: |
57 | 68 | resc: make(chan error, numWorkers), |
58 | 69 | } |
59 | defer close(w.donec) | |
70 | ||
60 | 71 | // TODO(bradfitz): start the workers as needed? maybe not worth it. |
72 | var wg sync.WaitGroup | |
61 | 73 | for i := 0; i < numWorkers; i++ { |
62 | go w.doWork() | |
63 | } | |
74 | wg.Add(1) | |
75 | go w.doWork(&wg) | |
76 | } | |
77 | ||
64 | 78 | todo := []walkItem{{dir: root}} |
65 | 79 | out := 0 |
66 | 80 | for { |
78 | 92 | case it := <-w.enqueuec: |
79 | 93 | todo = append(todo, it) |
80 | 94 | case err := <-w.resc: |
81 | out-- | |
82 | 95 | if err != nil { |
96 | // Signal to the workers to close. | |
97 | close(w.donec) | |
98 | ||
99 | // Drain the results channel from the other workers which | |
100 | // haven't returned yet. | |
101 | go func() { | |
102 | for { | |
103 | select { | |
104 | case _, ok := <-w.resc: | |
105 | if !ok { | |
106 | return | |
107 | } | |
108 | } | |
109 | } | |
110 | }() | |
111 | ||
112 | wg.Wait() | |
83 | 113 | return err |
84 | 114 | } |
115 | ||
116 | out-- | |
85 | 117 | if out == 0 && len(todo) == 0 { |
86 | 118 | // It's safe to quit here, as long as the buffered |
87 | 119 | // enqueue channel isn't also readable, which might |
93 | 125 | case it := <-w.enqueuec: |
94 | 126 | todo = append(todo, it) |
95 | 127 | default: |
128 | // Signal to the workers to close, and wait for all of | |
129 | // them to return. | |
130 | close(w.donec) | |
131 | wg.Wait() | |
96 | 132 | return nil |
97 | 133 | } |
98 | 134 | } |
102 | 138 | |
103 | 139 | // doWork reads directories as instructed (via workc) and runs the |
104 | 140 | // user's callback function. |
105 | func (w *walker) doWork() { | |
141 | func (w *walker) doWork(wg *sync.WaitGroup) { | |
106 | 142 | for { |
107 | 143 | select { |
108 | 144 | case <-w.donec: |
145 | wg.Done() | |
109 | 146 | return |
110 | 147 | case it := <-w.workc: |
111 | 148 | w.resc <- w.walk(it.dir, !it.callbackDone) |
17 | 17 | func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { |
18 | 18 | fis, err := ioutil.ReadDir(dirName) |
19 | 19 | if err != nil { |
20 | return err | |
20 | return nil | |
21 | 21 | } |
22 | 22 | for _, fi := range fis { |
23 | 23 | if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil { |
0 | // Copyright 2016 The Go Authors. All rights reserved. | |
1 | // Use of this source code is governed by a BSD-style | |
2 | // license that can be found in the LICENSE file. | |
3 | ||
4 | 0 | package fastwalk |
5 | 1 | |
6 | 2 | import ( |
7 | "bytes" | |
8 | "flag" | |
9 | "fmt" | |
3 | "io/ioutil" | |
10 | 4 | "os" |
11 | 5 | "path/filepath" |
12 | "reflect" | |
13 | "runtime" | |
14 | "sort" | |
15 | "strings" | |
16 | "sync" | |
17 | 6 | "testing" |
18 | 7 | ) |
19 | 8 | |
20 | func formatFileModes(m map[string]os.FileMode) string { | |
21 | var keys []string | |
22 | for k := range m { | |
23 | keys = append(keys, k) | |
9 | func TestFastWalk(t *testing.T) { | |
10 | var tmpdir string | |
11 | var err error | |
12 | ||
13 | if tmpdir, err = ioutil.TempDir("", "zglob"); err != nil { | |
14 | t.Fatal(err) | |
24 | 15 | } |
25 | sort.Strings(keys) | |
26 | var buf bytes.Buffer | |
27 | for _, k := range keys { | |
28 | fmt.Fprintf(&buf, "%-20s: %v\n", k, m[k]) | |
29 | } | |
30 | return buf.String() | |
31 | } | |
16 | defer os.RemoveAll(tmpdir) | |
32 | 17 | |
33 | func testFastWalk(t *testing.T, files map[string]string, callback func(path string, typ os.FileMode) error, want map[string]os.FileMode) { | |
34 | testConfig{ | |
35 | gopathFiles: files, | |
36 | }.test(t, func(t *goimportTest) { | |
37 | got := map[string]os.FileMode{} | |
38 | var mu sync.Mutex | |
39 | if err := fastWalk(t.gopath, func(path string, typ os.FileMode) error { | |
40 | mu.Lock() | |
41 | defer mu.Unlock() | |
42 | if !strings.HasPrefix(path, t.gopath) { | |
43 | t.Fatal("bogus prefix on %q, expect %q", path, t.gopath) | |
44 | } | |
45 | key := filepath.ToSlash(strings.TrimPrefix(path, t.gopath)) | |
46 | if old, dup := got[key]; dup { | |
47 | t.Fatalf("callback called twice for key %q: %v -> %v", key, old, typ) | |
48 | } | |
49 | got[key] = typ | |
50 | return callback(path, typ) | |
51 | }); err != nil { | |
52 | t.Fatalf("callback returned: %v", err) | |
53 | } | |
54 | if !reflect.DeepEqual(got, want) { | |
55 | t.Errorf("walk mismatch.\n got:\n%v\nwant:\n%v", formatFileModes(got), formatFileModes(want)) | |
56 | } | |
57 | }) | |
58 | } | |
59 | ||
60 | func TestFastWalk_Basic(t *testing.T) { | |
61 | testFastWalk(t, map[string]string{ | |
62 | "foo/foo.go": "one", | |
63 | "bar/bar.go": "two", | |
64 | "skip/skip.go": "skip", | |
65 | }, | |
66 | func(path string, typ os.FileMode) error { | |
67 | return nil | |
68 | }, | |
69 | map[string]os.FileMode{ | |
70 | "": os.ModeDir, | |
71 | "/src": os.ModeDir, | |
72 | "/src/bar": os.ModeDir, | |
73 | "/src/bar/bar.go": 0, | |
74 | "/src/foo": os.ModeDir, | |
75 | "/src/foo/foo.go": 0, | |
76 | "/src/skip": os.ModeDir, | |
77 | "/src/skip/skip.go": 0, | |
78 | }) | |
79 | } | |
80 | ||
81 | func TestFastWalk_Symlink(t *testing.T) { | |
82 | switch runtime.GOOS { | |
83 | case "windows", "plan9": | |
84 | t.Skipf("skipping on %s", runtime.GOOS) | |
85 | } | |
86 | testFastWalk(t, map[string]string{ | |
87 | "foo/foo.go": "one", | |
88 | "bar/bar.go": "LINK:../foo.go", | |
89 | "symdir": "LINK:foo", | |
90 | }, | |
91 | func(path string, typ os.FileMode) error { | |
92 | return nil | |
93 | }, | |
94 | map[string]os.FileMode{ | |
95 | "": os.ModeDir, | |
96 | "/src": os.ModeDir, | |
97 | "/src/bar": os.ModeDir, | |
98 | "/src/bar/bar.go": os.ModeSymlink, | |
99 | "/src/foo": os.ModeDir, | |
100 | "/src/foo/foo.go": 0, | |
101 | "/src/symdir": os.ModeSymlink, | |
102 | }) | |
103 | } | |
104 | ||
105 | func TestFastWalk_SkipDir(t *testing.T) { | |
106 | testFastWalk(t, map[string]string{ | |
107 | "foo/foo.go": "one", | |
108 | "bar/bar.go": "two", | |
109 | "skip/skip.go": "skip", | |
110 | }, | |
111 | func(path string, typ os.FileMode) error { | |
112 | if typ == os.ModeDir && strings.HasSuffix(path, "skip") { | |
113 | return filepath.SkipDir | |
114 | } | |
115 | return nil | |
116 | }, | |
117 | map[string]os.FileMode{ | |
118 | "": os.ModeDir, | |
119 | "/src": os.ModeDir, | |
120 | "/src/bar": os.ModeDir, | |
121 | "/src/bar/bar.go": 0, | |
122 | "/src/foo": os.ModeDir, | |
123 | "/src/foo/foo.go": 0, | |
124 | "/src/skip": os.ModeDir, | |
125 | }) | |
126 | } | |
127 | ||
128 | func TestFastWalk_TraverseSymlink(t *testing.T) { | |
129 | switch runtime.GOOS { | |
130 | case "windows", "plan9": | |
131 | t.Skipf("skipping on %s", runtime.GOOS) | |
18 | if err = os.Chdir(tmpdir); err != nil { | |
19 | t.Fatal(err) | |
132 | 20 | } |
133 | 21 | |
134 | testFastWalk(t, map[string]string{ | |
135 | "foo/foo.go": "one", | |
136 | "bar/bar.go": "two", | |
137 | "skip/skip.go": "skip", | |
138 | "symdir": "LINK:foo", | |
139 | }, | |
140 | func(path string, typ os.FileMode) error { | |
141 | if typ == os.ModeSymlink { | |
142 | return traverseLink | |
22 | os.MkdirAll(filepath.Join(tmpdir, "foo/bar/baz"), 0755) | |
23 | ioutil.WriteFile(filepath.Join(tmpdir, "foo/bar/baz.txt"), []byte{}, 0644) | |
24 | ioutil.WriteFile(filepath.Join(tmpdir, "foo/bar/baz/noo.txt"), []byte{}, 0644) | |
25 | ||
26 | cases := []struct { | |
27 | path string | |
28 | dir bool | |
29 | triggered bool | |
30 | }{ | |
31 | {path: "foo/bar", dir: true, triggered: false}, | |
32 | {path: "foo/bar/baz", dir: true, triggered: false}, | |
33 | {path: "foo/bar/baz.txt", dir: false, triggered: false}, | |
34 | {path: "foo/bar/baz/noo.txt", dir: false, triggered: false}, | |
35 | } | |
36 | ||
37 | for i, tt := range cases { | |
38 | err = FastWalk(tt.path, func(path string, mode os.FileMode) error { | |
39 | if path != tt.path { | |
40 | return nil | |
143 | 41 | } |
42 | ||
43 | if tt.dir != mode.IsDir() { | |
44 | t.Errorf("expected path %q to be: dir:%v, but got dir:%v", tt.path, tt.dir, mode.IsDir()) | |
45 | } | |
46 | cases[i].triggered = true | |
144 | 47 | return nil |
145 | }, | |
146 | map[string]os.FileMode{ | |
147 | "": os.ModeDir, | |
148 | "/src": os.ModeDir, | |
149 | "/src/bar": os.ModeDir, | |
150 | "/src/bar/bar.go": 0, | |
151 | "/src/foo": os.ModeDir, | |
152 | "/src/foo/foo.go": 0, | |
153 | "/src/skip": os.ModeDir, | |
154 | "/src/skip/skip.go": 0, | |
155 | "/src/symdir": os.ModeSymlink, | |
156 | "/src/symdir/foo.go": 0, | |
157 | 48 | }) |
158 | } | |
49 | if err != nil { | |
50 | t.Errorf("error running FastWalk on %q: %v", tt.path, err) | |
51 | continue | |
52 | } | |
159 | 53 | |
160 | var benchDir = flag.String("benchdir", runtime.GOROOT(), "The directory to scan for BenchmarkFastWalk") | |
161 | ||
162 | func BenchmarkFastWalk(b *testing.B) { | |
163 | b.ReportAllocs() | |
164 | for i := 0; i < b.N; i++ { | |
165 | err := fastWalk(*benchDir, func(path string, typ os.FileMode) error { return nil }) | |
166 | if err != nil { | |
167 | b.Fatal(err) | |
54 | if !cases[i].triggered { | |
55 | t.Errorf("expected %q to be triggered, but got %v", tt.path, cases[i].triggered) | |
168 | 56 | } |
169 | 57 | } |
170 | 58 | } |
17 | 17 | ) |
18 | 18 | |
19 | 19 | type zenv struct { |
20 | dre *regexp.Regexp | |
21 | fre *regexp.Regexp | |
22 | root string | |
23 | } | |
24 | ||
25 | func makePattern(pattern string) (*zenv, error) { | |
20 | dre *regexp.Regexp | |
21 | fre *regexp.Regexp | |
22 | pattern string | |
23 | root string | |
24 | } | |
25 | ||
26 | func New(pattern string) (*zenv, error) { | |
26 | 27 | globmask := "" |
27 | 28 | root := "" |
28 | 29 | for n, i := range strings.Split(filepath.ToSlash(pattern), "/") { |
29 | if root == "" && strings.Index(i, "*") != -1 { | |
30 | if root == "" && (strings.Index(i, "*") != -1 || strings.Index(i, "{") != -1) { | |
30 | 31 | if globmask == "" { |
31 | 32 | root = "." |
32 | 33 | } else { |
55 | 56 | } |
56 | 57 | if root == "" { |
57 | 58 | return &zenv{ |
58 | dre: nil, | |
59 | fre: nil, | |
60 | root: "", | |
59 | dre: nil, | |
60 | fre: nil, | |
61 | pattern: pattern, | |
62 | root: "", | |
61 | 63 | }, nil |
62 | 64 | } |
63 | 65 | if globmask == "" { |
80 | 82 | filemask += "[^/]*" |
81 | 83 | } |
82 | 84 | } else { |
85 | if cc[i] == '{' { | |
86 | pattern := "" | |
87 | for j := i + 1; j < len(cc); j++ { | |
88 | if cc[j] == ',' { | |
89 | pattern += "|" | |
90 | } else if cc[j] == '}' { | |
91 | i = j | |
92 | break | |
93 | } else { | |
94 | c := cc[j] | |
95 | if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c { | |
96 | pattern += string(c) | |
97 | } else { | |
98 | pattern += fmt.Sprintf("[\\x%02X]", c) | |
99 | } | |
100 | } | |
101 | } | |
102 | if pattern != "" { | |
103 | filemask += "(" + pattern + ")" | |
104 | continue | |
105 | } | |
106 | } | |
83 | 107 | c := cc[i] |
84 | 108 | if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c { |
85 | 109 | filemask += string(c) |
105 | 129 | filemask = "(?i:" + filemask + ")" |
106 | 130 | } |
107 | 131 | return &zenv{ |
108 | dre: regexp.MustCompile("^" + dirmask), | |
109 | fre: regexp.MustCompile("^" + filemask + "$"), | |
110 | root: filepath.Clean(root), | |
132 | dre: regexp.MustCompile("^" + dirmask), | |
133 | fre: regexp.MustCompile("^" + filemask + "$"), | |
134 | pattern: pattern, | |
135 | root: filepath.Clean(root), | |
111 | 136 | }, nil |
112 | 137 | } |
113 | 138 | |
120 | 145 | } |
121 | 146 | |
122 | 147 | func glob(pattern string, followSymlinks bool) ([]string, error) { |
123 | zenv, err := makePattern(pattern) | |
148 | zenv, err := New(pattern) | |
124 | 149 | if err != nil { |
125 | 150 | return nil, err |
126 | 151 | } |
179 | 204 | } |
180 | 205 | |
181 | 206 | func Match(pattern, name string) (matched bool, err error) { |
182 | zenv, err := makePattern(pattern) | |
207 | zenv, err := New(pattern) | |
183 | 208 | if err != nil { |
184 | 209 | return false, err |
185 | 210 | } |
186 | if zenv.root == "" { | |
187 | return pattern == name, nil | |
211 | return zenv.Match(name), nil | |
212 | } | |
213 | ||
214 | func (z *zenv) Match(name string) bool { | |
215 | if z.root == "" { | |
216 | return z.pattern == name | |
188 | 217 | } |
189 | 218 | |
190 | 219 | name = filepath.ToSlash(name) |
191 | 220 | |
192 | if name == "." || len(name) <= len(zenv.root) { | |
193 | return false, nil | |
194 | } | |
195 | ||
196 | if zenv.fre.MatchString(name) { | |
197 | return true, nil | |
198 | } | |
199 | return false, nil | |
200 | } | |
221 | if name == "." || len(name) <= len(z.root) { | |
222 | return false | |
223 | } | |
224 | ||
225 | if z.fre.MatchString(name) { | |
226 | return true | |
227 | } | |
228 | return false | |
229 | } |
38 | 38 | {`doo`, nil, os.ErrNotExist}, |
39 | 39 | {`./f*`, []string{`foo`}, nil}, |
40 | 40 | {`**/bar/**/*.txt`, []string{`foo/bar/baz.txt`, `foo/bar/baz/noo.txt`}, nil}, |
41 | {`**/bar/**/*.{jpg,png}`, []string{`zzz/bar/baz/joo.png`, `zzz/bar/baz/zoo.jpg`}, nil}, | |
42 | {`zzz/bar/baz/zoo.{jpg,png}`, []string{`zzz/bar/baz/zoo.jpg`}, nil}, | |
41 | 43 | } |
42 | 44 | |
43 | 45 | func setup(t *testing.T) string { |
53 | 55 | ioutil.WriteFile(filepath.Join(tmpdir, "foo/bar/baz/noo.txt"), []byte{}, 0644) |
54 | 56 | os.MkdirAll(filepath.Join(tmpdir, "hoo/bar"), 0755) |
55 | 57 | ioutil.WriteFile(filepath.Join(tmpdir, "foo/bar/baz.txt"), []byte{}, 0644) |
58 | os.MkdirAll(filepath.Join(tmpdir, "zzz/bar/baz"), 0755) | |
59 | ioutil.WriteFile(filepath.Join(tmpdir, "zzz/bar/baz/zoo.jpg"), []byte{}, 0644) | |
60 | ioutil.WriteFile(filepath.Join(tmpdir, "zzz/bar/baz/joo.png"), []byte{}, 0644) | |
56 | 61 | |
57 | 62 | return tmpdir |
58 | 63 | } |