1
2
3
4
5 package filepath
6
7 import (
8 "errors"
9 "os"
10 "runtime"
11 "sort"
12 "strings"
13 "unicode/utf8"
14 )
15
16
17 var ErrBadPattern = errors.New("syntax error in pattern")
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 func Match(pattern, name string) (matched bool, err error) {
44 Pattern:
45 for len(pattern) > 0 {
46 var star bool
47 var chunk string
48 star, chunk, pattern = scanChunk(pattern)
49 if star && chunk == "" {
50
51 return !strings.Contains(name, string(Separator)), nil
52 }
53
54 t, ok, err := matchChunk(chunk, name)
55
56
57
58 if ok && (len(t) == 0 || len(pattern) > 0) {
59 name = t
60 continue
61 }
62 if err != nil {
63 return false, err
64 }
65 if star {
66
67
68 for i := 0; i < len(name) && name[i] != Separator; i++ {
69 t, ok, err := matchChunk(chunk, name[i+1:])
70 if ok {
71
72 if len(pattern) == 0 && len(t) > 0 {
73 continue
74 }
75 name = t
76 continue Pattern
77 }
78 if err != nil {
79 return false, err
80 }
81 }
82 }
83 return false, nil
84 }
85 return len(name) == 0, nil
86 }
87
88
89
90 func scanChunk(pattern string) (star bool, chunk, rest string) {
91 for len(pattern) > 0 && pattern[0] == '*' {
92 pattern = pattern[1:]
93 star = true
94 }
95 inrange := false
96 var i int
97 Scan:
98 for i = 0; i < len(pattern); i++ {
99 switch pattern[i] {
100 case '\\':
101 if runtime.GOOS != "windows" {
102
103 if i+1 < len(pattern) {
104 i++
105 }
106 }
107 case '[':
108 inrange = true
109 case ']':
110 inrange = false
111 case '*':
112 if !inrange {
113 break Scan
114 }
115 }
116 }
117 return star, pattern[0:i], pattern[i:]
118 }
119
120
121
122
123 func matchChunk(chunk, s string) (rest string, ok bool, err error) {
124
125
126
127 failed := false
128 for len(chunk) > 0 {
129 if !failed && len(s) == 0 {
130 failed = true
131 }
132 switch chunk[0] {
133 case '[':
134
135 var r rune
136 if !failed {
137 var n int
138 r, n = utf8.DecodeRuneInString(s)
139 s = s[n:]
140 }
141 chunk = chunk[1:]
142
143 negated := false
144 if len(chunk) > 0 && chunk[0] == '^' {
145 negated = true
146 chunk = chunk[1:]
147 }
148
149 match := false
150 nrange := 0
151 for {
152 if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
153 chunk = chunk[1:]
154 break
155 }
156 var lo, hi rune
157 if lo, chunk, err = getEsc(chunk); err != nil {
158 return "", false, err
159 }
160 hi = lo
161 if chunk[0] == '-' {
162 if hi, chunk, err = getEsc(chunk[1:]); err != nil {
163 return "", false, err
164 }
165 }
166 if lo <= r && r <= hi {
167 match = true
168 }
169 nrange++
170 }
171 if match == negated {
172 failed = true
173 }
174
175 case '?':
176 if !failed {
177 if s[0] == Separator {
178 failed = true
179 }
180 _, n := utf8.DecodeRuneInString(s)
181 s = s[n:]
182 }
183 chunk = chunk[1:]
184
185 case '\\':
186 if runtime.GOOS != "windows" {
187 chunk = chunk[1:]
188 if len(chunk) == 0 {
189 return "", false, ErrBadPattern
190 }
191 }
192 fallthrough
193
194 default:
195 if !failed {
196 if chunk[0] != s[0] {
197 failed = true
198 }
199 s = s[1:]
200 }
201 chunk = chunk[1:]
202 }
203 }
204 if failed {
205 return "", false, nil
206 }
207 return s, true, nil
208 }
209
210
211 func getEsc(chunk string) (r rune, nchunk string, err error) {
212 if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
213 err = ErrBadPattern
214 return
215 }
216 if chunk[0] == '\\' && runtime.GOOS != "windows" {
217 chunk = chunk[1:]
218 if len(chunk) == 0 {
219 err = ErrBadPattern
220 return
221 }
222 }
223 r, n := utf8.DecodeRuneInString(chunk)
224 if r == utf8.RuneError && n == 1 {
225 err = ErrBadPattern
226 }
227 nchunk = chunk[n:]
228 if len(nchunk) == 0 {
229 err = ErrBadPattern
230 }
231 return
232 }
233
234
235
236
237
238
239
240
241
242 func Glob(pattern string) (matches []string, err error) {
243 return globWithLimit(pattern, 0)
244 }
245
246 func globWithLimit(pattern string, depth int) (matches []string, err error) {
247
248 const pathSeparatorsLimit = 10000
249 if depth == pathSeparatorsLimit {
250 return nil, ErrBadPattern
251 }
252
253
254 if _, err := Match(pattern, ""); err != nil {
255 return nil, err
256 }
257 if !hasMeta(pattern) {
258 if _, err = os.Lstat(pattern); err != nil {
259 return nil, nil
260 }
261 return []string{pattern}, nil
262 }
263
264 dir, file := Split(pattern)
265 volumeLen := 0
266 if runtime.GOOS == "windows" {
267 volumeLen, dir = cleanGlobPathWindows(dir)
268 } else {
269 dir = cleanGlobPath(dir)
270 }
271
272 if !hasMeta(dir[volumeLen:]) {
273 return glob(dir, file, nil)
274 }
275
276
277 if dir == pattern {
278 return nil, ErrBadPattern
279 }
280
281 var m []string
282 m, err = globWithLimit(dir, depth+1)
283 if err != nil {
284 return
285 }
286 for _, d := range m {
287 matches, err = glob(d, file, matches)
288 if err != nil {
289 return
290 }
291 }
292 return
293 }
294
295
296 func cleanGlobPath(path string) string {
297 switch path {
298 case "":
299 return "."
300 case string(Separator):
301
302 return path
303 default:
304 return path[0 : len(path)-1]
305 }
306 }
307
308
309 func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) {
310 vollen := volumeNameLen(path)
311 switch {
312 case path == "":
313 return 0, "."
314 case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]):
315
316 return vollen + 1, path
317 case vollen == len(path) && len(path) == 2:
318 return vollen, path + "."
319 default:
320 if vollen >= len(path) {
321 vollen = len(path) - 1
322 }
323 return vollen, path[0 : len(path)-1]
324 }
325 }
326
327
328
329
330
331 func glob(dir, pattern string, matches []string) (m []string, e error) {
332 m = matches
333 fi, err := os.Stat(dir)
334 if err != nil {
335 return
336 }
337 if !fi.IsDir() {
338 return
339 }
340 d, err := os.Open(dir)
341 if err != nil {
342 return
343 }
344 defer d.Close()
345
346 names, _ := d.Readdirnames(-1)
347 sort.Strings(names)
348
349 for _, n := range names {
350 matched, err := Match(pattern, n)
351 if err != nil {
352 return m, err
353 }
354 if matched {
355 m = append(m, Join(dir, n))
356 }
357 }
358 return
359 }
360
361
362
363 func hasMeta(path string) bool {
364 magicChars := `*?[`
365 if runtime.GOOS != "windows" {
366 magicChars = `*?[\`
367 }
368 return strings.ContainsAny(path, magicChars)
369 }
370
View as plain text