github.com/MangoDowner/go-gm@v0.0.0-20180818020936-8baa2bd4408c/src/path/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 path 6 7 import ( 8 "errors" 9 "strings" 10 "unicode/utf8" 11 ) 12 13 // ErrBadPattern indicates a globbing pattern was malformed. 14 var ErrBadPattern = errors.New("syntax error in pattern") 15 16 // Match reports whether name matches the shell file name pattern. 17 // The pattern syntax is: 18 // 19 // pattern: 20 // { term } 21 // term: 22 // '*' matches any sequence of non-/ characters 23 // '?' matches any single non-/ character 24 // '[' [ '^' ] { character-range } ']' 25 // character class (must be non-empty) 26 // c matches character c (c != '*', '?', '\\', '[') 27 // '\\' c matches character c 28 // 29 // character-range: 30 // c matches character c (c != '\\', '-', ']') 31 // '\\' c matches character c 32 // lo '-' hi matches character c for lo <= c <= hi 33 // 34 // Match requires pattern to match all of name, not just a substring. 35 // The only possible returned error is ErrBadPattern, when pattern 36 // is malformed. 37 // 38 func Match(pattern, name string) (matched bool, err error) { 39 Pattern: 40 for len(pattern) > 0 { 41 var star bool 42 var chunk string 43 star, chunk, pattern = scanChunk(pattern) 44 if star && chunk == "" { 45 // Trailing * matches rest of string unless it has a /. 46 return !strings.Contains(name, "/"), nil 47 } 48 // Look for match at current position. 49 t, ok, err := matchChunk(chunk, name) 50 // if we're the last chunk, make sure we've exhausted the name 51 // otherwise we'll give a false result even if we could still match 52 // using the star 53 if ok && (len(t) == 0 || len(pattern) > 0) { 54 name = t 55 continue 56 } 57 if err != nil { 58 return false, err 59 } 60 if star { 61 // Look for match skipping i+1 bytes. 62 // Cannot skip /. 63 for i := 0; i < len(name) && name[i] != '/'; i++ { 64 t, ok, err := matchChunk(chunk, name[i+1:]) 65 if ok { 66 // if we're the last chunk, make sure we exhausted the name 67 if len(pattern) == 0 && len(t) > 0 { 68 continue 69 } 70 name = t 71 continue Pattern 72 } 73 if err != nil { 74 return false, err 75 } 76 } 77 } 78 return false, nil 79 } 80 return len(name) == 0, nil 81 } 82 83 // scanChunk gets the next segment of pattern, which is a non-star string 84 // possibly preceded by a star. 85 func scanChunk(pattern string) (star bool, chunk, rest string) { 86 for len(pattern) > 0 && pattern[0] == '*' { 87 pattern = pattern[1:] 88 star = true 89 } 90 inrange := false 91 var i int 92 Scan: 93 for i = 0; i < len(pattern); i++ { 94 switch pattern[i] { 95 case '\\': 96 // error check handled in matchChunk: bad pattern. 97 if i+1 < len(pattern) { 98 i++ 99 } 100 case '[': 101 inrange = true 102 case ']': 103 inrange = false 104 case '*': 105 if !inrange { 106 break Scan 107 } 108 } 109 } 110 return star, pattern[0:i], pattern[i:] 111 } 112 113 // matchChunk checks whether chunk matches the beginning of s. 114 // If so, it returns the remainder of s (after the match). 115 // Chunk is all single-character operators: literals, char classes, and ?. 116 func matchChunk(chunk, s string) (rest string, ok bool, err error) { 117 for len(chunk) > 0 { 118 if len(s) == 0 { 119 return 120 } 121 switch chunk[0] { 122 case '[': 123 // character class 124 r, n := utf8.DecodeRuneInString(s) 125 s = s[n:] 126 chunk = chunk[1:] 127 // possibly negated 128 notNegated := true 129 if len(chunk) > 0 && chunk[0] == '^' { 130 notNegated = false 131 chunk = chunk[1:] 132 } 133 // parse all ranges 134 match := false 135 nrange := 0 136 for { 137 if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { 138 chunk = chunk[1:] 139 break 140 } 141 var lo, hi rune 142 if lo, chunk, err = getEsc(chunk); err != nil { 143 return 144 } 145 hi = lo 146 if chunk[0] == '-' { 147 if hi, chunk, err = getEsc(chunk[1:]); err != nil { 148 return 149 } 150 } 151 if lo <= r && r <= hi { 152 match = true 153 } 154 nrange++ 155 } 156 if match != notNegated { 157 return 158 } 159 160 case '?': 161 if s[0] == '/' { 162 return 163 } 164 _, n := utf8.DecodeRuneInString(s) 165 s = s[n:] 166 chunk = chunk[1:] 167 168 case '\\': 169 chunk = chunk[1:] 170 if len(chunk) == 0 { 171 err = ErrBadPattern 172 return 173 } 174 fallthrough 175 176 default: 177 if chunk[0] != s[0] { 178 return 179 } 180 s = s[1:] 181 chunk = chunk[1:] 182 } 183 } 184 return s, true, nil 185 } 186 187 // getEsc gets a possibly-escaped character from chunk, for a character class. 188 func getEsc(chunk string) (r rune, nchunk string, err error) { 189 if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { 190 err = ErrBadPattern 191 return 192 } 193 if chunk[0] == '\\' { 194 chunk = chunk[1:] 195 if len(chunk) == 0 { 196 err = ErrBadPattern 197 return 198 } 199 } 200 r, n := utf8.DecodeRuneInString(chunk) 201 if r == utf8.RuneError && n == 1 { 202 err = ErrBadPattern 203 } 204 nchunk = chunk[n:] 205 if len(nchunk) == 0 { 206 err = ErrBadPattern 207 } 208 return 209 }