github.com/MangoDowner/go-gm@v0.0.0-20180818020936-8baa2bd4408c/src/path/filepath/match.go (about) 1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package filepath 6 7 import ( 8 "errors" 9 "os" 10 "runtime" 11 "sort" 12 "strings" 13 "unicode/utf8" 14 ) 15 16 // ErrBadPattern indicates a globbing pattern was malformed. 17 var ErrBadPattern = errors.New("syntax error in pattern") 18 19 // Match reports whether name matches the shell file name pattern. 20 // The pattern syntax is: 21 // 22 // pattern: 23 // { term } 24 // term: 25 // '*' matches any sequence of non-Separator characters 26 // '?' matches any single non-Separator character 27 // '[' [ '^' ] { character-range } ']' 28 // character class (must be non-empty) 29 // c matches character c (c != '*', '?', '\\', '[') 30 // '\\' c matches character c 31 // 32 // character-range: 33 // c matches character c (c != '\\', '-', ']') 34 // '\\' c matches character c 35 // lo '-' hi matches character c for lo <= c <= hi 36 // 37 // Match requires pattern to match all of name, not just a substring. 38 // The only possible returned error is ErrBadPattern, when pattern 39 // is malformed. 40 // 41 // On Windows, escaping is disabled. Instead, '\\' is treated as 42 // path separator. 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 // Trailing * matches rest of string unless it has a /. 52 return !strings.Contains(name, string(Separator)), nil 53 } 54 // Look for match at current position. 55 t, ok, err := matchChunk(chunk, name) 56 // if we're the last chunk, make sure we've exhausted the name 57 // otherwise we'll give a false result even if we could still match 58 // using the star 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 // Look for match skipping i+1 bytes. 68 // Cannot skip /. 69 for i := 0; i < len(name) && name[i] != Separator; i++ { 70 t, ok, err := matchChunk(chunk, name[i+1:]) 71 if ok { 72 // if we're the last chunk, make sure we exhausted the name 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 // scanChunk gets the next segment of pattern, which is a non-star string 90 // possibly preceded by a star. 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 // error check handled in matchChunk: bad pattern. 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 // matchChunk checks whether chunk matches the beginning of s. 122 // If so, it returns the remainder of s (after the match). 123 // Chunk is all single-character operators: literals, char classes, and ?. 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 // character class 132 r, n := utf8.DecodeRuneInString(s) 133 s = s[n:] 134 chunk = chunk[1:] 135 // We can't end right after '[', we're expecting at least 136 // a closing bracket and possibly a caret. 137 if len(chunk) == 0 { 138 err = ErrBadPattern 139 return 140 } 141 // possibly negated 142 negated := chunk[0] == '^' 143 if negated { 144 chunk = chunk[1:] 145 } 146 // parse all ranges 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 // getEsc gets a possibly-escaped character from chunk, for a character class. 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 // Glob returns the names of all files matching pattern or nil 227 // if there is no matching file. The syntax of patterns is the same 228 // as in Match. The pattern may describe hierarchical names such as 229 // /usr/*/bin/ed (assuming the Separator is '/'). 230 // 231 // Glob ignores file system errors such as I/O errors reading directories. 232 // The only possible returned error is ErrBadPattern, when pattern 233 // is malformed. 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 // Prevent infinite recursion. See issue 15879. 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 // cleanGlobPath prepares path for glob matching. 274 func cleanGlobPath(path string) string { 275 switch path { 276 case "": 277 return "." 278 case string(Separator): 279 // do nothing to the path 280 return path 281 default: 282 return path[0 : len(path)-1] // chop off trailing separator 283 } 284 } 285 286 // cleanGlobPathWindows is windows version of cleanGlobPath. 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]): // /, \, C:\ and C:/ 293 // do nothing to the path 294 return vollen + 1, path 295 case vollen == len(path) && len(path) == 2: // C: 296 return vollen, path + "." // convert C: into C:. 297 default: 298 if vollen >= len(path) { 299 vollen = len(path) - 1 300 } 301 return vollen, path[0 : len(path)-1] // chop off trailing separator 302 } 303 } 304 305 // glob searches for files matching pattern in the directory dir 306 // and appends them to matches. If the directory cannot be 307 // opened, it returns the existing matches. New matches are 308 // added in lexicographical order. 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 // hasMeta reports whether path contains any of the magic characters 340 // recognized by Match. 341 func hasMeta(path string) bool { 342 // TODO(niemeyer): Should other magic characters be added here? 343 return strings.ContainsAny(path, "*?[") 344 }