Skip to content

Commit a2d90ae

Browse files
committed
✨ feat(#9): support flags option and add casefold option
Signed-off-by: Adrien Kara <[email protected]>
1 parent 80faff1 commit a2d90ae

File tree

4 files changed

+153
-94
lines changed

4 files changed

+153
-94
lines changed

README.md

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,21 @@ So, this library is a very simple, very fast and a more flexible alternative to
1212
There are no dependencies and is alocation free. 🥳
1313

1414
## 🧰 Features
15-
There are the supported patterns operators:
15+
### Supported patterns operators
1616
- `*` match zero or more characters
1717
- `?` match zero or one character
1818
- `.` match exactly one character
1919

20+
### Supported flags
21+
- `FLAG_NONE` no flag
22+
- `FLAG_CASEFOLD` ignore case
23+
24+
This is irrelevant for now, but you can combine them with `|` operator.
25+
For example: `FLAG_CASEFOLD|FLAG_NONE`
26+
27+
Because of the `strings.ToLower` operation, using `FLAG_CASEFOLD` flag is slower and result allocation...
28+
Even if this function is not self recursive, prefer to prepare your data before, because `strings.ToLower` is called for pattern and given string.
29+
2030
## 🧐 How to
2131
>⚠️ WARNING: Unlike the GNU "libc", this library have no equivalent to "FNM_FILE_NAME".
2232
>To do this you can use "path/filepath" https://pkg.go.dev/path/filepath#Match
@@ -51,49 +61,59 @@ The tested fonctions are:
5161
- filepath.Match(t.pattern, t.name)
5262
- oldMatchSimple(t.pattern, t.name) `From the commit a899be92514ed08aa5271bc3b93320b719ce2114`
5363
- oldMatch(t.pattern, t.name) `From the commit a899be92514ed08aa5271bc3b93320b719ce2114`
54-
- Match(t.pattern, t.name) `The actual implementation`
64+
- Match(t.pattern, t.name, FLAG_NONE) `The actual implementation`
65+
- Match(t.pattern, t.name, FLAG_CASEFOLD) `The actual implementation, but with a strings.ToLower operation`
5566

5667
```bash
5768
goos: linux
5869
goarch: amd64
5970
pkg: github.com/IGLOU-EU/go-wildcard
6071
cpu: AMD Ryzen 7 PRO 6850U with Radeon Graphics
61-
BenchmarkRegex/0-16 1000000 1322 ns/op 765 B/op 9 allocs/op
62-
BenchmarkRegex/1-16 134851 10461 ns/op 6592 B/op 26 allocs/op
63-
BenchmarkRegex/2-16 5871756 280.8 ns/op 160 B/op 2 allocs/op
64-
BenchmarkRegex/3-16 108092 12096 ns/op 6647 B/op 26 allocs/op
65-
BenchmarkRegex/4-16 92070 13924 ns/op 7436 B/op 38 allocs/op
66-
BenchmarkRegex/5-16 4702372 277.6 ns/op 160 B/op 2 allocs/op
67-
68-
BenchmarkFilepath/0-16 548771120 1.836 ns/op 0 B/op 0 allocs/op
69-
BenchmarkFilepath/1-16 9451810 117.8 ns/op 0 B/op 0 allocs/op
70-
BenchmarkFilepath/2-16 151409767 7.853 ns/op 0 B/op 0 allocs/op
71-
BenchmarkFilepath/3-16 8656650 143.8 ns/op 0 B/op 0 allocs/op
72-
BenchmarkFilepath/4-16 67589983 18.33 ns/op 0 B/op 0 allocs/op
73-
BenchmarkFilepath/5-16 4805623 240.3 ns/op 0 B/op 0 allocs/op
74-
75-
BenchmarkOldMatchSimple/0-16 1000000000 0.4971 ns/op 0 B/op 0 allocs/op
76-
BenchmarkOldMatchSimple/1-16 4738023 292.5 ns/op 176 B/op 1 allocs/op
77-
BenchmarkOldMatchSimple/2-16 1000000000 0.9130 ns/op 0 B/op 0 allocs/op
78-
BenchmarkOldMatchSimple/3-16 1688683 763.8 ns/op 352 B/op 2 allocs/op
79-
BenchmarkOldMatchSimple/4-16 2242758 514.0 ns/op 336 B/op 2 allocs/op
80-
BenchmarkOldMatchSimple/5-16 10435084 110.7 ns/op 0 B/op 0 allocs/op
81-
82-
BenchmarkOldMatch/0-16 1000000000 0.4568 ns/op 0 B/op 0 allocs/op
83-
BenchmarkOldMatch/1-16 5300286 286.8 ns/op 176 B/op 1 allocs/op
84-
BenchmarkOldMatch/2-16 1000000000 0.7127 ns/op 0 B/op 0 allocs/op
85-
BenchmarkOldMatch/3-16 1608777 772.9 ns/op 352 B/op 2 allocs/op
86-
BenchmarkOldMatch/4-16 2283015 548.9 ns/op 336 B/op 2 allocs/op
87-
BenchmarkOldMatch/5-16 10425933 113.0 ns/op 0 B/op 0 allocs/op
88-
89-
BenchmarkMatch/0-16 654065395 1.774 ns/op 0 B/op 0 allocs/op
90-
BenchmarkMatch/1-16 352847413 2.973 ns/op 0 B/op 0 allocs/op
91-
BenchmarkMatch/2-16 652602918 1.822 ns/op 0 B/op 0 allocs/op
92-
BenchmarkMatch/3-16 412494770 2.940 ns/op 0 B/op 0 allocs/op
93-
BenchmarkMatch/4-16 197380323 5.447 ns/op 0 B/op 0 allocs/op
94-
BenchmarkMatch/5-16 39741439 27.96 ns/op 0 B/op 0 allocs/op
72+
73+
BenchmarkRegex/0-16 1000000 1344 ns/op 766 B/op 9 allocs/op
74+
BenchmarkRegex/1-16 115628 11343 ns/op 6592 B/op 26 allocs/op
75+
BenchmarkRegex/2-16 5015937 263.6 ns/op 160 B/op 2 allocs/op
76+
BenchmarkRegex/3-16 109844 13607 ns/op 6646 B/op 26 allocs/op
77+
BenchmarkRegex/4-16 119311 13226 ns/op 7440 B/op 38 allocs/op
78+
BenchmarkRegex/5-16 5427733 247.1 ns/op 160 B/op 2 allocs/op
79+
80+
BenchmarkFilepath/0-16 479149471 2.109 ns/op 0 B/op 0 allocs/op
81+
BenchmarkFilepath/1-16 9473259 119.8 ns/op 0 B/op 0 allocs/op
82+
BenchmarkFilepath/2-16 151451250 7.945 ns/op 0 B/op 0 allocs/op
83+
BenchmarkFilepath/3-16 8295160 144.5 ns/op 0 B/op 0 allocs/op
84+
BenchmarkFilepath/4-16 57564092 19.49 ns/op 0 B/op 0 allocs/op
85+
BenchmarkFilepath/5-16 4911076 240.2 ns/op 0 B/op 0 allocs/op
86+
87+
BenchmarkOldMatchSimple/0-16 1000000000 0.4878 ns/op 0 B/op 0 allocs/op
88+
BenchmarkOldMatchSimple/1-16 4959806 283.4 ns/op 176 B/op 1 allocs/op
89+
BenchmarkOldMatchSimple/2-16 1000000000 0.9326 ns/op 0 B/op 0 allocs/op
90+
BenchmarkOldMatchSimple/3-16 1910574 725.4 ns/op 352 B/op 2 allocs/op
91+
BenchmarkOldMatchSimple/4-16 2918268 511.0 ns/op 336 B/op 2 allocs/op
92+
BenchmarkOldMatchSimple/5-16 10512922 113.3 ns/op 0 B/op 0 allocs/op
93+
94+
BenchmarkOldMatch/0-16 1000000000 0.5195 ns/op 0 B/op 0 allocs/op
95+
BenchmarkOldMatch/1-16 5393407 279.9 ns/op 176 B/op 1 allocs/op
96+
BenchmarkOldMatch/2-16 1000000000 0.7294 ns/op 0 B/op 0 allocs/op
97+
BenchmarkOldMatch/3-16 2123334 775.0 ns/op 352 B/op 2 allocs/op
98+
BenchmarkOldMatch/4-16 2616628 496.1 ns/op 336 B/op 2 allocs/op
99+
BenchmarkOldMatch/5-16 10818404 114.0 ns/op 0 B/op 0 allocs/op
100+
101+
BenchmarkMatch/0-16 454892713 2.281 ns/op 0 B/op 0 allocs/op
102+
BenchmarkMatch/1-16 349304181 2.928 ns/op 0 B/op 0 allocs/op
103+
BenchmarkMatch/2-16 476728009 2.127 ns/op 0 B/op 0 allocs/op
104+
BenchmarkMatch/3-16 290518609 3.822 ns/op 0 B/op 0 allocs/op
105+
BenchmarkMatch/4-16 124291632 8.709 ns/op 0 B/op 0 allocs/op
106+
BenchmarkMatch/5-16 23469135 48.56 ns/op 0 B/op 0 allocs/op
107+
108+
BenchmarkMatchCasefold/0-16 7673000 216.3 ns/op 48 B/op 1 allocs/op
109+
BenchmarkMatchCasefold/1-16 7057550 223.6 ns/op 48 B/op 1 allocs/op
110+
BenchmarkMatchCasefold/2-16 6580888 215.7 ns/op 48 B/op 1 allocs/op
111+
BenchmarkMatchCasefold/3-16 3193930 451.2 ns/op 96 B/op 2 allocs/op
112+
BenchmarkMatchCasefold/4-16 3213775 473.1 ns/op 96 B/op 2 allocs/op
113+
BenchmarkMatchCasefold/5-16 2305406 481.4 ns/op 32 B/op 1 allocs/op
114+
95115
PASS
96-
ok github.com/IGLOU-EU/go-wildcard 48.707s
116+
ok github.com/IGLOU-EU/go-wildcard 60.533s
97117
```
98118

99119
## 🕰 History

wildcard.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,35 @@
88

99
package wildcard
1010

11-
// Match returns true if the pattern matches the s string.
12-
// The pattern can contain the wildcard characters '?' '.' and '*'.
13-
func Match(pattern, s string) bool {
11+
import "strings"
12+
13+
// Flags type is used to specify matching options for the Match function
14+
type Flags uint8
15+
16+
// Constants definition for matching options
17+
const (
18+
FLAG_NONE = 1 << iota // No special behavior
19+
FLAG_CASEFOLD // Case-insensitive match
20+
)
21+
22+
// Match function checks if the given string s matches the wildcard pattern
23+
// with specified matching options (Flags).
24+
//
25+
// Supported wildcards:
26+
// `*` match zero or more characters
27+
// `?` match zero or one character
28+
// `.` match exactly one character
29+
//
30+
// Supported matching options:
31+
// FLAG_NONE - No special behavior
32+
// FLAG_CASEFOLD - Case-insensitive match
33+
func Match(pattern, s string, option Flags) bool {
34+
// If FLAG_CASEFOLD is set, convert both pattern and string to lowercase
35+
if option&FLAG_CASEFOLD != 0 {
36+
s = strings.ToLower(s)
37+
pattern = strings.ToLower(pattern)
38+
}
39+
1440
if pattern == "" {
1541
return s == pattern
1642
}

wildcard_bench_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,17 @@ func BenchmarkMatch(b *testing.B) {
6565
for i, t := range TestSet {
6666
b.Run(fmt.Sprint(i), func(b *testing.B) {
6767
for i := 0; i < b.N; i++ {
68-
wildcard.Match(t.pattern, t.name)
68+
wildcard.Match(t.pattern, t.name, wildcard.FLAG_NONE)
69+
}
70+
})
71+
}
72+
}
73+
74+
func BenchmarkMatchCasefold(b *testing.B) {
75+
for i, t := range TestSet {
76+
b.Run(fmt.Sprint(i), func(b *testing.B) {
77+
for i := 0; i < b.N; i++ {
78+
wildcard.Match(t.pattern, t.name, wildcard.FLAG_CASEFOLD)
6979
}
7080
})
7181
}

wildcard_test.go

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,70 +21,73 @@ func TestMatch(t *testing.T) {
2121
cases := []struct {
2222
s string
2323
pattern string
24+
flag wildcard.Flags
2425
result bool
2526
}{
26-
{"", "", true},
27-
{"", "*", true},
28-
{"", "**", true},
29-
{"", "?", true},
30-
{"", "??", true},
31-
{"", "?*", true},
32-
{"", "*?", true},
33-
{"", ".", false},
34-
{"", ".?", false},
35-
{"", "?.", false},
36-
{"", ".*", false},
37-
{"", "*.", false},
38-
{"", "*.?", false},
39-
{"", "?.*", false},
27+
{"", "", wildcard.FLAG_NONE, true},
28+
{"", "*", wildcard.FLAG_NONE, true},
29+
{"", "**", wildcard.FLAG_NONE, true},
30+
{"", "?", wildcard.FLAG_NONE, true},
31+
{"", "??", wildcard.FLAG_NONE, true},
32+
{"", "?*", wildcard.FLAG_NONE, true},
33+
{"", "*?", wildcard.FLAG_NONE, true},
34+
{"", ".", wildcard.FLAG_NONE, false},
35+
{"", ".?", wildcard.FLAG_NONE, false},
36+
{"", "?.", wildcard.FLAG_NONE, false},
37+
{"", ".*", wildcard.FLAG_NONE, false},
38+
{"", "*.", wildcard.FLAG_NONE, false},
39+
{"", "*.?", wildcard.FLAG_NONE, false},
40+
{"", "?.*", wildcard.FLAG_NONE, false},
4041

41-
{"a", "", false},
42-
{"a", "a", true},
43-
{"a", "*", true},
44-
{"a", "**", true},
45-
{"a", "?", true},
46-
{"a", "??", true},
47-
{"a", ".", true},
48-
{"a", ".?", true},
49-
{"a", "?.", false},
50-
{"a", ".*", true},
51-
{"a", "*.", true},
52-
{"a", "*.?", true},
53-
{"a", "?.*", false},
42+
{"a", "", wildcard.FLAG_NONE, false},
43+
{"a", "a", wildcard.FLAG_NONE, true},
44+
{"a", "*", wildcard.FLAG_NONE, true},
45+
{"a", "**", wildcard.FLAG_NONE, true},
46+
{"a", "?", wildcard.FLAG_NONE, true},
47+
{"a", "??", wildcard.FLAG_NONE, true},
48+
{"a", ".", wildcard.FLAG_NONE, true},
49+
{"a", ".?", wildcard.FLAG_NONE, true},
50+
{"a", "?.", wildcard.FLAG_NONE, false},
51+
{"a", ".*", wildcard.FLAG_NONE, true},
52+
{"a", "*.", wildcard.FLAG_NONE, true},
53+
{"a", "*.?", wildcard.FLAG_NONE, true},
54+
{"a", "?.*", wildcard.FLAG_NONE, false},
5455

55-
{"match the exact string", "match the exact string", true},
56-
{"do not match a different string", "this is a different string", false},
57-
{"Match The Exact String WITH DIFFERENT CASE", "Match The Exact String WITH DIFFERENT CASE", true},
58-
{"do not match a different string WITH DIFFERENT CASE", "this is a different string WITH DIFFERENT CASE", false},
59-
{"Do Not Match The Exact String With Different Case", "do not match the exact string with different case", false},
60-
{"match an emoji 😃", "match an emoji 😃", true},
61-
{"do not match because of different emoji 😃", "do not match because of different emoji 😄", false},
62-
{"🌅☕️📰👨‍💼👩‍💼🏢🖥️💼💻📊📈📉👨‍👩‍👧‍👦🍝🕰️💪🏋️‍♂️🏋️‍♀️🏋️‍♂️💼🚴‍♂️🚴‍♀️🚴‍♂️🛀💤🌃", "🌅☕️📰👨‍💼👩‍💼🏢🖥️💼💻📊📈📉👨‍👩‍👧‍👦🍝🕰️💪🏋️‍♂️🏋️‍♀️🏋️‍♂️💼🚴‍♂️🚴‍♀️🚴‍♂️🛀💤🌃", true},
63-
{"🌅☕️📰👨‍💼👩‍💼🏢🖥️💼💻📊📈📉👨‍👩‍👧‍👦🍝🕰️💪🏋️‍♂️🏋️‍♀️🏋️‍♂️💼🚴‍♂️🚴‍♀️🚴‍♂️🛀💤🌃", "🦌🐇🦡🐿️🌲🌳🏰🌳🌲🌞🌧️❄️🌬️⛈️🔥🎄🎅🎁🎉🎊🥳👨‍👩‍👧‍👦💏👪💖👩‍💼🛀", false},
56+
{"match the exact string", "match the exact string", wildcard.FLAG_NONE, true},
57+
{"do not match a different string", "this is a different string", wildcard.FLAG_NONE, false},
58+
{"Match The Exact String WITH DIFFERENT CASE", "Match The Exact String WITH DIFFERENT CASE", wildcard.FLAG_NONE, true},
59+
{"do not match a different string WITH DIFFERENT CASE", "this is a different string WITH DIFFERENT CASE", wildcard.FLAG_NONE, false},
60+
{"Do Not Match The Exact String With Different Case", "do not match the exact string with different case", wildcard.FLAG_NONE, false},
61+
{"match an emoji 😃", "match an emoji 😃", wildcard.FLAG_NONE, true},
62+
{"do not match because of different emoji 😃", "do not match because of different emoji 😄", wildcard.FLAG_NONE, false},
63+
{"🌅☕️📰👨‍💼👩‍💼🏢🖥️💼💻📊📈📉👨‍👩‍👧‍👦🍝🕰️💪🏋️‍♂️🏋️‍♀️🏋️‍♂️💼🚴‍♂️🚴‍♀️🚴‍♂️🛀💤🌃", "🌅☕️📰👨‍💼👩‍💼🏢🖥️💼💻📊📈📉👨‍👩‍👧‍👦🍝🕰️💪🏋️‍♂️🏋️‍♀️🏋️‍♂️💼🚴‍♂️🚴‍♀️🚴‍♂️🛀💤🌃", wildcard.FLAG_NONE, true},
64+
{"🌅☕️📰👨‍💼👩‍💼🏢🖥️💼💻📊📈📉👨‍👩‍👧‍👦🍝🕰️💪🏋️‍♂️🏋️‍♀️🏋️‍♂️💼🚴‍♂️🚴‍♀️🚴‍♂️🛀💤🌃", "🦌🐇🦡🐿️🌲🌳🏰🌳🌲🌞🌧️❄️🌬️⛈️🔥🎄🎅🎁🎉🎊🥳👨‍👩‍👧‍👦💏👪💖👩‍💼🛀", wildcard.FLAG_NONE, false},
6465

65-
{"match a string with a *", "match a string *", true},
66-
{"match a string with a * at the beginning", "* at the beginning", true},
67-
{"match a string with two *", "match * with *", true},
68-
{"do not match a string with extra and a *", "do not match a string * with more", false},
66+
{"match a string with a *", "match a string *", wildcard.FLAG_NONE, true},
67+
{"match a string with a * at the beginning", "* at the beginning", wildcard.FLAG_NONE, true},
68+
{"match a string with two *", "match * with *", wildcard.FLAG_NONE, true},
69+
{"do not match a string with extra and a *", "do not match a string * with more", wildcard.FLAG_NONE, false},
6970

70-
{"match a string with a ?", "match ? string with a ?", true},
71-
{"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", true},
72-
{"match a string with two ?", "match a string with two ??", true},
73-
{"match a optional char with a ?", "match a optional? char with a ?", true},
74-
{"match a optional char with a ?", "match a optional? char with a ?", true},
75-
{"do not match a string with extra and a ?", "do not match ? string with extra and a ? like this", false},
71+
{"match a string with a ?", "match ? string with a ?", wildcard.FLAG_NONE, true},
72+
{"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", wildcard.FLAG_NONE, true},
73+
{"match a string with two ?", "match a string with two ??", wildcard.FLAG_NONE, true},
74+
{"match a optional char with a ?", "match a optional? char with a ?", wildcard.FLAG_NONE, true},
75+
{"match a optional char with a ?", "match a optional? char with a ?", wildcard.FLAG_NONE, true},
76+
{"do not match a string with extra and a ?", "do not match ? string with extra and a ? like this", wildcard.FLAG_NONE, false},
7677

77-
{"match a string with a .", "match . string with a .", true},
78-
{"match a string with a . at the beginning", ".atch a string with a . at the beginning", true},
79-
{"match a string with two .", "match a ..ring with two .", true},
80-
{"do not match a string with extra .", "do not match a string with extra ..", false},
78+
{"match a string with a .", "match . string with a .", wildcard.FLAG_NONE, true},
79+
{"match a string with a . at the beginning", ".atch a string with a . at the beginning", wildcard.FLAG_NONE, true},
80+
{"match a string with two .", "match a ..ring with two .", wildcard.FLAG_NONE, true},
81+
{"do not match a string with extra .", "do not match a string with extra ..", wildcard.FLAG_NONE, false},
8182

82-
{"A big brown fox jumps over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", true},
83-
{"A big brown fox fails to jump over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", false},
83+
{"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},
84+
{"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},
85+
86+
{"This IS a StrinG witH soMMe UppeRCase FriendS", "thIs is A stRINg wITh sOMmE uPpERcAse fRiENds", wildcard.FLAG_CASEFOLD, true},
8487
}
8588

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

0 commit comments

Comments
 (0)