remove unused packages
This commit is contained in:
parent
a65c341690
commit
2c8c81a24b
@ -1,349 +0,0 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package glob
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Glob is an LSP-compliant glob pattern, as defined by the spec:
|
||||
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentFilter
|
||||
//
|
||||
// NOTE: this implementation is currently only intended for testing. In order
|
||||
// to make it production ready, we'd need to:
|
||||
// - verify it against the VS Code implementation
|
||||
// - add more tests
|
||||
// - microbenchmark, likely avoiding the element interface
|
||||
// - resolve the question of what is meant by "character". If it's a UTF-16
|
||||
// code (as we suspect) it'll be a bit more work.
|
||||
//
|
||||
// Quoting from the spec:
|
||||
// Glob patterns can have the following syntax:
|
||||
// - `*` to match one or more characters in a path segment
|
||||
// - `?` to match on one character in a path segment
|
||||
// - `**` to match any number of path segments, including none
|
||||
// - `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}`
|
||||
// matches all TypeScript and JavaScript files)
|
||||
// - `[]` to declare a range of characters to match in a path segment
|
||||
// (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
|
||||
// - `[!...]` to negate a range of characters to match in a path segment
|
||||
// (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but
|
||||
// not `example.0`)
|
||||
//
|
||||
// Expanding on this:
|
||||
// - '/' matches one or more literal slashes.
|
||||
// - any other character matches itself literally.
|
||||
type Glob struct {
|
||||
elems []element // pattern elements
|
||||
}
|
||||
|
||||
// Parse builds a Glob for the given pattern, returning an error if the pattern
|
||||
// is invalid.
|
||||
func Parse(pattern string) (*Glob, error) {
|
||||
g, _, err := parse(pattern, false)
|
||||
return g, err
|
||||
}
|
||||
|
||||
func parse(pattern string, nested bool) (*Glob, string, error) {
|
||||
g := new(Glob)
|
||||
for len(pattern) > 0 {
|
||||
switch pattern[0] {
|
||||
case '/':
|
||||
pattern = pattern[1:]
|
||||
g.elems = append(g.elems, slash{})
|
||||
|
||||
case '*':
|
||||
if len(pattern) > 1 && pattern[1] == '*' {
|
||||
if (len(g.elems) > 0 && g.elems[len(g.elems)-1] != slash{}) || (len(pattern) > 2 && pattern[2] != '/') {
|
||||
return nil, "", errors.New("** may only be adjacent to '/'")
|
||||
}
|
||||
pattern = pattern[2:]
|
||||
g.elems = append(g.elems, starStar{})
|
||||
break
|
||||
}
|
||||
pattern = pattern[1:]
|
||||
g.elems = append(g.elems, star{})
|
||||
|
||||
case '?':
|
||||
pattern = pattern[1:]
|
||||
g.elems = append(g.elems, anyChar{})
|
||||
|
||||
case '{':
|
||||
var gs group
|
||||
for pattern[0] != '}' {
|
||||
pattern = pattern[1:]
|
||||
groupG, pat, err := parse(pattern, true)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if len(pat) == 0 {
|
||||
return nil, "", errors.New("unmatched '{'")
|
||||
}
|
||||
pattern = pat
|
||||
gs = append(gs, groupG)
|
||||
}
|
||||
pattern = pattern[1:]
|
||||
g.elems = append(g.elems, gs)
|
||||
|
||||
case '}', ',':
|
||||
if nested {
|
||||
return g, pattern, nil
|
||||
}
|
||||
pattern = g.parseLiteral(pattern, false)
|
||||
|
||||
case '[':
|
||||
pattern = pattern[1:]
|
||||
if len(pattern) == 0 {
|
||||
return nil, "", errBadRange
|
||||
}
|
||||
negate := false
|
||||
if pattern[0] == '!' {
|
||||
pattern = pattern[1:]
|
||||
negate = true
|
||||
}
|
||||
low, sz, err := readRangeRune(pattern)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
pattern = pattern[sz:]
|
||||
if len(pattern) == 0 || pattern[0] != '-' {
|
||||
return nil, "", errBadRange
|
||||
}
|
||||
pattern = pattern[1:]
|
||||
high, sz, err := readRangeRune(pattern)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
pattern = pattern[sz:]
|
||||
if len(pattern) == 0 || pattern[0] != ']' {
|
||||
return nil, "", errBadRange
|
||||
}
|
||||
pattern = pattern[1:]
|
||||
g.elems = append(g.elems, charRange{negate, low, high})
|
||||
|
||||
default:
|
||||
pattern = g.parseLiteral(pattern, nested)
|
||||
}
|
||||
}
|
||||
return g, "", nil
|
||||
}
|
||||
|
||||
// helper for decoding a rune in range elements, e.g. [a-z]
|
||||
func readRangeRune(input string) (rune, int, error) {
|
||||
r, sz := utf8.DecodeRuneInString(input)
|
||||
var err error
|
||||
if r == utf8.RuneError {
|
||||
// See the documentation for DecodeRuneInString.
|
||||
switch sz {
|
||||
case 0:
|
||||
err = errBadRange
|
||||
case 1:
|
||||
err = errInvalidUTF8
|
||||
}
|
||||
}
|
||||
return r, sz, err
|
||||
}
|
||||
|
||||
var (
|
||||
errBadRange = errors.New("'[' patterns must be of the form [x-y]")
|
||||
errInvalidUTF8 = errors.New("invalid UTF-8 encoding")
|
||||
)
|
||||
|
||||
func (g *Glob) parseLiteral(pattern string, nested bool) string {
|
||||
var specialChars string
|
||||
if nested {
|
||||
specialChars = "*?{[/},"
|
||||
} else {
|
||||
specialChars = "*?{[/"
|
||||
}
|
||||
end := strings.IndexAny(pattern, specialChars)
|
||||
if end == -1 {
|
||||
end = len(pattern)
|
||||
}
|
||||
g.elems = append(g.elems, literal(pattern[:end]))
|
||||
return pattern[end:]
|
||||
}
|
||||
|
||||
func (g *Glob) String() string {
|
||||
var b strings.Builder
|
||||
for _, e := range g.elems {
|
||||
fmt.Fprint(&b, e)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// element holds a glob pattern element, as defined below.
|
||||
type element fmt.Stringer
|
||||
|
||||
// element types.
|
||||
type (
|
||||
slash struct{} // One or more '/' separators
|
||||
literal string // string literal, not containing /, *, ?, {}, or []
|
||||
star struct{} // *
|
||||
anyChar struct{} // ?
|
||||
starStar struct{} // **
|
||||
group []*Glob // {foo, bar, ...} grouping
|
||||
charRange struct { // [a-z] character range
|
||||
negate bool
|
||||
low, high rune
|
||||
}
|
||||
)
|
||||
|
||||
func (s slash) String() string { return "/" }
|
||||
func (l literal) String() string { return string(l) }
|
||||
func (s star) String() string { return "*" }
|
||||
func (a anyChar) String() string { return "?" }
|
||||
func (s starStar) String() string { return "**" }
|
||||
func (g group) String() string {
|
||||
var parts []string
|
||||
for _, g := range g {
|
||||
parts = append(parts, g.String())
|
||||
}
|
||||
return "{" + strings.Join(parts, ",") + "}"
|
||||
}
|
||||
|
||||
func (r charRange) String() string {
|
||||
return "[" + string(r.low) + "-" + string(r.high) + "]"
|
||||
}
|
||||
|
||||
// Match reports whether the input string matches the glob pattern.
|
||||
func (g *Glob) Match(input string) bool {
|
||||
return match(g.elems, input)
|
||||
}
|
||||
|
||||
func match(elems []element, input string) (ok bool) {
|
||||
var elem interface{}
|
||||
for len(elems) > 0 {
|
||||
elem, elems = elems[0], elems[1:]
|
||||
switch elem := elem.(type) {
|
||||
case slash:
|
||||
if len(input) == 0 || input[0] != '/' {
|
||||
return false
|
||||
}
|
||||
for input[0] == '/' {
|
||||
input = input[1:]
|
||||
}
|
||||
|
||||
case starStar:
|
||||
// Special cases:
|
||||
// - **/a matches "a"
|
||||
// - **/ matches everything
|
||||
//
|
||||
// Note that if ** is followed by anything, it must be '/' (this is
|
||||
// enforced by Parse).
|
||||
if len(elems) > 0 {
|
||||
elems = elems[1:]
|
||||
}
|
||||
|
||||
// A trailing ** matches anything.
|
||||
if len(elems) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Backtracking: advance pattern segments until the remaining pattern
|
||||
// elements match.
|
||||
for len(input) != 0 {
|
||||
if match(elems, input) {
|
||||
return true
|
||||
}
|
||||
_, input = split(input)
|
||||
}
|
||||
return false
|
||||
|
||||
case literal:
|
||||
if !strings.HasPrefix(input, string(elem)) {
|
||||
return false
|
||||
}
|
||||
input = input[len(elem):]
|
||||
|
||||
case star:
|
||||
var segInput string
|
||||
segInput, input = split(input)
|
||||
|
||||
elemEnd := len(elems)
|
||||
for i, e := range elems {
|
||||
if e == (slash{}) {
|
||||
elemEnd = i
|
||||
break
|
||||
}
|
||||
}
|
||||
segElems := elems[:elemEnd]
|
||||
elems = elems[elemEnd:]
|
||||
|
||||
// A trailing * matches the entire segment.
|
||||
if len(segElems) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Backtracking: advance characters until remaining subpattern elements
|
||||
// match.
|
||||
matched := false
|
||||
for i := range segInput {
|
||||
if match(segElems, segInput[i:]) {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
|
||||
case anyChar:
|
||||
if len(input) == 0 || input[0] == '/' {
|
||||
return false
|
||||
}
|
||||
input = input[1:]
|
||||
|
||||
case group:
|
||||
// Append remaining pattern elements to each group member looking for a
|
||||
// match.
|
||||
var branch []element
|
||||
for _, m := range elem {
|
||||
branch = branch[:0]
|
||||
branch = append(branch, m.elems...)
|
||||
branch = append(branch, elems...)
|
||||
if match(branch, input) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
case charRange:
|
||||
if len(input) == 0 || input[0] == '/' {
|
||||
return false
|
||||
}
|
||||
c, sz := utf8.DecodeRuneInString(input)
|
||||
if c < elem.low || c > elem.high {
|
||||
return false
|
||||
}
|
||||
input = input[sz:]
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("segment type %T not implemented", elem))
|
||||
}
|
||||
}
|
||||
|
||||
return len(input) == 0
|
||||
}
|
||||
|
||||
// split returns the portion before and after the first slash
|
||||
// (or sequence of consecutive slashes). If there is no slash
|
||||
// it returns (input, nil).
|
||||
func split(input string) (first, rest string) {
|
||||
i := strings.IndexByte(input, '/')
|
||||
if i < 0 {
|
||||
return input, ""
|
||||
}
|
||||
first = input[:i]
|
||||
for j := i; j < len(input); j++ {
|
||||
if input[j] != '/' {
|
||||
return first, input[j:]
|
||||
}
|
||||
}
|
||||
return first, ""
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user