mirror of https://github.com/gobwas/glob.git
259 lines
4.0 KiB
Go
259 lines
4.0 KiB
Go
|
package glob
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"github.com/gobwas/glob/match"
|
||
|
)
|
||
|
|
||
|
func optimize(matcher match.Matcher) match.Matcher {
|
||
|
switch m := matcher.(type) {
|
||
|
|
||
|
case match.Any:
|
||
|
if m.Separators == "" {
|
||
|
return match.Super{}
|
||
|
}
|
||
|
|
||
|
case match.BTree:
|
||
|
m.Left = optimize(m.Left)
|
||
|
m.Right = optimize(m.Right)
|
||
|
|
||
|
r, ok := m.Value.(match.Raw)
|
||
|
if !ok {
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
leftNil := m.Left == nil
|
||
|
rightNil := m.Right == nil
|
||
|
|
||
|
if leftNil && rightNil {
|
||
|
return match.Raw{r.Str}
|
||
|
}
|
||
|
|
||
|
_, leftSuper := m.Left.(match.Super)
|
||
|
lp, leftPrefix := m.Left.(match.Prefix)
|
||
|
|
||
|
_, rightSuper := m.Right.(match.Super)
|
||
|
rs, rightSuffix := m.Right.(match.Suffix)
|
||
|
|
||
|
if leftSuper && rightSuper {
|
||
|
return match.Contains{r.Str, false}
|
||
|
}
|
||
|
|
||
|
if leftSuper && rightNil {
|
||
|
return match.Suffix{r.Str}
|
||
|
}
|
||
|
|
||
|
if rightSuper && leftNil {
|
||
|
return match.Prefix{r.Str}
|
||
|
}
|
||
|
|
||
|
if leftNil && rightSuffix {
|
||
|
return match.Every{match.Matchers{match.Prefix{r.Str}, rs}}
|
||
|
}
|
||
|
|
||
|
if rightNil && leftPrefix {
|
||
|
return match.Every{match.Matchers{lp, match.Suffix{r.Str}}}
|
||
|
}
|
||
|
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
return matcher
|
||
|
}
|
||
|
|
||
|
func glueMatchers(matchers []match.Matcher) match.Matcher {
|
||
|
switch len(matchers) {
|
||
|
case 0:
|
||
|
return nil
|
||
|
case 1:
|
||
|
return matchers[0]
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
hasAny bool
|
||
|
hasSuper bool
|
||
|
hasSingle bool
|
||
|
min int
|
||
|
separator string
|
||
|
)
|
||
|
|
||
|
for i, matcher := range matchers {
|
||
|
var sep string
|
||
|
switch m := matcher.(type) {
|
||
|
|
||
|
case match.Super:
|
||
|
sep = ""
|
||
|
hasSuper = true
|
||
|
|
||
|
case match.Any:
|
||
|
sep = m.Separators
|
||
|
hasAny = true
|
||
|
|
||
|
case match.Single:
|
||
|
sep = m.Separators
|
||
|
hasSingle = true
|
||
|
min++
|
||
|
|
||
|
case match.List:
|
||
|
if !m.Not {
|
||
|
return nil
|
||
|
}
|
||
|
sep = m.List
|
||
|
hasSingle = true
|
||
|
min++
|
||
|
|
||
|
default:
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// initialize
|
||
|
if i == 0 {
|
||
|
separator = sep
|
||
|
}
|
||
|
|
||
|
if sep == separator {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if hasSuper && !hasAny && !hasSingle {
|
||
|
return match.Super{}
|
||
|
}
|
||
|
|
||
|
if hasAny && !hasSuper && !hasSingle {
|
||
|
return match.Any{separator}
|
||
|
}
|
||
|
|
||
|
if (hasAny || hasSuper) && min > 0 && separator == "" {
|
||
|
return match.Min{min}
|
||
|
}
|
||
|
|
||
|
every := match.Every{}
|
||
|
|
||
|
if min > 0 {
|
||
|
every.Add(match.Min{min})
|
||
|
|
||
|
if !hasAny && !hasSuper {
|
||
|
every.Add(match.Max{min})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if separator != "" {
|
||
|
every.Add(match.Contains{separator, true})
|
||
|
}
|
||
|
|
||
|
return every
|
||
|
}
|
||
|
|
||
|
func convertMatchers(matchers []match.Matcher) (match.Matcher, error) {
|
||
|
if m := glueMatchers(matchers); m != nil {
|
||
|
return m, nil
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
val match.Primitive
|
||
|
idx int
|
||
|
)
|
||
|
|
||
|
for i, matcher := range matchers {
|
||
|
if p, ok := matcher.(match.Primitive); ok {
|
||
|
idx = i
|
||
|
val = p
|
||
|
|
||
|
if _, ok := matcher.(match.Raw); ok {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if val == nil {
|
||
|
return nil, fmt.Errorf("could not convert matchers %s: need at least one primitive", match.Matchers(matchers))
|
||
|
}
|
||
|
|
||
|
left := matchers[:idx]
|
||
|
var right []match.Matcher
|
||
|
if len(matchers) > idx+1 {
|
||
|
right = matchers[idx+1:]
|
||
|
}
|
||
|
|
||
|
tree := match.BTree{Value: val}
|
||
|
|
||
|
if len(left) > 0 {
|
||
|
l, err := convertMatchers(left)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
tree.Left = l
|
||
|
}
|
||
|
|
||
|
if len(right) > 0 {
|
||
|
r, err := convertMatchers(right)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
tree.Right = r
|
||
|
}
|
||
|
|
||
|
return tree, nil
|
||
|
}
|
||
|
|
||
|
func do(node node, s string) (m match.Matcher, err error) {
|
||
|
switch n := node.(type) {
|
||
|
|
||
|
case *nodeAnyOf, *nodePattern:
|
||
|
var matchers []match.Matcher
|
||
|
for _, desc := range node.children() {
|
||
|
m, err := do(desc, s)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
matchers = append(matchers, optimize(m))
|
||
|
}
|
||
|
|
||
|
if _, ok := node.(*nodeAnyOf); ok {
|
||
|
m = match.AnyOf{matchers}
|
||
|
} else {
|
||
|
m, err = convertMatchers(matchers)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case *nodeList:
|
||
|
m = match.List{n.chars, n.not}
|
||
|
|
||
|
case *nodeRange:
|
||
|
m = match.Range{n.lo, n.hi, n.not}
|
||
|
|
||
|
case *nodeAny:
|
||
|
m = match.Any{s}
|
||
|
|
||
|
case *nodeSuper:
|
||
|
m = match.Super{}
|
||
|
|
||
|
case *nodeSingle:
|
||
|
m = match.Single{s}
|
||
|
|
||
|
case *nodeText:
|
||
|
m = match.Raw{n.text}
|
||
|
|
||
|
default:
|
||
|
return nil, fmt.Errorf("could not compile tree: unknown node type")
|
||
|
}
|
||
|
|
||
|
return optimize(m), nil
|
||
|
}
|
||
|
|
||
|
func compile(ast *nodePattern, s string) (Glob, error) {
|
||
|
g, err := do(ast, s)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return g, nil
|
||
|
}
|