| // Copyright 2020 CUE Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Copyright 2010 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package path |
| |
| import ( |
| "errors" |
| "strings" |
| "unicode/utf8" |
| ) |
| |
| // ErrBadPattern indicates a pattern was malformed. |
| var ErrBadPattern = errors.New("syntax error in pattern") |
| |
| // Match reports whether name matches the shell file name pattern. |
| // The pattern syntax is: |
| // |
| // pattern: |
| // { term } |
| // term: |
| // '*' matches any sequence of non-Separator characters |
| // '?' matches any single non-Separator character |
| // '[' [ '^' ] { character-range } ']' |
| // character class (must be non-empty) |
| // c matches character c (c != '*', '?', '\\', '[') |
| // '\\' c matches character c |
| // |
| // character-range: |
| // c matches character c (c != '\\', '-', ']') |
| // '\\' c matches character c |
| // lo '-' hi matches character c for lo <= c <= hi |
| // |
| // Match requires pattern to match all of name, not just a substring. |
| // The only possible returned error is ErrBadPattern, when pattern |
| // is malformed. |
| // |
| // On Windows, escaping is disabled. Instead, '\\' is treated as |
| // path separator. |
| // |
| func Match(pattern, name string, o OS) (matched bool, err error) { |
| os := getOS(o) |
| Pattern: |
| for len(pattern) > 0 { |
| var star bool |
| var chunk string |
| star, chunk, pattern = scanChunk(pattern, os) |
| if star && chunk == "" { |
| // Trailing * matches rest of string unless it has a /. |
| return !strings.Contains(name, string(os.Separator)), nil |
| } |
| // Look for match at current position. |
| t, ok, err := matchChunk(chunk, name, os) |
| // if we're the last chunk, make sure we've exhausted the name |
| // otherwise we'll give a false result even if we could still match |
| // using the star |
| if ok && (len(t) == 0 || len(pattern) > 0) { |
| name = t |
| continue |
| } |
| if err != nil { |
| return false, err |
| } |
| if star { |
| // Look for match skipping i+1 bytes. |
| // Cannot skip /. |
| for i := 0; i < len(name) && name[i] != os.Separator; i++ { |
| t, ok, err := matchChunk(chunk, name[i+1:], os) |
| if ok { |
| // if we're the last chunk, make sure we exhausted the name |
| if len(pattern) == 0 && len(t) > 0 { |
| continue |
| } |
| name = t |
| continue Pattern |
| } |
| if err != nil { |
| return false, err |
| } |
| } |
| } |
| return false, nil |
| } |
| return len(name) == 0, nil |
| } |
| |
| // scanChunk gets the next segment of pattern, which is a non-star string |
| // possibly preceded by a star. |
| func scanChunk(pattern string, os os) (star bool, chunk, rest string) { |
| for len(pattern) > 0 && pattern[0] == '*' { |
| pattern = pattern[1:] |
| star = true |
| } |
| inrange := false |
| var i int |
| Scan: |
| for i = 0; i < len(pattern); i++ { |
| switch pattern[i] { |
| case '\\': |
| if !os.isWindows() { |
| // error check handled in matchChunk: bad pattern. |
| if i+1 < len(pattern) { |
| i++ |
| } |
| } |
| case '[': |
| inrange = true |
| case ']': |
| inrange = false |
| case '*': |
| if !inrange { |
| break Scan |
| } |
| } |
| } |
| return star, pattern[0:i], pattern[i:] |
| } |
| |
| // matchChunk checks whether chunk matches the beginning of s. |
| // If so, it returns the remainder of s (after the match). |
| // Chunk is all single-character operators: literals, char classes, and ?. |
| func matchChunk(chunk, s string, os os) (rest string, ok bool, err error) { |
| // failed records whether the match has failed. |
| // After the match fails, the loop continues on processing chunk, |
| // checking that the pattern is well-formed but no longer reading s. |
| failed := false |
| for len(chunk) > 0 { |
| if !failed && len(s) == 0 { |
| failed = true |
| } |
| switch chunk[0] { |
| case '[': |
| // character class |
| var r rune |
| if !failed { |
| var n int |
| r, n = utf8.DecodeRuneInString(s) |
| s = s[n:] |
| } |
| chunk = chunk[1:] |
| // possibly negated |
| negated := false |
| if len(chunk) > 0 && chunk[0] == '^' { |
| negated = true |
| chunk = chunk[1:] |
| } |
| // parse all ranges |
| match := false |
| nrange := 0 |
| for { |
| if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { |
| chunk = chunk[1:] |
| break |
| } |
| var lo, hi rune |
| if lo, chunk, err = getEsc(chunk, os); err != nil { |
| return "", false, err |
| } |
| hi = lo |
| if chunk[0] == '-' { |
| if hi, chunk, err = getEsc(chunk[1:], os); err != nil { |
| return "", false, err |
| } |
| } |
| if lo <= r && r <= hi { |
| match = true |
| } |
| nrange++ |
| } |
| if match == negated { |
| failed = true |
| } |
| |
| case '?': |
| if !failed { |
| if s[0] == os.Separator { |
| failed = true |
| } |
| _, n := utf8.DecodeRuneInString(s) |
| s = s[n:] |
| } |
| chunk = chunk[1:] |
| |
| case '\\': |
| if !os.isWindows() { |
| chunk = chunk[1:] |
| if len(chunk) == 0 { |
| return "", false, ErrBadPattern |
| } |
| } |
| fallthrough |
| |
| default: |
| if !failed { |
| if chunk[0] != s[0] { |
| failed = true |
| } |
| s = s[1:] |
| } |
| chunk = chunk[1:] |
| } |
| } |
| if failed { |
| return "", false, nil |
| } |
| return s, true, nil |
| } |
| |
| // getEsc gets a possibly-escaped character from chunk, for a character class. |
| func getEsc(chunk string, os os) (r rune, nchunk string, err error) { |
| if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { |
| err = ErrBadPattern |
| return |
| } |
| if chunk[0] == '\\' && !os.isWindows() { |
| chunk = chunk[1:] |
| if len(chunk) == 0 { |
| err = ErrBadPattern |
| return |
| } |
| } |
| r, n := utf8.DecodeRuneInString(chunk) |
| if r == utf8.RuneError && n == 1 { |
| err = ErrBadPattern |
| } |
| nchunk = chunk[n:] |
| if len(nchunk) == 0 { |
| err = ErrBadPattern |
| } |
| return |
| } |