diff --git a/test-proxy/go.mod b/test-proxy/go.mod index b1e5e9909..db5d612cc 100644 --- a/test-proxy/go.mod +++ b/test-proxy/go.mod @@ -1,9 +1,9 @@ module github.com/wavefronthq/observability-for-kubernetes/test-proxy -go 1.20 +go 1.22.0 require ( - github.com/gobwas/glob v0.2.3 + github.com/gobwas/glob v1.0.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 github.com/wavefronthq/wavefront-sdk-go v0.15.0 diff --git a/test-proxy/go.sum b/test-proxy/go.sum index 51e300a15..f8e82fdc2 100644 --- a/test-proxy/go.sum +++ b/test-proxy/go.sum @@ -4,8 +4,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/glob v1.0.0 h1:p+FKbLEIsK1yZ39/OINwFvqNb5oyPY4H8xcy6uYu8dg= +github.com/gobwas/glob v1.0.0/go.mod h1:oWCdo522i2P1n/hMXGNWs7yoV4wy/ciZuUIbvKj5rkc= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= @@ -18,18 +18,21 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -73,6 +76,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -95,3 +99,4 @@ sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/test-proxy/vendor/github.com/gobwas/glob/.gitignore b/test-proxy/vendor/github.com/gobwas/glob/.gitignore index b4ae623be..534b466c5 100644 --- a/test-proxy/vendor/github.com/gobwas/glob/.gitignore +++ b/test-proxy/vendor/github.com/gobwas/glob/.gitignore @@ -6,3 +6,5 @@ glob.iml *.dot *.png *.svg +patterns.txt +*.bench diff --git a/test-proxy/vendor/github.com/gobwas/glob/.travis.yml b/test-proxy/vendor/github.com/gobwas/glob/.travis.yml deleted file mode 100644 index e8a276826..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false - -language: go - -go: - - 1.5.3 - -script: - - go test -v ./... diff --git a/test-proxy/vendor/github.com/gobwas/glob/bench.sh b/test-proxy/vendor/github.com/gobwas/glob/bench.sh index 804cf22e6..8570697b8 100644 --- a/test-proxy/vendor/github.com/gobwas/glob/bench.sh +++ b/test-proxy/vendor/github.com/gobwas/glob/bench.sh @@ -1,26 +1,41 @@ -#! /bin/bash +#!/bin/bash +# +# Compares the benchmarks of the current branch against a git revision: +# +# ./bench.sh v0.2.3 # all the benchmarks +# ./bench.sh master 'Match' # the ones matching a -bench regexp +# +# The results are written to *.bench files in the current directory and +# compared with benchstat (go install golang.org/x/perf/cmd/benchstat@latest). -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 -} +set -eu + +prev=$1 +what=${2:-.} +curr=$(git rev-parse --abbrev-ref HEAD) +rnd=$(head -c4 "$out" + echo "OK" + git checkout -q "$curr" + sleep 5 +} -bench ${to} $2 -bench ${current} $2 +bench "$prev" +bench "$curr" -benchcmp $3 "/tmp/${to}-$2.bench" "/tmp/${current}-$2.bench" +benchstat "$(file "$prev")" "$(file "$curr")" diff --git a/test-proxy/vendor/github.com/gobwas/glob/compiler/compiler.go b/test-proxy/vendor/github.com/gobwas/glob/compiler/compiler.go deleted file mode 100644 index 02e7de80a..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/compiler/compiler.go +++ /dev/null @@ -1,525 +0,0 @@ -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 -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/glob.go b/test-proxy/vendor/github.com/gobwas/glob/glob.go index 2afde343a..fb3e47554 100644 --- a/test-proxy/vendor/github.com/gobwas/glob/glob.go +++ b/test-proxy/vendor/github.com/gobwas/glob/glob.go @@ -1,80 +1,158 @@ package glob import ( - "github.com/gobwas/glob/compiler" + "fmt" + + "github.com/gobwas/glob/internal/debug" "github.com/gobwas/glob/syntax" ) -// Glob represents compiled glob pattern. -type Glob interface { - Match(string) bool +// SyntaxError is returned by [Compile] when the given pattern can not be +// parsed. Offset points at the place in the pattern the error was detected +// at, so the tooling can do things like: +// +// {a,b +// ----^ unclosed `{` +type SyntaxError struct { + // Offset is a byte offset in the pattern. + Offset int + // Reason describes the error. + Reason string } -// Compile creates Glob for given pattern and strings (if any present after pattern) as separators. -// The pattern syntax is: +func (s *SyntaxError) Error() string { + return fmt.Sprintf("glob: syntax error at %d: %s", s.Offset, s.Reason) +} + +// Pattern represents a compiled glob pattern. // -// pattern: -// { term } +// A pattern is compiled into a tree of matchers: // -// 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 +// `a` => "a" +// `a*` => ["a"·*] +// `{a*,b}` => {["a"·*]|"b"} // -// character-range: -// c matches character c (c != `\\`, `-`, `]`) -// `\` c matches character c -// lo `-` hi matches character c for lo <= c <= hi +// Matching is a backtracking walk over that tree; see [Pattern.Match]. +type Pattern struct { + // str is the pattern text the Pattern was compiled from; see + // [Pattern.String]. + str string + + // sep are the separators the Pattern was compiled with; see + // [Pattern.Separators]. + sep []rune + + // m is the root of the matcher tree; see [matcher]. + m matcher + + // state tells whether matching m needs the backtracking state, that + // is, whether it may save checkpoints; see [needsState]. A pattern + // without them is matched with a plain call chain. + state bool + + // The match preconditions: every matching string is at least minLen + // bytes and ends with suffix. They fail the obvious mismatches in O(1) + // instead of a backtracking walk -- e.g. `a*a*a*b` requires the + // trailing `b`, no matter how the stars go. + // + // A precondition pays off only when it catches a mismatch earlier than + // the walk would, which is why: + // + // - there is no required prefix: the walk is left-to-right, so a + // leading literal is the first thing checked anyway, while a bad + // suffix or length is discovered last, after the whole + // backtracking exploration; + // + // - they are computed for the stateful patterns only: a stateless + // pattern is a plain call chain whose matchers perform these very + // checks themselves (e.g. suffixMatcher is a HasSuffix), so the + // precondition would only duplicate them. + minLen int + suffix string +} + +// String returns the source text used to compile the pattern, the same way +// [regexp.Regexp.String] does. // -// pattern-list: -// pattern { `,` pattern } -// comma-separated (without spaces) patterns +// Note that separators are not part of String: they are given to Compile +// alongside the pattern text. +func (p *Pattern) String() string { + return p.str +} + +// Separators returns the separators the pattern was compiled with, in the +// order they were given to Compile; nil when there are none. // -func Compile(pattern string, separators ...rune) (Glob, error) { - ast, err := syntax.Parse(pattern) - if err != nil { - return nil, err - } +// The returned slice is the very one given to Compile, sharing its backing +// array: it is not copied on the way in or out. Matching does not use it. +func (p *Pattern) Separators() []rune { + return p.sep +} - matcher, err := compiler.Compile(ast, separators) - if err != nil { - return nil, err +func init() { + // The matcher tree is unexported; hand its rendering to the in-module + // tooling (cmd/globtest -v) without widening the public API. + debug.Tree = func(p any) string { + return p.(*Pattern).m.String() } +} - return matcher, nil +// Compile compiles the glob pattern. The separators, if given, are the +// characters `*` and `?` do not match (`**` does); they can not be changed +// after the compilation, see [Pattern.Separators]. A malformed pattern is +// reported with a [*SyntaxError]. +// +// 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 +// `[` [ `!` ] class `]` +// character class; `!` negates it +// `{` pattern-list `}` +// pattern alternatives +// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) +// `\` c matches character c +// +// class: +// lo `-` hi matches character c for lo <= c <= hi +// { c } matches any of the listed characters (c != `\`, `]`; +// `\` c matches c, `-` is literal here); must be non-empty +// +// pattern-list: +// pattern { `,` pattern } +// comma-separated (without spaces) patterns +func Compile(pattern string, separators ...rune) (*Pattern, error) { + return compile(pattern, separators) } -// MustCompile is the same as Compile, except that if Compile returns error, this will panic -func MustCompile(pattern string, separators ...rune) Glob { +// MustCompile is the same as Compile, except that if Compile returns error, +// this will panic. +func MustCompile(pattern string, separators ...rune) *Pattern { 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\*\]`. +// QuoteMeta returns a copy of the s having all glob meta characters escaped. func QuoteMeta(s string) string { + // 2 is a pessimistic way of allocating an extra byte per each byte in s. b := make([]byte, 2*len(s)) - - // a byte loop is correct because all meta characters are ASCII j := 0 + // A byte loop is correct here because all meta characters are ASCII. for i := 0; i < len(s); i++ { - if syntax.Special(s[i]) { + if syntax.IsSpecial(s[i]) { b[j] = '\\' j++ } b[j] = s[i] j++ } - return string(b[0:j]) } diff --git a/test-proxy/vendor/github.com/gobwas/glob/internal/debug/debug_disabled.go b/test-proxy/vendor/github.com/gobwas/glob/internal/debug/debug_disabled.go new file mode 100644 index 000000000..1dcc50d44 --- /dev/null +++ b/test-proxy/vendor/github.com/gobwas/glob/internal/debug/debug_disabled.go @@ -0,0 +1,8 @@ +//go:build !globdebug +// +build !globdebug + +package debug + +const Enabled = false + +func Printf(f string, args ...any) {} diff --git a/test-proxy/vendor/github.com/gobwas/glob/internal/debug/debug_enabled.go b/test-proxy/vendor/github.com/gobwas/glob/internal/debug/debug_enabled.go new file mode 100644 index 000000000..26aeebd61 --- /dev/null +++ b/test-proxy/vendor/github.com/gobwas/glob/internal/debug/debug_enabled.go @@ -0,0 +1,14 @@ +//go:build globdebug +// +build globdebug + +package debug + +import ( + "fmt" +) + +const Enabled = true + +func Printf(f string, args ...any) { + fmt.Printf(f, args...) +} diff --git a/test-proxy/vendor/github.com/gobwas/glob/internal/debug/tree.go b/test-proxy/vendor/github.com/gobwas/glob/internal/debug/tree.go new file mode 100644 index 000000000..c716a141e --- /dev/null +++ b/test-proxy/vendor/github.com/gobwas/glob/internal/debug/tree.go @@ -0,0 +1,8 @@ +package debug + +// Tree renders the matcher tree of a compiled *glob.Pattern. +// +// It is set by package glob at init time: the pattern internals are +// unexported, and this keeps them so while letting the in-module tooling +// (cmd/globtest -v) print them. The format is not stable. +var Tree func(pattern any) string diff --git a/test-proxy/vendor/github.com/gobwas/glob/match.go b/test-proxy/vendor/github.com/gobwas/glob/match.go new file mode 100644 index 000000000..2b8425706 --- /dev/null +++ b/test-proxy/vendor/github.com/gobwas/glob/match.go @@ -0,0 +1,788 @@ +package glob + +import ( + "fmt" + "slices" + "strconv" + "strings" + "sync" + "unicode/utf8" + "unsafe" + + "github.com/gobwas/glob/internal/debug" +) + +// Match reports whether s matches the pattern. +func (p *Pattern) Match(s string) bool { + var x matchContext + if p.state { + if len(s) < p.minLen || !strings.HasSuffix(s, p.suffix) { + return false + } + state := acquireState() + defer releaseState(state) + x.state = state + } + for { + n, match := p.m.Match(x, s[x.offset:]) + // Note: debug.Enabled is a build-tag constant; when it is false the + // whole block (including the argument evaluation) is compiled away. + if debug.Enabled && x.state != nil { + debug.Printf("stack: %s\n", formatStack(x.state.stack)) + debug.Printf("stars: %s\n", formatStack(x.state.stars)) + } + if match && n == len(s[x.offset:]) { + if debug.Enabled { + debug.Printf("match!\n") + } + return true + } + if x.state == nil { + // The pattern never saves checkpoints (see [needsState]): + // nothing to backtrack to. + return false + } + var ( + c checkpoint + k checkpointKind + ) + switch { + case len(x.state.stars) > 0: + if debug.Enabled { + debug.Printf("has star\n") + } + c = popLast(&x.state.stars) + k = checkpointStars + + case len(x.state.stack) > 0: + if debug.Enabled { + debug.Printf("has stack\n") + } + c = popLast(&x.state.stack) + k = checkpointStack + + default: + if debug.Enabled { + debug.Printf("no match\n") + } + return false + } + x.offset = c.offset + x.kind = k + x.frame = frame{ + path: c.path, + depth: 0, + } + } +} + +// matcher is a node of the tree a pattern compiles into. +// +// Match matches the beginning of s and reports how many bytes it consumed; +// the caller goes on with the rest. A matcher may consume nothing and still +// match: a void does, and so does a non-terminal star -- it stores its +// restart points instead and lets the walk continue, to be resumed from one +// of them on a mismatch later; see [Pattern.Match]. The context tells where +// in the input and in the tree the matcher is; see [matchContext]. +// +// String renders the node for the debug output and cmd/globtest -v; the +// notation is described at [Pattern]. +type matcher interface { + Match(matchContext, string) (n int, matched bool) + String() string +} + +// frame tells where in the matcher tree the walk currently is: the path from +// the root down to the current node, and the depth of the node. +// +// A checkpoint stores the frame's path; resuming it re-enters the tree from +// the root and follows the path back to the very node that saved it (an +// alternative to try, or a star to restart) -- see [Pattern.Match], +// [multiMatcher.Match] and [altMatcher.Match]. +// +// For `{a*b,c}y`, compiled into [{["a"·*·"b"]|"c"}·"y"], the frame at each +// node the walk visits is: +// +// node path depth +// [{["a"·*·"b"]|"c"}·"y"] [] 0 the root +// {["a"·*·"b"]|"c"} [0]* 1 +// ["a"·*·"b"] [0 0]* 2 +// "a" [0 0 0]* 3 +// * [0 0 1] 3 +// "b" [0 0 2] 3 +// "c" [0 1] 2 reached only by resuming the +// checkpoint the alt saved +// "y" [1] 1 +// +// * the zeros are virtual: turning to child #0 records nothing, so the +// path actually stored is [] -- see path below. +type frame struct { + // path addresses the current node: path[d] is the index of the child taken + // at depth d, that is, in the multiMatcher or altMatcher d levels below + // the root. + // + // For `{a*b,c}y`, compiled into [{["a"·*·"b"]|"c"}·"y"]: + // + // [0 1] the "c" alternative -- what the alt checkpoints when it + // enters ["a"·*·"b"], to be tried on a mismatch + // + // [0 0 1] the star: child #1 of ["a"·*·"b"], which is alternative #0 + // of the alt, which is child #0 of the root sequence -- what + // the star's restart points carry + // + // It is recorded lazily: an index is stored only when the walk turns to a + // child other than #0 (see [matchContext.branch]), so the path may be + // shorter than the current depth -- the missing trailing entries are + // implicitly zero, and [frame.index] reads them as such. In the example + // above, at "a" the path is still empty, not [0 0 0]: the alt, alternative + // #0 and "a" were all entered as child #0. + path []int + + // depth is the depth of the current node (the one [frame.path] leads to): + // 0 at the root, 1 at its children, and so on; [matchContext.next] + // increments it on every descent. It is also where the node's own entry in + // path lives: path[depth] is the child to take next -- [frame.index] reads + // it, [matchContext.branch] writes it. + // + // It is not len(path): the path is recorded lazily, so it may fall short + // of the depth, and, when resuming a checkpoint, it is the whole path of + // the checkpoint, reaching beyond the depth all the way down to the node + // to resume at. + // + // To say it the other way, depth is what len(path) would be were the path + // always recorded in full -- the virtual zeros included -- and cut at the + // current node: the length of the node's full address in the tree. + // + // For `{a*b,c}y`, compiled into [{["a"·*·"b"]|"c"}·"y"], the nodes at + // each depth are: + // + // 0 [{["a"·*·"b"]|"c"}·"y"] the root sequence + // 1 {["a"·*·"b"]|"c"}, "y" its children + // 2 ["a"·*·"b"], "c" the alternatives + // 3 "a", *, "b" the children of ["a"·*·"b"] + depth int +} + +// index returns the index of the child to take at the current node: the one +// the path leads to when resuming a checkpoint, #0 otherwise. +func (v frame) index() int { + if v.depth >= len(v.path) { + return 0 + } + return v.path[v.depth] +} + +// checkpoint is a place to resume the walk from on a mismatch. +type checkpoint struct { + // offset is the position in the input to resume at. + offset int + + // path leads to the node that saved the checkpoint; see [frame]. + // + // Unlike a live frame's, it is always at full length -- the virtual zeros + // written out -- so len(path) is the depth of that node, and a checkpoint + // needs no depth of its own: resuming starts at the root and tells it has + // arrived by comparing the walk's depth against len(path); see + // [matchContext.branch], [matchContext.storeStar] and [altMatcher.Match]. + path []int +} + +// checkpointKind tells which pile a checkpoint was taken from during the +// backtracking in [Pattern.Match]. +type checkpointKind int + +const ( + // checkpointStack is an alternative checkpoint saved by altMatcher. + checkpointStack checkpointKind = iota + // checkpointStars is a star restart point saved by starMatcher. + checkpointStars +) + +// matchContext is what a matcher is called with: where in the input and in +// the tree it is, plus the backtracking state shared by the whole walk. It +// is passed by value, so a matcher's changes to it are seen by its +// descendants only. +type matchContext struct { + // offset is the position in the whole input the current node matches from. + // The matchers see only the remainder of the input, so it is what a + // checkpoint records to resume at the same place; see [checkpoint]. + offset int + + // frame is where in the matcher tree the current node is; see [frame]. + frame frame + + // state holds the checkpoint piles and the path arena shared by the + // whole walk. + state *matchState + + // kind tells which pile the checkpoint being resumed was taken from. + // See [altMatcher.Match] for its use. + kind checkpointKind + + // starsFloor is the number of star restart points that existed when + // the walk entered the current alternative. The entries below it were + // born outside of the alternative and must not be discarded by the + // stars inside it; see [matchContext.storeStar]. + starsFloor int +} + +// push saves an alternative checkpoint at the current offset for the node f +// leads to; see [altMatcher.Match]. +func (x matchContext) push(f frame) { + x.state.stack = append(x.state.stack, checkpoint{ + offset: x.offset, + path: f.path, + }) + if debug.Enabled { + debug.Printf( + "checkpoint offset=%d path=%v\n", + x.offset, f.path, + ) + } +} + +// storeStar saves a restart point for the current star at offset bytes +// further in the input; reset tells whether the star may discard the pending +// restart points first, see below. +func (x matchContext) storeStar(offset int, reset bool) { + path := x.frame.path + if d := x.frame.depth; len(path) < d { + // The walk records an index in path only when it turns to a child + // other than the first one; levels entered at child #0 are implicit. + // Store the path at its full length (the missing entries are always + // zeros) so that the alts above can tell this checkpoint from their + // own. See [altMatcher.Match]. + p := x.state.allocPath(d) + copy(p, path) + path = p + } + if reset { + // This star can extend over anything the pending restart points could + // reach -- they are redundant, discard them. See + // research.swtch.com/glob. + // + // However, only the restart points born inside the current alternative + // may be discarded. An outer star, when resumed, re-enters the + // enclosing alt and may pick another alternative -- something this + // star, locked inside its own alternative, can not absorb. See the + // `*{*0,}` test: the outer star must survive the inner one to reach + // the empty alternative. + x.state.stars = x.state.stars[:x.starsFloor] + } + x.state.stars = append(x.state.stars, checkpoint{ + offset: x.offset + offset, + path: path, + }) + if debug.Enabled { + debug.Printf( + "star offset=%d path=%v reset=%t\n", + x.offset+offset, path, reset, + ) + } +} + +// next returns the context for a child of the current node, matching offset +// bytes further in the input: one level deeper in the tree. +func (x matchContext) next(offset int) matchContext { + x.offset = x.offset + offset + x.frame.depth += 1 + return x +} + +// branch returns a copy of the current frame with its path turned to child i +// at the current level, discarding the deeper levels. The new path is +// allocated from the state's arena. +func (x matchContext) branch(i int) frame { + f := x.frame + path := x.state.allocPath(f.depth + 1) + copy(path, f.path) + path[f.depth] = i + f.path = path + return f +} + +// matchState holds the backtracking state of a single [Pattern.Match] call. +// The states are pooled globally: the buffers keep their grown capacity +// between the matches, so a steady-state Match does not allocate them. +type matchState struct { + // stars are the star restart points and stack the alternative + // checkpoints, both LIFO. On a mismatch the walk resumes from the most + // recent restart point, if any, before the most recent alternative; see + // [Pattern.Match]. + stars []checkpoint + stack []checkpoint + + // arena is the buffer the checkpoint paths are allocated from; see + // [matchState.allocPath]. It is bulk-freed when the match ends, which + // spares the per-path lifetime reasoning: a path may be shared between + // the current frame and several checkpoints. + arena []int +} + +// allocPath returns a zeroed []int of length n allocated from the state's +// arena. When the arena runs out of capacity, a fresh chunk is started; the +// paths allocated from the previous chunks stay valid, since the chunks are +// kept alive by the paths referencing them. +func (st *matchState) allocPath(n int) []int { + if cap(st.arena)-len(st.arena) < n { + st.arena = make([]int, 0, max(2*cap(st.arena), n, 32)) + } + p := st.arena[len(st.arena) : len(st.arena)+n : len(st.arena)+n] + st.arena = st.arena[:len(st.arena)+n] + clear(p) + return p +} + +var statePool sync.Pool // Pool[*matchState] + +// acquireState takes a state from the pool, or makes a new one. +func acquireState() *matchState { + if st, _ := statePool.Get().(*matchState); st != nil { + return st + } + return &matchState{} +} + +// releaseState empties the state and puts it back to the pool. +func releaseState(st *matchState) { + resetCheckpoints(&st.stars) + resetCheckpoints(&st.stack) + // The arena holds no references; keep the (largest) chunk as is. + st.arena = st.arena[:0] + statePool.Put(st) +} + +// resetCheckpoints empties s keeping its capacity. The whole backing array +// is zeroed (not only the live part) to drop the references to the +// checkpoint paths popped during the match. +func resetCheckpoints(s *[]checkpoint) { + full := (*s)[:cap(*s)] + clear(full) + *s = full[:0] +} + +// multiMatcher is a sequence, ["a"·*·"b"]: it matches its children one +// after another, each on the input the previous ones left. +type multiMatcher []matcher + +func (ms multiMatcher) String() string { + var sb strings.Builder + sb.WriteByte('[') + for i, m := range ms { + if i > 0 { + sb.WriteString("·") + } + sb.WriteString(m.String()) + } + sb.WriteByte(']') + return sb.String() +} + +func (ms multiMatcher) Match(x matchContext, s string) (n int, ok bool) { + for i := x.frame.index(); i < len(ms); i++ { + if i != x.frame.index() && x.state != nil { + // The path is recorded for the checkpoints the descendants may + // save; in a stateless walk (see [needsState]) there are none + // and nobody would ever read it. + x.frame = x.branch(i) + } + child := x.next(n) + k, ok := ms[i].Match(child, s[n:]) + if debug.Enabled { + debug.Printf( + "[%T@%p] #%d match %#q against %[5]T(%[5]s) at path=%v depth=%d => %d %t\n", + ms, unsafe.SliceData(ms), i, s[n:], ms[i], + child.frame.path, child.frame.depth, k, ok, + ) + } + if !ok { + return 0, false + } + n += k + } + return n, true +} + +// altMatcher is a group of alternatives, {"a"|"b"}: it matches the one the +// walk is at (the first one when entered anew), having saved a checkpoint +// for the next one to be tried on a mismatch later. +type altMatcher []matcher + +func (ms altMatcher) String() string { + var sb strings.Builder + sb.WriteByte('{') + for i, m := range ms { + if i > 0 { + sb.WriteString("|") + } + sb.WriteString(m.String()) + } + sb.WriteByte('}') + return sb.String() +} + +func (ms altMatcher) Match(x matchContext, s string) (int, bool) { + i := x.frame.index() + // Save a checkpoint for the next alternative to consider it in case of + // a mismatch later (if any). This must be done only when: + // + // - the alt is entered for the first time (the resume path ends above + // this level, or there is none); + // + // - the resume path ends exactly at this level with an alternative + // checkpoint -- its job is "try alternative #i", so the one for the + // next alternative must be saved now. Note that a star restart point + // may end at this level too (a star being a direct child of the alt, + // as in `{*,b}`) -- it must not trigger a save. + // + // Otherwise the walk is merely passing through this alt on its way to + // resume a deeper checkpoint -- the one for the next alternative was + // already saved when the alt was entered for the first time, and saving + // it again on every star restart would blow the stack up exponentially. + // See the "alternatives" tests. + if next := i + 1; next < len(ms) { + d := len(x.frame.path) + if x.frame.depth >= d || (x.frame.depth == d-1 && x.kind == checkpointStack) { + x.push(x.branch(next)) + } + } + // The stars below this level may only discard the restart points born + // inside the same alternative; see [matchContext.storeStar]. + x.starsFloor = len(x.state.stars) + child := x.next(0) + n, match := ms[i].Match(child, s) + if debug.Enabled { + debug.Printf( + "[%T@%p] #%d match %#q against %[5]T(%[5]s) at path=%v depth=%d => %d %t\n", + ms, unsafe.SliceData(ms), i, s, ms[i], + child.frame.path, child.frame.depth, n, match, + ) + } + return n, match +} + +// textMatcher is a literal, "abc". +type textMatcher struct { + Text string +} + +func (m *textMatcher) String() string { + return strconv.Quote(m.Text) +} + +func (m *textMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.HasPrefix(s, m.Text) { + return len(m.Text), true + } + return 0, false +} + +// charMatcher is a `?`: any single character but a separator. +type charMatcher struct { + Sep []rune +} + +func (m *charMatcher) String() string { + var sb strings.Builder + sb.WriteByte('?') + if len(m.Sep) > 0 { + sb.WriteByte('(') + formatRunes(&sb, m.Sep) + sb.WriteByte(')') + } + return sb.String() +} + +func (m *charMatcher) Match(_ matchContext, s string) (int, bool) { + if len(s) == 0 { + return 0, false + } + r, n := utf8.DecodeRuneInString(s) + if slices.Contains(m.Sep, r) { + return 0, false + } + return n, true +} + +// starMatcher is a `*` or a `**`: any sequence of characters, but for the +// separators in the former case. +type starMatcher struct { + // Sep are the separators the star may not extend over; empty for `**`. + Sep []rune + // SepStr is Sep as a string, for the byte-wise scans below. + SepStr string + + // Next is the literal the matcher right after this star begins with + // (when it is a textMatcher), set by [annotateStars]. A restart point + // at a position where the literal does not occur is a guaranteed + // mismatch, so the star jumps between its occurrences instead of + // retrying at every rune. + Next string + // Terminal is set by [annotateStars] when nothing follows this star + // anywhere in the pattern: the star then consumes everything in its + // reach at once, and no restart point can change the outcome. + Terminal bool +} + +// reach returns the length of the prefix of s the star may extend over: +// everything up to the nearest separator. +func (m *starMatcher) reach(s string) int { + if m.SepStr == "" { + return len(s) + } + if e := strings.IndexAny(s, m.SepStr); e >= 0 { + return e + } + return len(s) +} + +// storeSkip stores the restart point at the next occurrence of the m.Next +// literal instead of the next rune. +func (m *starMatcher) storeSkip(x matchContext, s string) { + reach := m.reach(s) + // Look for the occurrences starting within the star's reach; the + // literal itself may extend past it (it may contain the separators). + // Note that a valid UTF-8 literal can not match at a mid-rune + // position, so the one-byte skip below is rune-safe. + end := min(reach+len(m.Next), len(s)) + j := strings.Index(s[1:end], m.Next) + if j < 0 || 1+j > reach { + return + } + x.storeStar(1+j, len(m.Sep) == 0) +} + +func (m *starMatcher) String() string { + var sb strings.Builder + sb.WriteByte('*') + if len(m.Sep) > 0 { + sb.WriteByte('(') + formatRunes(&sb, m.Sep) + sb.WriteByte(')') + } + return sb.String() +} + +func (m *starMatcher) Match(x matchContext, s string) (int, bool) { + if m.Terminal { + // Nothing follows this star in the pattern: either it consumes + // the whole remainder within its reach, or the match fails. + return m.reach(s), true + } + if len(s) == 0 { + return 0, true + } + if m.Next != "" { + m.storeSkip(x, s) + return 0, true + } + r, n := utf8.DecodeRuneInString(s) + if !slices.Contains(m.Sep, r) { + // The star may extend over the rune: save the restart point past + // it. A separator-free star (`**`) can extend over anything the + // pending restart points could reach, so it may discard them; see + // [matchContext.storeStar]. + x.storeStar(n, len(m.Sep) == 0) + } + return 0, true +} + +// runeRangeMatcher is a character range class, `[a-z]` or `[!a-z]`. +type runeRangeMatcher struct { + Lo rune + Hi rune + Not bool +} + +func (m *runeRangeMatcher) String() string { + var sb strings.Builder + if m.Not { + sb.WriteByte('!') + } + sb.WriteByte('[') + sb.WriteRune(m.Lo) + sb.WriteByte('-') + sb.WriteRune(m.Hi) + sb.WriteByte(']') + return sb.String() +} + +func (m *runeRangeMatcher) Match(_ matchContext, s string) (int, bool) { + // Note that an invalid byte decodes as U+FFFD, and is matched as such, + // the same way regexp does; only the empty input is a mismatch. + r, n := utf8.DecodeRuneInString(s) + if n == 0 { + return 0, false + } + ok := m.Lo <= r && r <= m.Hi + if ok != m.Not { + return n, true + } + return 0, false +} + +// runeSetMatcher is a character set class, `[abc]` or `[!abc]`. +type runeSetMatcher struct { + Set map[rune]struct{} + Not bool +} + +// formatRunes writes rs, sorted and comma-separated, to sb. +func formatRunes(sb *strings.Builder, rs []rune) { + rs = slices.Clone(rs) // Not to reorder the caller's, e.g. a matcher's Sep. + slices.Sort(rs) + for i, r := range rs { + if i > 0 { + sb.WriteByte(',') + } + sb.WriteRune(r) + } +} + +func (m *runeSetMatcher) String() string { + rs := make([]rune, 0, len(m.Set)) + for r := range m.Set { + rs = append(rs, r) + } + var sb strings.Builder + if m.Not { + sb.WriteByte('!') + } + sb.WriteByte('[') + formatRunes(&sb, rs) + sb.WriteByte(']') + return sb.String() +} + +func (m *runeSetMatcher) Match(_ matchContext, s string) (int, bool) { + // See the note in runeRangeMatcher.Match. + r, n := utf8.DecodeRuneInString(s) + if n == 0 { + return 0, false + } + if _, has := m.Set[r]; has != m.Not { + return n, true + } + return 0, false +} + +// voidMatcher matches the empty string: an empty alternative, `{a,}`. In a +// sequence it is dropped by [normalizeSequence]. +type voidMatcher struct{} + +func (*voidMatcher) String() string { + return "void" +} + +func (*voidMatcher) Match(matchContext, string) (int, bool) { + return 0, true +} + +// The shaped matchers below are the compile-time rewrites of the common +// terminal sub-sequences; see [specialize]. Nothing may follow them in the +// pattern, so each one either consumes the whole remainder of the input or +// fails -- deterministically, storing no checkpoints. + +// prefixMatcher is a terminal `abc*`. +type prefixMatcher struct { + Text string + Sep string +} + +func (m *prefixMatcher) String() string { + return "prefix(" + strconv.Quote(m.Text) + ")" +} + +func (m *prefixMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.HasPrefix(s, m.Text) && noSep(s[len(m.Text):], m.Sep) { + return len(s), true + } + return 0, false +} + +// suffixMatcher is a terminal `*abc`. +type suffixMatcher struct { + Text string + Sep string +} + +func (m *suffixMatcher) String() string { + return "suffix(" + strconv.Quote(m.Text) + ")" +} + +func (m *suffixMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.HasSuffix(s, m.Text) && noSep(s[:len(s)-len(m.Text)], m.Sep) { + return len(s), true + } + return 0, false +} + +// prefixSuffixMatcher is a terminal `abc*def`. +type prefixSuffixMatcher struct { + Prefix string + Suffix string + Sep string +} + +func (m *prefixSuffixMatcher) String() string { + return "prefix_suffix(" + strconv.Quote(m.Prefix) + "," + strconv.Quote(m.Suffix) + ")" +} + +func (m *prefixSuffixMatcher) Match(_ matchContext, s string) (int, bool) { + // The length check keeps the prefix and the suffix from overlapping: + // `a*ant` must not match `ant`. + if len(s) >= len(m.Prefix)+len(m.Suffix) && + strings.HasPrefix(s, m.Prefix) && + strings.HasSuffix(s, m.Suffix) && + noSep(s[len(m.Prefix):len(s)-len(m.Suffix)], m.Sep) { + return len(s), true + } + return 0, false +} + +// containsMatcher is a terminal `*abc*` with the separator-free stars. +type containsMatcher struct { + Text string +} + +func (m *containsMatcher) String() string { + return "contains(" + strconv.Quote(m.Text) + ")" +} + +func (m *containsMatcher) Match(_ matchContext, s string) (int, bool) { + if strings.Contains(s, m.Text) { + return len(s), true + } + return 0, false +} + +// noSep reports whether s contains none of the separators. +func noSep(s, sep string) bool { + return sep == "" || !strings.ContainsAny(s, sep) +} + +// formatCheckpoint renders c as path@offset for the debug output. +func formatCheckpoint(c checkpoint) string { + return fmt.Sprintf("%v@%d", c.path, c.offset) +} + +// formatStack renders the checkpoint pile s for the debug output, the most +// recent one last. +func formatStack(s []checkpoint) string { + var sb strings.Builder + for i, c := range s { + if i > 0 { + sb.WriteString(" -> ") + } + sb.WriteString(formatCheckpoint(c)) + } + return sb.String() +} + +// popLast panics if s is empty. +func popLast[T any, E ~[]T](s *E) T { + n := len(*s) + r := (*s)[n-1] + *s = (*s)[:n-1] + return r +} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/any.go b/test-proxy/vendor/github.com/gobwas/glob/match/any.go deleted file mode 100644 index 514a9a5c4..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/any.go +++ /dev/null @@ -1,45 +0,0 @@ -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("", string(self.Separators)) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/any_of.go b/test-proxy/vendor/github.com/gobwas/glob/match/any_of.go deleted file mode 100644 index 8e65356cd..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/any_of.go +++ /dev/null @@ -1,82 +0,0 @@ -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("", self.Matchers) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/btree.go b/test-proxy/vendor/github.com/gobwas/glob/match/btree.go deleted file mode 100644 index a8130e93e..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/btree.go +++ /dev/null @@ -1,146 +0,0 @@ -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 = "" - 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("%s]>", l, self.Value, r) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/contains.go b/test-proxy/vendor/github.com/gobwas/glob/match/contains.go deleted file mode 100644 index 0998e95b0..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/contains.go +++ /dev/null @@ -1,58 +0,0 @@ -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("", not, self.Needle) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/every_of.go b/test-proxy/vendor/github.com/gobwas/glob/match/every_of.go deleted file mode 100644 index 7c968ee36..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/every_of.go +++ /dev/null @@ -1,99 +0,0 @@ -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("", self.Matchers) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/list.go b/test-proxy/vendor/github.com/gobwas/glob/match/list.go deleted file mode 100644 index 7fd763ecd..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/list.go +++ /dev/null @@ -1,49 +0,0 @@ -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("", not, string(self.List)) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/match.go b/test-proxy/vendor/github.com/gobwas/glob/match/match.go deleted file mode 100644 index f80e007fb..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/match.go +++ /dev/null @@ -1,81 +0,0 @@ -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] - } -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/max.go b/test-proxy/vendor/github.com/gobwas/glob/match/max.go deleted file mode 100644 index d72f69eff..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/max.go +++ /dev/null @@ -1,49 +0,0 @@ -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("", self.Limit) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/min.go b/test-proxy/vendor/github.com/gobwas/glob/match/min.go deleted file mode 100644 index db57ac8eb..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/min.go +++ /dev/null @@ -1,57 +0,0 @@ -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("", self.Limit) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/nothing.go b/test-proxy/vendor/github.com/gobwas/glob/match/nothing.go deleted file mode 100644 index 0d4ecd36b..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/nothing.go +++ /dev/null @@ -1,27 +0,0 @@ -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("") -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/prefix.go b/test-proxy/vendor/github.com/gobwas/glob/match/prefix.go deleted file mode 100644 index a7347250e..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/prefix.go +++ /dev/null @@ -1,50 +0,0 @@ -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("", self.Prefix) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/prefix_any.go b/test-proxy/vendor/github.com/gobwas/glob/match/prefix_any.go deleted file mode 100644 index 8ee58fe1b..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/prefix_any.go +++ /dev/null @@ -1,55 +0,0 @@ -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("", self.Prefix, string(self.Separators)) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/prefix_suffix.go b/test-proxy/vendor/github.com/gobwas/glob/match/prefix_suffix.go deleted file mode 100644 index 8208085a1..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/prefix_suffix.go +++ /dev/null @@ -1,62 +0,0 @@ -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("", self.Prefix, self.Suffix) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/range.go b/test-proxy/vendor/github.com/gobwas/glob/match/range.go deleted file mode 100644 index ce30245a4..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/range.go +++ /dev/null @@ -1,48 +0,0 @@ -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("", not, string(self.Lo), string(self.Hi)) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/row.go b/test-proxy/vendor/github.com/gobwas/glob/match/row.go deleted file mode 100644 index 4379042e4..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/row.go +++ /dev/null @@ -1,77 +0,0 @@ -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("", self.RunesLength, self.Matchers) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/segments.go b/test-proxy/vendor/github.com/gobwas/glob/match/segments.go deleted file mode 100644 index 9ea6f3094..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/segments.go +++ /dev/null @@ -1,91 +0,0 @@ -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) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/single.go b/test-proxy/vendor/github.com/gobwas/glob/match/single.go deleted file mode 100644 index ee6e3954c..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/single.go +++ /dev/null @@ -1,43 +0,0 @@ -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("", string(self.Separators)) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/suffix.go b/test-proxy/vendor/github.com/gobwas/glob/match/suffix.go deleted file mode 100644 index 85bea8c68..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/suffix.go +++ /dev/null @@ -1,35 +0,0 @@ -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("", self.Suffix) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/suffix_any.go b/test-proxy/vendor/github.com/gobwas/glob/match/suffix_any.go deleted file mode 100644 index c5106f819..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/suffix_any.go +++ /dev/null @@ -1,43 +0,0 @@ -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("", string(self.Separators), self.Suffix) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/super.go b/test-proxy/vendor/github.com/gobwas/glob/match/super.go deleted file mode 100644 index 3875950bb..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/super.go +++ /dev/null @@ -1,33 +0,0 @@ -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("") -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/match/text.go b/test-proxy/vendor/github.com/gobwas/glob/match/text.go deleted file mode 100644 index 0a17616d3..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/match/text.go +++ /dev/null @@ -1,45 +0,0 @@ -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("", self.Str) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/parse.go b/test-proxy/vendor/github.com/gobwas/glob/parse.go new file mode 100644 index 000000000..30114dfcf --- /dev/null +++ b/test-proxy/vendor/github.com/gobwas/glob/parse.go @@ -0,0 +1,679 @@ +package glob + +import ( + "slices" + "unicode/utf8" + + "github.com/gobwas/glob/internal/debug" + "github.com/gobwas/glob/syntax" +) + +// compile parses the pattern into a matcher tree (see below), simplifies and +// specializes it, and computes the match-time hints and preconditions; see +// [simplify], [specialize], [annotateStars], [needsState], [minLength] and +// [requiredSuffix]. It is what [Compile] wraps. +func compile(str string, sep []rune) (*Pattern, error) { + if debug.Enabled { + debug.Printf("compiling %#q\n", str) + } + // The matchers keep sep and read it while matching, and the variadic slice + // may alias an array owned by the caller: give them a copy of their own. + // + // The pattern itself keeps the slice as given, to return it from + // Separators() without cloning. + var ( + sepCopy = slices.Clone(sep) + sepStr = string(sep) + ) + + type operator struct { + kind int + index int + } + const ( + opTerms = iota + opList + ) + /* + Stack-based parsing is a technique used to evaluate mathematical + expressions by leveraging the properties of the LIFO (Last-In, + First-Out) data structure, the stack. It involves using two stacks: one + for operands (numbers) and one for operators. By processing the + expression from left to right and strategically pushing and popping + elements from the stacks, the expression can be effectively evaluated. + + https://cp-algorithms.com/string/expression_parsing.html + + Here the operands are matchers and the only operators are the braces + and the commas inside them, so it goes as follows: + + - a leaf token (text, `?`, `*`, `**`, `[...]`) pushes its matcher + onto the stack; + + - `{` pushes two operators, both remembering the current stack + length: opTerms marks where the alternatives of the group will + be collected, opList marks where the terms of the current + alternative begin; + + - `,` pops the opList, collapses the terms above its index into a + single multiMatcher (or a voidMatcher when there are none, as in + `{,a}`), and pushes a fresh opList for the next alternative; + + - `}` pops the opList and collapses the last alternative the same + way, then pops the opTerms and collapses everything above its + index -- one matcher per alternative by now -- into an + altMatcher; + + - at the EOF whatever is left on the stack is the top-level + sequence; a leftover operator means an unclosed `{`. + + For example, `a{b*,c}d` goes like this (list@i is an opList with + index i, likewise terms@i): + + token stack operators + a "a" + { "a" terms@1 list@1 + b "a" "b" terms@1 list@1 + * "a" "b" * terms@1 list@1 + , "a" ["b"·*] terms@1 list@2 + c "a" ["b"·*] "c" terms@1 list@2 + } "a" ["b"·*] ["c"] terms@1 + "a" {["b"·*]|["c"]} + d "a" {["b"·*]|["c"]} "d" + EOF ["a"·{["b"·*]|["c"]}·"d"] + + The result is then simplified (["c"] becomes "c") and specialized; + see [simplify] and [specialize]. + */ + var ( + stack []matcher + operators []operator + ) + lex := syntax.NewLexer(str) +parsing: + for { + token := lex.Next() + if debug.Enabled { + debug.Printf("token: %s\n", token) + } + switch token.Type { + case syntax.EOF: + break parsing + + case syntax.Error: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: token.Data, + } + + case syntax.Single: + stack = append(stack, &charMatcher{ + Sep: sepCopy, + }) + + case syntax.Text: + stack = append(stack, &textMatcher{ + Text: token.Data, + }) + + case syntax.RangeOpen: + m, err := parseRange(lex) + if err != nil { + return nil, err + } + stack = append(stack, m) + + case syntax.Any: + stack = append(stack, &starMatcher{ + Sep: sepCopy, + SepStr: sepStr, + }) + + case syntax.Super: + stack = append(stack, &starMatcher{ + Sep: nil, + }) + + case syntax.TermsOpen: + // Note that the `{` opens both the group and its first + // alternative: every alternative is delimited by an opList + // operator. This way TermsClose always collapses the trailing + // alternative into a single matcher first, even when the group + // has no commas at all, e.g. `{ab*}`. + operators = append(operators, + operator{kind: opTerms, index: len(stack)}, + operator{kind: opList, index: len(stack)}, + ) + if debug.Enabled { + debug.Printf("terms enter: %d\n", len(stack)) + } + + case syntax.TermSeparator: + k := len(operators) - 1 + if k < 0 { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected `,`", + } + } + x := operators[k] + if x.kind == opList { + // Remove the most recent "comma" operator. + // Note that the previous one is the terms operator. + operators = operators[:k] + } + i := x.index + // Handle the `{,a}` case. + if i == len(stack) { + // Empty matchers. + stack = append(stack, &voidMatcher{}) + } else { + stack[i] = multiMatcher(slices.Clone(stack[i:])) + stack = stack[:i+1] + if debug.Enabled { + debug.Printf("terms next: %d: %s\n", i, stack[i]) + } + } + operators = append(operators, operator{ + kind: opList, + index: len(stack), + }) + if debug.Enabled { + debug.Printf("terms separator: %d\n", len(stack)) + } + + case syntax.TermsClose: + for { + k := len(operators) - 1 + if k < 0 { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected `}`", + } + } + x := operators[k] + operators = operators[:k] + + i := x.index + c := slices.Clone(stack[i:]) + var m matcher + switch x.kind { + case opTerms: + m = altMatcher(c) + case opList: + m = multiMatcher(c) + } + // Handle the `{a,}` case. + if i == len(stack) { + stack = append(stack, m) + } else { + stack = stack[:i+1] + stack[i] = m + } + + if debug.Enabled { + debug.Printf( + "terms leave(%d): %d: %s\n", + x.kind, i, stack[i], + ) + } + if x.kind == opTerms { + break + } + } + + default: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected token " + token.String(), + } + } + } + if len(operators) != 0 { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unclosed `{`", + } + } + m := simplify(multiMatcher(stack)) + m = specialize(m, true) + annotateStars(m, true) + if debug.Enabled { + debug.Printf("compiled %#q: %s\n", str, m) + } + p := &Pattern{ + str: str, + sep: sep, + m: m, + state: needsState(m), + } + if p.state { + p.minLen = minLength(m) + p.suffix = requiredSuffix(m) + } + return p, nil +} + +// parseRange parses a character class, called right after its opening `[` +// was read; it consumes the tokens up to and including the closing `]`. The +// class is either a range, `[a-c]`, or a set, `[abc]`, either possibly +// negated with a leading `!`; see [runeRangeMatcher] and [runeSetMatcher]. +func parseRange(lex *syntax.Lexer) (matcher, error) { + // -1 marks a range boundary as unset: any decoded rune, including + // U+0000, is non-negative. + var ( + not bool + lo, hi rune = -1, -1 + chars map[rune]struct{} + ) + for { + token := lex.Next() + switch token.Type { + case syntax.EOF: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unclosed `[`", + } + + case syntax.Error: + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: token.Data, + } + + case syntax.Not: + not = true + + case syntax.RangeLo: + r, w := utf8.DecodeRuneInString(token.Data) + if len(token.Data) > w { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected length of range lo character", + } + } + lo = r + + case syntax.RangeBetween: + // The `-` between lo and hi: nothing to do. + + case syntax.RangeHi: + r, w := utf8.DecodeRuneInString(token.Data) + if len(token.Data) > w { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "unexpected length of range hi character", + } + } + hi = r + + if hi < lo { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "range hi character is less than lo", + } + } + + case syntax.Text: + chars = make(map[rune]struct{}) + for _, r := range token.Data { + chars[r] = struct{}{} + } + + case syntax.RangeClose: + isRange := lo >= 0 && hi >= 0 + isChars := chars != nil + + if isChars == isRange { + return nil, &SyntaxError{ + Offset: lex.Offset(), + Reason: "could not parse range", + } + } + if isRange { + return &runeRangeMatcher{ + Lo: lo, + Hi: hi, + Not: not, + }, nil + } + return &runeSetMatcher{ + Set: chars, + Not: not, + }, nil + } + } +} + +// simplify rewrites the freshly parsed tree into its canonical shape, bottom +// up: the sequences are normalized (see [normalizeSequence]), and a sequence +// or a group of alternatives with a single child is replaced by the child, +// with none -- by a void. +func simplify(m matcher) matcher { + var ( + ms []matcher + isMulti bool + ) + switch v := m.(type) { + case multiMatcher: + ms, isMulti = v, true + case altMatcher: + ms = v + default: + return m + } + for i, m := range ms { + ms[i] = simplify(m) + } + if isMulti { + ms = normalizeSequence(ms) + } + switch len(ms) { + case 0: + return &voidMatcher{} + case 1: + return ms[0] + } + if isMulti { + return multiMatcher(ms) + } + return altMatcher(ms) +} + +// normalizeSequence rewrites a sequence of (already simplified) matchers +// into a simpler equivalent one: +// +// ["a"·["b"·"c"]·"d"] => ["a"·"b"·"c"·"d"] inline the nested sequences +// ["a"·void] => ["a"] drop the void matchers +// ["a"·"b"] => ["ab"] merge the adjacent literals +// [*·**] => [**] coalesce the adjacent stars +// +// Longer literals also make better star jumps; see [annotateStars]. +func normalizeSequence(ms []matcher) []matcher { + if !needsNormalize(ms) { + // The common case: nothing to rewrite, no copy needed. + return ms + } + out := make([]matcher, 0, len(ms)) + var push func(m matcher) + push = func(m matcher) { + switch v := m.(type) { + case multiMatcher: + for _, c := range v { + push(c) + } + return + case *voidMatcher: + return + case *textMatcher: + if len(out) > 0 { + if prev, ok := out[len(out)-1].(*textMatcher); ok { + out[len(out)-1] = &textMatcher{Text: prev.Text + v.Text} + return + } + } + case *starMatcher: + if len(out) > 0 { + if prev, ok := out[len(out)-1].(*starMatcher); ok { + // Adjacent stars are equivalent to the most general + // of them: the one not limited by separators, if any. + if len(prev.Sep) > 0 && len(v.Sep) == 0 { + out[len(out)-1] = v + } + return + } + } + } + out = append(out, m) + } + for _, m := range ms { + push(m) + } + return out +} + +// needsNormalize reports whether [normalizeSequence] would change ms, so +// that the common case skips the copy. +func needsNormalize(ms []matcher) bool { + for i, m := range ms { + switch m.(type) { + case multiMatcher, *voidMatcher: + return true + case *textMatcher: + if i > 0 { + if _, ok := ms[i-1].(*textMatcher); ok { + return true + } + } + case *starMatcher: + if i > 0 { + if _, ok := ms[i-1].(*starMatcher); ok { + return true + } + } + } + } + return false +} + +// specialize rewrites the terminal sub-sequences of the simplified matcher +// tree into the shaped matchers -- [prefixMatcher], [suffixMatcher], +// [prefixSuffixMatcher] and [containsMatcher]; see [foldTail] for the +// rewrites. The tail flag tells whether nothing follows m in the pattern; +// only there the rewrites apply, since a shaped matcher consumes the whole +// remainder of the input. +func specialize(m matcher, tail bool) matcher { + switch v := m.(type) { + case altMatcher: + // Every alternative ends where the alt ends. + for i, c := range v { + v[i] = specialize(c, tail) + } + return v + + case multiMatcher: + for i, c := range v { + v[i] = specialize(c, tail && i == len(v)-1) + } + if !tail { + return v + } + ms := foldTail([]matcher(v)) + if len(ms) == 1 { + return ms[0] + } + return multiMatcher(ms) + } + return m +} + +// foldTail repeatedly folds the two trailing matchers of the terminal +// sequence ms into a shaped one, while possible: +// +// [..·"abc"·*] => [..·prefix("abc")] +// [..·*·"abc"] => [..·suffix("abc")] +// [..·"abc"·prefix("def")] => [..·prefix("abcdef")] +// [..·*·prefix("abc")] => [..·contains("abc")] (separator-free) +// [..·"abc"·suffix("def")] => [..·prefix_suffix("abc","def")] +// [..·*·contains("abc")] => [..·contains("abc")] (separator-free) +func foldTail(ms []matcher) []matcher { + for len(ms) >= 2 { + var ( + prev = ms[len(ms)-2] + folded matcher + ) + switch last := ms[len(ms)-1].(type) { + case *starMatcher: + if t, ok := prev.(*textMatcher); ok { + folded = &prefixMatcher{Text: t.Text, Sep: last.SepStr} + } + + case *textMatcher: + if star, ok := prev.(*starMatcher); ok { + folded = &suffixMatcher{Text: last.Text, Sep: star.SepStr} + } + + case *prefixMatcher: + switch p := prev.(type) { + case *textMatcher: + folded = &prefixMatcher{Text: p.Text + last.Text, Sep: last.Sep} + case *starMatcher: + if p.SepStr == "" && last.Sep == "" { + folded = &containsMatcher{Text: last.Text} + } + } + + case *suffixMatcher: + if t, ok := prev.(*textMatcher); ok { + folded = &prefixSuffixMatcher{ + Prefix: t.Text, + Suffix: last.Text, + Sep: last.Sep, + } + } + + case *containsMatcher: + if star, ok := prev.(*starMatcher); ok && star.SepStr == "" { + folded = last + } + } + if folded == nil { + break + } + ms = ms[:len(ms)-1] + ms[len(ms)-1] = folded + } + return ms +} + +// annotateStars computes the compile-time hints for the star matchers, in +// order to keep the number of restart points they store at match time low: +// +// - a star directly followed by a literal jumps between the literal +// occurrences instead of retrying at every rune (see +// [starMatcher.storeSkip]); +// +// - a star with nothing after it anywhere in the pattern (tail is true +// for m and the star closes it) consumes its whole reach at once and +// stores no restart points at all. +func annotateStars(m matcher, tail bool) { + switch v := m.(type) { + case multiMatcher: + for i, c := range v { + last := i == len(v)-1 + star, ok := c.(*starMatcher) + if !ok { + annotateStars(c, tail && last) + continue + } + star.Terminal = tail && last + if !last { + star.Next = leadingLiteral(v[i+1]) + } + } + case altMatcher: + for _, c := range v { + annotateStars(c, tail) + } + case *starMatcher: + v.Terminal = tail + } +} + +// leadingLiteral returns the literal the given matcher is guaranteed to +// begin its match with, if any. +func leadingLiteral(m matcher) string { + switch v := m.(type) { + case *textMatcher: + return v.Text + case *prefixMatcher: + return v.Text + case *prefixSuffixMatcher: + return v.Prefix + } + return "" +} + +// minLength returns the minimum length in bytes of a string m can match. +func minLength(m matcher) (n int) { + switch v := m.(type) { + case *textMatcher: + return len(v.Text) + case *charMatcher, *runeRangeMatcher, *runeSetMatcher: + return 1 + case *prefixMatcher: + return len(v.Text) + case *suffixMatcher: + return len(v.Text) + case *prefixSuffixMatcher: + return len(v.Prefix) + len(v.Suffix) + case *containsMatcher: + return len(v.Text) + case multiMatcher: + for _, c := range v { + n += minLength(c) + } + return n + case altMatcher: + n = minLength(v[0]) + for _, c := range v[1:] { + n = min(n, minLength(c)) + } + return n + } + return 0 // A star or a void. +} + +// requiredSuffix returns the literal every string m matches must end with. +func requiredSuffix(m matcher) string { + switch v := m.(type) { + case *textMatcher: + return v.Text + case *suffixMatcher: + return v.Text + case *prefixSuffixMatcher: + return v.Suffix + case multiMatcher: + return requiredSuffix(v[len(v)-1]) + case altMatcher: + s := requiredSuffix(v[0]) + for _, c := range v[1:] { + s = commonSuffix(s, requiredSuffix(c)) + if s == "" { + break + } + } + return s + } + return "" // A star, a single-character matcher or a void. +} + +// commonSuffix returns the longest common suffix of a and b, never splitting +// a multi-byte rune. +func commonSuffix(a, b string) string { + i := 0 + for i < len(a) && i < len(b) { + ra, wa := utf8.DecodeLastRuneInString(a[:len(a)-i]) + rb, wb := utf8.DecodeLastRuneInString(b[:len(b)-i]) + if ra != rb || wa != wb { + break + } + i += wa + } + return a[len(a)-i:] +} + +// needsState reports whether matching m may save a checkpoint. Only the +// alts and the non-terminal stars do; a pattern without them is matched +// with a plain call chain -- see [Pattern.Match]. +func needsState(m matcher) bool { + switch v := m.(type) { + case altMatcher: + return true + case multiMatcher: + return slices.ContainsFunc(v, needsState) + case *starMatcher: + return !v.Terminal + } + return false +} diff --git a/test-proxy/vendor/github.com/gobwas/glob/readme.md b/test-proxy/vendor/github.com/gobwas/glob/readme.md index f58144e73..9f692571f 100644 --- a/test-proxy/vendor/github.com/gobwas/glob/readme.md +++ b/test-proxy/vendor/github.com/gobwas/glob/readme.md @@ -1,6 +1,6 @@ # glob.[go](https://golang.org) -[![GoDoc][godoc-image]][godoc-url] [![Build Status][travis-image]][travis-url] +[![GoDoc][godoc-image]][godoc-url] [![CI][ci-image]][ci-url] > Go Globbing Library. @@ -19,130 +19,225 @@ package main import "github.com/gobwas/glob" func main() { - var g glob.Glob - + var g *glob.Pattern + // create simple glob g = glob.MustCompile("*.github.com") g.Match("api.github.com") // true - - // quote meta characters and then create simple glob + + // 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.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 + + // 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.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 + + // 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.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 + g.Match("at") // false + g.Match("zat") // false } ``` +`Compile` reports malformed patterns with a `*glob.SyntaxError` carrying the +byte offset and the reason: + +```go +_, err := glob.Compile("{a,b") +// err: glob: syntax error at 4: unclosed `{` +``` + +A compiled `Pattern` captures what it was compiled from, so it can be passed +around instead of the raw arguments and inspected when needed (`String()` makes +it a `fmt.Stringer`, like `regexp.Regexp`): + +```go +g := glob.MustCompile("*.github.com", '.') +g.String() // "*.github.com" +g.Separators() // []rune{'.'} +``` + +## Syntax + +Syntax is inspired by [standard wildcards](http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm), +with one addition: `**` (the "super-asterisk"), which matches any sequence +of characters *including* the separators, where `*` stops at them. Note that +it is just that -- a `*` that crosses separators -- and not the `**/` +"globstar" of shells and file globbers: `**/x` requires the literal `/`, so +it does not match `x`; use `{**/,}x` for that. The same applies to a +`**` between separators, e.g. `a/**/b` does not match `a/b`. + +``` +pattern: + { term } + +term: + `*` matches any sequence of non-separator characters + `**` matches any sequence of characters + `?` matches any single non-separator character + `[` [ `!` ] class `]` + character class; `!` negates it + `{` pattern-list `}` + pattern alternatives + c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`) + `\` c matches character c + +class: + lo `-` hi matches character c for lo <= c <= hi + { c } matches any of the listed characters (c != `\`, `]`; + `\` c matches c, `-` is literal here); must be non-empty + +pattern-list: + pattern { `,` pattern } + comma-separated (without spaces) patterns +``` + +### Escaping + +The backslash is the escape character: `\*` is a literal asterisk, and a +backslash itself is `\\`. Mind the Go string literals: `"foo\\bar"` is the +pattern `foo\bar`, which is the literal `foobar`, not `foo\bar`. To match a +backslash (e.g. in the Windows paths) write `"foo\\\\bar"` or `` `foo\\bar` ``, +or use `QuoteMeta` on the literal part. + +### Separators + +The separators are not part of the pattern syntax -- they are configured +once, at compilation time, as the extra arguments of `Compile`: + +```go +g := glob.MustCompile("api.*.com", '.', '/') +``` + +They only limit the wildcards: `*` and `?` never match a separator, while +`**` matches across them; the literals and the character classes are not +affected. With no separators given, `*` and `**` are equivalent. A compiled +`*glob.Pattern` keeps its separators for all matches -- to match the same +pattern with different separators, compile it again. + ## 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. +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. +If you will not use compiled `*glob.Pattern` 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: +`Match` performs zero allocations and is safe for concurrent use. Common +pattern shapes (literals, prefixes, suffixes, substrings) are recognized at +compile time and matched with plain string comparisons; the backtracking +engine behind the rest is differentially fuzzed against the `regexp` package +(see `FuzzMatchRegexp`). -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: +Run `go test -bench=.` from source root to see the benchmarks (the numbers +below are from an Apple M4): 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. \ No newline at end of file +`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my cat has very bright eyes` | `true` | 141 +`[a-z][!a-x]*cat*[h][!b]*eyes*` | `my dog has very bright eyes` | `false` | 46 +`https://*.google.*` | `https://account.google.com` | `true` | 16 +`https://*.google.*` | `https://google.com` | `false` | 13 +`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://yahoo.com` | `true` | 61 +`{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}` | `http://google.com` | `false` | 70 +`{https://*gobwas.com,http://exclude.gobwas.com}` | `https://safe.gobwas.com` | `true` | 24 +`{https://*gobwas.com,http://exclude.gobwas.com}` | `http://safe.gobwas.com` | `false` | 32 +`google.com` | `google.com` | `true` | 5.0 +`google.com` | `gobwas.com` | `false` | 3.9 +`abc*` | `abcdef` | `true` | 4.1 +`abc*` | `af` | `false` | 3.0 +`*def` | `abcdef` | `true` | 4.1 +`*def` | `af` | `false` | 2.9 +`ab*ef` | `abcdef` | `true` | 6.0 +`ab*ef` | `af` | `false` | 3.0 + +The same things with the `regexp` package -- not to pick on it (it is a +general-purpose engine with much stronger guarantees), but as a reference +for how the glob-shaped specialization pays off per pattern. The regular +expressions are the exact equivalents: anchored, and with the `s` flag +where there is a `*`, since a `*` matches a newline like any other +character (see `BenchmarkCompareGlobAndRegexp`): + +Pattern | Fixture | Match | Speed (ns/op) | glob is +--------|---------|-------|---------------|-------- +`(?s)^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my cat has very bright eyes` | `true` | 505 | 3.6x faster +`(?s)^[a-z][^a-x].*cat.*[h][^b].*eyes.*$` | `my dog has very bright eyes` | `false` | 221 | 4.9x faster +`(?s)^https://.*\.google\..*$` | `https://account.google.com` | `true` | 251 | 16x faster +`(?s)^https://.*\.google\..*$` | `https://google.com` | `false` | 128 | 9.6x faster +`(?s)^(https://.*\.google\..*\|.*yandex\..*\|.*yahoo\..*\|.*mail\.ru)$` | `http://yahoo.com` | `true` | 396 | 6.5x faster +`(?s)^(https://.*\.google\..*\|.*yandex\..*\|.*yahoo\..*\|.*mail\.ru)$` | `http://google.com` | `false` | 558 | 8.0x faster +`(?s)^(https://.*gobwas\.com\|http://exclude\.gobwas\.com)$` | `https://safe.gobwas.com` | `true` | 210 | 8.8x faster +`(?s)^(https://.*gobwas\.com\|http://exclude\.gobwas\.com)$` | `http://safe.gobwas.com` | `false` | 46 | 1.4x faster +`^google\.com$` | `google.com` | `true` | 25 | 5.0x faster +`^google\.com$` | `gobwas.com` | `false` | 17 | 4.3x faster +`(?s)^abc.*$` | `abcdef` | `true` | 43 | 10x faster +`(?s)^abc.*$` | `af` | `false` | 1.5 | 2.0x slower +`(?s)^.*def$` | `abcdef` | `true` | 73 | 18x faster +`(?s)^.*def$` | `af` | `false` | 1.5 | 1.9x slower +`(?s)^ab.*ef$` | `abcdef` | `true` | 77 | 13x faster +`(?s)^ab.*ef$` | `af` | `false` | 1.5 | 2.0x slower + +(The three `slower` rows are the tiny-mismatch cases. Both engines reject +them with the same literal check; `regexp` just reaches it through less +call overhead. In absolute terms it is 1.5ns vs 3ns -- negligible either +way.) + +[godoc-image]: https://pkg.go.dev/badge/github.com/gobwas/glob.svg +[godoc-url]: https://pkg.go.dev/github.com/gobwas/glob +[ci-image]: https://github.com/gobwas/glob/actions/workflows/ci.yml/badge.svg?branch=master +[ci-url]: https://github.com/gobwas/glob/actions/workflows/ci.yml diff --git a/test-proxy/vendor/github.com/gobwas/glob/syntax/ast/ast.go b/test-proxy/vendor/github.com/gobwas/glob/syntax/ast/ast.go deleted file mode 100644 index 3220a694a..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/syntax/ast/ast.go +++ /dev/null @@ -1,122 +0,0 @@ -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 "" - } -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/syntax/ast/parser.go b/test-proxy/vendor/github.com/gobwas/glob/syntax/ast/parser.go deleted file mode 100644 index 429b40943..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/syntax/ast/parser.go +++ /dev/null @@ -1,157 +0,0 @@ -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 - } - } -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer.go b/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer.go new file mode 100644 index 000000000..428608023 --- /dev/null +++ b/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer.go @@ -0,0 +1,381 @@ +// Package syntax implements the lexer of the glob pattern syntax. The parser +// lives in package glob; the syntax itself is described at [glob.Compile]. +package syntax + +import ( + "bytes" + "fmt" + "slices" + "unicode/utf8" +) + +// TokenType tells the kind of a [Token]. +type TokenType int + +const ( + // EOF marks the end of the input; the lexer returns it repeatedly. + EOF TokenType = iota + // Error carries an error message in Token.Data; the lexer keeps + // returning it once it happened. Note that the lexer catches only the + // errors local to a token (an invalid UTF-8 sequence, a malformed + // character class): the structural ones, like an unclosed `{`, are for + // the parser to detect. + Error + // Text is a run of literal characters, with the escapes resolved. + Text + // Any is the `*` wildcard. + Any + // Super is the `**` wildcard. + Super + // Single is the `?` wildcard. + Single + // Not is the `!` right after the `[` of a character class. + Not + // TermSeparator is the `,` between the alternatives of a `{...}` group. + // Outside of a group a comma is a plain Text character. + TermSeparator + // RangeOpen and RangeClose are the `[` and `]` of a character class. + // Between them the lexer produces either a Text token (a set of + // characters, `[abc]`) or a RangeLo, RangeBetween, RangeHi triple (a + // range, `[a-c]`), possibly preceded by Not. + RangeOpen + RangeClose + RangeLo + RangeHi + RangeBetween + // TermsOpen and TermsClose are the `{` and `}` of an alternatives group. + TermsOpen + TermsClose +) + +func (tt TokenType) String() string { + switch tt { + case EOF: + return "eof" + case Error: + return "error" + case Text: + return "text" + case Any: + return "any" + case Super: + return "super" + case Single: + return "single" + case Not: + return "not" + case TermSeparator: + 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 "" + } +} + +// Token is a lexeme of the pattern: its kind and the source text it was +// read from (or the error message for Error, the literal characters with +// the escapes resolved for Text). +type Token struct { + Type TokenType + Data string +} + +func (t Token) String() string { + return fmt.Sprintf("%v<%q>", t.Type, t.Data) +} + +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, +} + +// IsSpecial reports whether c is a glob meta character, that is, one that +// [glob.QuoteMeta] escapes. Note that `,`, `!` and `-` are not among them: +// they are special only inside `{...}` and `[...]` respectively, which are. +func IsSpecial(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 +} + +// eof is the end-of-input sentinel. It must not collide with any rune that +// can appear in a valid pattern -- note that U+0000 can. +const eof rune = -1 + +// Lexer splits a pattern into tokens; see [Lexer.Next]. +type Lexer struct { + data string + pos int + err error + + tokens tokens + termsLevel int + + lastRune rune + lastRuneSize int + hasRune bool +} + +// NewLexer returns a lexer over the source pattern. +func NewLexer(source string) *Lexer { + l := &Lexer{ + data: source, + tokens: tokens(make([]Token, 0, 4)), + } + return l +} + +// Offset returns the byte offset in the source the lexer stopped at, that +// is, the position right after the most recently returned token. +func (l *Lexer) Offset() int { + return l.pos +} + +// Next returns the next token. Once the input is over it returns EOF, and +// once an error happened it returns that Error, repeatedly. +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 && w == 1 { + // An invalid encoding: a valid U+FFFD decodes at its width of 3. + l.errorf("invalid UTF-8 sequence") + 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 ...any) { + 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{TermSeparator, 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 { + if escaped { + l.errorf("trailing backslash") + } + break + } + + if !escaped { + if r == char_escape { + escaped = true + continue + } + if slices.Index(breakers, r) != -1 { + l.unread() + break reading + } + } + + escaped = false + data = append(data, r) + } + + if len(data) > 0 { + l.tokens.push(Token{Text, string(data)}) + } +} diff --git a/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go b/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go deleted file mode 100644 index a1c8d1962..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer/lexer.go +++ /dev/null @@ -1,273 +0,0 @@ -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)}) - } -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer/token.go b/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer/token.go deleted file mode 100644 index 2797c4e83..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/syntax/lexer/token.go +++ /dev/null @@ -1,88 +0,0 @@ -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) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/syntax/syntax.go b/test-proxy/vendor/github.com/gobwas/glob/syntax/syntax.go deleted file mode 100644 index 1d168b148..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/syntax/syntax.go +++ /dev/null @@ -1,14 +0,0 @@ -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) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/util/runes/runes.go b/test-proxy/vendor/github.com/gobwas/glob/util/runes/runes.go deleted file mode 100644 index a72355641..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/util/runes/runes.go +++ /dev/null @@ -1,154 +0,0 @@ -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) -} diff --git a/test-proxy/vendor/github.com/gobwas/glob/util/strings/strings.go b/test-proxy/vendor/github.com/gobwas/glob/util/strings/strings.go deleted file mode 100644 index e8ee1920b..000000000 --- a/test-proxy/vendor/github.com/gobwas/glob/util/strings/strings.go +++ /dev/null @@ -1,39 +0,0 @@ -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 -} diff --git a/test-proxy/vendor/modules.txt b/test-proxy/vendor/modules.txt index 7369255f7..5f35a832a 100644 --- a/test-proxy/vendor/modules.txt +++ b/test-proxy/vendor/modules.txt @@ -4,16 +4,11 @@ github.com/davecgh/go-spew/spew # github.com/go-logr/logr v1.2.0 ## explicit; go 1.16 github.com/go-logr/logr -# github.com/gobwas/glob v0.2.3 -## explicit +# github.com/gobwas/glob v1.0.0 +## explicit; go 1.22.0 github.com/gobwas/glob -github.com/gobwas/glob/compiler -github.com/gobwas/glob/match +github.com/gobwas/glob/internal/debug github.com/gobwas/glob/syntax -github.com/gobwas/glob/syntax/ast -github.com/gobwas/glob/syntax/lexer -github.com/gobwas/glob/util/runes -github.com/gobwas/glob/util/strings # github.com/gogo/protobuf v1.3.2 ## explicit; go 1.15 github.com/gogo/protobuf/proto