Implement webhook branch filter (#7791)
* Fix validate() function to handle errors in embedded anon structs * Implement webhook branch filter See #2025, #3998.tokarchuk/v1.17
parent
0118b6aaf8
commit
6ddd3b0b47
@ -0,0 +1,62 @@ |
|||||||
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package validation |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
|
||||||
|
"gitea.com/macaron/binding" |
||||||
|
"github.com/gobwas/glob" |
||||||
|
) |
||||||
|
|
||||||
|
func getGlobPatternErrorString(pattern string) string { |
||||||
|
// It would be unwise to rely on that glob
|
||||||
|
// compilation errors don't ever change.
|
||||||
|
if _, err := glob.Compile(pattern); err != nil { |
||||||
|
return err.Error() |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
var globValidationTestCases = []validationTestCase{ |
||||||
|
{ |
||||||
|
description: "Empty glob pattern", |
||||||
|
data: TestForm{ |
||||||
|
GlobPattern: "", |
||||||
|
}, |
||||||
|
expectedErrors: binding.Errors{}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
description: "Valid glob", |
||||||
|
data: TestForm{ |
||||||
|
GlobPattern: "{master,release*}", |
||||||
|
}, |
||||||
|
expectedErrors: binding.Errors{}, |
||||||
|
}, |
||||||
|
|
||||||
|
{ |
||||||
|
description: "Invalid glob", |
||||||
|
data: TestForm{ |
||||||
|
GlobPattern: "[a-", |
||||||
|
}, |
||||||
|
expectedErrors: binding.Errors{ |
||||||
|
binding.Error{ |
||||||
|
FieldNames: []string{"GlobPattern"}, |
||||||
|
Classification: ErrGlobPattern, |
||||||
|
Message: getGlobPatternErrorString("[a-"), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func Test_GlobPatternValidation(t *testing.T) { |
||||||
|
AddBindingRules() |
||||||
|
|
||||||
|
for _, testCase := range globValidationTestCases { |
||||||
|
t.Run(testCase.description, func(t *testing.T) { |
||||||
|
performValidationTest(t, testCase) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
glob.iml |
||||||
|
.idea |
||||||
|
*.cpu |
||||||
|
*.mem |
||||||
|
*.test |
||||||
|
*.dot |
||||||
|
*.png |
||||||
|
*.svg |
@ -0,0 +1,9 @@ |
|||||||
|
sudo: false |
||||||
|
|
||||||
|
language: go |
||||||
|
|
||||||
|
go: |
||||||
|
- 1.5.3 |
||||||
|
|
||||||
|
script: |
||||||
|
- go test -v ./... |
@ -0,0 +1,21 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2016 Sergey Kamardin |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,26 @@ |
|||||||
|
#! /bin/bash |
||||||
|
|
||||||
|
bench() { |
||||||
|
filename="/tmp/$1-$2.bench" |
||||||
|
if test -e "${filename}"; |
||||||
|
then |
||||||
|
echo "Already exists ${filename}" |
||||||
|
else |
||||||
|
backup=`git rev-parse --abbrev-ref HEAD` |
||||||
|
git checkout $1 |
||||||
|
echo -n "Creating ${filename}... " |
||||||
|
go test ./... -run=NONE -bench=$2 > "${filename}" -benchmem |
||||||
|
echo "OK" |
||||||
|
git checkout ${backup} |
||||||
|
sleep 5 |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
to=$1 |
||||||
|
current=`git rev-parse --abbrev-ref HEAD` |
||||||
|
|
||||||
|
bench ${to} $2 |
||||||
|
bench ${current} $2 |
||||||
|
|
||||||
|
benchcmp $3 "/tmp/${to}-$2.bench" "/tmp/${current}-$2.bench" |
@ -0,0 +1,525 @@ |
|||||||
|
package compiler |
||||||
|
|
||||||
|
// TODO use constructor with all matchers, and to their structs private
|
||||||
|
// TODO glue multiple Text nodes (like after QuoteMeta)
|
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
|
||||||
|
"github.com/gobwas/glob/match" |
||||||
|
"github.com/gobwas/glob/syntax/ast" |
||||||
|
"github.com/gobwas/glob/util/runes" |
||||||
|
) |
||||||
|
|
||||||
|
func optimizeMatcher(matcher match.Matcher) match.Matcher { |
||||||
|
switch m := matcher.(type) { |
||||||
|
|
||||||
|
case match.Any: |
||||||
|
if len(m.Separators) == 0 { |
||||||
|
return match.NewSuper() |
||||||
|
} |
||||||
|
|
||||||
|
case match.AnyOf: |
||||||
|
if len(m.Matchers) == 1 { |
||||||
|
return m.Matchers[0] |
||||||
|
} |
||||||
|
|
||||||
|
return m |
||||||
|
|
||||||
|
case match.List: |
||||||
|
if m.Not == false && len(m.List) == 1 { |
||||||
|
return match.NewText(string(m.List)) |
||||||
|
} |
||||||
|
|
||||||
|
return m |
||||||
|
|
||||||
|
case match.BTree: |
||||||
|
m.Left = optimizeMatcher(m.Left) |
||||||
|
m.Right = optimizeMatcher(m.Right) |
||||||
|
|
||||||
|
r, ok := m.Value.(match.Text) |
||||||
|
if !ok { |
||||||
|
return m |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
leftNil = m.Left == nil |
||||||
|
rightNil = m.Right == nil |
||||||
|
) |
||||||
|
if leftNil && rightNil { |
||||||
|
return match.NewText(r.Str) |
||||||
|
} |
||||||
|
|
||||||
|
_, leftSuper := m.Left.(match.Super) |
||||||
|
lp, leftPrefix := m.Left.(match.Prefix) |
||||||
|
la, leftAny := m.Left.(match.Any) |
||||||
|
|
||||||
|
_, rightSuper := m.Right.(match.Super) |
||||||
|
rs, rightSuffix := m.Right.(match.Suffix) |
||||||
|
ra, rightAny := m.Right.(match.Any) |
||||||
|
|
||||||
|
switch { |
||||||
|
case leftSuper && rightSuper: |
||||||
|
return match.NewContains(r.Str, false) |
||||||
|
|
||||||
|
case leftSuper && rightNil: |
||||||
|
return match.NewSuffix(r.Str) |
||||||
|
|
||||||
|
case rightSuper && leftNil: |
||||||
|
return match.NewPrefix(r.Str) |
||||||
|
|
||||||
|
case leftNil && rightSuffix: |
||||||
|
return match.NewPrefixSuffix(r.Str, rs.Suffix) |
||||||
|
|
||||||
|
case rightNil && leftPrefix: |
||||||
|
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 matcher |
||||||
|
} |
||||||
|
|
||||||
|
func compileMatchers(matchers []match.Matcher) (match.Matcher, error) { |
||||||
|
if len(matchers) == 0 { |
||||||
|
return nil, fmt.Errorf("compile error: need at least one matcher") |
||||||
|
} |
||||||
|
if len(matchers) == 1 { |
||||||
|
return matchers[0], nil |
||||||
|
} |
||||||
|
if m := glueMatchers(matchers); m != nil { |
||||||
|
return m, nil |
||||||
|
} |
||||||
|
|
||||||
|
idx := -1 |
||||||
|
maxLen := -1 |
||||||
|
var val match.Matcher |
||||||
|
for i, matcher := range matchers { |
||||||
|
if l := matcher.Len(); l != -1 && l >= maxLen { |
||||||
|
maxLen = l |
||||||
|
idx = i |
||||||
|
val = matcher |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if val == nil { // not found matcher with static length
|
||||||
|
r, err := compileMatchers(matchers[1:]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return match.NewBTree(matchers[0], nil, r), nil |
||||||
|
} |
||||||
|
|
||||||
|
left := matchers[:idx] |
||||||
|
var right []match.Matcher |
||||||
|
if len(matchers) > idx+1 { |
||||||
|
right = matchers[idx+1:] |
||||||
|
} |
||||||
|
|
||||||
|
var l, r match.Matcher |
||||||
|
var err error |
||||||
|
if len(left) > 0 { |
||||||
|
l, err = compileMatchers(left) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(right) > 0 { |
||||||
|
r, err = compileMatchers(right) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return match.NewBTree(val, l, r), nil |
||||||
|
} |
||||||
|
|
||||||
|
func glueMatchers(matchers []match.Matcher) match.Matcher { |
||||||
|
if m := glueMatchersAsEvery(matchers); m != nil { |
||||||
|
return m |
||||||
|
} |
||||||
|
if m := glueMatchersAsRow(matchers); m != nil { |
||||||
|
return m |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func glueMatchersAsRow(matchers []match.Matcher) match.Matcher { |
||||||
|
if len(matchers) <= 1 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
c []match.Matcher |
||||||
|
l int |
||||||
|
) |
||||||
|
for _, matcher := range matchers { |
||||||
|
if ml := matcher.Len(); ml == -1 { |
||||||
|
return nil |
||||||
|
} else { |
||||||
|
c = append(c, matcher) |
||||||
|
l += ml |
||||||
|
} |
||||||
|
} |
||||||
|
return match.NewRow(l, c...) |
||||||
|
} |
||||||
|
|
||||||
|
func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher { |
||||||
|
if len(matchers) <= 1 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
hasAny bool |
||||||
|
hasSuper bool |
||||||
|
hasSingle bool |
||||||
|
min int |
||||||
|
separator []rune |
||||||
|
) |
||||||
|
|
||||||
|
for i, matcher := range matchers { |
||||||
|
var sep []rune |
||||||
|
|
||||||
|
switch m := matcher.(type) { |
||||||
|
case match.Super: |
||||||
|
sep = []rune{} |
||||||
|
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 runes.Equal(sep, separator) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if hasSuper && !hasAny && !hasSingle { |
||||||
|
return match.NewSuper() |
||||||
|
} |
||||||
|
|
||||||
|
if hasAny && !hasSuper && !hasSingle { |
||||||
|
return match.NewAny(separator) |
||||||
|
} |
||||||
|
|
||||||
|
if (hasAny || hasSuper) && min > 0 && len(separator) == 0 { |
||||||
|
return match.NewMin(min) |
||||||
|
} |
||||||
|
|
||||||
|
every := match.NewEveryOf() |
||||||
|
|
||||||
|
if min > 0 { |
||||||
|
every.Add(match.NewMin(min)) |
||||||
|
|
||||||
|
if !hasAny && !hasSuper { |
||||||
|
every.Add(match.NewMax(min)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(separator) > 0 { |
||||||
|
every.Add(match.NewContains(string(separator), true)) |
||||||
|
} |
||||||
|
|
||||||
|
return every |
||||||
|
} |
||||||
|
|
||||||
|
func minimizeMatchers(matchers []match.Matcher) []match.Matcher { |
||||||
|
var done match.Matcher |
||||||
|
var left, right, count int |
||||||
|
|
||||||
|
for l := 0; l < len(matchers); l++ { |
||||||
|
for r := len(matchers); r > l; r-- { |
||||||
|
if glued := glueMatchers(matchers[l:r]); glued != nil { |
||||||
|
var swap bool |
||||||
|
|
||||||
|
if done == nil { |
||||||
|
swap = true |
||||||
|
} else { |
||||||
|
cl, gl := done.Len(), glued.Len() |
||||||
|
swap = cl > -1 && gl > -1 && gl > cl |
||||||
|
swap = swap || count < r-l |
||||||
|
} |
||||||
|
|
||||||
|
if swap { |
||||||
|
done = glued |
||||||
|
left = l |
||||||
|
right = r |
||||||
|
count = r - l |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if done == nil { |
||||||
|
return matchers |
||||||
|
} |
||||||
|
|
||||||
|
next := append(append([]match.Matcher{}, matchers[:left]...), done) |
||||||
|
if right < len(matchers) { |
||||||
|
next = append(next, matchers[right:]...) |
||||||
|
} |
||||||
|
|
||||||
|
if len(next) == len(matchers) { |
||||||
|
return next |
||||||
|
} |
||||||
|
|
||||||
|
return minimizeMatchers(next) |
||||||
|
} |
||||||
|
|
||||||
|
// minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree
|
||||||
|
func minimizeTree(tree *ast.Node) *ast.Node { |
||||||
|
switch tree.Kind { |
||||||
|
case ast.KindAnyOf: |
||||||
|
return minimizeTreeAnyOf(tree) |
||||||
|
default: |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// minimizeAnyOf tries to find common children of given node of AnyOf pattern
|
||||||
|
// it searches for common children from left and from right
|
||||||
|
// if any common children are found – then it returns new optimized ast tree
|
||||||
|
// else it returns nil
|
||||||
|
func minimizeTreeAnyOf(tree *ast.Node) *ast.Node { |
||||||
|
if !areOfSameKind(tree.Children, ast.KindPattern) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
commonLeft, commonRight := commonChildren(tree.Children) |
||||||
|
commonLeftCount, commonRightCount := len(commonLeft), len(commonRight) |
||||||
|
if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
var result []*ast.Node |
||||||
|
if commonLeftCount > 0 { |
||||||
|
result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...)) |
||||||
|
} |
||||||
|
|
||||||
|
var anyOf []*ast.Node |
||||||
|
for _, child := range tree.Children { |
||||||
|
reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount] |
||||||
|
var node *ast.Node |
||||||
|
if len(reuse) == 0 { |
||||||
|
// this pattern is completely reduced by commonLeft and commonRight patterns
|
||||||
|
// so it become nothing
|
||||||
|
node = ast.NewNode(ast.KindNothing, nil) |
||||||
|
} else { |
||||||
|
node = ast.NewNode(ast.KindPattern, nil, reuse...) |
||||||
|
} |
||||||
|
anyOf = appendIfUnique(anyOf, node) |
||||||
|
} |
||||||
|
switch { |
||||||
|
case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing: |
||||||
|
result = append(result, anyOf[0]) |
||||||
|
case len(anyOf) > 1: |
||||||
|
result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...)) |
||||||
|
} |
||||||
|
|
||||||
|
if commonRightCount > 0 { |
||||||
|
result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...)) |
||||||
|
} |
||||||
|
|
||||||
|
return ast.NewNode(ast.KindPattern, nil, result...) |
||||||
|
} |
||||||
|
|
||||||
|
func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) { |
||||||
|
if len(nodes) <= 1 { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// find node that has least number of children
|
||||||
|
idx := leastChildren(nodes) |
||||||
|
if idx == -1 { |
||||||
|
return |
||||||
|
} |
||||||
|
tree := nodes[idx] |
||||||
|
treeLength := len(tree.Children) |
||||||
|
|
||||||
|
// allocate max able size for rightCommon slice
|
||||||
|
// to get ability insert elements in reverse order (from end to start)
|
||||||
|
// without sorting
|
||||||
|
commonRight = make([]*ast.Node, treeLength) |
||||||
|
lastRight := treeLength // will use this to get results as commonRight[lastRight:]
|
||||||
|
|
||||||
|
var ( |
||||||
|
breakLeft bool |
||||||
|
breakRight bool |
||||||
|
commonTotal int |
||||||
|
) |
||||||
|
for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 { |
||||||
|
treeLeft := tree.Children[i] |
||||||
|
treeRight := tree.Children[j] |
||||||
|
|
||||||
|
for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ { |
||||||
|
// skip least children node
|
||||||
|
if k == idx { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
restLeft := nodes[k].Children[i] |
||||||
|
restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength] |
||||||
|
|
||||||
|
breakLeft = breakLeft || !treeLeft.Equal(restLeft) |
||||||
|
|
||||||
|
// disable searching for right common parts, if left part is already overlapping
|
||||||
|
breakRight = breakRight || (!breakLeft && j <= i) |
||||||
|
breakRight = breakRight || !treeRight.Equal(restRight) |
||||||
|
} |
||||||
|
|
||||||
|
if !breakLeft { |
||||||
|
commonTotal++ |
||||||
|
commonLeft = append(commonLeft, treeLeft) |
||||||
|
} |
||||||
|
if !breakRight { |
||||||
|
commonTotal++ |
||||||
|
lastRight = j |
||||||
|
commonRight[j] = treeRight |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
commonRight = commonRight[lastRight:] |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node { |
||||||
|
for _, n := range target { |
||||||
|
if reflect.DeepEqual(n, val) { |
||||||
|
return target |
||||||
|
} |
||||||
|
} |
||||||
|
return append(target, val) |
||||||
|
} |
||||||
|
|
||||||
|
func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool { |
||||||
|
for _, n := range nodes { |
||||||
|
if n.Kind != kind { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func leastChildren(nodes []*ast.Node) int { |
||||||
|
min := -1 |
||||||
|
idx := -1 |
||||||
|
for i, n := range nodes { |
||||||
|
if idx == -1 || (len(n.Children) < min) { |
||||||
|
min = len(n.Children) |
||||||
|
idx = i |
||||||
|
} |
||||||
|
} |
||||||
|
return idx |
||||||
|
} |
||||||
|
|
||||||
|
func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) { |
||||||
|
var matchers []match.Matcher |
||||||
|
for _, desc := range tree.Children { |
||||||
|
m, err := compile(desc, sep) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
matchers = append(matchers, optimizeMatcher(m)) |
||||||
|
} |
||||||
|
return matchers, nil |
||||||
|
} |
||||||
|
|
||||||
|
func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) { |
||||||
|
switch tree.Kind { |
||||||
|
case ast.KindAnyOf: |
||||||
|
// todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go)
|
||||||
|
if n := minimizeTree(tree); n != nil { |
||||||
|
return compile(n, sep) |
||||||
|
} |
||||||
|
matchers, err := compileTreeChildren(tree, sep) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return match.NewAnyOf(matchers...), nil |
||||||
|
|
||||||
|
case ast.KindPattern: |
||||||
|
if len(tree.Children) == 0 { |
||||||
|
return match.NewNothing(), nil |
||||||
|
} |
||||||
|
matchers, err := compileTreeChildren(tree, sep) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
m, err = compileMatchers(minimizeMatchers(matchers)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
case ast.KindAny: |
||||||
|
m = match.NewAny(sep) |
||||||
|
|
||||||
|
case ast.KindSuper: |
||||||
|
m = match.NewSuper() |
||||||
|
|
||||||
|
case ast.KindSingle: |
||||||
|
m = match.NewSingle(sep) |
||||||
|
|
||||||
|
case ast.KindNothing: |
||||||
|
m = match.NewNothing() |
||||||
|
|
||||||
|
case ast.KindList: |
||||||
|
l := tree.Value.(ast.List) |
||||||
|
m = match.NewList([]rune(l.Chars), l.Not) |
||||||
|
|
||||||
|
case ast.KindRange: |
||||||
|
r := tree.Value.(ast.Range) |
||||||
|
m = match.NewRange(r.Lo, r.Hi, r.Not) |
||||||
|
|
||||||
|
case ast.KindText: |
||||||
|
t := tree.Value.(ast.Text) |
||||||
|
m = match.NewText(t.Text) |
||||||
|
|
||||||
|
default: |
||||||
|
return nil, fmt.Errorf("could not compile tree: unknown node type") |
||||||
|
} |
||||||
|
|
||||||
|
return optimizeMatcher(m), nil |
||||||
|
} |
||||||
|
|
||||||
|
func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) { |
||||||
|
m, err := compile(tree, sep) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return m, nil |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
package glob |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/gobwas/glob/compiler" |
||||||
|
"github.com/gobwas/glob/syntax" |
||||||
|
) |
||||||
|
|
||||||
|
// Glob represents compiled glob pattern.
|
||||||
|
type Glob interface { |
||||||
|
Match(string) bool |
||||||
|
} |
||||||
|
|
||||||
|
// Compile creates Glob for given pattern and strings (if any present after pattern) 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
|
||||||
|
// `[` [ `!` ] { character-range } `]`
|
||||||
|
// character class (must be non-empty)
|
||||||
|
// `{` pattern-list `}`
|
||||||
|
// pattern alternatives
|
||||||
|
// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
|
||||||
|
// `\` c matches character c
|
||||||
|
//
|
||||||
|
// character-range:
|
||||||
|
// c matches character c (c != `\\`, `-`, `]`)
|
||||||
|
// `\` c matches character c
|
||||||
|
// lo `-` hi matches character c for lo <= c <= hi
|
||||||
|
//
|
||||||
|
// pattern-list:
|
||||||
|
// pattern { `,` pattern }
|
||||||
|
// comma-separated (without spaces) patterns
|
||||||
|
//
|
||||||
|
func Compile(pattern string, separators ...rune) (Glob, error) { |
||||||
|
ast, err := syntax.Parse(pattern) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
matcher, err := compiler.Compile(ast, separators) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return matcher, nil |
||||||
|
} |
||||||
|
|
||||||
|
// MustCompile is the same as Compile, except that if Compile returns error, this will panic
|
||||||
|
func MustCompile(pattern string, separators ...rune) Glob { |
||||||
|
g, err := Compile(pattern, separators...) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
return g |
||||||
|
} |
||||||
|
|
||||||
|
// QuoteMeta returns a string that quotes all glob pattern meta characters
|
||||||
|
// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`.
|
||||||
|
func QuoteMeta(s string) string { |
||||||
|
b := make([]byte, 2*len(s)) |
||||||
|
|
||||||
|
// a byte loop is correct because all meta characters are ASCII
|
||||||
|
j := 0 |
||||||
|
for i := 0; i < len(s); i++ { |
||||||
|
if syntax.Special(s[i]) { |
||||||
|
b[j] = '\\' |
||||||
|
j++ |
||||||
|
} |
||||||
|
b[j] = s[i] |
||||||
|
j++ |
||||||
|
} |
||||||
|
|
||||||
|
return string(b[0:j]) |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"github.com/gobwas/glob/util/strings" |
||||||
|
) |
||||||
|
|
||||||
|
type Any struct { |
||||||
|
Separators []rune |
||||||
|
} |
||||||
|
|
||||||
|
func NewAny(s []rune) Any { |
||||||
|
return Any{s} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Any) Match(s string) bool { |
||||||
|
return strings.IndexAnyRunes(s, self.Separators) == -1 |
||||||
|
} |
||||||
|
|
||||||
|
func (self Any) Index(s string) (int, []int) { |
||||||
|
found := strings.IndexAnyRunes(s, self.Separators) |
||||||
|
switch found { |
||||||
|
case -1: |
||||||
|
case 0: |
||||||
|
return 0, segments0 |
||||||
|
default: |
||||||
|
s = s[:found] |
||||||
|
} |
||||||
|
|
||||||
|
segments := acquireSegments(len(s)) |
||||||
|
for i := range s { |
||||||
|
segments = append(segments, i) |
||||||
|
} |
||||||
|
segments = append(segments, len(s)) |
||||||
|
|
||||||
|
return 0, segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self Any) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self Any) String() string { |
||||||
|
return fmt.Sprintf("<any:![%s]>", string(self.Separators)) |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type AnyOf struct { |
||||||
|
Matchers Matchers |
||||||
|
} |
||||||
|
|
||||||
|
func NewAnyOf(m ...Matcher) AnyOf { |
||||||
|
return AnyOf{Matchers(m)} |
||||||
|
} |
||||||
|
|
||||||
|
func (self *AnyOf) Add(m Matcher) error { |
||||||
|
self.Matchers = append(self.Matchers, m) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (self AnyOf) Match(s string) bool { |
||||||
|
for _, m := range self.Matchers { |
||||||
|
if m.Match(s) { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (self AnyOf) Index(s string) (int, []int) { |
||||||
|
index := -1 |
||||||
|
|
||||||
|
segments := acquireSegments(len(s)) |
||||||
|
for _, m := range self.Matchers { |
||||||
|
idx, seg := m.Index(s) |
||||||
|
if idx == -1 { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if index == -1 || idx < index { |
||||||
|
index = idx |
||||||
|
segments = append(segments[:0], seg...) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if idx > index { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// here idx == index
|
||||||
|
segments = appendMerge(segments, seg) |
||||||
|
} |
||||||
|
|
||||||
|
if index == -1 { |
||||||
|
releaseSegments(segments) |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
return index, segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self AnyOf) Len() (l int) { |
||||||
|
l = -1 |
||||||
|
for _, m := range self.Matchers { |
||||||
|
ml := m.Len() |
||||||
|
switch { |
||||||
|
case l == -1: |
||||||
|
l = ml |
||||||
|
continue |
||||||
|
|
||||||
|
case ml == -1: |
||||||
|
return -1 |
||||||
|
|
||||||
|
case l != ml: |
||||||
|
return -1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (self AnyOf) String() string { |
||||||
|
return fmt.Sprintf("<any_of:[%s]>", self.Matchers) |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type BTree struct { |
||||||
|
Value Matcher |
||||||
|
Left Matcher |
||||||
|
Right Matcher |
||||||
|
ValueLengthRunes int |
||||||
|
LeftLengthRunes int |
||||||
|
RightLengthRunes int |
||||||
|
LengthRunes int |
||||||
|
} |
||||||
|
|
||||||
|
func NewBTree(Value, Left, Right Matcher) (tree BTree) { |
||||||
|
tree.Value = Value |
||||||
|
tree.Left = Left |
||||||
|
tree.Right = Right |
||||||
|
|
||||||
|
lenOk := true |
||||||
|
if tree.ValueLengthRunes = Value.Len(); tree.ValueLengthRunes == -1 { |
||||||
|
lenOk = false |
||||||
|
} |
||||||
|
|
||||||
|
if Left != nil { |
||||||
|
if tree.LeftLengthRunes = Left.Len(); tree.LeftLengthRunes == -1 { |
||||||
|
lenOk = false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if Right != nil { |
||||||
|
if tree.RightLengthRunes = Right.Len(); tree.RightLengthRunes == -1 { |
||||||
|
lenOk = false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if lenOk { |
||||||
|
tree.LengthRunes = tree.LeftLengthRunes + tree.ValueLengthRunes + tree.RightLengthRunes |
||||||
|
} else { |
||||||
|
tree.LengthRunes = -1 |
||||||
|
} |
||||||
|
|
||||||
|
return tree |
||||||
|
} |
||||||
|
|
||||||
|
func (self BTree) Len() int { |
||||||
|
return self.LengthRunes |
||||||
|
} |
||||||
|
|
||||||
|
// todo?
|
||||||
|
func (self BTree) Index(s string) (int, []int) { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (self BTree) Match(s string) bool { |
||||||
|
inputLen := len(s) |
||||||
|
|
||||||
|
// self.Length, self.RLen and self.LLen are values meaning the length of runes for each part
|
||||||
|
// here we manipulating byte length for better optimizations
|
||||||
|
// but these checks still works, cause minLen of 1-rune string is 1 byte.
|
||||||
|
if self.LengthRunes != -1 && self.LengthRunes > inputLen { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// try to cut unnecessary parts
|
||||||
|
// by knowledge of length of right and left part
|
||||||
|
var offset, limit int |
||||||
|
if self.LeftLengthRunes >= 0 { |
||||||
|
offset = self.LeftLengthRunes |
||||||
|
} |
||||||
|
if self.RightLengthRunes >= 0 { |
||||||
|
limit = inputLen - self.RightLengthRunes |
||||||
|
} else { |
||||||
|
limit = inputLen |
||||||
|
} |
||||||
|
|
||||||
|
for offset < limit { |
||||||
|
// search for matching part in substring
|
||||||
|
index, segments := self.Value.Index(s[offset:limit]) |
||||||
|
if index == -1 { |
||||||
|
releaseSegments(segments) |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
l := s[:offset+index] |
||||||
|
var left bool |
||||||
|
if self.Left != nil { |
||||||
|
left = self.Left.Match(l) |
||||||
|
} else { |
||||||
|
left = l == "" |
||||||
|
} |
||||||
|
|
||||||
|
if left { |
||||||
|
for i := len(segments) - 1; i >= 0; i-- { |
||||||
|
length := segments[i] |
||||||
|
|
||||||
|
var right bool |
||||||
|
var r string |
||||||
|
// if there is no string for the right branch
|
||||||
|
if inputLen <= offset+index+length { |
||||||
|
r = "" |
||||||
|
} else { |
||||||
|
r = s[offset+index+length:] |
||||||
|
} |
||||||
|
|
||||||
|
if self.Right != nil { |
||||||
|
right = self.Right.Match(r) |
||||||
|
} else { |
||||||
|
right = r == "" |
||||||
|
} |
||||||
|
|
||||||
|
if right { |
||||||
|
releaseSegments(segments) |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_, step := utf8.DecodeRuneInString(s[offset+index:]) |
||||||
|
offset += index + step |
||||||
|
|
||||||
|
releaseSegments(segments) |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (self BTree) String() string { |
||||||
|
const n string = "<nil>" |
||||||
|
var l, r string |
||||||
|
if self.Left == nil { |
||||||
|
l = n |
||||||
|
} else { |
||||||
|
l = self.Left.String() |
||||||
|
} |
||||||
|
if self.Right == nil { |
||||||
|
r = n |
||||||
|
} else { |
||||||
|
r = self.Right.String() |
||||||
|
} |
||||||
|
|
||||||
|
return fmt.Sprintf("<btree:[%s<-%s->%s]>", l, self.Value, r) |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type Contains struct { |
||||||
|
Needle string |
||||||
|
Not bool |
||||||
|
} |
||||||
|
|
||||||
|
func NewContains(needle string, not bool) Contains { |
||||||
|
return Contains{needle, not} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Contains) Match(s string) bool { |
||||||
|
return strings.Contains(s, self.Needle) != self.Not |
||||||
|
} |
||||||
|
|
||||||
|
func (self Contains) Index(s string) (int, []int) { |
||||||
|
var offset int |
||||||
|
|
||||||
|
idx := strings.Index(s, self.Needle) |
||||||
|
|
||||||
|
if !self.Not { |
||||||
|
if idx == -1 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
offset = idx + len(self.Needle) |
||||||
|
if len(s) <= offset { |
||||||
|
return 0, []int{offset} |
||||||
|
} |
||||||
|
s = s[offset:] |
||||||
|
} else if idx != -1 { |
||||||
|
s = s[:idx] |
||||||
|
} |
||||||
|
|
||||||
|
segments := acquireSegments(len(s) + 1) |
||||||
|
for i := range s { |
||||||
|
segments = append(segments, offset+i) |
||||||
|
} |
||||||
|
|
||||||
|
return 0, append(segments, offset+len(s)) |
||||||
|
} |
||||||
|
|
||||||
|
func (self Contains) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self Contains) String() string { |
||||||
|
var not string |
||||||
|
if self.Not { |
||||||
|
not = "!" |
||||||
|
} |
||||||
|
return fmt.Sprintf("<contains:%s[%s]>", not, self.Needle) |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type EveryOf struct { |
||||||
|
Matchers Matchers |
||||||
|
} |
||||||
|
|
||||||
|
func NewEveryOf(m ...Matcher) EveryOf { |
||||||
|
return EveryOf{Matchers(m)} |
||||||
|
} |
||||||
|
|
||||||
|
func (self *EveryOf) Add(m Matcher) error { |
||||||
|
self.Matchers = append(self.Matchers, m) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (self EveryOf) Len() (l int) { |
||||||
|
for _, m := range self.Matchers { |
||||||
|
if ml := m.Len(); l > 0 { |
||||||
|
l += ml |
||||||
|
} else { |
||||||
|
return -1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (self EveryOf) Index(s string) (int, []int) { |
||||||
|
var index int |
||||||
|
var offset int |
||||||
|
|
||||||
|
// make `in` with cap as len(s),
|
||||||
|
// cause it is the maximum size of output segments values
|
||||||
|
next := acquireSegments(len(s)) |
||||||
|
current := acquireSegments(len(s)) |
||||||
|
|
||||||
|
sub := s |
||||||
|
for i, m := range self.Matchers { |
||||||
|
idx, seg := m.Index(sub) |
||||||
|
if idx == -1 { |
||||||
|
releaseSegments(next) |
||||||
|
releaseSegments(current) |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
if i == 0 { |
||||||
|
// we use copy here instead of `current = seg`
|
||||||
|
// cause seg is a slice from reusable buffer `in`
|
||||||
|
// and it could be overwritten in next iteration
|
||||||
|
current = append(current, seg...) |
||||||
|
} else { |
||||||
|
// clear the next
|
||||||
|
next = next[:0] |
||||||
|
|
||||||
|
delta := index - (idx + offset) |
||||||
|
for _, ex := range current { |
||||||
|
for _, n := range seg { |
||||||
|
if ex+delta == n { |
||||||
|
next = append(next, n) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(next) == 0 { |
||||||
|
releaseSegments(next) |
||||||
|
releaseSegments(current) |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
current = append(current[:0], next...) |
||||||
|
} |
||||||
|
|
||||||
|
index = idx + offset |
||||||
|
sub = s[index:] |
||||||
|
offset += idx |
||||||
|
} |
||||||
|
|
||||||
|
releaseSegments(next) |
||||||
|
|
||||||
|
return index, current |
||||||
|
} |
||||||
|
|
||||||
|
func (self EveryOf) Match(s string) bool { |
||||||
|
for _, m := range self.Matchers { |
||||||
|
if !m.Match(s) { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (self EveryOf) String() string { |
||||||
|
return fmt.Sprintf("<every_of:[%s]>", self.Matchers) |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"github.com/gobwas/glob/util/runes" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type List struct { |
||||||
|
List []rune |
||||||
|
Not bool |
||||||
|
} |
||||||
|
|
||||||
|
func NewList(list []rune, not bool) List { |
||||||
|
return List{list, not} |
||||||
|
} |
||||||
|
|
||||||
|
func (self List) Match(s string) bool { |
||||||
|
r, w := utf8.DecodeRuneInString(s) |
||||||
|
if len(s) > w { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
inList := runes.IndexRune(self.List, r) != -1 |
||||||
|
return inList == !self.Not |
||||||
|
} |
||||||
|
|
||||||
|
func (self List) Len() int { |
||||||
|
return lenOne |
||||||
|
} |
||||||
|
|
||||||
|
func (self List) Index(s string) (int, []int) { |
||||||
|
for i, r := range s { |
||||||
|
if self.Not == (runes.IndexRune(self.List, r) == -1) { |
||||||
|
return i, segmentsByRuneLength[utf8.RuneLen(r)] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (self List) String() string { |
||||||
|
var not string |
||||||
|
if self.Not { |
||||||
|
not = "!" |
||||||
|
} |
||||||
|
|
||||||
|
return fmt.Sprintf("<list:%s[%s]>", not, string(self.List)) |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
// todo common table of rune's length
|
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
const lenOne = 1 |
||||||
|
const lenZero = 0 |
||||||
|
const lenNo = -1 |
||||||
|
|
||||||
|
type Matcher interface { |
||||||
|
Match(string) bool |
||||||
|
Index(string) (int, []int) |
||||||
|
Len() int |
||||||
|
String() string |
||||||
|
} |
||||||
|
|
||||||
|
type Matchers []Matcher |
||||||
|
|
||||||
|
func (m Matchers) String() string { |
||||||
|
var s []string |
||||||
|
for _, matcher := range m { |
||||||
|
s = append(s, fmt.Sprint(matcher)) |
||||||
|
} |
||||||
|
|
||||||
|
return fmt.Sprintf("%s", strings.Join(s, ",")) |
||||||
|
} |
||||||
|
|
||||||
|
// appendMerge merges and sorts given already SORTED and UNIQUE segments.
|
||||||
|
func appendMerge(target, sub []int) []int { |
||||||
|
lt, ls := len(target), len(sub) |
||||||
|
out := make([]int, 0, lt+ls) |
||||||
|
|
||||||
|
for x, y := 0, 0; x < lt || y < ls; { |
||||||
|
if x >= lt { |
||||||
|
out = append(out, sub[y:]...) |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
if y >= ls { |
||||||
|
out = append(out, target[x:]...) |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
xValue := target[x] |
||||||
|
yValue := sub[y] |
||||||
|
|
||||||
|
switch { |
||||||
|
|
||||||
|
case xValue == yValue: |
||||||
|
out = append(out, xValue) |
||||||
|
x++ |
||||||
|
y++ |
||||||
|
|
||||||
|
case xValue < yValue: |
||||||
|
out = append(out, xValue) |
||||||
|
x++ |
||||||
|
|
||||||
|
case yValue < xValue: |
||||||
|
out = append(out, yValue) |
||||||
|
y++ |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
target = append(target[:0], out...) |
||||||
|
|
||||||
|
return target |
||||||
|
} |
||||||
|
|
||||||
|
func reverseSegments(input []int) { |
||||||
|
l := len(input) |
||||||
|
m := l / 2 |
||||||
|
|
||||||
|
for i := 0; i < m; i++ { |
||||||
|
input[i], input[l-i-1] = input[l-i-1], input[i] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type Max struct { |
||||||
|
Limit int |
||||||
|
} |
||||||
|
|
||||||
|
func NewMax(l int) Max { |
||||||
|
return Max{l} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Max) Match(s string) bool { |
||||||
|
var l int |
||||||
|
for range s { |
||||||
|
l += 1 |
||||||
|
if l > self.Limit { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (self Max) Index(s string) (int, []int) { |
||||||
|
segments := acquireSegments(self.Limit + 1) |
||||||
|
segments = append(segments, 0) |
||||||
|
var count int |
||||||
|
for i, r := range s { |
||||||
|
count++ |
||||||
|
if count > self.Limit { |
||||||
|
break |
||||||
|
} |
||||||
|
segments = append(segments, i+utf8.RuneLen(r)) |
||||||
|
} |
||||||
|
|
||||||
|
return 0, segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self Max) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self Max) String() string { |
||||||
|
return fmt.Sprintf("<max:%d>", self.Limit) |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type Min struct { |
||||||
|
Limit int |
||||||
|
} |
||||||
|
|
||||||
|
func NewMin(l int) Min { |
||||||
|
return Min{l} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Min) Match(s string) bool { |
||||||
|
var l int |
||||||
|
for range s { |
||||||
|
l += 1 |
||||||
|
if l >= self.Limit { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (self Min) Index(s string) (int, []int) { |
||||||
|
var count int |
||||||
|
|
||||||
|
c := len(s) - self.Limit + 1 |
||||||
|
if c <= 0 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
segments := acquireSegments(c) |
||||||
|
for i, r := range s { |
||||||
|
count++ |
||||||
|
if count >= self.Limit { |
||||||
|
segments = append(segments, i+utf8.RuneLen(r)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(segments) == 0 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
return 0, segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self Min) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self Min) String() string { |
||||||
|
return fmt.Sprintf("<min:%d>", self.Limit) |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type Nothing struct{} |
||||||
|
|
||||||
|
func NewNothing() Nothing { |
||||||
|
return Nothing{} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Nothing) Match(s string) bool { |
||||||
|
return len(s) == 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (self Nothing) Index(s string) (int, []int) { |
||||||
|
return 0, segments0 |
||||||
|
} |
||||||
|
|
||||||
|
func (self Nothing) Len() int { |
||||||
|
return lenZero |
||||||
|
} |
||||||
|
|
||||||
|
func (self Nothing) String() string { |
||||||
|
return fmt.Sprintf("<nothing>") |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type Prefix struct { |
||||||
|
Prefix string |
||||||
|
} |
||||||
|
|
||||||
|
func NewPrefix(p string) Prefix { |
||||||
|
return Prefix{p} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Prefix) Index(s string) (int, []int) { |
||||||
|
idx := strings.Index(s, self.Prefix) |
||||||
|
if idx == -1 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
length := len(self.Prefix) |
||||||
|
var sub string |
||||||
|
if len(s) > idx+length { |
||||||
|
sub = s[idx+length:] |
||||||
|
} else { |
||||||
|
sub = "" |
||||||
|
} |
||||||
|
|
||||||
|
segments := acquireSegments(len(sub) + 1) |
||||||
|
segments = append(segments, length) |
||||||
|
for i, r := range sub { |
||||||
|
segments = append(segments, length+i+utf8.RuneLen(r)) |
||||||
|
} |
||||||
|
|
||||||
|
return idx, segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self Prefix) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self Prefix) Match(s string) bool { |
||||||
|
return strings.HasPrefix(s, self.Prefix) |
||||||
|
} |
||||||
|
|
||||||
|
func (self Prefix) String() string { |
||||||
|
return fmt.Sprintf("<prefix:%s>", self.Prefix) |
||||||
|
} |
@ -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,62 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type PrefixSuffix struct { |
||||||
|
Prefix, Suffix string |
||||||
|
} |
||||||
|
|
||||||
|
func NewPrefixSuffix(p, s string) PrefixSuffix { |
||||||
|
return PrefixSuffix{p, s} |
||||||
|
} |
||||||
|
|
||||||
|
func (self PrefixSuffix) Index(s string) (int, []int) { |
||||||
|
prefixIdx := strings.Index(s, self.Prefix) |
||||||
|
if prefixIdx == -1 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
suffixLen := len(self.Suffix) |
||||||
|
if suffixLen <= 0 { |
||||||
|
return prefixIdx, []int{len(s) - prefixIdx} |
||||||
|
} |
||||||
|
|
||||||
|
if (len(s) - prefixIdx) <= 0 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
segments := acquireSegments(len(s) - prefixIdx) |
||||||
|
for sub := s[prefixIdx:]; ; { |
||||||
|
suffixIdx := strings.LastIndex(sub, self.Suffix) |
||||||
|
if suffixIdx == -1 { |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
segments = append(segments, suffixIdx+suffixLen) |
||||||
|
sub = sub[:suffixIdx] |
||||||
|
} |
||||||
|
|
||||||
|
if len(segments) == 0 { |
||||||
|
releaseSegments(segments) |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
reverseSegments(segments) |
||||||
|
|
||||||
|
return prefixIdx, segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self PrefixSuffix) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self PrefixSuffix) Match(s string) bool { |
||||||
|
return strings.HasPrefix(s, self.Prefix) && strings.HasSuffix(s, self.Suffix) |
||||||
|
} |
||||||
|
|
||||||
|
func (self PrefixSuffix) String() string { |
||||||
|
return fmt.Sprintf("<prefix_suffix:[%s,%s]>", self.Prefix, self.Suffix) |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type Range struct { |
||||||
|
Lo, Hi rune |
||||||
|
Not bool |
||||||
|
} |
||||||
|
|
||||||
|
func NewRange(lo, hi rune, not bool) Range { |
||||||
|
return Range{lo, hi, not} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Range) Len() int { |
||||||
|
return lenOne |
||||||
|
} |
||||||
|
|
||||||
|
func (self Range) Match(s string) bool { |
||||||
|
r, w := utf8.DecodeRuneInString(s) |
||||||
|
if len(s) > w { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
inRange := r >= self.Lo && r <= self.Hi |
||||||
|
|
||||||
|
return inRange == !self.Not |
||||||
|
} |
||||||
|
|
||||||
|
func (self Range) Index(s string) (int, []int) { |
||||||
|
for i, r := range s { |
||||||
|
if self.Not != (r >= self.Lo && r <= self.Hi) { |
||||||
|
return i, segmentsByRuneLength[utf8.RuneLen(r)] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (self Range) String() string { |
||||||
|
var not string |
||||||
|
if self.Not { |
||||||
|
not = "!" |
||||||
|
} |
||||||
|
return fmt.Sprintf("<range:%s[%s,%s]>", not, string(self.Lo), string(self.Hi)) |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type Row struct { |
||||||
|
Matchers Matchers |
||||||
|
RunesLength int |
||||||
|
Segments []int |
||||||
|
} |
||||||
|
|
||||||
|
func NewRow(len int, m ...Matcher) Row { |
||||||
|
return Row{ |
||||||
|
Matchers: Matchers(m), |
||||||
|
RunesLength: len, |
||||||
|
Segments: []int{len}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Row) matchAll(s string) bool { |
||||||
|
var idx int |
||||||
|
for _, m := range self.Matchers { |
||||||
|
length := m.Len() |
||||||
|
|
||||||
|
var next, i int |
||||||
|
for next = range s[idx:] { |
||||||
|
i++ |
||||||
|
if i == length { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if i < length || !m.Match(s[idx:idx+next+1]) { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
idx += next + 1 |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (self Row) lenOk(s string) bool { |
||||||
|
var i int |
||||||
|
for range s { |
||||||
|
i++ |
||||||
|
if i > self.RunesLength { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
return self.RunesLength == i |
||||||
|
} |
||||||
|
|
||||||
|
func (self Row) Match(s string) bool { |
||||||
|
return self.lenOk(s) && self.matchAll(s) |
||||||
|
} |
||||||
|
|
||||||
|
func (self Row) Len() (l int) { |
||||||
|
return self.RunesLength |
||||||
|
} |
||||||
|
|
||||||
|
func (self Row) Index(s string) (int, []int) { |
||||||
|
for i := range s { |
||||||
|
if len(s[i:]) < self.RunesLength { |
||||||
|
break |
||||||
|
} |
||||||
|
if self.matchAll(s[i:]) { |
||||||
|
return i, self.Segments |
||||||
|
} |
||||||
|
} |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (self Row) String() string { |
||||||
|
return fmt.Sprintf("<row_%d:[%s]>", self.RunesLength, self.Matchers) |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
type SomePool interface { |
||||||
|
Get() []int |
||||||
|
Put([]int) |
||||||
|
} |
||||||
|
|
||||||
|
var segmentsPools [1024]sync.Pool |
||||||
|
|
||||||
|
func toPowerOfTwo(v int) int { |
||||||
|
v-- |
||||||
|
v |= v >> 1 |
||||||
|
v |= v >> 2 |
||||||
|
v |= v >> 4 |
||||||
|
v |= v >> 8 |
||||||
|
v |= v >> 16 |
||||||
|
v++ |
||||||
|
|
||||||
|
return v |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
cacheFrom = 16 |
||||||
|
cacheToAndHigher = 1024 |
||||||
|
cacheFromIndex = 15 |
||||||
|
cacheToAndHigherIndex = 1023 |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
segments0 = []int{0} |
||||||
|
segments1 = []int{1} |
||||||
|
segments2 = []int{2} |
||||||
|
segments3 = []int{3} |
||||||
|
segments4 = []int{4} |
||||||
|
) |
||||||
|
|
||||||
|
var segmentsByRuneLength [5][]int = [5][]int{ |
||||||
|
0: segments0, |
||||||
|
1: segments1, |
||||||
|
2: segments2, |
||||||
|
3: segments3, |
||||||
|
4: segments4, |
||||||
|
} |
||||||
|
|
||||||
|
func init() { |
||||||
|
for i := cacheToAndHigher; i >= cacheFrom; i >>= 1 { |
||||||
|
func(i int) { |
||||||
|
segmentsPools[i-1] = sync.Pool{New: func() interface{} { |
||||||
|
return make([]int, 0, i) |
||||||
|
}} |
||||||
|
}(i) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func getTableIndex(c int) int { |
||||||
|
p := toPowerOfTwo(c) |
||||||
|
switch { |
||||||
|
case p >= cacheToAndHigher: |
||||||
|
return cacheToAndHigherIndex |
||||||
|
case p <= cacheFrom: |
||||||
|
return cacheFromIndex |
||||||
|
default: |
||||||
|
return p - 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func acquireSegments(c int) []int { |
||||||
|
// make []int with less capacity than cacheFrom
|
||||||
|
// is faster than acquiring it from pool
|
||||||
|
if c < cacheFrom { |
||||||
|
return make([]int, 0, c) |
||||||
|
} |
||||||
|
|
||||||
|
return segmentsPools[getTableIndex(c)].Get().([]int)[:0] |
||||||
|
} |
||||||
|
|
||||||
|
func releaseSegments(s []int) { |
||||||
|
c := cap(s) |
||||||
|
|
||||||
|
// make []int with less capacity than cacheFrom
|
||||||
|
// is faster than acquiring it from pool
|
||||||
|
if c < cacheFrom { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
segmentsPools[getTableIndex(c)].Put(s) |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"github.com/gobwas/glob/util/runes" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
// single represents ?
|
||||||
|
type Single struct { |
||||||
|
Separators []rune |
||||||
|
} |
||||||
|
|
||||||
|
func NewSingle(s []rune) Single { |
||||||
|
return Single{s} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Single) Match(s string) bool { |
||||||
|
r, w := utf8.DecodeRuneInString(s) |
||||||
|
if len(s) > w { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return runes.IndexRune(self.Separators, r) == -1 |
||||||
|
} |
||||||
|
|
||||||
|
func (self Single) Len() int { |
||||||
|
return lenOne |
||||||
|
} |
||||||
|
|
||||||
|
func (self Single) Index(s string) (int, []int) { |
||||||
|
for i, r := range s { |
||||||
|
if runes.IndexRune(self.Separators, r) == -1 { |
||||||
|
return i, segmentsByRuneLength[utf8.RuneLen(r)] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (self Single) String() string { |
||||||
|
return fmt.Sprintf("<single:![%s]>", string(self.Separators)) |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type Suffix struct { |
||||||
|
Suffix string |
||||||
|
} |
||||||
|
|
||||||
|
func NewSuffix(s string) Suffix { |
||||||
|
return Suffix{s} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Suffix) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self Suffix) Match(s string) bool { |
||||||
|
return strings.HasSuffix(s, self.Suffix) |
||||||
|
} |
||||||
|
|
||||||
|
func (self Suffix) Index(s string) (int, []int) { |
||||||
|
idx := strings.Index(s, self.Suffix) |
||||||
|
if idx == -1 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
return 0, []int{idx + len(self.Suffix)} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Suffix) String() string { |
||||||
|
return fmt.Sprintf("<suffix:%s>", self.Suffix) |
||||||
|
} |
@ -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,33 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type Super struct{} |
||||||
|
|
||||||
|
func NewSuper() Super { |
||||||
|
return Super{} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Super) Match(s string) bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (self Super) Len() int { |
||||||
|
return lenNo |
||||||
|
} |
||||||
|
|
||||||
|
func (self Super) Index(s string) (int, []int) { |
||||||
|
segments := acquireSegments(len(s) + 1) |
||||||
|
for i := range s { |
||||||
|
segments = append(segments, i) |
||||||
|
} |
||||||
|
segments = append(segments, len(s)) |
||||||
|
|
||||||
|
return 0, segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self Super) String() string { |
||||||
|
return fmt.Sprintf("<super>") |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package match |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
// raw represents raw string to match
|
||||||
|
type Text struct { |
||||||
|
Str string |
||||||
|
RunesLength int |
||||||
|
BytesLength int |
||||||
|
Segments []int |
||||||
|
} |
||||||
|
|
||||||
|
func NewText(s string) Text { |
||||||
|
return Text{ |
||||||
|
Str: s, |
||||||
|
RunesLength: utf8.RuneCountInString(s), |
||||||
|
BytesLength: len(s), |
||||||
|
Segments: []int{len(s)}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (self Text) Match(s string) bool { |
||||||
|
return self.Str == s |
||||||
|
} |
||||||
|
|
||||||
|
func (self Text) Len() int { |
||||||
|
return self.RunesLength |
||||||
|
} |
||||||
|
|
||||||
|
func (self Text) Index(s string) (int, []int) { |
||||||
|
index := strings.Index(s, self.Str) |
||||||
|
if index == -1 { |
||||||
|
return -1, nil |
||||||
|
} |
||||||
|
|
||||||
|
return index, self.Segments |
||||||
|
} |
||||||
|
|
||||||
|
func (self Text) String() string { |
||||||
|
return fmt.Sprintf("<text:`%v`>", self.Str) |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
# glob.[go](https://golang.org) |
||||||
|
|
||||||
|
[![GoDoc][godoc-image]][godoc-url] [![Build Status][travis-image]][travis-url] |
||||||
|
|
||||||
|
> Go Globbing Library. |
||||||
|
|
||||||
|
## Install |
||||||
|
|
||||||
|
```shell |
||||||
|
go get github.com/gobwas/glob |
||||||
|
``` |
||||||
|
|
||||||
|
## Example |
||||||
|
|
||||||
|
```go |
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import "github.com/gobwas/glob" |
||||||
|
|
||||||
|
func main() { |
||||||
|
var g glob.Glob |
||||||
|
|
||||||
|
// create simple glob |
||||||
|
g = glob.MustCompile("*.github.com") |
||||||
|
g.Match("api.github.com") // true |
||||||
|
|
||||||
|
// quote meta characters and then create simple glob |
||||||
|
g = glob.MustCompile(glob.QuoteMeta("*.github.com")) |
||||||
|
g.Match("*.github.com") // true |
||||||
|
|
||||||
|
// create new glob with set of delimiters as ["."] |
||||||
|
g = glob.MustCompile("api.*.com", '.') |
||||||
|
g.Match("api.github.com") // true |
||||||
|
g.Match("api.gi.hub.com") // false |
||||||
|
|
||||||
|
// create new glob with set of delimiters as ["."] |
||||||
|
// but now with super wildcard |
||||||
|
g = glob.MustCompile("api.**.com", '.') |
||||||
|
g.Match("api.github.com") // true |
||||||
|
g.Match("api.gi.hub.com") // true |
||||||
|
|
||||||
|
// create glob with single symbol wildcard |
||||||
|
g = glob.MustCompile("?at") |
||||||
|
g.Match("cat") // true |
||||||
|
g.Match("fat") // true |
||||||
|
g.Match("at") // false |
||||||
|
|
||||||
|
// create glob with single symbol wildcard and delimiters ['f'] |
||||||
|
g = glob.MustCompile("?at", 'f') |
||||||
|
g.Match("cat") // true |
||||||
|
g.Match("fat") // false |
||||||
|
g.Match("at") // false |
||||||
|
|
||||||
|
// create glob with character-list matchers |
||||||
|
g = glob.MustCompile("[abc]at") |
||||||
|
g.Match("cat") // true |
||||||
|
g.Match("bat") // true |
||||||
|
g.Match("fat") // false |
||||||
|
g.Match("at") // false |
||||||
|
|
||||||
|
// create glob with character-list matchers |
||||||
|
g = glob.MustCompile("[!abc]at") |
||||||
|
g.Match("cat") // false |
||||||
|
g.Match("bat") // false |
||||||
|
g.Match("fat") // true |
||||||
|
g.Match("at") // false |
||||||
|
|
||||||
|
// create glob with character-range matchers |
||||||
|
g = glob.MustCompile("[a-c]at") |
||||||
|
g.Match("cat") // true |
||||||
|
g.Match("bat") // true |
||||||
|
g.Match("fat") // false |
||||||
|
g.Match("at") // false |
||||||
|
|
||||||
|
// create glob with character-range matchers |
||||||
|
g = glob.MustCompile("[!a-c]at") |
||||||
|
g.Match("cat") // false |
||||||
|
g.Match("bat") // false |
||||||
|
g.Match("fat") // true |
||||||
|
g.Match("at") // false |
||||||
|
|
||||||
|
// create glob with pattern-alternatives list |
||||||
|
g = glob.MustCompile("{cat,bat,[fr]at}") |
||||||
|
g.Match("cat") // true |
||||||
|
g.Match("bat") // true |
||||||
|
g.Match("fat") // true |
||||||
|
g.Match("rat") // true |
||||||
|
g.Match("at") // false |
||||||
|
g.Match("zat") // false |
||||||
|
} |
||||||
|
|
||||||
|
``` |
||||||
|
|
||||||
|
## Performance |
||||||
|
|
||||||
|
This library is created for compile-once patterns. This means, that compilation could take time, but |
||||||
|
strings matching is done faster, than in case when always parsing template. |
||||||
|
|
||||||
|
If you will not use compiled `glob.Glob` object, and do `g := glob.MustCompile(pattern); g.Match(...)` every time, then your code will be much more slower. |
||||||
|
|
||||||
|
Run `go test -bench=.` from source root to see the benchmarks: |
||||||
|
|
||||||
|
Pattern | Fixture | Match | Speed (ns/op) |
||||||
|
--------|---------|-------|-------------- |
||||||
|
`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my cat has very bright eyes` | `true` | 432 |
||||||
|
`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my dog has very bright eyes` | `false` | 199 |
||||||
|
`https://*.google.*` | `https://account.google.com` | `true` | 96 |
||||||
|
`https://*.google.*` | `https://google.com` | `false` | 66 |
||||||
|
`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://yahoo.com` | `true` | 163 |
||||||
|
`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://google.com` | `false` | 197 |
||||||
|
`{https://*gobwas.com,http://exclude.gobwas.com}` | `https://safe.gobwas.com` | `true` | 22 |
||||||
|
`{https://*gobwas.com,http://exclude.gobwas.com}` | `http://safe.gobwas.com` | `false` | 24 |
||||||
|
`abc*` | `abcdef` | `true` | 8.15 |
||||||
|
`abc*` | `af` | `false` | 5.68 |
||||||
|
`*def` | `abcdef` | `true` | 8.84 |
||||||
|
`*def` | `af` | `false` | 5.74 |
||||||
|
`ab*ef` | `abcdef` | `true` | 15.2 |
||||||
|
`ab*ef` | `af` | `false` | 10.4 |
||||||
|
|
||||||
|
The same things with `regexp` package: |
||||||
|
|
||||||
|
Pattern | Fixture | Match | Speed (ns/op) |
||||||
|
--------|---------|-------|-------------- |
||||||
|
`^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my cat has very bright eyes` | `true` | 2553 |
||||||
|
`^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my dog has very bright eyes` | `false` | 1383 |
||||||
|
`^https:\/\/.*\.google\..*$` | `https://account.google.com` | `true` | 1205 |
||||||
|
`^https:\/\/.*\.google\..*$` | `https://google.com` | `false` | 767 |
||||||
|
`^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$` | `http://yahoo.com` | `true` | 1435 |
||||||
|
`^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$` | `http://google.com` | `false` | 1674 |
||||||
|
`^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$` | `https://safe.gobwas.com` | `true` | 1039 |
||||||
|
`^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$` | `http://safe.gobwas.com` | `false` | 272 |
||||||
|
`^abc.*$` | `abcdef` | `true` | 237 |
||||||
|
`^abc.*$` | `af` | `false` | 100 |
||||||
|
`^.*def$` | `abcdef` | `true` | 464 |
||||||
|
`^.*def$` | `af` | `false` | 265 |
||||||
|
`^ab.*ef$` | `abcdef` | `true` | 375 |
||||||
|
`^ab.*ef$` | `af` | `false` | 145 |
||||||
|
|
||||||
|
[godoc-image]: https://godoc.org/github.com/gobwas/glob?status.svg |
||||||
|
[godoc-url]: https://godoc.org/github.com/gobwas/glob |
||||||
|
[travis-image]: https://travis-ci.org/gobwas/glob.svg?branch=master |
||||||
|
[travis-url]: https://travis-ci.org/gobwas/glob |
||||||
|
|
||||||
|
## Syntax |
||||||
|
|
||||||
|
Syntax is inspired by [standard wildcards](http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm), |
||||||
|
except that `**` is aka super-asterisk, that do not sensitive for separators. |
@ -0,0 +1,122 @@ |
|||||||
|
package ast |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
) |
||||||
|
|
||||||
|
type Node struct { |
||||||
|
Parent *Node |
||||||
|
Children []*Node |
||||||
|
Value interface{} |
||||||
|
Kind Kind |
||||||
|
} |
||||||
|
|
||||||
|
func NewNode(k Kind, v interface{}, ch ...*Node) *Node { |
||||||
|
n := &Node{ |
||||||
|
Kind: k, |
||||||
|
Value: v, |
||||||
|
} |
||||||
|
for _, c := range ch { |
||||||
|
Insert(n, c) |
||||||
|
} |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
func (a *Node) Equal(b *Node) bool { |
||||||
|
if a.Kind != b.Kind { |
||||||
|
return false |
||||||
|
} |
||||||
|
if a.Value != b.Value { |
||||||
|
return false |
||||||
|
} |
||||||
|
if len(a.Children) != len(b.Children) { |
||||||
|
return false |
||||||
|
} |
||||||
|
for i, c := range a.Children { |
||||||
|
if !c.Equal(b.Children[i]) { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (a *Node) String() string { |
||||||
|
var buf bytes.Buffer |
||||||
|
buf.WriteString(a.Kind.String()) |
||||||
|
if a.Value != nil { |
||||||
|
buf.WriteString(" =") |
||||||
|
buf.WriteString(fmt.Sprintf("%v", a.Value)) |
||||||
|
} |
||||||
|
if len(a.Children) > 0 { |
||||||
|
buf.WriteString(" [") |
||||||
|
for i, c := range a.Children { |
||||||
|
if i > 0 { |
||||||
|
buf.WriteString(", ") |
||||||
|
} |
||||||
|
buf.WriteString(c.String()) |
||||||
|
} |
||||||
|
buf.WriteString("]") |
||||||
|
} |
||||||
|
return buf.String() |
||||||
|
} |
||||||
|
|
||||||
|
func Insert(parent *Node, children ...*Node) { |
||||||
|
parent.Children = append(parent.Children, children...) |
||||||
|
for _, ch := range children { |
||||||
|
ch.Parent = parent |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type List struct { |
||||||
|
Not bool |
||||||
|
Chars string |
||||||
|
} |
||||||
|
|
||||||
|
type Range struct { |
||||||
|
Not bool |
||||||
|
Lo, Hi rune |
||||||
|
} |
||||||
|
|
||||||
|
type Text struct { |
||||||
|
Text string |
||||||
|
} |
||||||
|
|
||||||
|
type Kind int |
||||||
|
|
||||||
|
const ( |
||||||
|
KindNothing Kind = iota |
||||||
|
KindPattern |
||||||
|
KindList |
||||||
|
KindRange |
||||||
|
KindText |
||||||
|
KindAny |
||||||
|
KindSuper |
||||||
|
KindSingle |
||||||
|
KindAnyOf |
||||||
|
) |
||||||
|
|
||||||
|
func (k Kind) String() string { |
||||||
|
switch k { |
||||||
|
case KindNothing: |
||||||
|
return "Nothing" |
||||||
|
case KindPattern: |
||||||
|
return "Pattern" |
||||||
|
case KindList: |
||||||
|
return "List" |
||||||
|
case KindRange: |
||||||
|
return "Range" |
||||||
|
case KindText: |
||||||
|
return "Text" |
||||||
|
case KindAny: |
||||||
|
return "Any" |
||||||
|
case KindSuper: |
||||||
|
return "Super" |
||||||
|
case KindSingle: |
||||||
|
return "Single" |
||||||
|
case KindAnyOf: |
||||||
|
return "AnyOf" |
||||||
|
default: |
||||||
|
return "" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
package ast |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"github.com/gobwas/glob/syntax/lexer" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type Lexer interface { |
||||||
|
Next() lexer.Token |
||||||
|
} |
||||||
|
|
||||||
|
type parseFn func(*Node, Lexer) (parseFn, *Node, error) |
||||||
|
|
||||||
|
func Parse(lexer Lexer) (*Node, error) { |
||||||
|
var parser parseFn |
||||||
|
|
||||||
|
root := NewNode(KindPattern, nil) |
||||||
|
|
||||||
|
var ( |
||||||
|
tree *Node |
||||||
|
err error |
||||||
|
) |
||||||
|
for parser, tree = parserMain, root; parser != nil; { |
||||||
|
parser, tree, err = parser(tree, lexer) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return root, nil |
||||||
|
} |
||||||
|
|
||||||
|
func parserMain(tree *Node, lex Lexer) (parseFn, *Node, error) { |
||||||
|
for { |
||||||
|
token := lex.Next() |
||||||
|
switch token.Type { |
||||||
|
case lexer.EOF: |
||||||
|
return nil, tree, nil |
||||||
|
|
||||||
|
case lexer.Error: |
||||||
|
return nil, tree, errors.New(token.Raw) |
||||||
|
|
||||||
|
case lexer.Text: |
||||||
|
Insert(tree, NewNode(KindText, Text{token.Raw})) |
||||||
|
return parserMain, tree, nil |
||||||
|
|
||||||
|
case lexer.Any: |
||||||
|
Insert(tree, NewNode(KindAny, nil)) |
||||||
|
return parserMain, tree, nil |
||||||
|
|
||||||
|
case lexer.Super: |
||||||
|
Insert(tree, NewNode(KindSuper, nil)) |
||||||
|
return parserMain, tree, nil |
||||||
|
|
||||||
|
case lexer.Single: |
||||||
|
Insert(tree, NewNode(KindSingle, nil)) |
||||||
|
return parserMain, tree, nil |
||||||
|
|
||||||
|
case lexer.RangeOpen: |
||||||
|
return parserRange, tree, nil |
||||||
|
|
||||||
|
case lexer.TermsOpen: |
||||||
|
a := NewNode(KindAnyOf, nil) |
||||||
|
Insert(tree, a) |
||||||
|
|
||||||
|
p := NewNode(KindPattern, nil) |
||||||
|
Insert(a, p) |
||||||
|
|
||||||
|
return parserMain, p, nil |
||||||
|
|
||||||
|
case lexer.Separator: |
||||||
|
p := NewNode(KindPattern, nil) |
||||||
|
Insert(tree.Parent, p) |
||||||
|
|
||||||
|
return parserMain, p, nil |
||||||
|
|
||||||
|
case lexer.TermsClose: |
||||||
|
return parserMain, tree.Parent.Parent, nil |
||||||
|
|
||||||
|
default: |
||||||
|
return nil, tree, fmt.Errorf("unexpected token: %s", token) |
||||||
|
} |
||||||
|
} |
||||||
|
return nil, tree, fmt.Errorf("unknown error") |
||||||
|
} |
||||||
|
|
||||||
|
func parserRange(tree *Node, lex Lexer) (parseFn, *Node, error) { |
||||||
|
var ( |
||||||
|
not bool |
||||||
|
lo rune |
||||||
|
hi rune |
||||||
|
chars string |
||||||
|
) |
||||||
|
for { |
||||||
|
token := lex.Next() |
||||||
|
switch token.Type { |
||||||
|
case lexer.EOF: |
||||||
|
return nil, tree, errors.New("unexpected end") |
||||||
|
|
||||||
|
case lexer.Error: |
||||||
|
return nil, tree, errors.New(token.Raw) |
||||||
|
|
||||||
|
case lexer.Not: |
||||||
|
not = true |
||||||
|
|
||||||
|
case lexer.RangeLo: |
||||||
|
r, w := utf8.DecodeRuneInString(token.Raw) |
||||||
|
if len(token.Raw) > w { |
||||||
|
return nil, tree, fmt.Errorf("unexpected length of lo character") |
||||||
|
} |
||||||
|
lo = r |
||||||
|
|
||||||
|
case lexer.RangeBetween: |
||||||
|
//
|
||||||
|
|
||||||
|
case lexer.RangeHi: |
||||||
|
r, w := utf8.DecodeRuneInString(token.Raw) |
||||||
|
if len(token.Raw) > w { |
||||||
|
return nil, tree, fmt.Errorf("unexpected length of lo character") |
||||||
|
} |
||||||
|
|
||||||
|
hi = r |
||||||
|
|
||||||
|
if hi < lo { |
||||||
|
return nil, tree, fmt.Errorf("hi character '%s' should be greater than lo '%s'", string(hi), string(lo)) |
||||||
|
} |
||||||
|
|
||||||
|
case lexer.Text: |
||||||
|
chars = token.Raw |
||||||
|
|
||||||
|
case lexer.RangeClose: |
||||||
|
isRange := lo != 0 && hi != 0 |
||||||
|
isChars := chars != "" |
||||||
|
|
||||||
|
if isChars == isRange { |
||||||
|
return nil, tree, fmt.Errorf("could not parse range") |
||||||
|
} |
||||||
|
|
||||||
|
if isRange { |
||||||
|
Insert(tree, NewNode(KindRange, Range{ |
||||||
|
Lo: lo, |
||||||
|
Hi: hi, |
||||||
|
Not: not, |
||||||
|
})) |
||||||
|
} else { |
||||||
|
Insert(tree, NewNode(KindList, List{ |
||||||
|
Chars: chars, |
||||||
|
Not: not, |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
return parserMain, tree, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,273 @@ |
|||||||
|
package lexer |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"github.com/gobwas/glob/util/runes" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
char_any = '*' |
||||||
|
char_comma = ',' |
||||||
|
char_single = '?' |
||||||
|
char_escape = '\\' |
||||||
|
char_range_open = '[' |
||||||
|
char_range_close = ']' |
||||||
|
char_terms_open = '{' |
||||||
|
char_terms_close = '}' |
||||||
|
char_range_not = '!' |
||||||
|
char_range_between = '-' |
||||||
|
) |
||||||
|
|
||||||
|
var specials = []byte{ |
||||||
|
char_any, |
||||||
|
char_single, |
||||||
|
char_escape, |
||||||
|
char_range_open, |
||||||
|
char_range_close, |
||||||
|
char_terms_open, |
||||||
|
char_terms_close, |
||||||
|
} |
||||||
|
|
||||||
|
func Special(c byte) bool { |
||||||
|
return bytes.IndexByte(specials, c) != -1 |
||||||
|
} |
||||||
|
|
||||||
|
type tokens []Token |
||||||
|
|
||||||
|
func (i *tokens) shift() (ret Token) { |
||||||
|
ret = (*i)[0] |
||||||
|
copy(*i, (*i)[1:]) |
||||||
|
*i = (*i)[:len(*i)-1] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (i *tokens) push(v Token) { |
||||||
|
*i = append(*i, v) |
||||||
|
} |
||||||
|
|
||||||
|
func (i *tokens) empty() bool { |
||||||
|
return len(*i) == 0 |
||||||
|
} |
||||||
|
|
||||||
|
var eof rune = 0 |
||||||
|
|
||||||
|
type lexer struct { |
||||||
|
data string |
||||||
|
pos int |
||||||
|
err error |
||||||
|
|
||||||
|
tokens tokens |
||||||
|
termsLevel int |
||||||
|
|
||||||
|
lastRune rune |
||||||
|
lastRuneSize int |
||||||
|
hasRune bool |
||||||
|
} |
||||||
|
|
||||||
|
func NewLexer(source string) *lexer { |
||||||
|
l := &lexer{ |
||||||
|
data: source, |
||||||
|
tokens: tokens(make([]Token, 0, 4)), |
||||||
|
} |
||||||
|
return l |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) Next() Token { |
||||||
|
if l.err != nil { |
||||||
|
return Token{Error, l.err.Error()} |
||||||
|
} |
||||||
|
if !l.tokens.empty() { |
||||||
|
return l.tokens.shift() |
||||||
|
} |
||||||
|
|
||||||
|
l.fetchItem() |
||||||
|
return l.Next() |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) peek() (r rune, w int) { |
||||||
|
if l.pos == len(l.data) { |
||||||
|
return eof, 0 |
||||||
|
} |
||||||
|
|
||||||
|
r, w = utf8.DecodeRuneInString(l.data[l.pos:]) |
||||||
|
if r == utf8.RuneError { |
||||||
|
l.errorf("could not read rune") |
||||||
|
r = eof |
||||||
|
w = 0 |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) read() rune { |
||||||
|
if l.hasRune { |
||||||
|
l.hasRune = false |
||||||
|
l.seek(l.lastRuneSize) |
||||||
|
return l.lastRune |
||||||
|
} |
||||||
|
|
||||||
|
r, s := l.peek() |
||||||
|
l.seek(s) |
||||||
|
|
||||||
|
l.lastRune = r |
||||||
|
l.lastRuneSize = s |
||||||
|
|
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) seek(w int) { |
||||||
|
l.pos += w |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) unread() { |
||||||
|
if l.hasRune { |
||||||
|
l.errorf("could not unread rune") |
||||||
|
return |
||||||
|
} |
||||||
|
l.seek(-l.lastRuneSize) |
||||||
|
l.hasRune = true |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) errorf(f string, v ...interface{}) { |
||||||
|
l.err = fmt.Errorf(f, v...) |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) inTerms() bool { |
||||||
|
return l.termsLevel > 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) termsEnter() { |
||||||
|
l.termsLevel++ |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) termsLeave() { |
||||||
|
l.termsLevel-- |
||||||
|
} |
||||||
|
|
||||||
|
var inTextBreakers = []rune{char_single, char_any, char_range_open, char_terms_open} |
||||||
|
var inTermsBreakers = append(inTextBreakers, char_terms_close, char_comma) |
||||||
|
|
||||||
|
func (l *lexer) fetchItem() { |
||||||
|
r := l.read() |
||||||
|
switch { |
||||||
|
case r == eof: |
||||||
|
l.tokens.push(Token{EOF, ""}) |
||||||
|
|
||||||
|
case r == char_terms_open: |
||||||
|
l.termsEnter() |
||||||
|
l.tokens.push(Token{TermsOpen, string(r)}) |
||||||
|
|
||||||
|
case r == char_comma && l.inTerms(): |
||||||
|
l.tokens.push(Token{Separator, string(r)}) |
||||||
|
|
||||||
|
case r == char_terms_close && l.inTerms(): |
||||||
|
l.tokens.push(Token{TermsClose, string(r)}) |
||||||
|
l.termsLeave() |
||||||
|
|
||||||
|
case r == char_range_open: |
||||||
|
l.tokens.push(Token{RangeOpen, string(r)}) |
||||||
|
l.fetchRange() |
||||||
|
|
||||||
|
case r == char_single: |
||||||
|
l.tokens.push(Token{Single, string(r)}) |
||||||
|
|
||||||
|
case r == char_any: |
||||||
|
if l.read() == char_any { |
||||||
|
l.tokens.push(Token{Super, string(r) + string(r)}) |
||||||
|
} else { |
||||||
|
l.unread() |
||||||
|
l.tokens.push(Token{Any, string(r)}) |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
l.unread() |
||||||
|
|
||||||
|
var breakers []rune |
||||||
|
if l.inTerms() { |
||||||
|
breakers = inTermsBreakers |
||||||
|
} else { |
||||||
|
breakers = inTextBreakers |
||||||
|
} |
||||||
|
l.fetchText(breakers) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) fetchRange() { |
||||||
|
var wantHi bool |
||||||
|
var wantClose bool |
||||||
|
var seenNot bool |
||||||
|
for { |
||||||
|
r := l.read() |
||||||
|
if r == eof { |
||||||
|
l.errorf("unexpected end of input") |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if wantClose { |
||||||
|
if r != char_range_close { |
||||||
|
l.errorf("expected close range character") |
||||||
|
} else { |
||||||
|
l.tokens.push(Token{RangeClose, string(r)}) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if wantHi { |
||||||
|
l.tokens.push(Token{RangeHi, string(r)}) |
||||||
|
wantClose = true |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if !seenNot && r == char_range_not { |
||||||
|
l.tokens.push(Token{Not, string(r)}) |
||||||
|
seenNot = true |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if n, w := l.peek(); n == char_range_between { |
||||||
|
l.seek(w) |
||||||
|
l.tokens.push(Token{RangeLo, string(r)}) |
||||||
|
l.tokens.push(Token{RangeBetween, string(n)}) |
||||||
|
wantHi = true |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
l.unread() // unread first peek and fetch as text
|
||||||
|
l.fetchText([]rune{char_range_close}) |
||||||
|
wantClose = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lexer) fetchText(breakers []rune) { |
||||||
|
var data []rune |
||||||
|
var escaped bool |
||||||
|
|
||||||
|
reading: |
||||||
|
for { |
||||||
|
r := l.read() |
||||||
|
if r == eof { |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
if !escaped { |
||||||
|
if r == char_escape { |
||||||
|
escaped = true |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if runes.IndexRune(breakers, r) != -1 { |
||||||
|
l.unread() |
||||||
|
break reading |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
escaped = false |
||||||
|
data = append(data, r) |
||||||
|
} |
||||||
|
|
||||||
|
if len(data) > 0 { |
||||||
|
l.tokens.push(Token{Text, string(data)}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,88 @@ |
|||||||
|
package lexer |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type TokenType int |
||||||
|
|
||||||
|
const ( |
||||||
|
EOF TokenType = iota |
||||||
|
Error |
||||||
|
Text |
||||||
|
Char |
||||||
|
Any |
||||||
|
Super |
||||||
|
Single |
||||||
|
Not |
||||||
|
Separator |
||||||
|
RangeOpen |
||||||
|
RangeClose |
||||||
|
RangeLo |
||||||
|
RangeHi |
||||||
|
RangeBetween |
||||||
|
TermsOpen |
||||||
|
TermsClose |
||||||
|
) |
||||||
|
|
||||||
|
func (tt TokenType) String() string { |
||||||
|
switch tt { |
||||||
|
case EOF: |
||||||
|
return "eof" |
||||||
|
|
||||||
|
case Error: |
||||||
|
return "error" |
||||||
|
|
||||||
|
case Text: |
||||||
|
return "text" |
||||||
|
|
||||||
|
case Char: |
||||||
|
return "char" |
||||||
|
|
||||||
|
case Any: |
||||||
|
return "any" |
||||||
|
|
||||||
|
case Super: |
||||||
|
return "super" |
||||||
|
|
||||||
|
case Single: |
||||||
|
return "single" |
||||||
|
|
||||||
|
case Not: |
||||||
|
return "not" |
||||||
|
|
||||||
|
case Separator: |
||||||
|
return "separator" |
||||||
|
|
||||||
|
case RangeOpen: |
||||||
|
return "range_open" |
||||||
|
|
||||||
|
case RangeClose: |
||||||
|
return "range_close" |
||||||
|
|
||||||
|
case RangeLo: |
||||||
|
return "range_lo" |
||||||
|
|
||||||
|
case RangeHi: |
||||||
|
return "range_hi" |
||||||
|
|
||||||
|
case RangeBetween: |
||||||
|
return "range_between" |
||||||
|
|
||||||
|
case TermsOpen: |
||||||
|
return "terms_open" |
||||||
|
|
||||||
|
case TermsClose: |
||||||
|
return "terms_close" |
||||||
|
|
||||||
|
default: |
||||||
|
return "undef" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type Token struct { |
||||||
|
Type TokenType |
||||||
|
Raw string |
||||||
|
} |
||||||
|
|
||||||
|
func (t Token) String() string { |
||||||
|
return fmt.Sprintf("%v<%q>", t.Type, t.Raw) |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package syntax |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/gobwas/glob/syntax/ast" |
||||||
|
"github.com/gobwas/glob/syntax/lexer" |
||||||
|
) |
||||||
|
|
||||||
|
func Parse(s string) (*ast.Node, error) { |
||||||
|
return ast.Parse(lexer.NewLexer(s)) |
||||||
|
} |
||||||
|
|
||||||
|
func Special(b byte) bool { |
||||||
|
return lexer.Special(b) |
||||||
|
} |
@ -0,0 +1,154 @@ |
|||||||
|
package runes |
||||||
|
|
||||||
|
func Index(s, needle []rune) int { |
||||||
|
ls, ln := len(s), len(needle) |
||||||
|
|
||||||
|
switch { |
||||||
|
case ln == 0: |
||||||
|
return 0 |
||||||
|
case ln == 1: |
||||||
|
return IndexRune(s, needle[0]) |
||||||
|
case ln == ls: |
||||||
|
if Equal(s, needle) { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return -1 |
||||||
|
case ln > ls: |
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
head: |
||||||
|
for i := 0; i < ls && ls-i >= ln; i++ { |
||||||
|
for y := 0; y < ln; y++ { |
||||||
|
if s[i+y] != needle[y] { |
||||||
|
continue head |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return i |
||||||
|
} |
||||||
|
|
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
func LastIndex(s, needle []rune) int { |
||||||
|
ls, ln := len(s), len(needle) |
||||||
|
|
||||||
|
switch { |
||||||
|
case ln == 0: |
||||||
|
if ls == 0 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return ls |
||||||
|
case ln == 1: |
||||||
|
return IndexLastRune(s, needle[0]) |
||||||
|
case ln == ls: |
||||||
|
if Equal(s, needle) { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return -1 |
||||||
|
case ln > ls: |
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
head: |
||||||
|
for i := ls - 1; i >= 0 && i >= ln; i-- { |
||||||
|
for y := ln - 1; y >= 0; y-- { |
||||||
|
if s[i-(ln-y-1)] != needle[y] { |
||||||
|
continue head |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return i - ln + 1 |
||||||
|
} |
||||||
|
|
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
// IndexAny returns the index of the first instance of any Unicode code point
|
||||||
|
// from chars in s, or -1 if no Unicode code point from chars is present in s.
|
||||||
|
func IndexAny(s, chars []rune) int { |
||||||
|
if len(chars) > 0 { |
||||||
|
for i, c := range s { |
||||||
|
for _, m := range chars { |
||||||
|
if c == m { |
||||||
|
return i |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
func Contains(s, needle []rune) bool { |
||||||
|
return Index(s, needle) >= 0 |
||||||
|
} |
||||||
|
|
||||||
|
func Max(s []rune) (max rune) { |
||||||
|
for _, r := range s { |
||||||
|
if r > max { |
||||||
|
max = r |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func Min(s []rune) rune { |
||||||
|
min := rune(-1) |
||||||
|
for _, r := range s { |
||||||
|
if min == -1 { |
||||||
|
min = r |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if r < min { |
||||||
|
min = r |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return min |
||||||
|
} |
||||||
|
|
||||||
|
func IndexRune(s []rune, r rune) int { |
||||||
|
for i, c := range s { |
||||||
|
if c == r { |
||||||
|
return i |
||||||
|
} |
||||||
|
} |
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
func IndexLastRune(s []rune, r rune) int { |
||||||
|
for i := len(s) - 1; i >= 0; i-- { |
||||||
|
if s[i] == r { |
||||||
|
return i |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
func Equal(a, b []rune) bool { |
||||||
|
if len(a) == len(b) { |
||||||
|
for i := 0; i < len(a); i++ { |
||||||
|
if a[i] != b[i] { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// HasPrefix tests whether the string s begins with prefix.
|
||||||
|
func HasPrefix(s, prefix []rune) bool { |
||||||
|
return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix) |
||||||
|
} |
||||||
|
|
||||||
|
// HasSuffix tests whether the string s ends with suffix.
|
||||||
|
func HasSuffix(s, suffix []rune) bool { |
||||||
|
return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix) |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
package strings |
||||||
|
|
||||||
|
import ( |
||||||
|
"strings" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
func IndexAnyRunes(s string, rs []rune) int { |
||||||
|
for _, r := range rs { |
||||||
|
if i := strings.IndexRune(s, r); i != -1 { |
||||||
|
return i |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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