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