This commit is contained in:
s.kamardin 2015-11-30 17:58:20 +03:00
commit d4d579d13e
3 changed files with 289 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
glob.iml
.idea

155
glob.go Normal file
View File

@ -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
}

132
glob_test.go Normal file
View File

@ -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)
}
}