this repo has no description
at master 264 lines 6.7 kB view raw
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 19package path 20 21import ( 22 "errors" 23 "strings" 24 "unicode/utf8" 25) 26 27// ErrBadPattern indicates a pattern was malformed. 28var ErrBadPattern = errors.New("syntax error in pattern") 29 30var 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. 59func Match(pattern, name string, o OS) (matched bool, err error) { 60 os := getOS(o) 61Pattern: 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. 118func 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 129Scan: 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 ?. 155func 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. 243func 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}