glob/glob.go

189 lines
4.0 KiB
Go
Raw Normal View History

2015-11-30 17:58:20 +03:00
package glob
import (
"strings"
2015-12-24 17:54:54 +03:00
"errors"
"github.com/gobwas/glob/match"
2015-11-30 17:58:20 +03:00
)
const (
2015-12-24 17:54:54 +03:00
any = '*'
single = '?'
escape = '\\'
range_open = '['
range_close = ']'
2015-11-30 17:58:20 +03:00
)
2015-12-24 17:54:54 +03:00
const (
inside_range_not = '!'
inside_range_minus = '-'
2015-12-01 17:22:17 +03:00
)
2015-12-24 17:54:54 +03:00
var syntaxPhrases = string([]byte{any, single, escape, range_open, range_close})
2015-11-30 18:33:50 +03:00
// Glob represents compiled glob pattern.
2015-11-30 18:24:20 +03:00
type Glob interface {
2015-11-30 17:58:20 +03:00
Match(string) bool
}
2015-11-30 19:01:49 +03:00
// New creates Glob for given pattern and uses other given (if any) strings as separators.
// The pattern syntax is:
//
// pattern:
// { term }
// term:
// `*` matches any sequence of non-separator characters
// `**` matches any sequence of characters
// `?` matches any single non-separator character
// c matches character c (c != `*`, `**`, `?`, `\`)
// `\` c matches character c
2015-12-24 17:54:54 +03:00
func New(pattern string, separators ...string) (Glob, error) {
chunks, err := parse(pattern, strings.Join(separators, ""), state{})
if err != nil {
return nil, err
}
2015-11-30 17:58:20 +03:00
2015-12-01 17:22:17 +03:00
switch len(chunks) {
case 1:
2015-12-24 17:54:54 +03:00
return chunks[0].matcher, nil
2015-12-01 17:22:17 +03:00
case 2:
2015-12-24 17:54:54 +03:00
if chunks[0].matcher.Kind() == match.KindRaw && chunks[1].matcher.Kind() == match.KindMultipleSuper {
return &match.Prefix{chunks[0].str}, nil
2015-12-01 17:22:17 +03:00
}
2015-12-24 17:54:54 +03:00
if chunks[1].matcher.Kind() == match.KindRaw && chunks[0].matcher.Kind() == match.KindMultipleSuper {
return &match.Suffix{chunks[1].str}, nil
2015-12-01 17:22:17 +03:00
}
case 3:
2015-12-24 17:54:54 +03:00
if chunks[0].matcher.Kind() == match.KindRaw && chunks[1].matcher.Kind() == match.KindMultipleSuper && chunks[2].matcher.Kind() == match.KindRaw {
return &match.PrefixSuffix{chunks[0].str, chunks[2].str}, nil
2015-12-01 17:22:17 +03:00
}
2015-11-30 17:58:20 +03:00
}
2015-12-24 17:54:54 +03:00
var c []match.Matcher
2015-12-01 17:22:17 +03:00
for _, chunk := range chunks {
2015-12-24 17:54:54 +03:00
c = append(c, chunk.matcher)
2015-12-01 17:22:17 +03:00
}
2015-12-24 17:54:54 +03:00
return &match.Composite{c}, nil
2015-12-01 17:22:17 +03:00
}
2015-11-30 19:01:49 +03:00
2015-12-24 17:54:54 +03:00
// parse parsed given pattern into list of tokens
func parse(str string, sep string, st state) ([]token, error) {
if len(str) == 0 {
return st.tokens, nil
2015-11-30 17:58:20 +03:00
}
2015-12-24 17:54:54 +03:00
// if there are no syntax symbols - pattern is simple string
i := strings.IndexAny(str, syntaxPhrases)
2015-11-30 17:58:20 +03:00
if i == -1 {
2015-12-24 17:54:54 +03:00
return append(st.tokens, token{match.Raw{str}, str}), nil
2015-11-30 17:58:20 +03:00
}
2015-12-24 17:54:54 +03:00
c := string(str[i])
// if syntax symbol is not at the start of pattern - add raw part before it
2015-11-30 17:58:20 +03:00
if i > 0 {
2015-12-24 17:54:54 +03:00
st.tokens = append(st.tokens, token{match.Raw{str[0:i]}, str[0:i]})
2015-11-30 17:58:20 +03:00
}
2015-12-24 17:54:54 +03:00
// if we are in escape state
if st.escape {
st.tokens = append(st.tokens, token{match.Raw{c}, c})
st.escape = false
2015-11-30 19:01:49 +03:00
} else {
2015-12-24 17:54:54 +03:00
switch str[i] {
case range_open:
closed := indexByteNonEscaped(str, range_close, escape, 0)
if closed == -1 {
return nil, errors.New("invalid format")
}
2015-12-01 17:22:17 +03:00
2015-12-24 17:54:54 +03:00
r := str[i+1:closed]
g, err := parseRange(r)
if err != nil {
return nil, err
}
st.tokens = append(st.tokens, token{g, r})
2015-12-01 17:22:17 +03:00
2015-12-24 17:54:54 +03:00
if closed == len(str) -1 {
return st.tokens, nil
}
2015-11-30 17:58:20 +03:00
2015-12-24 17:54:54 +03:00
return parse(str[closed+1:], sep, st)
2015-11-30 17:58:20 +03:00
2015-12-24 17:54:54 +03:00
case escape:
st.escape = true
case any:
if len(str) > i+1 && str[i+1] == any {
st.tokens = append(st.tokens, token{match.Multiple{}, c})
return parse(str[i+len(c)+1:], sep, st)
}
2015-11-30 18:24:20 +03:00
2015-12-24 17:54:54 +03:00
st.tokens = append(st.tokens, token{match.Multiple{sep}, c})
case single:
st.tokens = append(st.tokens, token{match.Single{sep}, c})
}
2015-12-01 17:22:17 +03:00
}
2015-12-24 17:54:54 +03:00
return parse(str[i+len(c):], sep, st)
2015-11-30 17:58:20 +03:00
}
2015-12-24 17:54:54 +03:00
func parseRange(def string) (match.Matcher, error) {
var (
not bool
esc bool
minus bool
b []byte
)
2015-12-01 17:22:17 +03:00
2015-12-24 17:54:54 +03:00
for i, c := range []byte(def) {
if esc {
b = append(b, c)
esc = false
continue
}
2015-11-30 17:58:20 +03:00
2015-12-24 17:54:54 +03:00
switch c{
case inside_range_not:
if i == 0 {
not = true
2015-11-30 17:58:20 +03:00
}
2015-12-24 17:54:54 +03:00
case escape:
if i == len(def) - 1 {
return nil, errors.New("escape character without follower")
2015-11-30 17:58:20 +03:00
}
2015-12-24 17:54:54 +03:00
esc = true
case inside_range_minus:
minus = true
default:
b = append(b, c)
2015-11-30 17:58:20 +03:00
}
}
2015-12-24 17:54:54 +03:00
def = string(b)
2015-11-30 18:24:20 +03:00
2015-12-24 17:54:54 +03:00
if minus {
r := []rune(def)
if len(r) != 3 || r[1] != inside_range_minus {
return nil, errors.New("invalid range syntax")
2015-11-30 18:24:20 +03:00
}
2015-12-24 17:54:54 +03:00
return &match.Between{r[0], r[2], not}, nil
2015-11-30 18:24:20 +03:00
}
2015-12-24 17:54:54 +03:00
return &match.RangeList{def, not}, nil
2015-12-01 17:22:17 +03:00
}
2015-12-24 17:54:54 +03:00
type token struct {
matcher match.Matcher
str string
2015-12-01 17:22:17 +03:00
}
2015-12-24 17:54:54 +03:00
type state struct {
escape bool
tokens []token
2015-12-01 17:22:17 +03:00
}