diff --git a/compiler/compiler.go b/compiler/compiler.go index fc70cae..02e7de8 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -43,37 +43,43 @@ func optimizeMatcher(matcher match.Matcher) match.Matcher { return m } - leftNil := m.Left == nil - rightNil := m.Right == nil - + var ( + leftNil = m.Left == nil + rightNil = m.Right == nil + ) if leftNil && rightNil { return match.NewText(r.Str) } _, leftSuper := m.Left.(match.Super) lp, leftPrefix := m.Left.(match.Prefix) + la, leftAny := m.Left.(match.Any) _, rightSuper := m.Right.(match.Super) rs, rightSuffix := m.Right.(match.Suffix) + ra, rightAny := m.Right.(match.Any) - if leftSuper && rightSuper { + switch { + case leftSuper && rightSuper: return match.NewContains(r.Str, false) - } - if leftSuper && rightNil { + case leftSuper && rightNil: return match.NewSuffix(r.Str) - } - if rightSuper && leftNil { + case rightSuper && leftNil: return match.NewPrefix(r.Str) - } - if leftNil && rightSuffix { + case leftNil && rightSuffix: return match.NewPrefixSuffix(r.Str, rs.Suffix) - } - if rightNil && leftPrefix { + case rightNil && leftPrefix: return match.NewPrefixSuffix(lp.Prefix, r.Str) + + case rightNil && leftAny: + return match.NewSuffixAny(r.Str, la.Separators) + + case leftNil && rightAny: + return match.NewPrefixAny(r.Str, ra.Separators) } return m diff --git a/glob_test.go b/glob_test.go index 86ccbe9..9893451 100644 --- a/glob_test.go +++ b/glob_test.go @@ -120,6 +120,16 @@ func TestGlob(t *testing.T) { glob(true, "/{rate,[0-9][0-9][0-9]}*", "/rate"), glob(true, "/{rate,[a-z][a-z][a-z]}*", "/usd"), + glob(true, "{*.google.*,*.yandex.*}", "www.google.com", '.'), + glob(true, "{*.google.*,*.yandex.*}", "www.yandex.com", '.'), + glob(false, "{*.google.*,*.yandex.*}", "yandex.com", '.'), + glob(false, "{*.google.*,*.yandex.*}", "google.com", '.'), + + glob(true, "{*.google.*,yandex.*}", "www.google.com", '.'), + glob(true, "{*.google.*,yandex.*}", "yandex.com", '.'), + glob(false, "{*.google.*,yandex.*}", "www.yandex.com", '.'), + glob(false, "{*.google.*,yandex.*}", "google.com", '.'), + glob(true, pattern_all, fixture_all_match), glob(false, pattern_all, fixture_all_mismatch), @@ -149,16 +159,16 @@ func TestGlob(t *testing.T) { glob(true, pattern_prefix_suffix, fixture_prefix_suffix_match), glob(false, pattern_prefix_suffix, fixture_prefix_suffix_mismatch), } { - g, err := Compile(test.pattern, test.delimiters...) - if err != nil { - t.Errorf("parsing pattern %q error: %s", test.pattern, err) - continue - } - - result := g.Match(test.match) - if result != test.should { - t.Errorf("pattern %q matching %q should be %v but got %v\n%s", test.pattern, test.match, test.should, result, g) - } + t.Run("", func(t *testing.T) { + g := MustCompile(test.pattern, test.delimiters...) + result := g.Match(test.match) + if result != test.should { + t.Errorf( + "pattern %q matching %q should be %v but got %v\n%s", + test.pattern, test.match, test.should, result, g, + ) + } + }) } } diff --git a/match/any_of.go b/match/any_of.go index 6037043..8e65356 100644 --- a/match/any_of.go +++ b/match/any_of.go @@ -1,8 +1,6 @@ package match -import ( - "fmt" -) +import "fmt" type AnyOf struct { Matchers Matchers diff --git a/match/prefix_any.go b/match/prefix_any.go new file mode 100644 index 0000000..8ee58fe --- /dev/null +++ b/match/prefix_any.go @@ -0,0 +1,55 @@ +package match + +import ( + "fmt" + "strings" + "unicode/utf8" + + sutil "github.com/gobwas/glob/util/strings" +) + +type PrefixAny struct { + Prefix string + Separators []rune +} + +func NewPrefixAny(s string, sep []rune) PrefixAny { + return PrefixAny{s, sep} +} + +func (self PrefixAny) Index(s string) (int, []int) { + idx := strings.Index(s, self.Prefix) + if idx == -1 { + return -1, nil + } + + n := len(self.Prefix) + sub := s[idx+n:] + i := sutil.IndexAnyRunes(sub, self.Separators) + if i > -1 { + sub = sub[:i] + } + + seg := acquireSegments(len(sub) + 1) + seg = append(seg, n) + for i, r := range sub { + seg = append(seg, n+i+utf8.RuneLen(r)) + } + + return idx, seg +} + +func (self PrefixAny) Len() int { + return lenNo +} + +func (self PrefixAny) Match(s string) bool { + if !strings.HasPrefix(s, self.Prefix) { + return false + } + return sutil.IndexAnyRunes(s[len(self.Prefix):], self.Separators) == -1 +} + +func (self PrefixAny) String() string { + return fmt.Sprintf("", self.Prefix, string(self.Separators)) +} diff --git a/match/prefix_any_test.go b/match/prefix_any_test.go new file mode 100644 index 0000000..e6990e3 --- /dev/null +++ b/match/prefix_any_test.go @@ -0,0 +1,47 @@ +package match + +import ( + "reflect" + "testing" +) + +func TestPrefixAnyIndex(t *testing.T) { + for id, test := range []struct { + prefix string + separators []rune + fixture string + index int + segments []int + }{ + { + "ab", + []rune{'.'}, + "ab", + 0, + []int{2}, + }, + { + "ab", + []rune{'.'}, + "abc", + 0, + []int{2, 3}, + }, + { + "ab", + []rune{'.'}, + "qw.abcd.efg", + 3, + []int{2, 3, 4}, + }, + } { + p := NewPrefixAny(test.prefix, test.separators) + index, segments := p.Index(test.fixture) + if index != test.index { + t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) + } + if !reflect.DeepEqual(segments, test.segments) { + t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) + } + } +} diff --git a/match/suffix_any.go b/match/suffix_any.go new file mode 100644 index 0000000..c5106f8 --- /dev/null +++ b/match/suffix_any.go @@ -0,0 +1,43 @@ +package match + +import ( + "fmt" + "strings" + + sutil "github.com/gobwas/glob/util/strings" +) + +type SuffixAny struct { + Suffix string + Separators []rune +} + +func NewSuffixAny(s string, sep []rune) SuffixAny { + return SuffixAny{s, sep} +} + +func (self SuffixAny) Index(s string) (int, []int) { + idx := strings.Index(s, self.Suffix) + if idx == -1 { + return -1, nil + } + + i := sutil.LastIndexAnyRunes(s[:idx], self.Separators) + 1 + + return i, []int{idx + len(self.Suffix) - i} +} + +func (self SuffixAny) Len() int { + return lenNo +} + +func (self SuffixAny) Match(s string) bool { + if !strings.HasSuffix(s, self.Suffix) { + return false + } + return sutil.IndexAnyRunes(s[:len(s)-len(self.Suffix)], self.Separators) == -1 +} + +func (self SuffixAny) String() string { + return fmt.Sprintf("", string(self.Separators), self.Suffix) +} diff --git a/match/suffix_any_test.go b/match/suffix_any_test.go new file mode 100644 index 0000000..eed6e59 --- /dev/null +++ b/match/suffix_any_test.go @@ -0,0 +1,47 @@ +package match + +import ( + "reflect" + "testing" +) + +func TestSuffixAnyIndex(t *testing.T) { + for id, test := range []struct { + suffix string + separators []rune + fixture string + index int + segments []int + }{ + { + "ab", + []rune{'.'}, + "ab", + 0, + []int{2}, + }, + { + "ab", + []rune{'.'}, + "cab", + 0, + []int{3}, + }, + { + "ab", + []rune{'.'}, + "qw.cdab.efg", + 3, + []int{4}, + }, + } { + p := NewSuffixAny(test.suffix, test.separators) + index, segments := p.Index(test.fixture) + if index != test.index { + t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) + } + if !reflect.DeepEqual(segments, test.segments) { + t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) + } + } +} diff --git a/util/strings/strings.go b/util/strings/strings.go index 1be48f7..e8ee192 100644 --- a/util/strings/strings.go +++ b/util/strings/strings.go @@ -1,6 +1,9 @@ package strings -import "strings" +import ( + "strings" + "unicode/utf8" +) func IndexAnyRunes(s string, rs []rune) int { for _, r := range rs { @@ -11,3 +14,26 @@ func IndexAnyRunes(s string, rs []rune) int { return -1 } + +func LastIndexAnyRunes(s string, rs []rune) int { + for _, r := range rs { + i := -1 + if 0 <= r && r < utf8.RuneSelf { + i = strings.LastIndexByte(s, byte(r)) + } else { + sub := s + for len(sub) > 0 { + j := strings.IndexRune(s, r) + if j == -1 { + break + } + i = j + sub = sub[i+1:] + } + } + if i != -1 { + return i + } + } + return -1 +}