Skip to content

Commit

Permalink
✨ feat(#9): support flags option and add casefold option
Browse files Browse the repository at this point in the history
Signed-off-by: Adrien Kara <[email protected]>
  • Loading branch information
IGLOU-EU committed May 8, 2023
1 parent 80faff1 commit a4c5115
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 94 deletions.
94 changes: 57 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ So, this library is a very simple, very fast and a more flexible alternative to
There are no dependencies and is alocation free. πŸ₯³

## 🧰 Features
There are the supported patterns operators:
### Supported patterns operators
- `*` match zero or more characters
- `?` match zero or one character
- `.` match exactly one character

### Supported flags
- `FLAG_NONE` no flag
- `FLAG_CASEFOLD` ignore case

This is irrelevant for now, but you can combine them with `|` operator.
For example: `FLAG_CASEFOLD|FLAG_NONE`

Because of the `strings.ToLower` operation, using `FLAG_CASEFOLD` flag is slower and result allocation...
Even if this function is not self recursive, prefer to prepare your data before, because `strings.ToLower` is called for pattern and given string.

## 🧐 How to
>⚠️ WARNING: Unlike the GNU "libc", this library have no equivalent to "FNM_FILE_NAME".
>To do this you can use "path/filepath" https://pkg.go.dev/path/filepath#Match
Expand Down Expand Up @@ -51,49 +61,59 @@ The tested fonctions are:
- filepath.Match(t.pattern, t.name)
- oldMatchSimple(t.pattern, t.name) `From the commit a899be92514ed08aa5271bc3b93320b719ce2114`
- oldMatch(t.pattern, t.name) `From the commit a899be92514ed08aa5271bc3b93320b719ce2114`
- Match(t.pattern, t.name) `The actual implementation`
- Match(t.pattern, t.name, FLAG_NONE) `The actual implementation`
- Match(t.pattern, t.name, FLAG_CASEFOLD) `The actual implementation, but with a strings.ToLower operation`

```bash
goos: linux
goarch: amd64
pkg: github.com/IGLOU-EU/go-wildcard
cpu: AMD Ryzen 7 PRO 6850U with Radeon Graphics
BenchmarkRegex/0-16 1000000 1322 ns/op 765 B/op 9 allocs/op
BenchmarkRegex/1-16 134851 10461 ns/op 6592 B/op 26 allocs/op
BenchmarkRegex/2-16 5871756 280.8 ns/op 160 B/op 2 allocs/op
BenchmarkRegex/3-16 108092 12096 ns/op 6647 B/op 26 allocs/op
BenchmarkRegex/4-16 92070 13924 ns/op 7436 B/op 38 allocs/op
BenchmarkRegex/5-16 4702372 277.6 ns/op 160 B/op 2 allocs/op

BenchmarkFilepath/0-16 548771120 1.836 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/1-16 9451810 117.8 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/2-16 151409767 7.853 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/3-16 8656650 143.8 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/4-16 67589983 18.33 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/5-16 4805623 240.3 ns/op 0 B/op 0 allocs/op

BenchmarkOldMatchSimple/0-16 1000000000 0.4971 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatchSimple/1-16 4738023 292.5 ns/op 176 B/op 1 allocs/op
BenchmarkOldMatchSimple/2-16 1000000000 0.9130 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatchSimple/3-16 1688683 763.8 ns/op 352 B/op 2 allocs/op
BenchmarkOldMatchSimple/4-16 2242758 514.0 ns/op 336 B/op 2 allocs/op
BenchmarkOldMatchSimple/5-16 10435084 110.7 ns/op 0 B/op 0 allocs/op

BenchmarkOldMatch/0-16 1000000000 0.4568 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatch/1-16 5300286 286.8 ns/op 176 B/op 1 allocs/op
BenchmarkOldMatch/2-16 1000000000 0.7127 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatch/3-16 1608777 772.9 ns/op 352 B/op 2 allocs/op
BenchmarkOldMatch/4-16 2283015 548.9 ns/op 336 B/op 2 allocs/op
BenchmarkOldMatch/5-16 10425933 113.0 ns/op 0 B/op 0 allocs/op

BenchmarkMatch/0-16 654065395 1.774 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/1-16 352847413 2.973 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/2-16 652602918 1.822 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/3-16 412494770 2.940 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/4-16 197380323 5.447 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/5-16 39741439 27.96 ns/op 0 B/op 0 allocs/op

BenchmarkRegex/0-16 1000000 1344 ns/op 766 B/op 9 allocs/op
BenchmarkRegex/1-16 115628 11343 ns/op 6592 B/op 26 allocs/op
BenchmarkRegex/2-16 5015937 263.6 ns/op 160 B/op 2 allocs/op
BenchmarkRegex/3-16 109844 13607 ns/op 6646 B/op 26 allocs/op
BenchmarkRegex/4-16 119311 13226 ns/op 7440 B/op 38 allocs/op
BenchmarkRegex/5-16 5427733 247.1 ns/op 160 B/op 2 allocs/op

BenchmarkFilepath/0-16 479149471 2.109 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/1-16 9473259 119.8 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/2-16 151451250 7.945 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/3-16 8295160 144.5 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/4-16 57564092 19.49 ns/op 0 B/op 0 allocs/op
BenchmarkFilepath/5-16 4911076 240.2 ns/op 0 B/op 0 allocs/op

BenchmarkOldMatchSimple/0-16 1000000000 0.4878 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatchSimple/1-16 4959806 283.4 ns/op 176 B/op 1 allocs/op
BenchmarkOldMatchSimple/2-16 1000000000 0.9326 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatchSimple/3-16 1910574 725.4 ns/op 352 B/op 2 allocs/op
BenchmarkOldMatchSimple/4-16 2918268 511.0 ns/op 336 B/op 2 allocs/op
BenchmarkOldMatchSimple/5-16 10512922 113.3 ns/op 0 B/op 0 allocs/op

BenchmarkOldMatch/0-16 1000000000 0.5195 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatch/1-16 5393407 279.9 ns/op 176 B/op 1 allocs/op
BenchmarkOldMatch/2-16 1000000000 0.7294 ns/op 0 B/op 0 allocs/op
BenchmarkOldMatch/3-16 2123334 775.0 ns/op 352 B/op 2 allocs/op
BenchmarkOldMatch/4-16 2616628 496.1 ns/op 336 B/op 2 allocs/op
BenchmarkOldMatch/5-16 10818404 114.0 ns/op 0 B/op 0 allocs/op

BenchmarkMatch/0-16 454892713 2.281 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/1-16 349304181 2.928 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/2-16 476728009 2.127 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/3-16 290518609 3.822 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/4-16 124291632 8.709 ns/op 0 B/op 0 allocs/op
BenchmarkMatch/5-16 23469135 48.56 ns/op 0 B/op 0 allocs/op

BenchmarkMatchCasefold/0-16 7673000 216.3 ns/op 48 B/op 1 allocs/op
BenchmarkMatchCasefold/1-16 7057550 223.6 ns/op 48 B/op 1 allocs/op
BenchmarkMatchCasefold/2-16 6580888 215.7 ns/op 48 B/op 1 allocs/op
BenchmarkMatchCasefold/3-16 3193930 451.2 ns/op 96 B/op 2 allocs/op
BenchmarkMatchCasefold/4-16 3213775 473.1 ns/op 96 B/op 2 allocs/op
BenchmarkMatchCasefold/5-16 2305406 481.4 ns/op 32 B/op 1 allocs/op

PASS
ok github.com/IGLOU-EU/go-wildcard 48.707s
ok github.com/IGLOU-EU/go-wildcard 60.533s
```

## πŸ•° History
Expand Down
31 changes: 28 additions & 3 deletions wildcard.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,34 @@

package wildcard

// Match returns true if the pattern matches the s string.
// The pattern can contain the wildcard characters '?' '.' and '*'.
func Match(pattern, s string) bool {
import "strings"

// Flags type is used to specify matching options for the Match function
type Flags uint8

// Constants definition for matching options
const (
FLAG_NONE = 1 << iota // No special behavior
FLAG_CASEFOLD // Case-insensitive match
)

// Match function checks if the given string s matches the wildcard pattern
// with specified matching options (Flags).
//
// Supported wildcards:
// `*` match zero or more characters
// `?` match zero or one character
// `.` match exactly one character
//
// Supported matching options:
// FLAG_NONE - No special behavior
// FLAG_CASEFOLD - Case-insensitive match
func Match(pattern, s string, option Flags) bool {
if option&FLAG_CASEFOLD != 0 {
s = strings.ToLower(s)
pattern = strings.ToLower(pattern)
}

if pattern == "" {
return s == pattern
}
Expand Down
12 changes: 11 additions & 1 deletion wildcard_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,17 @@ func BenchmarkMatch(b *testing.B) {
for i, t := range TestSet {
b.Run(fmt.Sprint(i), func(b *testing.B) {
for i := 0; i < b.N; i++ {
wildcard.Match(t.pattern, t.name)
wildcard.Match(t.pattern, t.name, wildcard.FLAG_NONE)
}
})
}
}

func BenchmarkMatchCasefold(b *testing.B) {
for i, t := range TestSet {
b.Run(fmt.Sprint(i), func(b *testing.B) {
for i := 0; i < b.N; i++ {
wildcard.Match(t.pattern, t.name, wildcard.FLAG_CASEFOLD)
}
})
}
Expand Down
109 changes: 56 additions & 53 deletions wildcard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,70 +21,73 @@ func TestMatch(t *testing.T) {
cases := []struct {
s string
pattern string
flag wildcard.Flags
result bool
}{
{"", "", true},
{"", "*", true},
{"", "**", true},
{"", "?", true},
{"", "??", true},
{"", "?*", true},
{"", "*?", true},
{"", ".", false},
{"", ".?", false},
{"", "?.", false},
{"", ".*", false},
{"", "*.", false},
{"", "*.?", false},
{"", "?.*", false},
{"", "", wildcard.FLAG_NONE, true},
{"", "*", wildcard.FLAG_NONE, true},
{"", "**", wildcard.FLAG_NONE, true},
{"", "?", wildcard.FLAG_NONE, true},
{"", "??", wildcard.FLAG_NONE, true},
{"", "?*", wildcard.FLAG_NONE, true},
{"", "*?", wildcard.FLAG_NONE, true},
{"", ".", wildcard.FLAG_NONE, false},
{"", ".?", wildcard.FLAG_NONE, false},
{"", "?.", wildcard.FLAG_NONE, false},
{"", ".*", wildcard.FLAG_NONE, false},
{"", "*.", wildcard.FLAG_NONE, false},
{"", "*.?", wildcard.FLAG_NONE, false},
{"", "?.*", wildcard.FLAG_NONE, false},

{"a", "", false},
{"a", "a", true},
{"a", "*", true},
{"a", "**", true},
{"a", "?", true},
{"a", "??", true},
{"a", ".", true},
{"a", ".?", true},
{"a", "?.", false},
{"a", ".*", true},
{"a", "*.", true},
{"a", "*.?", true},
{"a", "?.*", false},
{"a", "", wildcard.FLAG_NONE, false},
{"a", "a", wildcard.FLAG_NONE, true},
{"a", "*", wildcard.FLAG_NONE, true},
{"a", "**", wildcard.FLAG_NONE, true},
{"a", "?", wildcard.FLAG_NONE, true},
{"a", "??", wildcard.FLAG_NONE, true},
{"a", ".", wildcard.FLAG_NONE, true},
{"a", ".?", wildcard.FLAG_NONE, true},
{"a", "?.", wildcard.FLAG_NONE, false},
{"a", ".*", wildcard.FLAG_NONE, true},
{"a", "*.", wildcard.FLAG_NONE, true},
{"a", "*.?", wildcard.FLAG_NONE, true},
{"a", "?.*", wildcard.FLAG_NONE, false},

{"match the exact string", "match the exact string", true},
{"do not match a different string", "this is a different string", false},
{"Match The Exact String WITH DIFFERENT CASE", "Match The Exact String WITH DIFFERENT CASE", true},
{"do not match a different string WITH DIFFERENT CASE", "this is a different string WITH DIFFERENT CASE", false},
{"Do Not Match The Exact String With Different Case", "do not match the exact string with different case", false},
{"match an emoji πŸ˜ƒ", "match an emoji πŸ˜ƒ", true},
{"do not match because of different emoji πŸ˜ƒ", "do not match because of different emoji πŸ˜„", false},
{"πŸŒ…β˜•οΈπŸ“°πŸ‘¨β€πŸ’ΌπŸ‘©β€πŸ’ΌπŸ’πŸ–₯οΈπŸ’ΌπŸ’»πŸ“ŠπŸ“ˆπŸ“‰πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸπŸ•°οΈπŸ’ͺπŸ‹οΈβ€β™‚οΈπŸ‹οΈβ€β™€οΈπŸ‹οΈβ€β™‚οΈπŸ’ΌπŸš΄β€β™‚οΈπŸš΄β€β™€οΈπŸš΄β€β™‚οΈπŸ›€πŸ’€πŸŒƒ", "πŸŒ…β˜•οΈπŸ“°πŸ‘¨β€πŸ’ΌπŸ‘©β€πŸ’ΌπŸ’πŸ–₯οΈπŸ’ΌπŸ’»πŸ“ŠπŸ“ˆπŸ“‰πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸπŸ•°οΈπŸ’ͺπŸ‹οΈβ€β™‚οΈπŸ‹οΈβ€β™€οΈπŸ‹οΈβ€β™‚οΈπŸ’ΌπŸš΄β€β™‚οΈπŸš΄β€β™€οΈπŸš΄β€β™‚οΈπŸ›€πŸ’€πŸŒƒ", true},
{"πŸŒ…β˜•οΈπŸ“°πŸ‘¨β€πŸ’ΌπŸ‘©β€πŸ’ΌπŸ’πŸ–₯οΈπŸ’ΌπŸ’»πŸ“ŠπŸ“ˆπŸ“‰πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸπŸ•°οΈπŸ’ͺπŸ‹οΈβ€β™‚οΈπŸ‹οΈβ€β™€οΈπŸ‹οΈβ€β™‚οΈπŸ’ΌπŸš΄β€β™‚οΈπŸš΄β€β™€οΈπŸš΄β€β™‚οΈπŸ›€πŸ’€πŸŒƒ", "πŸ¦ŒπŸ‡πŸ¦‘πŸΏοΈπŸŒ²πŸŒ³πŸ°πŸŒ³πŸŒ²πŸŒžπŸŒ§οΈβ„οΈπŸŒ¬οΈβ›ˆοΈπŸ”₯πŸŽ„πŸŽ…πŸŽπŸŽ‰πŸŽŠπŸ₯³πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ’πŸ‘ͺπŸ’–πŸ‘©β€πŸ’ΌπŸ›€", false},
{"match the exact string", "match the exact string", wildcard.FLAG_NONE, true},
{"do not match a different string", "this is a different string", wildcard.FLAG_NONE, false},
{"Match The Exact String WITH DIFFERENT CASE", "Match The Exact String WITH DIFFERENT CASE", wildcard.FLAG_NONE, true},
{"do not match a different string WITH DIFFERENT CASE", "this is a different string WITH DIFFERENT CASE", wildcard.FLAG_NONE, false},
{"Do Not Match The Exact String With Different Case", "do not match the exact string with different case", wildcard.FLAG_NONE, false},
{"match an emoji πŸ˜ƒ", "match an emoji πŸ˜ƒ", wildcard.FLAG_NONE, true},
{"do not match because of different emoji πŸ˜ƒ", "do not match because of different emoji πŸ˜„", wildcard.FLAG_NONE, false},
{"πŸŒ…β˜•οΈπŸ“°πŸ‘¨β€πŸ’ΌπŸ‘©β€πŸ’ΌπŸ’πŸ–₯οΈπŸ’ΌπŸ’»πŸ“ŠπŸ“ˆπŸ“‰πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸπŸ•°οΈπŸ’ͺπŸ‹οΈβ€β™‚οΈπŸ‹οΈβ€β™€οΈπŸ‹οΈβ€β™‚οΈπŸ’ΌπŸš΄β€β™‚οΈπŸš΄β€β™€οΈπŸš΄β€β™‚οΈπŸ›€πŸ’€πŸŒƒ", "πŸŒ…β˜•οΈπŸ“°πŸ‘¨β€πŸ’ΌπŸ‘©β€πŸ’ΌπŸ’πŸ–₯οΈπŸ’ΌπŸ’»πŸ“ŠπŸ“ˆπŸ“‰πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸπŸ•°οΈπŸ’ͺπŸ‹οΈβ€β™‚οΈπŸ‹οΈβ€β™€οΈπŸ‹οΈβ€β™‚οΈπŸ’ΌπŸš΄β€β™‚οΈπŸš΄β€β™€οΈπŸš΄β€β™‚οΈπŸ›€πŸ’€πŸŒƒ", wildcard.FLAG_NONE, true},
{"πŸŒ…β˜•οΈπŸ“°πŸ‘¨β€πŸ’ΌπŸ‘©β€πŸ’ΌπŸ’πŸ–₯οΈπŸ’ΌπŸ’»πŸ“ŠπŸ“ˆπŸ“‰πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸπŸ•°οΈπŸ’ͺπŸ‹οΈβ€β™‚οΈπŸ‹οΈβ€β™€οΈπŸ‹οΈβ€β™‚οΈπŸ’ΌπŸš΄β€β™‚οΈπŸš΄β€β™€οΈπŸš΄β€β™‚οΈπŸ›€πŸ’€πŸŒƒ", "πŸ¦ŒπŸ‡πŸ¦‘πŸΏοΈπŸŒ²πŸŒ³πŸ°πŸŒ³πŸŒ²πŸŒžπŸŒ§οΈβ„οΈπŸŒ¬οΈβ›ˆοΈπŸ”₯πŸŽ„πŸŽ…πŸŽπŸŽ‰πŸŽŠπŸ₯³πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ’πŸ‘ͺπŸ’–πŸ‘©β€πŸ’ΌπŸ›€", wildcard.FLAG_NONE, false},

{"match a string with a *", "match a string *", true},
{"match a string with a * at the beginning", "* at the beginning", true},
{"match a string with two *", "match * with *", true},
{"do not match a string with extra and a *", "do not match a string * with more", false},
{"match a string with a *", "match a string *", wildcard.FLAG_NONE, true},
{"match a string with a * at the beginning", "* at the beginning", wildcard.FLAG_NONE, true},
{"match a string with two *", "match * with *", wildcard.FLAG_NONE, true},
{"do not match a string with extra and a *", "do not match a string * with more", wildcard.FLAG_NONE, false},

{"match a string with a ?", "match ? string with a ?", true},
{"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", true},
{"match a string with two ?", "match a string with two ??", true},
{"match a optional char with a ?", "match a optional? char with a ?", true},
{"match a optional char with a ?", "match a optional? char with a ?", true},
{"do not match a string with extra and a ?", "do not match ? string with extra and a ? like this", false},
{"match a string with a ?", "match ? string with a ?", wildcard.FLAG_NONE, true},
{"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", wildcard.FLAG_NONE, true},
{"match a string with two ?", "match a string with two ??", wildcard.FLAG_NONE, true},
{"match a optional char with a ?", "match a optional? char with a ?", wildcard.FLAG_NONE, true},
{"match a optional char with a ?", "match a optional? char with a ?", wildcard.FLAG_NONE, true},
{"do not match a string with extra and a ?", "do not match ? string with extra and a ? like this", wildcard.FLAG_NONE, false},

{"match a string with a .", "match . string with a .", true},
{"match a string with a . at the beginning", ".atch a string with a . at the beginning", true},
{"match a string with two .", "match a ..ring with two .", true},
{"do not match a string with extra .", "do not match a string with extra ..", false},
{"match a string with a .", "match . string with a .", wildcard.FLAG_NONE, true},
{"match a string with a . at the beginning", ".atch a string with a . at the beginning", wildcard.FLAG_NONE, true},
{"match a string with two .", "match a ..ring with two .", wildcard.FLAG_NONE, true},
{"do not match a string with extra .", "do not match a string with extra ..", wildcard.FLAG_NONE, false},

{"A big brown fox jumps over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", true},
{"A big brown fox fails to jump over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", false},
{"A big brown fox jumps over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", wildcard.FLAG_NONE, true},
{"A big brown fox fails to jump over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", wildcard.FLAG_NONE, false},

{"This IS a StrinG witH soMMe UppeRCase FriendS", "thIs is A stRINg wITh sOMmE uPpERcAse fRiENds", wildcard.FLAG_CASEFOLD, true},
}

for i, c := range cases {
result := wildcard.Match(c.pattern, c.s)
result := wildcard.Match(c.pattern, c.s, c.flag)
if c.result != result {
t.Errorf("Test %d: Expected `%v`, found `%v`; With Pattern: `%s` and String: `%s`", i+1, c.result, result, c.pattern, c.s)
}
Expand Down

0 comments on commit a4c5115

Please sign in to comment.