forked from mirror/glob
Avoid btree Index() call on pattern with separators.
To avoid hard Index()'ing of given text with btree matcher we implement an prefix_any and suffix_any matchers that can work well in many cases. BTree matcher Index() will be implemented in upcoming commits to prevent same bugs. Fixes #23
This commit is contained in:
parent
51eb1ee00b
commit
034ebb20be
|
@ -43,37 +43,43 @@ func optimizeMatcher(matcher match.Matcher) match.Matcher {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
leftNil := m.Left == nil
|
var (
|
||||||
rightNil := m.Right == nil
|
leftNil = m.Left == nil
|
||||||
|
rightNil = m.Right == nil
|
||||||
|
)
|
||||||
if leftNil && rightNil {
|
if leftNil && rightNil {
|
||||||
return match.NewText(r.Str)
|
return match.NewText(r.Str)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, leftSuper := m.Left.(match.Super)
|
_, leftSuper := m.Left.(match.Super)
|
||||||
lp, leftPrefix := m.Left.(match.Prefix)
|
lp, leftPrefix := m.Left.(match.Prefix)
|
||||||
|
la, leftAny := m.Left.(match.Any)
|
||||||
|
|
||||||
_, rightSuper := m.Right.(match.Super)
|
_, rightSuper := m.Right.(match.Super)
|
||||||
rs, rightSuffix := m.Right.(match.Suffix)
|
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)
|
return match.NewContains(r.Str, false)
|
||||||
}
|
|
||||||
|
|
||||||
if leftSuper && rightNil {
|
case leftSuper && rightNil:
|
||||||
return match.NewSuffix(r.Str)
|
return match.NewSuffix(r.Str)
|
||||||
}
|
|
||||||
|
|
||||||
if rightSuper && leftNil {
|
case rightSuper && leftNil:
|
||||||
return match.NewPrefix(r.Str)
|
return match.NewPrefix(r.Str)
|
||||||
}
|
|
||||||
|
|
||||||
if leftNil && rightSuffix {
|
case leftNil && rightSuffix:
|
||||||
return match.NewPrefixSuffix(r.Str, rs.Suffix)
|
return match.NewPrefixSuffix(r.Str, rs.Suffix)
|
||||||
}
|
|
||||||
|
|
||||||
if rightNil && leftPrefix {
|
case rightNil && leftPrefix:
|
||||||
return match.NewPrefixSuffix(lp.Prefix, r.Str)
|
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
|
return m
|
||||||
|
|
24
glob_test.go
24
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,[0-9][0-9][0-9]}*", "/rate"),
|
||||||
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/usd"),
|
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(true, pattern_all, fixture_all_match),
|
||||||
glob(false, pattern_all, fixture_all_mismatch),
|
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(true, pattern_prefix_suffix, fixture_prefix_suffix_match),
|
||||||
glob(false, pattern_prefix_suffix, fixture_prefix_suffix_mismatch),
|
glob(false, pattern_prefix_suffix, fixture_prefix_suffix_mismatch),
|
||||||
} {
|
} {
|
||||||
g, err := Compile(test.pattern, test.delimiters...)
|
t.Run("", func(t *testing.T) {
|
||||||
if err != nil {
|
g := MustCompile(test.pattern, test.delimiters...)
|
||||||
t.Errorf("parsing pattern %q error: %s", test.pattern, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result := g.Match(test.match)
|
result := g.Match(test.match)
|
||||||
if result != test.should {
|
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.Errorf(
|
||||||
|
"pattern %q matching %q should be %v but got %v\n%s",
|
||||||
|
test.pattern, test.match, test.should, result, g,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package match
|
package match
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AnyOf struct {
|
type AnyOf struct {
|
||||||
Matchers Matchers
|
Matchers Matchers
|
||||||
|
|
|
@ -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("<prefix_any:%s![%s]>", self.Prefix, string(self.Separators))
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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("<suffix_any:![%s]%s>", string(self.Separators), self.Suffix)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
package strings
|
package strings
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
func IndexAnyRunes(s string, rs []rune) int {
|
func IndexAnyRunes(s string, rs []rune) int {
|
||||||
for _, r := range rs {
|
for _, r := range rs {
|
||||||
|
@ -11,3 +14,26 @@ func IndexAnyRunes(s string, rs []rune) int {
|
||||||
|
|
||||||
return -1
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue