cuelang.org/go@v0.10.1/pkg/path/match.go (about) 1 // Copyright 2020 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Copyright 2010 The Go Authors. All rights reserved. 16 // Use of this source code is governed by a BSD-style 17 // license that can be found in the LICENSE file. 18 19 package path 20 21 import ( 22 "errors" 23 "strings" 24 "unicode/utf8" 25 ) 26 27 // ErrBadPattern indicates a pattern was malformed. 28 var ErrBadPattern = errors.New("syntax error in pattern") 29 30 var errStarStarDisallowed = errors.New("'**' is not supported in patterns as of yet") 31 32 // Match reports whether name matches the shell file name pattern. 33 // The pattern syntax is: 34 // 35 // pattern: 36 // { term } 37 // term: 38 // '*' matches any sequence of non-Separator characters 39 // '?' matches any single non-Separator character 40 // '[' [ '^' ] { character-range } ']' 41 // character class (must be non-empty) 42 // c matches character c (c != '*', '?', '\\', '[') 43 // '\\' c matches character c 44 // 45 // character-range: 46 // c matches character c (c != '\\', '-', ']') 47 // '\\' c matches character c 48 // lo '-' hi matches character c for lo <= c <= hi 49 // 50 // Match requires pattern to match all of name, not just a substring. 51 // The only possible returned error is ErrBadPattern, when pattern 52 // is malformed. 53 // 54 // On Windows, escaping is disabled. Instead, '\\' is treated as 55 // path separator. 56 // 57 // A pattern may not contain '**', as a wildcard matching separator characters 58 // is not supported at this time. 59 func Match(pattern, name string, o OS) (matched bool, err error) { 60 os := getOS(o) 61 Pattern: 62 for len(pattern) > 0 { 63 var star bool 64 var chunk string 65 star, chunk, pattern, err = scanChunk(pattern, os) 66 if err != nil { 67 return false, err 68 } 69 if star && chunk == "" { 70 // Trailing * matches rest of string unless it has a /. 71 return !strings.Contains(name, string(os.Separator)), nil 72 } 73 // Look for match at current position. 74 t, ok, err := matchChunk(chunk, name, os) 75 // if we're the last chunk, make sure we've exhausted the name 76 // otherwise we'll give a false result even if we could still match 77 // using the star 78 if ok && (len(t) == 0 || len(pattern) > 0) { 79 name = t 80 continue 81 } 82 if err != nil { 83 return false, err 84 } 85 if star { 86 // Look for match skipping i+1 bytes. 87 // Cannot skip /. 88 for i := 0; i < len(name) && name[i] != os.Separator; i++ { 89 t, ok, err := matchChunk(chunk, name[i+1:], os) 90 if ok { 91 // if we're the last chunk, make sure we exhausted the name 92 if len(pattern) == 0 && len(t) > 0 { 93 continue 94 } 95 name = t 96 continue Pattern 97 } 98 if err != nil { 99 return false, err 100 } 101 } 102 } 103 // Before returning false with no error, 104 // check that the remainder of the pattern is syntactically valid. 105 for len(pattern) > 0 { 106 _, _, pattern, err = scanChunk(pattern, os) 107 if err != nil { 108 return false, err 109 } 110 } 111 return false, nil 112 } 113 return len(name) == 0, nil 114 } 115 116 // scanChunk gets the next segment of pattern, which is a non-star string 117 // possibly preceded by a star. 118 func scanChunk(pattern string, os os) (star bool, chunk, rest string, _ error) { 119 if len(pattern) > 0 && pattern[0] == '*' { 120 pattern = pattern[1:] 121 star = true 122 if len(pattern) > 0 && pattern[0] == '*' { 123 // ** is disallowed to allow for future functionality. 124 return false, "", "", errStarStarDisallowed 125 } 126 } 127 inrange := false 128 var i int 129 Scan: 130 for i = 0; i < len(pattern); i++ { 131 switch pattern[i] { 132 case '\\': 133 if !os.isWindows() { 134 // error check handled in matchChunk: bad pattern. 135 if i+1 < len(pattern) { 136 i++ 137 } 138 } 139 case '[': 140 inrange = true 141 case ']': 142 inrange = false 143 case '*': 144 if !inrange { 145 break Scan 146 } 147 } 148 } 149 return star, pattern[0:i], pattern[i:], nil 150 } 151 152 // matchChunk checks whether chunk matches the beginning of s. 153 // If so, it returns the remainder of s (after the match). 154 // Chunk is all single-character operators: literals, char classes, and ?. 155 func matchChunk(chunk, s string, os os) (rest string, ok bool, err error) { 156 // failed records whether the match has failed. 157 // After the match fails, the loop continues on processing chunk, 158 // checking that the pattern is well-formed but no longer reading s. 159 failed := false 160 for len(chunk) > 0 { 161 if !failed && len(s) == 0 { 162 failed = true 163 } 164 switch chunk[0] { 165 case '[': 166 // character class 167 var r rune 168 if !failed { 169 var n int 170 r, n = utf8.DecodeRuneInString(s) 171 s = s[n:] 172 } 173 chunk = chunk[1:] 174 // possibly negated 175 negated := false 176 if len(chunk) > 0 && chunk[0] == '^' { 177 negated = true 178 chunk = chunk[1:] 179 } 180 // parse all ranges 181 match := false 182 nrange := 0 183 for { 184 if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { 185 chunk = chunk[1:] 186 break 187 } 188 var lo, hi rune 189 if lo, chunk, err = getEsc(chunk, os); err != nil { 190 return "", false, err 191 } 192 hi = lo 193 if chunk[0] == '-' { 194 if hi, chunk, err = getEsc(chunk[1:], os); err != nil { 195 return "", false, err 196 } 197 } 198 if lo <= r && r <= hi { 199 match = true 200 } 201 nrange++ 202 } 203 if match == negated { 204 failed = true 205 } 206 207 case '?': 208 if !failed { 209 if s[0] == os.Separator { 210 failed = true 211 } 212 _, n := utf8.DecodeRuneInString(s) 213 s = s[n:] 214 } 215 chunk = chunk[1:] 216 217 case '\\': 218 if !os.isWindows() { 219 chunk = chunk[1:] 220 if len(chunk) == 0 { 221 return "", false, ErrBadPattern 222 } 223 } 224 fallthrough 225 226 default: 227 if !failed { 228 if chunk[0] != s[0] { 229 failed = true 230 } 231 s = s[1:] 232 } 233 chunk = chunk[1:] 234 } 235 } 236 if failed { 237 return "", false, nil 238 } 239 return s, true, nil 240 } 241 242 // getEsc gets a possibly-escaped character from chunk, for a character class. 243 func getEsc(chunk string, os os) (r rune, nchunk string, err error) { 244 if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { 245 err = ErrBadPattern 246 return 247 } 248 if chunk[0] == '\\' && !os.isWindows() { 249 chunk = chunk[1:] 250 if len(chunk) == 0 { 251 err = ErrBadPattern 252 return 253 } 254 } 255 r, n := utf8.DecodeRuneInString(chunk) 256 if r == utf8.RuneError && n == 1 { 257 err = ErrBadPattern 258 } 259 nchunk = chunk[n:] 260 if len(nchunk) == 0 { 261 err = ErrBadPattern 262 } 263 return 264 }