-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com>
- Loading branch information
Showing
5 changed files
with
783 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
// 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" | ||
"unicode/utf8" | ||
|
||
"internal/bytealg" | ||
) | ||
|
||
// ErrBadPattern indicates a pattern was malformed. | ||
var ErrBadPattern = errors.New("syntax error in pattern") | ||
|
||
// Match reports whether name matches the shell pattern. | ||
// The pattern syntax is: | ||
// | ||
// pattern: | ||
// { term } | ||
// term: | ||
// '*' matches any sequence of non-/ characters | ||
// '?' matches any single non-/ 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. | ||
func Match(pattern, name string) (matched bool, err error) { | ||
Pattern: | ||
for len(pattern) > 0 { | ||
var star bool | ||
var chunk string | ||
star, chunk, pattern = scanChunk(pattern) | ||
if star && chunk == "" { | ||
// Trailing * matches rest of string unless it has a /. | ||
return bytealg.IndexByteString(name, '/') < 0, nil | ||
} | ||
// Look for match at current position. | ||
t, ok, err := matchChunk(chunk, name) | ||
// 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] != '/'; i++ { | ||
t, ok, err := matchChunk(chunk, name[i+1:]) | ||
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 | ||
} | ||
} | ||
} | ||
// Before returning false with no error, | ||
// check that the remainder of the pattern is syntactically valid. | ||
for len(pattern) > 0 { | ||
_, chunk, pattern = scanChunk(pattern) | ||
if _, _, err := matchChunk(chunk, ""); 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) (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 '\\': | ||
// 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) (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); err != nil { | ||
return "", false, err | ||
} | ||
hi = lo | ||
if chunk[0] == '-' { | ||
if hi, chunk, err = getEsc(chunk[1:]); err != nil { | ||
return "", false, err | ||
} | ||
} | ||
if lo <= r && r <= hi { | ||
match = true | ||
} | ||
nrange++ | ||
} | ||
if match == negated { | ||
failed = true | ||
} | ||
|
||
case '?': | ||
if !failed { | ||
if s[0] == '/' { | ||
failed = true | ||
} | ||
_, n := utf8.DecodeRuneInString(s) | ||
s = s[n:] | ||
} | ||
chunk = chunk[1:] | ||
|
||
case '\\': | ||
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) (r rune, nchunk string, err error) { | ||
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { | ||
err = ErrBadPattern | ||
return | ||
} | ||
if chunk[0] == '\\' { | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Copyright 2009 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 "testing" | ||
|
||
type MatchTest struct { | ||
pattern, s string | ||
match bool | ||
err error | ||
} | ||
|
||
var matchTests = []MatchTest{ | ||
{"abc", "abc", true, nil}, | ||
{"*", "abc", true, nil}, | ||
{"*c", "abc", true, nil}, | ||
{"a*", "a", true, nil}, | ||
{"a*", "abc", true, nil}, | ||
{"a*", "ab/c", false, nil}, | ||
{"a*/b", "abc/b", true, nil}, | ||
{"a*/b", "a/c/b", false, nil}, | ||
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, | ||
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, | ||
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, | ||
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, | ||
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, | ||
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, | ||
{"ab[c]", "abc", true, nil}, | ||
{"ab[b-d]", "abc", true, nil}, | ||
{"ab[e-g]", "abc", false, nil}, | ||
{"ab[^c]", "abc", false, nil}, | ||
{"ab[^b-d]", "abc", false, nil}, | ||
{"ab[^e-g]", "abc", true, nil}, | ||
{"a\\*b", "a*b", true, nil}, | ||
{"a\\*b", "ab", false, nil}, | ||
{"a?b", "a☺b", true, nil}, | ||
{"a[^a]b", "a☺b", true, nil}, | ||
{"a???b", "a☺b", false, nil}, | ||
{"a[^a][^a][^a]b", "a☺b", false, nil}, | ||
{"[a-ζ]*", "α", true, nil}, | ||
{"*[a-ζ]", "A", false, nil}, | ||
{"a?b", "a/b", false, nil}, | ||
{"a*b", "a/b", false, nil}, | ||
{"[\\]a]", "]", true, nil}, | ||
{"[\\-]", "-", true, nil}, | ||
{"[x\\-]", "x", true, nil}, | ||
{"[x\\-]", "-", true, nil}, | ||
{"[x\\-]", "z", false, nil}, | ||
{"[\\-x]", "x", true, nil}, | ||
{"[\\-x]", "-", true, nil}, | ||
{"[\\-x]", "a", false, nil}, | ||
{"[]a]", "]", false, ErrBadPattern}, | ||
{"[-]", "-", false, ErrBadPattern}, | ||
{"[x-]", "x", false, ErrBadPattern}, | ||
{"[x-]", "-", false, ErrBadPattern}, | ||
{"[x-]", "z", false, ErrBadPattern}, | ||
{"[-x]", "x", false, ErrBadPattern}, | ||
{"[-x]", "-", false, ErrBadPattern}, | ||
{"[-x]", "a", false, ErrBadPattern}, | ||
{"\\", "a", false, ErrBadPattern}, | ||
{"[a-b-c]", "a", false, ErrBadPattern}, | ||
{"[", "a", false, ErrBadPattern}, | ||
{"[^", "a", false, ErrBadPattern}, | ||
{"[^bc", "a", false, ErrBadPattern}, | ||
{"a[", "a", false, ErrBadPattern}, | ||
{"a[", "ab", false, ErrBadPattern}, | ||
{"a[", "x", false, ErrBadPattern}, | ||
{"a/b[", "x", false, ErrBadPattern}, | ||
{"*x", "xxx", true, nil}, | ||
} | ||
|
||
func TestMatch(t *testing.T) { | ||
for _, tt := range matchTests { | ||
ok, err := Match(tt.pattern, tt.s) | ||
if ok != tt.match || err != tt.err { | ||
t.Errorf("Match(%#q, %#q) = %v, %v want %v, %v", tt.pattern, tt.s, ok, err, tt.match, tt.err) | ||
} | ||
} | ||
} |
Oops, something went wrong.