-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from ucan-wg/v1-fuzz-match-and-simple-glob
Rewrite simple glob match + FuzzMatch
- Loading branch information
Showing
8 changed files
with
207 additions
and
10 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package policy | ||
|
||
import "fmt" | ||
|
||
type glob string | ||
|
||
// parseGlob ensures that the pattern conforms to the spec: only '*' and escaped '\*' are allowed. | ||
func parseGlob(pattern string) (glob, error) { | ||
for i := 0; i < len(pattern); i++ { | ||
if pattern[i] == '*' { | ||
continue | ||
} | ||
if pattern[i] == '\\' && i+1 < len(pattern) && pattern[i+1] == '*' { | ||
i++ // skip the escaped '*' | ||
continue | ||
} | ||
if pattern[i] == '\\' && i+1 < len(pattern) { | ||
i++ // skip the escaped character | ||
continue | ||
} | ||
if pattern[i] == '\\' { | ||
return "", fmt.Errorf("invalid escape sequence") | ||
} | ||
} | ||
|
||
return glob(pattern), nil | ||
} | ||
|
||
func mustParseGlob(pattern string) glob { | ||
g, err := parseGlob(pattern) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return g | ||
} | ||
|
||
// Match matches a string against the glob pattern with * wildcards, handling escaped '\*' literals. | ||
func (pattern glob) Match(str string) bool { | ||
// i is the index for the pattern | ||
// j is the index for the string | ||
var i, j int | ||
|
||
// starIdx keeps track of the position of the last * in the pattern. | ||
// matchIdx keeps track of the position in the string where the last * matched. | ||
var starIdx, matchIdx int = -1, -1 | ||
|
||
for j < len(str) { | ||
if i < len(pattern) && (pattern[i] == str[j] || pattern[i] == '\\' && i+1 < len(pattern) && pattern[i+1] == str[j]) { | ||
// characters match or if there's an escaped character that matches | ||
if pattern[i] == '\\' { | ||
// skip the escape character | ||
i++ | ||
} | ||
i++ | ||
j++ | ||
} else if i < len(pattern) && pattern[i] == '*' { | ||
// there's a * wildcard in the pattern | ||
starIdx = i | ||
matchIdx = j | ||
i++ | ||
} else if starIdx != -1 { | ||
// there's a previous * wildcard, backtrack | ||
i = starIdx + 1 | ||
matchIdx++ | ||
j = matchIdx | ||
} else { | ||
// no match found | ||
return false | ||
} | ||
} | ||
|
||
// check for remaining characters in the pattern | ||
for i < len(pattern) && pattern[i] == '*' { | ||
i++ | ||
} | ||
|
||
// the entire pattern is processed, it's a match | ||
return i == len(pattern) | ||
} |
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,73 @@ | ||
package policy | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestSimpleGlobMatch(t *testing.T) { | ||
tests := []struct { | ||
pattern string | ||
str string | ||
matches bool | ||
}{ | ||
// Basic matching | ||
{"*", "anything", true}, | ||
{"a*", "abc", true}, | ||
{"*c", "abc", true}, | ||
{"a*c", "abc", true}, | ||
{"a*c", "abxc", true}, | ||
{"a*c", "ac", true}, | ||
{"a*c", "a", false}, | ||
{"a*c", "ab", false}, | ||
|
||
// Escaped characters | ||
{"a\\*c", "a*c", true}, | ||
{"a\\*c", "abc", false}, | ||
|
||
// Mixed wildcards and literals | ||
{"a*b*c", "abc", true}, | ||
{"a*b*c", "aXbYc", true}, | ||
{"a*b*c", "aXbY", false}, | ||
{"a*b*c", "abYc", true}, | ||
{"a*b*c", "aXbc", true}, | ||
{"a*b*c", "aXbYcZ", false}, | ||
|
||
// Edge cases | ||
{"", "", true}, | ||
{"", "a", false}, | ||
{"*", "", true}, | ||
{"*", "a", true}, | ||
{"\\*", "*", true}, | ||
{"\\*", "a", false}, | ||
|
||
// Specified test cases | ||
{"Alice\\*, Bob*, Carol.", "Alice*, Bob, Carol.", true}, | ||
{"Alice\\*, Bob*, Carol.", "Alice*, Bob, Dan, Erin, Carol.", true}, | ||
{"Alice\\*, Bob*, Carol.", "Alice*, Bob , Carol.", true}, | ||
{"Alice\\*, Bob*, Carol.", "Alice*, Bob*, Carol.", true}, | ||
{"Alice\\*, Bob*, Carol.", "Alice*, Bob, Carol", false}, | ||
{"Alice\\*, Bob*, Carol.", "Alice*, Bob*, Carol!", false}, | ||
{"Alice\\*, Bob*, Carol.", "Alice, Bob, Carol.", false}, | ||
{"Alice\\*, Bob*, Carol.", "Alice Cooper, Bob, Carol.", false}, | ||
{"Alice\\*, Bob*, Carol.", " Alice*, Bob, Carol. ", false}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.pattern+"_"+tt.str, func(t *testing.T) { | ||
g, err := parseGlob(tt.pattern) | ||
require.NoError(t, err) | ||
require.Equal(t, tt.matches, g.Match(tt.str)) | ||
}) | ||
} | ||
} | ||
|
||
func BenchmarkGlob(b *testing.B) { | ||
b.ReportAllocs() | ||
|
||
for i := 0; i < b.N; i++ { | ||
g := mustParseGlob("Alice\\*, Bob*, Carol.") | ||
g.Match("Alice*, Bob*, Carol!") | ||
} | ||
} |
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
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
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