|
|
|
@ -16,123 +16,68 @@ |
|
|
|
|
|
|
|
|
|
package wildcard |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"strings" |
|
|
|
|
"unicode/utf8" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// Match - finds whether the text matches/satisfies the pattern string.
|
|
|
|
|
// MatchSimple - finds whether the text matches/satisfies the pattern string.
|
|
|
|
|
// supports only '*' wildcard in the pattern.
|
|
|
|
|
// considers a file system path as a flat name space.
|
|
|
|
|
func Match(pattern, text string) bool { |
|
|
|
|
func MatchSimple(pattern, name string) bool { |
|
|
|
|
if pattern == "" { |
|
|
|
|
return text == pattern |
|
|
|
|
return name == pattern |
|
|
|
|
} |
|
|
|
|
if pattern == "*" { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
parts := strings.Split(pattern, "*") |
|
|
|
|
if len(parts) == 1 { |
|
|
|
|
return text == pattern |
|
|
|
|
} |
|
|
|
|
tGlob := strings.HasSuffix(pattern, "*") |
|
|
|
|
end := len(parts) - 1 |
|
|
|
|
if !strings.HasPrefix(text, parts[0]) { |
|
|
|
|
return false |
|
|
|
|
rname := make([]rune, 0, len(name)) |
|
|
|
|
rpattern := make([]rune, 0, len(pattern)) |
|
|
|
|
for _, r := range name { |
|
|
|
|
rname = append(rname, r) |
|
|
|
|
} |
|
|
|
|
for i := 1; i < end; i++ { |
|
|
|
|
if !strings.Contains(text, parts[i]) { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
idx := strings.Index(text, parts[i]) + len(parts[i]) |
|
|
|
|
text = text[idx:] |
|
|
|
|
for _, r := range pattern { |
|
|
|
|
rpattern = append(rpattern, r) |
|
|
|
|
} |
|
|
|
|
return tGlob || strings.HasSuffix(text, parts[end]) |
|
|
|
|
simple := true // Does only wildcard '*' match.
|
|
|
|
|
return deepMatchRune(rname, rpattern, simple) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MatchExtended - finds whether the text matches/satisfies the pattern string.
|
|
|
|
|
// Match - finds whether the text matches/satisfies the pattern string.
|
|
|
|
|
// supports '*' and '?' wildcards in the pattern string.
|
|
|
|
|
// unlike path.Match(), considers a path as a flat name space while matching the pattern.
|
|
|
|
|
// The difference is illustrated in the example here https://play.golang.org/p/Ega9qgD4Qz .
|
|
|
|
|
func MatchExtended(pattern, name string) (matched bool) { |
|
|
|
|
Pattern: |
|
|
|
|
for len(pattern) > 0 { |
|
|
|
|
var star bool |
|
|
|
|
var chunk string |
|
|
|
|
star, chunk, pattern = scanChunk(pattern) |
|
|
|
|
if star && chunk == "" { |
|
|
|
|
// Trailing * matches rest of string.
|
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
// Look for match at current position.
|
|
|
|
|
t, ok := 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 star { |
|
|
|
|
// Look for match skipping i+1 bytes.
|
|
|
|
|
for i := 0; i < len(name); i++ { |
|
|
|
|
t, ok := 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 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return false |
|
|
|
|
func Match(pattern, name string) (matched bool) { |
|
|
|
|
if pattern == "" { |
|
|
|
|
return name == pattern |
|
|
|
|
} |
|
|
|
|
return len(name) == 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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 |
|
|
|
|
if pattern == "*" { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
inrange := false |
|
|
|
|
var i int |
|
|
|
|
Scan: |
|
|
|
|
for i = 0; i < len(pattern); i++ { |
|
|
|
|
switch pattern[i] { |
|
|
|
|
case '*': |
|
|
|
|
if !inrange { |
|
|
|
|
break Scan |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
rname := make([]rune, 0, len(name)) |
|
|
|
|
rpattern := make([]rune, 0, len(pattern)) |
|
|
|
|
for _, r := range name { |
|
|
|
|
rname = append(rname, r) |
|
|
|
|
} |
|
|
|
|
for _, r := range pattern { |
|
|
|
|
rpattern = append(rpattern, r) |
|
|
|
|
} |
|
|
|
|
return star, pattern[0:i], pattern[i:] |
|
|
|
|
simple := false // Does extended wildcard '*' and '?' match.
|
|
|
|
|
return deepMatchRune(rname, rpattern, simple) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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) { |
|
|
|
|
for len(chunk) > 0 { |
|
|
|
|
if len(s) == 0 { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
switch chunk[0] { |
|
|
|
|
case '?': |
|
|
|
|
_, n := utf8.DecodeRuneInString(s) |
|
|
|
|
s = s[n:] |
|
|
|
|
chunk = chunk[1:] |
|
|
|
|
func deepMatchRune(str, pattern []rune, simple bool) bool { |
|
|
|
|
for len(pattern) > 0 { |
|
|
|
|
switch pattern[0] { |
|
|
|
|
default: |
|
|
|
|
if chunk[0] != s[0] { |
|
|
|
|
return |
|
|
|
|
if len(str) == 0 || str[0] != pattern[0] { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
case '?': |
|
|
|
|
if len(str) == 0 && !simple { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
s = s[1:] |
|
|
|
|
chunk = chunk[1:] |
|
|
|
|
case '*': |
|
|
|
|
return deepMatchRune(str, pattern[1:], simple) || |
|
|
|
|
(len(str) > 0 && deepMatchRune(str[1:], pattern, simple)) |
|
|
|
|
} |
|
|
|
|
str = str[1:] |
|
|
|
|
pattern = pattern[1:] |
|
|
|
|
} |
|
|
|
|
return s, true |
|
|
|
|
return len(str) == 0 && len(pattern) == 0 |
|
|
|
|
} |
|
|
|
|