commit d4d579d13ebc6f92a4bdd5afee5b420f27b25d0a Author: s.kamardin Date: Mon Nov 30 17:58:20 2015 +0300 Initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4144260 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +glob.iml +.idea diff --git a/glob.go b/glob.go new file mode 100644 index 0000000..0853e87 --- /dev/null +++ b/glob.go @@ -0,0 +1,155 @@ +package glob + +import ( + "strings" + "fmt" +) + +const ( + Any = "*" + SuperAny = "**" + SingleAny = "?" +) + +var Chars = []string{Any, SuperAny, SingleAny} + +type Matcher interface { + Match(string) bool +} + +func firstIndexOfChars(p string, any []string) (min int, c string) { + l := len(p) + min = l + weight := 0 + + for _, s := range any { + w := len(s) + i := strings.Index(p, s) + if i != -1 && i <= min && w > weight { + min = i + weight = w + c = s + } + } + + if min == l { + return -1, "" + } + + return +} + +func parse(p string, m []Matcher, d []string) ([]Matcher, error) { + if len(p) == 0 { + return m, nil + } + + i, c := firstIndexOfChars(p, Chars) + if i == -1 { + return append(m, raw{p}), nil + } + + if i > 0 { + m = append(m, raw{p[0:i]}) + } + + switch c { + case SuperAny: + m = append(m, multiple{}) + case Any: + m = append(m, multiple{d}) + case SingleAny: + m = append(m, single{d}) + } + + return parse(p[i+len(c):], m, d) +} + +func New(pattern string, d ...string) (Matcher, error) { + chunks, err := parse(pattern, nil, d) + if err != nil { + return nil, err + } + + if len(chunks) == 1 { + return chunks[0], nil + } + + return &composite{chunks}, nil +} + +type raw struct { + s string +} +func (self raw) Match(s string) bool { + return self.s == s +} +func (self raw) String() string { + return fmt.Sprintf("[raw:%s]", self.s) +} + +type multiple struct { + delimiters []string +} +func (self multiple) Match(s string) bool { + i, _ := firstIndexOfChars(s, self.delimiters) + return i == -1 +} +func (self multiple) String() string { + return fmt.Sprintf("[multiple:%s]", self.delimiters) +} + +type single struct { + delimiters []string +} +func (self single) Match(s string) bool { + if len(s) != 1 { + return false + } + + i, _ := firstIndexOfChars(s, self.delimiters) + + return i == -1 +} +func (self single) String() string { + return fmt.Sprintf("[single:%s]", self.delimiters) +} + +type composite struct { + chunks []Matcher +} + + +func (self composite) Match(m string) bool { + var prev Matcher + + for _, c := range self.chunks { + if str, ok := c.(raw); ok { + i := strings.Index(m, str.s) + if i == -1 { + return false + } + + l := len(str.s) + + if prev != nil { + if !prev.Match(m[:i]) { + return false + } + + prev = nil + } + + m = m[i+l:] + continue + } + + prev = c + } + + if prev != nil { + return prev.Match(m) + } + + return len(m) == 0 +} diff --git a/glob_test.go b/glob_test.go new file mode 100644 index 0000000..f6ee7b8 --- /dev/null +++ b/glob_test.go @@ -0,0 +1,132 @@ +package glob +import ( + "testing" + rGlob "github.com/ryanuber/go-glob" + "regexp" + "strings" +) + + +type test struct { + pattern, match string + should bool + delimiters []string +} + +func glob(s bool, p, m string, d ...string) test { + return test{p, m, s, d} +} + +func TestFirstIndexOfChars(t *testing.T) { + for _, test := range []struct{ + s string + c []string + i int + r string + }{ + { + "**", + []string{"**", "*"}, + 0, + "**", + }, + { + "**", + []string{"*", "**"}, + 0, + "**", + }, + }{ + i, r := firstIndexOfChars(test.s, test.c) + if i != test.i || r != test.r { + t.Errorf("unexpeted index: expected %q at %v, got %q at %v", test.r, test.i, r, i) + } + } +} + +func TestGlob(t *testing.T) { + for _, test := range []test { + glob(true, "abc", "abc"), + glob(true, "a*c", "abc"), + glob(true, "a*c", "a12345c"), + glob(true, "a?c", "a1c"), + glob(true, "a.b", "a.b", "."), + glob(true, "a.*", "a.b", "."), + glob(true, "a.**", "a.b.c", "."), + glob(true, "a.?.c", "a.b.c", "."), + glob(true, "a.?.?", "a.b.c", "."), + glob(true, "?at", "cat"), + glob(true, "*", "abc"), + glob(true, "**", "a.b.c", "."), + + glob(false, "?at", "at"), + glob(false, "a.*", "a.b.c", "."), + glob(false, "a.?.c", "a.bb.c", "."), + glob(false, "*", "a.b.c", "."), + + glob(true, "*test", "this is a test"), + glob(true, "this*", "this is a test"), + glob(true, "*is *", "this is a test"), + glob(true, "*is*a*", "this is a test"), + glob(true, "**test**", "this is a test"), + glob(true, "**is**a***test*", "this is a test"), + + glob(false, "*is", "this is a test"), + glob(false, "*no*", "this is a test"), + }{ + g, err := New(test.pattern, test.delimiters...) + if err != nil { + t.Error(err) + continue + } + + result := g.Match(test.match) + if result != test.should { + t.Errorf("pattern %q matching %q should be %v but got %v", test.pattern, test.match, test.should, result) + } + } +} + + +const Pattern = "*cat*eyes*" +const ExpPattern = ".*cat.*eyes.*" +const String = "my cat has very bright eyes" + +func BenchmarkGobwas(b *testing.B) { + for i := 0; i < b.N; i++ { + m, err := New(Pattern) + if err != nil { + b.Fatal(err) + } + + _ = m.Match(String) + } +} + +func BenchmarkRyanuber(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = rGlob.Glob(Pattern, String) + } +} +func BenchmarkRegExp(b *testing.B) { + r := regexp.MustCompile(ExpPattern) + for i := 0; i < b.N; i++ { + _ = r.Match([]byte(String)) + } +} + +var ALPHABET_S = []string{"a", "b", "c"} +const ALPHABET = "abc" +const STR = "faafsdfcsdffc" + + +func BenchmarkIndexOfAny(b *testing.B) { + for i := 0; i < b.N; i++ { + strings.IndexAny(STR, ALPHABET) + } +} +func BenchmarkFirstIndexOfChars(b *testing.B) { + for i := 0; i < b.N; i++ { + firstIndexOfChars(STR, ALPHABET_S) + } +} \ No newline at end of file