Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat(#9): support flags option and add casefold option #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
32 changes: 29 additions & 3 deletions wildcard.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,35 @@

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 FLAG_CASEFOLD is set, convert both pattern and string to lowercase
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