From 44f6f7c2adabbfa01e8f617cb154703a36c43ff8 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 29 Jan 2018 17:48:59 -0800 Subject: [PATCH] vendor: vendor 'github.com/git-lfs/wildmatch' --- glide.lock | 6 +- glide.yaml | 2 + .../github.com/git-lfs/wildmatch/.travis.yml | 3 + .../github.com/git-lfs/wildmatch/LICENSE.md | 21 + vendor/github.com/git-lfs/wildmatch/README.md | 7 + .../github.com/git-lfs/wildmatch/package.go | 40 ++ .../github.com/git-lfs/wildmatch/wildmatch.go | 651 ++++++++++++++++++ .../git-lfs/wildmatch/wildmatch_linux.go | 7 + .../git-lfs/wildmatch/wildmatch_notlinux.go | 7 + .../git-lfs/wildmatch/wildmatch_test.go | 621 +++++++++++++++++ 10 files changed, 1363 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/git-lfs/wildmatch/.travis.yml create mode 100644 vendor/github.com/git-lfs/wildmatch/LICENSE.md create mode 100644 vendor/github.com/git-lfs/wildmatch/README.md create mode 100644 vendor/github.com/git-lfs/wildmatch/package.go create mode 100644 vendor/github.com/git-lfs/wildmatch/wildmatch.go create mode 100644 vendor/github.com/git-lfs/wildmatch/wildmatch_linux.go create mode 100644 vendor/github.com/git-lfs/wildmatch/wildmatch_notlinux.go create mode 100644 vendor/github.com/git-lfs/wildmatch/wildmatch_test.go diff --git a/glide.lock b/glide.lock index 110937ea..5069bb5c 100644 --- a/glide.lock +++ b/glide.lock @@ -1,10 +1,12 @@ -hash: e19b925b9eaca9a10a7742b4a4b1dc8047bff437584538dda59f4f10e69fa6ca -updated: 2018-01-29T17:36:52.158516-08:00 +hash: c9bdd0387a338276a0e612967c6bff94d04bd7e9126c1cfcd485bfba891e9fc5 +updated: 2018-02-15T15:49:22.305256-08:00 imports: - name: github.com/bgentry/go-netrc version: 9fd32a8b3d3d3f9d43c341bfe098430e07609480 subpackages: - netrc +- name: github.com/git-lfs/wildmatch + version: 62fc450048a8e2e2ffe3f0b5b488b41d05c9bf3e - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/kr/pty diff --git a/glide.yaml b/glide.yaml index 9faa9006..06297d86 100644 --- a/glide.yaml +++ b/glide.yaml @@ -26,3 +26,5 @@ import: version: 6b67b3fab74d992bd07f72550006ab2c6907c416 - package: github.com/pkg/errors version: c605e284fe17294bda444b34710735b29d1a9d90 +- package: github.com/git-lfs/wildmatch + version: 62fc450048a8e2e2ffe3f0b5b488b41d05c9bf3e diff --git a/vendor/github.com/git-lfs/wildmatch/.travis.yml b/vendor/github.com/git-lfs/wildmatch/.travis.yml new file mode 100644 index 00000000..9bbc8c18 --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/.travis.yml @@ -0,0 +1,3 @@ +language: go +notifications: + email: false diff --git a/vendor/github.com/git-lfs/wildmatch/LICENSE.md b/vendor/github.com/git-lfs/wildmatch/LICENSE.md new file mode 100644 index 00000000..1b3df300 --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018- GitHub, Inc. and Git LFS contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/git-lfs/wildmatch/README.md b/vendor/github.com/git-lfs/wildmatch/README.md new file mode 100644 index 00000000..8c8c3b33 --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/README.md @@ -0,0 +1,7 @@ +# wildmatch + +package `wildmatch` is a reimplementation of Git's `wildmatch.c`-style filepath pattern matching. + +For more information, see the [godoc][1]. + +[1]: https://godoc.org/github.com/git-lfs/wildmatch diff --git a/vendor/github.com/git-lfs/wildmatch/package.go b/vendor/github.com/git-lfs/wildmatch/package.go new file mode 100644 index 00000000..64a0d39e --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/package.go @@ -0,0 +1,40 @@ +// package Wildmatch is an implementation of Git's wildmatch.c-style pattern +// matching. +// +// Wildmatch patterns are comprised of any combination of the following three +// components: +// +// - String literals. A string literal is "foo", or "foo\*" (matching "foo", +// and "foo\", respectively). In general, string literals match their exact +// contents in a filepath, and cannot match over directories unless they +// include the operating system-specific path separator. +// +// - Wildcards. There are three types of wildcards: +// +// - Single-asterisk ('*'): matches any combination of characters, any +// number of times. Does not match path separators. +// +// - Single-question mark ('?'): matches any single character, but not a +// path separator. +// +// - Double-asterisk ('**'): greedily matches any number of directories. +// For example, '**/foo' matches '/foo', 'bar/baz/woot/foot', but not +// 'foo/bar'. Double-asterisks must be separated by filepath separators +// on either side. +// +// - Character groups. A character group is composed of a set of included and +// excluded character types. The set of included character types begins the +// character group, and a '^' or '!' separates it from the set of excluded +// character types. +// +// A character type can be one of the following: +// +// - Character literal: a single character, i.e., 'c'. +// +// - Character group: a group of characters, i.e., '[:alnum:]', etc. +// +// - Character range: a range of characters, i.e., 'a-z'. +// +// A Wildmatch pattern can be any combination of the above components, in any +// ordering, and repeated any number of times. +package wildmatch diff --git a/vendor/github.com/git-lfs/wildmatch/wildmatch.go b/vendor/github.com/git-lfs/wildmatch/wildmatch.go new file mode 100644 index 00000000..0562214c --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/wildmatch.go @@ -0,0 +1,651 @@ +package wildmatch + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" +) + +// opt is an option type for configuring a new Wildmatch instance. +type opt func(w *Wildmatch) + +var ( + // CaseFold allows the receiving Wildmatch to match paths with + // different case structuring as in the pattern. + CaseFold opt = func(w *Wildmatch) { + w.caseFold = true + } + + // SystemCase either folds or does not fold filepaths and patterns, + // according to whether or not the operating system on which Wildmatch + // runs supports case sensitive files or not. + SystemCase opt +) + +const ( + sep byte = '/' +) + +// Wildmatch implements pattern matching against filepaths using the format +// described in the package documentation. +// +// For more, see documentation for package 'wildmatch'. +type Wildmatch struct { + // ts are the token set used to match the given pattern. + ts []token + // p is the raw pattern used to derive the token set. + p string + + // caseFold allows the instance Wildmatch to match patterns with the + // same character but different case structures. + caseFold bool +} + +// NewWildmatch constructs a new Wildmatch instance which matches filepaths +// according to the given pattern and the rules for matching above. +// +// If the pattern is malformed, for instance, it has an unclosed character +// group, escape sequence, or character class, NewWildmatch will panic(). +func NewWildmatch(p string, opts ...opt) *Wildmatch { + w := &Wildmatch{p: p} + + for _, opt := range opts { + opt(w) + } + + if w.caseFold { + // Before parsing the pattern, convert it to lower-case. + w.p = strings.ToLower(w.p) + } + + w.ts = parseTokens(strings.Split(w.p, string(sep))) + + return w +} + +const ( + // escapes is a constant string containing all escapable characters + escapes = "\\[]*?" +) + +// escapable returns whether the given "c" is escapable. +func escapable(c byte) bool { + return strings.IndexByte(escapes, c) > -1 +} + +// parseTokens parses a separated list of patterns into a sequence of +// representative Tokens that will compose the pattern when applied in sequence. +func parseTokens(dirs []string) []token { + if len(dirs) == 0 { + return make([]token, 0) + } + + switch dirs[0] { + case "": + return parseTokens(dirs[1:]) + case "**": + rest := parseTokens(dirs[1:]) + if len(rest) == 0 { + // If there are no remaining tokens, return a lone + // doubleStar token. + return []token{&doubleStar{ + Until: nil, + }} + } + + // Otherwise, return a doubleStar token that will match greedily + // until the first component in the remainder of the pattern, + // and then the remainder of the pattern. + return append([]token{&doubleStar{ + Until: rest[0], + }}, rest[1:]...) + default: + // Ordinarily, simply return the appropriate component, and + // continue on. + return append([]token{&component{ + fns: parseComponent(dirs[0]), + }}, parseTokens(dirs[1:])...) + } +} + +// nonEmpty returns the non-empty strings in "all". +func nonEmpty(all []string) (ne []string) { + for _, x := range all { + if len(x) > 0 { + ne = append(ne, x) + } + } + return ne +} + +// Match returns true if and only if the pattern matched by the receiving +// Wildmatch matches the entire filepath "t". +func (w *Wildmatch) Match(t string) bool { + dirs, ok := w.consume(t) + if !ok { + return false + } + return len(dirs) == 0 +} + +// consume performs the inner match of "t" against the receiver's pattern, and +// returns a slice of remaining directory paths, and whether or not there was a +// disagreement while matching. +func (w *Wildmatch) consume(t string) ([]string, bool) { + if w.caseFold { + // If the receiving Wildmatch is case insensitive, the pattern + // "w.p" will be lower-case. + // + // To preserve insensitivity, lower the given path "t", as well. + t = strings.ToLower(t) + } + + dirs := strings.Split(t, string(sep)) + isDir := strings.HasSuffix(t, string(sep)) + + // Match each directory token-wise, allowing each token to consume more + // than one directory in the case of the '**' pattern. + for _, tok := range w.ts { + var ok bool + + dirs, ok = tok.Consume(dirs, isDir) + if !ok { + // If a pattern could not match the remainder of the + // filepath, return so immediately, along with the paths + // that we did successfully manage to match. + return dirs, false + } + } + return dirs, true +} + +// String implements fmt.Stringer and returns the receiver's pattern in the format +// specified above. +func (w *Wildmatch) String() string { + return w.p +} + +// token matches zero, one, or more directory components. +type token interface { + // Consume matches zero, one, or more directory components. + // + // Consider the following examples: + // + // (["foo", "bar", "baz"]) -> (["oo", "bar", baz"], true) + // (["foo", "bar", "baz"]) -> (["bar", baz"], true) + // (["foo", "bar", "baz"]) -> (["baz"], true) + // (["foo", "bar", "baz"]) -> ([], true) + // (["foo", "bar", "baz"]) -> (["foo", "bar", "baz"], false) + // (["foo", "bar", "baz"]) -> (["oo", "bar", "baz"], false) + // (["foo", "bar", "baz"]) -> (["bar", "baz"], false) + // + // The Consume operation can reduce the size of a single entry in the + // slice (see: example (1) above), or remove it entirely, (see: examples + // (2), (3), and (4) above). It can also refuse to match forward after + // making any amount of progress (see: examples (5), (6), and (7) + // above). + // + // Consume accepts a slice representing a path-delimited filepath on + // disk, and a bool indicating whether the given path is a directory + // (i.e., "foo/bar/" is, but "foo/bar" isn't). + Consume(path []string, isDir bool) ([]string, bool) + + // String returns the string representation this component of the + // pattern; i.e., a string that, when parsed, would form the same token. + String() string +} + +// doubleStar is an implementation of the Token interface which greedily matches +// one-or-more path components until a successor token. +type doubleStar struct { + Until token +} + +// Consume implements token.Consume as above. +func (d *doubleStar) Consume(path []string, isDir bool) ([]string, bool) { + // If there are no remaining tokens to match, allow matching the entire + // path. + if d.Until == nil { + return nil, true + } + + for i := len(path); i > 0; i-- { + rest, ok := d.Until.Consume(path[i:], false) + if ok { + return rest, ok + } + } + + // If no match has been found, we assume that the '**' token matches the + // empty string, and defer pattern matching to the rest of the path. + return d.Until.Consume(path, isDir) +} + +// String implements Component.String. +func (d *doubleStar) String() string { + if d.Until == nil { + return "**" + } + return fmt.Sprintf("**/%s", d.Until.String()) +} + +// componentFn is a functional type designed to match a single component of a +// directory structure by reducing the unmatched part, and returning whether or +// not a match was successful. +type componentFn interface { + Apply(s string) (rest string, ok bool) + String() string +} + +// cfn is a wrapper type for the Component interface that includes an applicable +// function, and a string that represents it. +type cfn struct { + fn func(s string) (rest string, ok bool) + str string +} + +// Apply executes the component function as described above. +func (c *cfn) Apply(s string) (rest string, ok bool) { + return c.fn(s) +} + +// String returns the string representation of this component. +func (c *cfn) String() string { + return c.str +} + +// component is an implementation of the Token interface, which matches a single +// component at the front of a tree structure by successively applying +// implementations of the componentFn type. +type component struct { + // fns is the list of componentFn implementations to be successively + // applied. + fns []componentFn +} + +// parseComponent parses a single component from its string representation, +// including wildcards, character classes, string literals, and escape +// sequences. +func parseComponent(s string) []componentFn { + if len(s) == 0 { + // The empty string represents the absence of componentFn's. + return make([]componentFn, 0) + } + + switch s[0] { + case '\\': + // If the first character is a '\', the following character is a + // part of an escape sequence, or it is unclosed. + if len(s) < 2 { + panic("wildmatch: unclosed escape sequence") + } + + literal := substring(string(s[1])) + + var rest []componentFn + if len(s) > 2 { + // If there is more to follow, i.e., "\*foo", then parse + // the remainder. + rest = parseComponent(s[2:]) + } + return cons(literal, rest) + case '[': + var ( + // i will denote the currently-inspected index of the character + // group. + i int = 1 + // include will denote the list of included runeFn's + // composing the character group. + include []runeFn + // exclude will denote the list of excluded runeFn's + // composing the character group. + exclude []runeFn + // run is the current run of strings (to either compose + // a range, or select "any") + run string + // neg is whether we have seen a negation marker. + neg bool + ) + + for i < len(s) { + if s[i] == '^' || s[i] == '!' { + // Once a '^' or '!' character has been seen, + // anything following it will be negated. + neg = !neg + i = i + 1 + } else if strings.HasPrefix(s[i:], "[:") { + close := strings.Index(s[i:], ":]") + if close < 0 { + panic("unclosed character class") + } + + if close == 1 { + // The case "[:]" has a prefix "[:", and + // a suffix ":]", but the atom refers to + // a character group including the + // literal ":", not an ill-formed + // character class. + // + // Parse it as such; increment one + // _less_ than expected, to terminate + // the group. + run += "[:]" + i = i + 2 + continue + } + + // Find the associated character class. + name := strings.TrimPrefix( + strings.ToLower(s[i:i+close]), "[:") + fn, ok := classes[name] + if !ok { + panic(fmt.Sprintf("wildmatch: unknown class: %q", name)) + } + + include, exclude = appendMaybe(!neg, include, exclude, fn) + // Advance to the first index beyond the closing + // ":]". + i = i + close + 2 + } else if s[i] == '-' { + if i < len(s) { + // If there is a range marker at the + // non-final position, construct a range + // and an optional "any" match: + var start, end byte + if len(run) > 0 { + // If there is at least one + // character in the run, use it + // as the starting point of the + // range, and remove it from the + // run. + start = run[len(run)-1] + run = run[:len(run)-1] + } + end = s[i+1] + + if len(run) > 0 { + // If there is still information + // in the run, construct a rune + // function matching any + // characters in the run. + cfn := anyRune(run) + + include, exclude = appendMaybe(!neg, include, exclude, cfn) + run = "" + } + + // Finally, construct the rune range and + // add it appropriately. + bfn := between(rune(start), rune(end)) + include, exclude = appendMaybe(!neg, + include, exclude, bfn) + + i = i + 2 + } else { + // If this is in the final position, add + // it to the run and exit the loop. + run = run + "-" + i = i + 2 + } + } else if s[i] == '\\' { + // If we encounter an escape sequence in the + // group, check its bounds and add it to the + // run. + if i+1 >= len(s) { + panic("wildmatch: unclosed escape") + } + run = run + string(s[i+1]) + i = i + 2 + } else if s[i] == ']' { + // If we encounter a closing ']', then stop + // parsing the group. + break + } else { + // Otherwise, add the character to the run and + // advance forward. + run = run + string(s[i]) + i = i + 1 + } + } + + if len(run) > 0 { + fn := anyRune(run) + include, exclude = appendMaybe(!neg, include, exclude, fn) + } + + var rest string + if i+1 < len(s) { + rest = s[i+1:] + } + // Assemble a character class, and cons it in front of the + // remainder of the component pattern. + return cons(charClass(include, exclude), parseComponent(rest)) + case '?': + return []componentFn{wildcard(1, parseComponent(s[1:]))} + case '*': + return []componentFn{wildcard(-1, parseComponent(s[1:]))} + default: + // Advance forward until we encounter a special character + // (either '*', '[', '*', or '?') and parse across the divider. + var i int + for ; i < len(s); i++ { + if s[i] == '[' || + s[i] == '*' || + s[i] == '?' || + s[i] == '\\' { + break + } + } + + return cons(substring(s[:i]), parseComponent(s[i:])) + } +} + +// appendMaybe appends the value "x" to either "a" or "b" depending on "yes". +func appendMaybe(yes bool, a, b []runeFn, x runeFn) (ax, bx []runeFn) { + if yes { + return append(a, x), b + } + return a, append(b, x) +} + +// cons prepends the "head" componentFn to the "tail" of componentFn's. +func cons(head componentFn, tail []componentFn) []componentFn { + return append([]componentFn{head}, tail...) +} + +// Consume implements token.Consume as above by applying the above set of +// componentFn's in succession to the first element of the path tree. +func (c *component) Consume(path []string, isDir bool) ([]string, bool) { + if len(path) == 0 { + return path, false + } + + head := path[0] + for _, fn := range c.fns { + var ok bool + + // Apply successively the component functions to make progress + // matching the head. + if head, ok = fn.Apply(head); !ok { + // If any of the functions failed to match, there are + // no other paths to match success, so return a failure + // immediately. + return path, false + } + } + + if len(head) > 0 { + return append([]string{head}, path[1:]...), false + } + + if len(path) == 1 { + // Components can not match directories. If we were matching the + // last path in a tree structure, we can only match if it + // _wasn't_ a directory. + return path[1:], !isDir + } + + return path[1:], true +} + +// String implements token.String. +func (c *component) String() string { + var str string + + for _, fn := range c.fns { + str += fn.String() + } + return str +} + +// substring returns a componentFn that matches a prefix of "sub". +func substring(sub string) componentFn { + return &cfn{ + fn: func(s string) (rest string, ok bool) { + if !strings.HasPrefix(s, sub) { + return s, false + } + return s[len(sub):], true + }, + str: sub, + } +} + +// wildcard returns a componentFn that greedily matches until a set of other +// component functions no longer matches. +func wildcard(n int, fns []componentFn) componentFn { + until := func(s string) (string, bool) { + head := s + for _, fn := range fns { + var ok bool + + if head, ok = fn.Apply(head); !ok { + return s, false + } + } + + if len(head) > 0 { + return s, false + } + return "", true + } + + var str string = "*" + for _, fn := range fns { + str += fn.String() + } + + return &cfn{ + fn: func(s string) (rest string, ok bool) { + if n > -1 { + if n > len(s) { + return "", false + } + return until(s[n:]) + } + + for i := len(s); i > 0; i-- { + rest, ok = until(s[i:]) + if ok { + return rest, ok + } + } + return until(s) + }, + str: str, + } +} + +// charClass returns a component function emulating a character class, i.e., +// that a single character can match if and only if it is included in one of the +// includes (or true if there were no includes) and none of the excludes. +func charClass(include, exclude []runeFn) componentFn { + return &cfn{ + fn: func(s string) (rest string, ok bool) { + if len(s) == 0 { + return s, false + } + + // Find "r", the first rune in the string "s". + r, l := utf8.DecodeRuneInString(s) + + var match bool + for _, ifn := range include { + // Attempt to find a match on "r" with "ifn". + if ifn(r) { + match = true + break + } + } + + // If there wasn't a match and there were some including + // patterns, return a failure to match. Otherwise, continue on + // to make sure that no patterns exclude the rune "r". + if !match && len(include) != 0 { + return s, false + } + + for _, efn := range exclude { + // Attempt to find a negative match on "r" with "efn". + if efn(r) { + return s, false + } + } + + // If we progressed this far, return the remainder of the + // string. + return s[l:], true + }, + str: "", + } +} + +// runeFn matches a single rune. +type runeFn func(rune) bool + +var ( + // classes is a mapping from character class name to a rune function + // that implements its behavior. + classes = map[string]runeFn{ + "alnum": func(r rune) bool { + return unicode.In(r, unicode.Number, unicode.Letter) + }, + "alpha": unicode.IsLetter, + "blank": func(r rune) bool { + return r == ' ' || r == '\t' + }, + "cntrl": unicode.IsControl, + "digit": unicode.IsDigit, + "graph": unicode.IsGraphic, + "lower": unicode.IsLower, + "print": unicode.IsPrint, + "punct": unicode.IsPunct, + "space": unicode.IsSpace, + "upper": unicode.IsUpper, + "xdigit": func(r rune) bool { + return unicode.IsDigit(r) || + ('a' <= r && r <= 'f') || + ('A' <= r && r <= 'F') + }, + } +) + +// anyRune returns true so long as the rune "r" appears in the string "s". +func anyRune(s string) runeFn { + return func(r rune) bool { + return strings.IndexRune(s, r) > -1 + } +} + +// between returns true so long as the rune "r" appears between "a" and "b". +func between(a, b rune) runeFn { + if b < a { + a, b = b, a + } + + return func(r rune) bool { + return a <= r && r <= b + } +} diff --git a/vendor/github.com/git-lfs/wildmatch/wildmatch_linux.go b/vendor/github.com/git-lfs/wildmatch/wildmatch_linux.go new file mode 100644 index 00000000..98af6ef0 --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/wildmatch_linux.go @@ -0,0 +1,7 @@ +// +build linux + +package wildmatch + +func init() { + SystemCase = CaseFold +} diff --git a/vendor/github.com/git-lfs/wildmatch/wildmatch_notlinux.go b/vendor/github.com/git-lfs/wildmatch/wildmatch_notlinux.go new file mode 100644 index 00000000..ba097327 --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/wildmatch_notlinux.go @@ -0,0 +1,7 @@ +// +build !linux + +package wildmatch + +func init() { + SystemCase = func(w *Wildmatch) {} +} diff --git a/vendor/github.com/git-lfs/wildmatch/wildmatch_test.go b/vendor/github.com/git-lfs/wildmatch/wildmatch_test.go new file mode 100644 index 00000000..29b18756 --- /dev/null +++ b/vendor/github.com/git-lfs/wildmatch/wildmatch_test.go @@ -0,0 +1,621 @@ +package wildmatch + +import ( + "testing" +) + +type Case struct { + Pattern string + Subject string + Match bool + Opts []opt +} + +func (c *Case) Assert(t *testing.T) { + defer func() { + if err := recover(); err != nil { + if c.Match { + t.Errorf("could not parse: %s (%s)", c.Pattern, err) + } + } + }() + + p := NewWildmatch(c.Pattern, c.Opts...) + if p.Match(c.Subject) != c.Match { + if c.Match { + t.Errorf("expected match: %s, %s", c.Pattern, c.Subject) + } else { + t.Errorf("unexpected match: %s, %s", c.Pattern, c.Subject) + } + } +} + +var Cases = []*Case{ + { + Pattern: `foo`, + Subject: `foo`, + Match: true, + }, + { + Pattern: `bar`, + Subject: `foo`, + Match: false, + }, + { + Pattern: `???`, + Subject: `foo`, + Match: true, + }, + { + Pattern: `??`, + Subject: `foo`, + Match: false, + }, + { + Pattern: `*`, + Subject: `foo`, + Match: true, + }, + { + Pattern: `f*`, + Subject: `foo`, + Match: true, + }, + { + Pattern: `*f`, + Subject: `foo`, + Match: false, + }, + { + Pattern: `*foo*`, + Subject: `foo`, + Match: true, + }, + { + Pattern: `*ob*a*r*`, + Subject: `foobar`, + Match: true, + }, + { + Pattern: `*ab`, + Subject: `aaaaaaabababab`, + Match: true, + }, + { + Pattern: `foo\*`, + Subject: `foo*`, + Match: true, + }, + { + Pattern: `foo\*bar`, + Subject: `foobar`, + Match: false, + }, + { + Pattern: `f\\oo`, + Subject: `f\oo`, + Match: true, + }, + { + Pattern: `*[al]?`, + Subject: `ball`, + Match: true, + }, + { + Pattern: `[ten]`, + Subject: `ten`, + Match: false, + }, + { + Pattern: `**[!te]`, + Subject: `ten`, + Match: true, + }, + { + Pattern: `**[!ten]`, + Subject: `ten`, + Match: false, + }, + { + Pattern: `t[a-g]n`, + Subject: `ten`, + Match: true, + }, + { + Pattern: `t[!a-g]n`, + Subject: `ten`, + Match: false, + }, + { + Pattern: `t[!a-g]n`, + Subject: `ton`, + Match: true, + }, + { + Pattern: `t[^a-g]n`, + Subject: `ton`, + Match: true, + }, + { + Pattern: `]`, + Subject: `]`, + Match: true, + }, + { + Pattern: `foo*bar`, + Subject: `foo/baz/bar`, + Match: false, + }, + { + Pattern: `foo?bar`, + Subject: `foo/bar`, + Match: false, + }, + { + Pattern: `foo[/]bar`, + Subject: `foo/bar`, + Match: false, + }, + { + Pattern: `f[^eiu][^eiu][^eiu][^eiu][^eiu]r`, + Subject: `foo/bar`, + Match: false, + }, + { + Pattern: `f[^eiu][^eiu][^eiu][^eiu][^eiu]r`, + Subject: `foo-bar`, + Match: true, + }, + { + Pattern: `**/foo`, + Subject: `foo`, + Match: true, + }, + { + Pattern: `**/foo`, + Subject: `/foo`, + Match: true, + }, + { + Pattern: `**/foo`, + Subject: `bar/baz/foo`, + Match: true, + }, + { + Pattern: `*/foo`, + Subject: `bar/baz/foo`, + Match: false, + }, + { + Pattern: `**/bar*`, + Subject: `foo/bar/baz`, + Match: false, + }, + { + Pattern: `**/bar/*`, + Subject: `deep/foo/bar/baz`, + Match: true, + }, + { + Pattern: `**/bar/*`, + Subject: `deep/foo/bar/baz/`, + Match: false, + }, + { + Pattern: `**/bar/**`, + Subject: `deep/foo/bar/baz/`, + Match: true, + }, + { + Pattern: `**/bar/*`, + Subject: `deep/foo/bar`, + Match: false, + }, + { + Pattern: `**/bar/**`, + Subject: `deep/foo/bar/`, + Match: true, + }, + { + Pattern: `*/bar/**`, + Subject: `foo/bar/baz/x`, + Match: true, + }, + { + Pattern: `*/bar/**`, + Subject: `deep/foo/bar/baz/x`, + Match: false, + }, + { + Pattern: `**/bar/*/*`, + Subject: `deep/foo/bar/baz/x`, + Match: true, + }, + { + Pattern: `*.txt`, + Subject: `foo/bar/baz.txt`, + Match: false, + }, + { + Pattern: `foo*`, + Subject: `foobar`, + Match: true, + }, + { + Pattern: `*foo*`, + Subject: `somethingfoobar`, + Match: true, + }, + { + Pattern: `*foo`, + Subject: `barfoo`, + Match: true, + }, + { + Pattern: `a[c-c]st`, + Subject: `acrt`, + Match: false, + }, + { + Pattern: `a[c-c]rt`, + Subject: `acrt`, + Match: true, + }, + { + Pattern: `\`, + Subject: `''`, + Match: false, + }, + { + Pattern: `\`, + Subject: `\`, + Match: false, + }, + { + Pattern: `*/\`, + Subject: `/\`, + Match: false, + }, + { + Pattern: `foo`, + Subject: `foo`, + Match: true, + }, + { + Pattern: `@foo`, + Subject: `@foo`, + Match: true, + }, + { + Pattern: `@foo`, + Subject: `foo`, + Match: false, + }, + { + Pattern: `\[ab]`, + Subject: `[ab]`, + Match: true, + }, + { + Pattern: `[[]ab]`, + Subject: `[ab]`, + Match: true, + }, + { + Pattern: `[[:]ab]`, + Subject: `[ab]`, + Match: true, + }, + { + Pattern: `[[::]ab]`, + Subject: `[ab]`, + Match: false, + }, + { + Pattern: `[[:digit]ab]`, + Subject: `[ab]`, + Match: false, + }, + { + Pattern: `[\[:]ab]`, + Subject: `[ab]`, + Match: true, + }, + { + Pattern: `\??\?b`, + Subject: `?a?b`, + Match: true, + }, + { + Pattern: `\a\b\c`, + Subject: `abc`, + Match: true, + }, + { + Pattern: `''`, + Subject: `foo`, + Match: false, + }, + { + Pattern: `**/t[o]`, + Subject: `foo/bar/baz/to`, + Match: true, + }, + { + Pattern: `[[:alpha:]][[:digit:]][[:upper:]]`, + Subject: `a1B`, + Match: true, + }, + { + Pattern: `[[:digit:][:upper:][:space:]]`, + Subject: `a`, + Match: false, + }, + { + Pattern: `[[:digit:][:upper:][:space:]]`, + Subject: `A`, + Match: true, + }, + { + Pattern: `[[:digit:][:upper:][:space:]]`, + Subject: `1`, + Match: true, + }, + { + Pattern: `[[:digit:][:upper:][:spaci:]]`, + Subject: `1`, + Match: false, + }, + { + Pattern: `'`, + Subject: `'`, + Match: true, + }, + { + Pattern: `[[:digit:][:upper:][:space:]]`, + Subject: `.`, + Match: false, + }, + { + Pattern: `[[:digit:][:punct:][:space:]]`, + Subject: `.`, + Match: true, + }, + { + Pattern: `[[:xdigit:]]`, + Subject: `5`, + Match: true, + }, + { + Pattern: `[[:xdigit:]]`, + Subject: `f`, + Match: true, + }, + { + Pattern: `[[:xdigit:]]`, + Subject: `D`, + Match: true, + }, + { + Pattern: `[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]`, + Subject: `_`, + Match: true, + }, + { + Pattern: `[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]`, + Subject: `.`, + Match: true, + }, + { + Pattern: `[a-c[:digit:]x-z]`, + Subject: `5`, + Match: true, + }, + { + Pattern: `[a-c[:digit:]x-z]`, + Subject: `b`, + Match: true, + }, + { + Pattern: `[a-c[:digit:]x-z]`, + Subject: `y`, + Match: true, + }, + { + Pattern: `[a-c[:digit:]x-z]`, + Subject: `q`, + Match: false, + }, + { + Pattern: `[\\-^]`, + Subject: `]`, + Match: true, + }, + { + Pattern: `[\\-^]`, + Subject: `[`, + Match: false, + }, + { + Pattern: `a[]b`, + Subject: `ab`, + Match: false, + }, + { + Pattern: `a[]b`, + Subject: `a[]b`, + Match: false, + }, + { + Pattern: `[!`, + Subject: `ab`, + Match: false, + }, + { + Pattern: `[-`, + Subject: `ab`, + Match: false, + }, + { + Pattern: `[-]`, + Subject: `-`, + Match: true, + }, + { + Pattern: `[a-`, + Subject: `-`, + Match: false, + }, + { + Pattern: `[!a-`, + Subject: `-`, + Match: false, + }, + { + Pattern: `'`, + Subject: `'`, + Match: true, + }, + { + Pattern: `'[`, + Subject: `0`, + Match: false, + }, + { + Pattern: `[---]`, + Subject: `-`, + Match: true, + }, + { + Pattern: `[------]`, + Subject: `-`, + Match: true, + }, + { + Pattern: `[!------]`, + Subject: `a`, + Match: true, + }, + { + Pattern: `[a^bc]`, + Subject: `^`, + Match: true, + }, + { + Pattern: `[\]`, + Subject: `\`, + Match: false, + }, + { + Pattern: `[\\]`, + Subject: `\`, + Match: true, + }, + { + Pattern: `[!\\]`, + Subject: `\`, + Match: false, + }, + { + Pattern: `[A-\\]`, + Subject: `G`, + Match: true, + }, + { + Pattern: `b*a`, + Subject: `aaabbb`, + Match: false, + }, + { + Pattern: `*ba*`, + Subject: `aabcaa`, + Match: false, + }, + { + Pattern: `[,]`, + Subject: `,`, + Match: true, + }, + { + Pattern: `[\\,]`, + Subject: `,`, + Match: true, + }, + { + Pattern: `[\\,]`, + Subject: `\`, + Match: true, + }, + { + Pattern: `[,-.]`, + Subject: `-`, + Match: true, + }, + { + Pattern: `[,-.]`, + Subject: `+`, + Match: false, + }, + { + Pattern: `[,-.]`, + Subject: `-.]`, + Match: false, + }, + { + Pattern: `[\1-\3]`, + Subject: `2`, + Match: true, + }, + { + Pattern: `[\1-\3]`, + Subject: `3`, + Match: true, + }, + { + Pattern: `-*-*-*-*-*-*-12-*-*-*-m-*-*-*`, + Subject: `-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1`, + Match: true, + }, + { + Pattern: `-*-*-*-*-*-*-12-*-*-*-m-*-*-*`, + Subject: `-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1`, + Match: false, + }, + { + Pattern: `-*-*-*-*-*-*-12-*-*-*-m-*-*-*`, + Subject: `-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1`, + Match: false, + }, + { + Pattern: `**/*a*b*g*n*t`, + Subject: `abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt`, + Match: true, + }, + { + Pattern: `**/*a*b*g*n*t`, + Subject: `abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz`, + Match: false, + }, + { + Pattern: `foo`, + Subject: `FOO`, + Match: false, + }, + { + Pattern: `foo`, + Subject: `FOO`, + Opts: []opt{CaseFold}, + Match: true, + }, + { + Pattern: `**/a*.txt`, + Subject: `foo-a.txt`, + Match: false, + }, +} + +func TestWildmatch(t *testing.T) { + for _, c := range Cases { + c.Assert(t) + } +}