github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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 "internal/bytealg" 10 "unicode/utf8" 11 ) 12 13 // ErrBadPattern indicates a pattern was malformed. 14 var ErrBadPattern = errors.New("syntax error in pattern") 15 16 // Match reports whether name matches the shell 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 func Match(pattern, name string) (matched bool, err error) { 38 Pattern: 39 for len(pattern) > 0 { 40 var star bool 41 var chunk string 42 star, chunk, pattern = scanChunk(pattern) 43 if star && chunk == "" { 44 // Trailing * matches rest of string unless it has a /. 45 return bytealg.IndexByteString(name, '/') < 0, nil 46 } 47 // Look for match at current position. 48 t, ok, err := matchChunk(chunk, name) 49 // if we're the last chunk, make sure we've exhausted the name 50 // otherwise we'll give a false result even if we could still match 51 // using the star 52 if ok && (len(t) == 0 || len(pattern) > 0) { 53 name = t 54 continue 55 } 56 if err != nil { 57 return false, err 58 } 59 if star { 60 // Look for match skipping i+1 bytes. 61 // Cannot skip /. 62 for i := 0; i < len(name) && name[i] != '/'; i++ { 63 t, ok, err := matchChunk(chunk, name[i+1:]) 64 if ok { 65 // if we're the last chunk, make sure we exhausted the name 66 if len(pattern) == 0 && len(t) > 0 { 67 continue 68 } 69 name = t 70 continue Pattern 71 } 72 if err != nil { 73 return false, err 74 } 75 } 76 } 77 // Before returning false with no error, 78 // check that the remainder of the pattern is syntactically valid. 79 for len(pattern) > 0 { 80 _, chunk, pattern = scanChunk(pattern) 81 if _, _, err := matchChunk(chunk, ""); err != nil { 82 return false, err 83 } 84 } 85 return false, nil 86 } 87 return len(name) == 0, nil 88 } 89 90 // scanChunk gets the next segment of pattern, which is a non-star string 91 // possibly preceded by a star. 92 func scanChunk(pattern string) (star bool, chunk, rest string) { 93 for len(pattern) > 0 && pattern[0] == '*' { 94 pattern = pattern[1:] 95 star = true 96 } 97 inrange := false 98 var i int 99 Scan: 100 for i = 0; i < len(pattern); i++ { 101 switch pattern[i] { 102 case '\\': 103 // error check handled in matchChunk: bad pattern. 104 if i+1 < len(pattern) { 105 i++ 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 // matchChunk checks whether chunk matches the beginning of s. 121 // If so, it returns the remainder of s (after the match). 122 // Chunk is all single-character operators: literals, char classes, and ?. 123 func matchChunk(chunk, s string) (rest string, ok bool, err error) { 124 // failed records whether the match has failed. 125 // After the match fails, the loop continues on processing chunk, 126 // checking that the pattern is well-formed but no longer reading s. 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 // character class 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 // possibly negated 143 negated := false 144 if len(chunk) > 0 && chunk[0] == '^' { 145 negated = true 146 chunk = chunk[1:] 147 } 148 // parse all ranges 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] == '/' { 178 failed = true 179 } 180 _, n := utf8.DecodeRuneInString(s) 181 s = s[n:] 182 } 183 chunk = chunk[1:] 184 185 case '\\': 186 chunk = chunk[1:] 187 if len(chunk) == 0 { 188 return "", false, ErrBadPattern 189 } 190 fallthrough 191 192 default: 193 if !failed { 194 if chunk[0] != s[0] { 195 failed = true 196 } 197 s = s[1:] 198 } 199 chunk = chunk[1:] 200 } 201 } 202 if failed { 203 return "", false, nil 204 } 205 return s, true, nil 206 } 207 208 // getEsc gets a possibly-escaped character from chunk, for a character class. 209 func getEsc(chunk string) (r rune, nchunk string, err error) { 210 if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { 211 err = ErrBadPattern 212 return 213 } 214 if chunk[0] == '\\' { 215 chunk = chunk[1:] 216 if len(chunk) == 0 { 217 err = ErrBadPattern 218 return 219 } 220 } 221 r, n := utf8.DecodeRuneInString(chunk) 222 if r == utf8.RuneError && n == 1 { 223 err = ErrBadPattern 224 } 225 nchunk = chunk[n:] 226 if len(nchunk) == 0 { 227 err = ErrBadPattern 228 } 229 return 230 }