Support recursive glob for path conditions (#327)

* Support glob with doublestar for path conditions

* Update docs and pipeline
This commit is contained in:
Anbraten 2021-09-21 06:18:43 +02:00 committed by GitHub
parent d802fdb26d
commit 96828b16c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1659 additions and 13 deletions

View file

@ -27,7 +27,7 @@ pipeline:
when:
event: push
branch: master
path: "docs/*"
path: "docs/**"
build-docs-pr:
image: node:14-alpine
@ -37,4 +37,4 @@ pipeline:
- yarn build
when:
event: [pull_request]
path: "docs/*"
path: "docs/**"

View file

@ -5,6 +5,7 @@ import (
"path/filepath"
"strings"
doublestar "github.com/bmatcuk/doublestar/v4"
libcompose "github.com/docker/libcompose/yaml"
"github.com/woodpecker-ci/woodpecker/cncd/pipeline/pipeline/frontend"
"github.com/woodpecker-ci/woodpecker/cncd/pipeline/pipeline/frontend/yaml/types"
@ -227,7 +228,7 @@ func (c *ConstraintPath) Match(v []string, message string) bool {
func (c *ConstraintPath) Includes(v []string) bool {
for _, pattern := range c.Include {
for _, file := range v {
if ok, _ := filepath.Match(pattern, file); ok {
if ok, _ := doublestar.Match(pattern, file); ok {
return true
}
}
@ -239,7 +240,7 @@ func (c *ConstraintPath) Includes(v []string) bool {
func (c *ConstraintPath) Excludes(v []string) bool {
for _, pattern := range c.Exclude {
for _, file := range v {
if ok, _ := filepath.Match(pattern, file); ok {
if ok, _ := doublestar.Match(pattern, file); ok {
return true
}
}

View file

@ -177,6 +177,26 @@ func TestConstraintList(t *testing.T) {
with: []string{"CHANGELOG.md", "README.md"},
want: true,
},
{
conf: "'docs/*'",
with: []string{"docs/README.md"},
want: true,
},
{
conf: "'docs/*'",
with: []string{"docs/sub/README.md"},
want: false,
},
{
conf: "'docs/**'",
with: []string{"docs/README.md", "docs/sub/README.md", "docs/sub-sub/README.md"},
want: true,
},
{
conf: "'docs/**'",
with: []string{"README.md"},
want: false,
},
{
conf: "{ include: [ README.md ] }",
with: []string{"CHANGELOG.md"},
@ -233,7 +253,7 @@ func TestConstraintList(t *testing.T) {
c := parseConstraintPath(test.conf)
got, want := c.Match(test.with, test.message), test.want
if got != want {
t.Errorf("Expect %q matches %q is %v", test.with, test.conf, want)
t.Errorf("Expect %q matches %q should be %v got %v", test.with, test.conf, want, got)
}
}
}

View file

@ -302,28 +302,26 @@ when:
### `path`
Execute a step only on commit with certain files added/removed/modified:
> NOTE: This feature is currently only available for GitHub and Gitea repositories.
> NOTE: Feature is only available for GitHub and Gitea repositories.
Execute a step only on a pipeline with certain files being changed:
```diff
when:
path: "src/*"
```
Execute a step only on commit excluding certain files added/removed/modified:
> NOTE: Feature is only available for GitHub and Gitea repositories.
You can use [glob patterns](https://github.com/bmatcuk/doublestar#patterns) to match the changed files and specify if the step should run if a file matching that pattern has been changed `include` or if some files have **not** been changed `exclude`.
```diff
when:
path:
exclude: [ '*.md', '*.ini' ]
include: [ '.woodpecker/*.yml', '*.ini' ]
exclude: [ '*.md', 'docs/**' ]
ignore_message: "[ALL]"
```
** Note for `path` conditions: passing `[ALL]` inside the commit message will ignore all path conditions. **
** Hint: ** Passing a defined ignore-message like `[ALL]` inside the commit message will ignore all path conditions.
## Step `group` - Parallel execution

1
go.mod
View file

@ -8,6 +8,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect
github.com/bmatcuk/doublestar v1.3.4
github.com/bmatcuk/doublestar/v4 v4.0.2
github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dgrijalva/jwt-go v0.0.0-20150904212456-c1da56349675

2
go.sum
View file

@ -20,6 +20,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmatcuk/doublestar/v4 v4.0.2 h1:X0krlUVAVmtr2cRoTqR8aDMrDqnB36ht8wpWTiQ3jsA=
github.com/bmatcuk/doublestar/v4 v4.0.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74 h1:fE72rAOk9gpizJL3mNv+Ez+3yt/GCoZWtkKEPLTZvvM=
github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74/go.mod h1:+zgWTTgi3saXD5N9SSA+LYteMbFoIJKJ9WEPXoV0jQA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

32
vendor/github.com/bmatcuk/doublestar/v4/.gitignore generated vendored Normal file
View file

@ -0,0 +1,32 @@
# vi
*~
*.swp
*.swo
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# test directory
test/

18
vendor/github.com/bmatcuk/doublestar/v4/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,18 @@
language: go
go:
- 1.16
os:
- linux
- windows
before_install:
- go get -t -v ./...
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

22
vendor/github.com/bmatcuk/doublestar/v4/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Bob Matcuk
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.

278
vendor/github.com/bmatcuk/doublestar/v4/README.md generated vendored Normal file
View file

@ -0,0 +1,278 @@
# doublestar
Path pattern matching and globbing supporting `doublestar` (`**`) patterns.
[![PkgGoDev](https://pkg.go.dev/badge/github.com/bmatcuk/doublestar)](https://pkg.go.dev/github.com/bmatcuk/doublestar/v4)
[![Release](https://img.shields.io/github/release/bmatcuk/doublestar.svg?branch=master)](https://github.com/bmatcuk/doublestar/releases)
[![Build Status](https://travis-ci.com/bmatcuk/doublestar.svg?branch=master)](https://travis-ci.com/bmatcuk/doublestar)
[![codecov.io](https://img.shields.io/codecov/c/github/bmatcuk/doublestar.svg?branch=master)](https://codecov.io/github/bmatcuk/doublestar?branch=master)
## About
#### [Upgrading?](UPGRADING.md)
**doublestar** is a [golang] implementation of path pattern matching and
globbing with support for "doublestar" (aka globstar: `**`) patterns.
doublestar patterns match files and directories recursively. For example, if
you had the following directory structure:
```bash
grandparent
`-- parent
|-- child1
`-- child2
```
You could find the children with patterns such as: `**/child*`,
`grandparent/**/child?`, `**/parent/*`, or even just `**` by itself (which will
return all files and directories recursively).
Bash's globstar is doublestar's inspiration and, as such, works similarly.
Note that the doublestar must appear as a path component by itself. A pattern
such as `/path**` is invalid and will be treated the same as `/path*`, but
`/path*/**` should achieve the desired result. Additionally, `/path/**` will
match all directories and files under the path directory, but `/path/**/` will
only match directories.
v4 is a complete rewrite with a focus on performance. Additionally,
[doublestar] has been updated to use the new [io/fs] package for filesystem
access. As a result, it is only supported by [golang] v1.16+.
## Installation
**doublestar** can be installed via `go get`:
```bash
go get github.com/bmatcuk/doublestar/v4
```
To use it in your code, you must import it:
```go
import "github.com/bmatcuk/doublestar/v4"
```
## Usage
### Match
```go
func Match(pattern, name string) (bool, error)
```
Match returns true if `name` matches the file name `pattern` ([see
"patterns"]). `name` and `pattern` are split on forward slash (`/`) characters
and may be relative or absolute.
Match requires pattern to match all of name, not just a substring. The only
possible returned error is ErrBadPattern, when pattern is malformed.
Note: this is meant as a drop-in replacement for `path.Match()` which always
uses `'/'` as the path separator. If you want to support systems which use a
different path separator (such as Windows), what you want is `PathMatch()`.
Alternatively, you can run `filepath.ToSlash()` on both pattern and name and
then use this function.
### PathMatch
```go
func PathMatch(pattern, name string) (bool, error)
```
PathMatch returns true if `name` matches the file name `pattern` ([see
"patterns"]). The difference between Match and PathMatch is that PathMatch will
automatically use your system's path separator to split `name` and `pattern`.
On systems where the path separator is `'\'`, escaping will be disabled.
Note: this is meant as a drop-in replacement for `filepath.Match()`. It assumes
that both `pattern` and `name` are using the system's path separator. If you
can't be sure of that, use `filepath.ToSlash()` on both `pattern` and `name`,
and then use the `Match()` function instead.
### Glob
```go
func Glob(fsys fs.FS, pattern string) ([]string, error)
```
Glob returns the names of all files matching pattern or nil if there is no
matching file. The syntax of patterns is the same as in `Match()`. The pattern
may describe hierarchical names such as `usr/*/bin/ed`.
Glob ignores file system errors such as I/O errors reading directories. The
only possible returned error is ErrBadPattern, reporting that the pattern is
malformed.
Note: this is meant as a drop-in replacement for `io/fs.Glob()`. Like
`io/fs.Glob()`, this function assumes that your pattern uses `/` as the path
separator even if that's not correct for your OS (like Windows). If you aren't
sure if that's the case, you can use `filepath.ToSlash()` on your pattern
before calling `Glob()`.
Like `io/fs.Glob()`, patterns containing `/./`, `/../`, or starting with `/`
will return no results and no errors. This seems to be a [conscious
decision](https://github.com/golang/go/issues/44092#issuecomment-774132549),
even if counter-intuitive. You can use [SplitPattern] to divide a pattern into
a base path (to initialize an `FS` object) and pattern.
### GlobWalk
```go
type GlobWalkFunc func(path string, d fs.DirEntry) error
func GlobWalk(fsys fs.FS, pattern string, fn GlobWalkFunc) error
```
GlobWalk calls the callback function `fn` for every file matching pattern. The
syntax of pattern is the same as in Match() and the behavior is the same as
Glob(), with regard to limitations (such as patterns containing `/./`, `/../`,
or starting with `/`). The pattern may describe hierarchical names such as
usr/*/bin/ed.
GlobWalk may have a small performance benefit over Glob if you do not need a
slice of matches because it can avoid allocating memory for the matches.
Additionally, GlobWalk gives you access to the `fs.DirEntry` objects for each
match, and lets you quit early by returning a non-nil error from your callback
function.
GlobWalk ignores file system errors such as I/O errors reading directories.
GlobWalk may return ErrBadPattern, reporting that the pattern is malformed.
Additionally, if the callback function `fn` returns an error, GlobWalk will
exit immediately and return that error.
Like Glob(), this function assumes that your pattern uses `/` as the path
separator even if that's not correct for your OS (like Windows). If you aren't
sure if that's the case, you can use filepath.ToSlash() on your pattern before
calling GlobWalk().
### SplitPattern
```go
func SplitPattern(p string) (base, pattern string)
```
SplitPattern is a utility function. Given a pattern, SplitPattern will return
two strings: the first string is everything up to the last slash (`/`) that
appears _before_ any unescaped "meta" characters (ie, `*?[{`). The second
string is everything after that slash. For example, given the pattern:
```
../../path/to/meta*/**
^----------- split here
```
SplitPattern returns "../../path/to" and "meta*/**". This is useful for
initializing os.DirFS() to call Glob() because Glob() will silently fail if
your pattern includes `/./` or `/../`. For example:
```go
base, pattern := SplitPattern("../../path/to/meta*/**")
fsys := os.DirFS(base)
matches, err := Glob(fsys, pattern)
```
If SplitPattern cannot find somewhere to split the pattern (for example,
`meta*/**`), it will return "." and the unaltered pattern (`meta*/**` in this
example).
Of course, it is your responsibility to decide if the returned base path is
"safe" in the context of your application. Perhaps you could use Match() to
validate against a list of approved base directories?
### ValidatePattern
```go
func ValidatePattern(s string) bool
```
Validate a pattern. Patterns are validated while they run in Match(),
PathMatch(), and Glob(), so, you normally wouldn't need to call this. However,
there are cases where this might be useful: for example, if your program allows
a user to enter a pattern that you'll run at a later time, you might want to
validate it.
ValidatePattern assumes your pattern uses '/' as the path separator.
### ValidatePathPattern
```go
func ValidatePathPattern(s string) bool
```
Like ValidatePattern, only uses your OS path separator. In other words, use
ValidatePattern if you would normally use Match() or Glob(). Use
ValidatePathPattern if you would normally use PathMatch(). Keep in mind, Glob()
requires '/' separators, even if your OS uses something else.
### Patterns
**doublestar** supports the following special terms in the patterns:
Special Terms | Meaning
------------- | -------
`*` | matches any sequence of non-path-separators
`/**/` | matches zero or more directories
`?` | matches any single non-path-separator character
`[class]` | matches any single non-path-separator character against a class of characters ([see "character classes"])
`{alt1,...}` | matches a sequence of characters if one of the comma-separated alternatives matches
Any character with a special meaning can be escaped with a backslash (`\`).
A doublestar (`**`) should appear surrounded by path separators such as `/**/`.
A mid-pattern doublestar (`**`) behaves like bash's globstar option: a pattern
such as `path/to/**.txt` would return the same results as `path/to/*.txt`. The
pattern you're looking for is `path/to/**/*.txt`.
#### Character Classes
Character classes support the following:
Class | Meaning
---------- | -------
`[abc]` | matches any single character within the set
`[a-z]` | matches any single character in the range
`[^class]` | matches any single character which does *not* match the class
`[!class]` | same as `^`: negates the class
## Performance
```
goos: darwin
goarch: amd64
pkg: github.com/bmatcuk/doublestar/v4
cpu: Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
BenchmarkMatch-8 285639 3868 ns/op 0 B/op 0 allocs/op
BenchmarkGoMatch-8 286945 3726 ns/op 0 B/op 0 allocs/op
BenchmarkPathMatch-8 320511 3493 ns/op 0 B/op 0 allocs/op
BenchmarkGoPathMatch-8 304236 3434 ns/op 0 B/op 0 allocs/op
BenchmarkGlob-8 466 2501123 ns/op 190225 B/op 2849 allocs/op
BenchmarkGlobWalk-8 476 2536293 ns/op 184017 B/op 2750 allocs/op
BenchmarkGoGlob-8 463 2574836 ns/op 194249 B/op 2929 allocs/op
```
These benchmarks (in `doublestar_test.go`) compare Match() to path.Match(),
PathMath() to filepath.Match(), and Glob() + GlobWalk() to io/fs.Glob(). They
only run patterns that the standard go packages can understand as well (so, no
`{alts}` or `**`) for a fair comparison. Of course, alts and doublestars will
be less performant than the other pattern meta characters.
Alts are essentially like running multiple patterns, the number of which can
get large if your pattern has alts nested inside alts. This affects both
matching (ie, Match()) and globbing (Glob()).
`**` performance in matching is actually pretty similar to a regular `*`, but
can cause a large number of reads when globbing as it will need to recursively
traverse your filesystem.
## License
[MIT License](LICENSE)
[SplitPattern]: #splitpattern
[doublestar]: https://github.com/bmatcuk/doublestar
[golang]: http://golang.org/
[io/fs]: https://golang.org/pkg/io/fs/
[see "character classes"]: #character-classes
[see "patterns"]: #patterns

63
vendor/github.com/bmatcuk/doublestar/v4/UPGRADING.md generated vendored Normal file
View file

@ -0,0 +1,63 @@
# Upgrading from v3 to v4
v4 is a complete rewrite with a focus on performance. Additionally,
[doublestar] has been updated to use the new [io/fs] package for filesystem
access. As a result, it is only supported by [golang] v1.16+.
`Match()` and `PathMatch()` mostly did not change, besides big performance
improvements. Their API is the same. However, note the following corner cases:
* In previous versions of [doublestar], `PathMatch()` could accept patterns
that used either platform-specific path separators, or `/`. This was
undocumented and didn't match `filepath.Match()`. In v4, both `pattern` and
`name` must be using appropriate path separators for the platform. You can
use `filepath.FromSlash()` to change `/` to platform-specific separators if
you aren't sure.
* In previous versions of [doublestar], a pattern such as `path/to/a/**` would
_not_ match `path/to/a`. In v4, this pattern _will_ match because if `a` was
a directory, `Glob()` would return it. In other words, the following returns
true: `Match("path/to/a/**", "path/to/a")`
`Glob()` changed from using a [doublestar]-specific filesystem abstraction (the
`OS` interface) to the [io/fs] package. As a result, it now takes a `fs.FS` as
its first argument. This change has a couple ramifications:
* Like `io/fs.Glob`, `pattern` must use a `/` as path separator, even on
platforms that use something else. You can use `filepath.ToSlash()` on your
patterns if you aren't sure.
* Patterns that contain `/./` or `/../` are invalid. The [io/fs] package
rejects them, returning an IO error. Since `Glob()` ignores IO errors, it'll
end up being silently rejected. You can run `path.Clean()` to ensure they are
removed from the pattern.
v4 also added a `GlobWalk()` function that is slightly more performant than
`Glob()` if you just need to iterate over the results and don't need a string
slice. You also get `fs.DirEntry` objects for each result, and can quit early
if your callback returns an error.
# Upgrading from v2 to v3
v3 introduced using `!` to negate character classes, in addition to `^`. If any
of your patterns include a character class that starts with an exclamation mark
(ie, `[!...]`), you'll need to update the pattern to escape or move the
exclamation mark. Note that, like the caret (`^`), it only negates the
character class if it is the first character in the character class.
# Upgrading from v1 to v2
The change from v1 to v2 was fairly minor: the return type of the `Open` method
on the `OS` interface was changed from `*os.File` to `File`, a new interface
exported by doublestar. The new `File` interface only defines the functionality
doublestar actually needs (`io.Closer` and `Readdir`), making it easier to use
doublestar with [go-billy], [afero], or something similar. If you were using
this functionality, updating should be as easy as updating `Open's` return
type, since `os.File` already implements `doublestar.File`.
If you weren't using this functionality, updating should be as easy as changing
your dependencies to point to v2.
[afero]: https://github.com/spf13/afero
[doublestar]: https://github.com/bmatcuk/doublestar
[go-billy]: https://github.com/src-d/go-billy
[golang]: http://golang.org/
[io/fs]: https://golang.org/pkg/io/fs/

View file

@ -0,0 +1,8 @@
package doublestar
import (
"path"
)
// ErrBadPattern indicates a pattern was malformed.
var ErrBadPattern = path.ErrBadPattern

395
vendor/github.com/bmatcuk/doublestar/v4/glob.go generated vendored Normal file
View file

@ -0,0 +1,395 @@
package doublestar
import (
"io/fs"
"path"
)
// Glob returns the names of all files matching pattern or nil if there is no
// matching file. The syntax of pattern is the same as in Match(). The pattern
// may describe hierarchical names such as usr/*/bin/ed.
//
// Glob ignores file system errors such as I/O errors reading directories.
// The only possible returned error is ErrBadPattern, reporting that the
// pattern is malformed.
//
// Note: this is meant as a drop-in replacement for io/fs.Glob(). Like
// io/fs.Glob(), this function assumes that your pattern uses `/` as the path
// separator even if that's not correct for your OS (like Windows). If you
// aren't sure if that's the case, you can use filepath.ToSlash() on your
// pattern before calling Glob().
//
// Like `io/fs.Glob()`, patterns containing `/./`, `/../`, or starting with `/`
// will return no results and no errors. You can use SplitPattern to divide a
// pattern into a base path (to initialize an `FS` object) and pattern.
//
func Glob(fsys fs.FS, pattern string) ([]string, error) {
if !ValidatePattern(pattern) {
return nil, ErrBadPattern
}
if hasMidDoubleStar(pattern) {
// If the pattern has a `**` anywhere but the very end, GlobWalk is more
// performant because it can get away with less allocations. If the pattern
// ends in a `**`, both methods are pretty much the same, but Glob has a
// _very_ slight advantage because of lower function call overhead.
var matches []string
err := doGlobWalk(fsys, pattern, true, func(p string, d fs.DirEntry) error {
matches = append(matches, p)
return nil
})
return matches, err
}
return doGlob(fsys, pattern, nil, true)
}
// Does the actual globbin'
func doGlob(fsys fs.FS, pattern string, m []string, firstSegment bool) (matches []string, err error) {
matches = m
patternStart := indexMeta(pattern)
if patternStart == -1 {
// pattern doesn't contain any meta characters - does a file matching the
// pattern exist?
if exists(fsys, pattern) {
matches = append(matches, pattern)
return
} else {
return
}
}
dir := "."
splitIdx := lastIndexSlashOrAlt(pattern)
if splitIdx != -1 {
if pattern[splitIdx] == '}' {
openingIdx := indexMatchedOpeningAlt(pattern[:splitIdx])
if openingIdx == -1 {
// if there's no matching opening index, technically Match() will treat
// an unmatched `}` as nothing special, so... we will, too!
splitIdx = lastIndexSlash(pattern[:splitIdx])
} else {
// otherwise, we have to handle the alts:
return globAlts(fsys, pattern, openingIdx, splitIdx, matches, firstSegment)
}
}
dir = pattern[:splitIdx]
pattern = pattern[splitIdx+1:]
}
// if `splitIdx` is less than `patternStart`, we know `dir` has no meta
// characters. They would be equal if they are both -1, which means `dir`
// will be ".", and we know that doesn't have meta characters either.
if splitIdx <= patternStart{
return globDir(fsys, dir, pattern, matches, firstSegment)
}
var dirs []string
dirs, err = doGlob(fsys, dir, matches, false)
if err != nil {
return
}
for _, d := range dirs {
matches, err = globDir(fsys, d, pattern, matches, firstSegment)
if err != nil {
return
}
}
return
}
// handle alts in the glob pattern - `openingIdx` and `closingIdx` are the
// indexes of `{` and `}`, respectively
func globAlts(fsys fs.FS, pattern string, openingIdx, closingIdx int, m []string, firstSegment bool) (matches []string, err error) {
matches = m
var dirs []string
startIdx := 0
afterIdx := closingIdx + 1
splitIdx := lastIndexSlashOrAlt(pattern[:openingIdx])
if splitIdx == -1 || pattern[splitIdx] == '}' {
// no common prefix
dirs = []string{""}
} else {
// our alts have a common prefix that we can process first
dirs, err = doGlob(fsys, pattern[:splitIdx], matches, false)
if err != nil {
return
}
startIdx = splitIdx + 1
}
for _, d := range dirs {
patIdx := openingIdx + 1
altResultsStartIdx := len(matches)
thisResultStartIdx := altResultsStartIdx
for patIdx < closingIdx {
nextIdx := indexNextAlt(pattern[patIdx:closingIdx], true)
if nextIdx == -1 {
nextIdx = closingIdx
} else {
nextIdx += patIdx
}
alt := buildAlt(d, pattern, startIdx, openingIdx, patIdx, nextIdx, afterIdx)
matches, err = doGlob(fsys, alt, matches, firstSegment)
if err != nil {
return
}
matchesLen := len(matches)
if altResultsStartIdx != thisResultStartIdx && thisResultStartIdx != matchesLen {
// Alts can result in matches that aren't sorted, or, worse, duplicates
// (consider the trivial pattern `path/to/{a,*}`). Since doGlob returns
// sorted results, we can do a sort of in-place merge and remove
// duplicates. But, we only need to do this if this isn't the first alt
// (ie, `altResultsStartIdx != thisResultsStartIdx`) and if the latest
// alt actually added some matches (`thisResultStartIdx !=
// len(matches)`)
matches = sortAndRemoveDups(matches, altResultsStartIdx, thisResultStartIdx, matchesLen)
// length of matches may have changed
thisResultStartIdx = len(matches)
} else {
thisResultStartIdx = matchesLen
}
patIdx = nextIdx + 1
}
}
return
}
// find files/subdirectories in the given `dir` that match `pattern`
func globDir(fsys fs.FS, dir, pattern string, matches []string, canMatchFiles bool) (m []string, e error) {
m = matches
if pattern == "" {
// pattern can be an empty string if the original pattern ended in a slash,
// in which case, we should just return dir, but only if it actually exists
// and it's a directory (or a symlink to a directory)
if isPathDir(fsys, dir) {
m = append(m, dir)
}
return
}
if pattern == "**" {
m = globDoubleStar(fsys, dir, m, canMatchFiles)
return
}
dirs, err := fs.ReadDir(fsys, dir)
if err != nil {
// ignore IO errors
return
}
var matched bool
for _, info := range dirs {
name := info.Name()
if canMatchFiles || isDir(fsys, dir, name, info) {
matched, e = matchWithSeparator(pattern, name, '/', false)
if e != nil {
return
}
if matched {
m = append(m, path.Join(dir, name))
}
}
}
return
}
func globDoubleStar(fsys fs.FS, dir string, matches []string, canMatchFiles bool) []string {
dirs, err := fs.ReadDir(fsys, dir)
if err != nil {
// ignore IO errors
return matches
}
// `**` can match *this* dir, so add it
matches = append(matches, dir)
for _, info := range dirs {
name := info.Name()
if isDir(fsys, dir, name, info) {
matches = globDoubleStar(fsys, path.Join(dir, name), matches, canMatchFiles)
} else if canMatchFiles {
matches = append(matches, path.Join(dir, name))
}
}
return matches
}
// Returns true if the pattern has a doublestar in the middle of the pattern.
// In this case, GlobWalk is faster because it can get away with less
// allocations. However, Glob has a _very_ slight edge if the pattern ends in
// `**`.
func hasMidDoubleStar(p string) bool {
// subtract 3: 2 because we want to return false if the pattern ends in `**`
// (Glob is _very_ slightly faster in that case), and the extra 1 because our
// loop checks p[i] and p[i+1].
l := len(p) - 3
for i := 0; i < l; i++ {
if p[i] == '\\' {
// escape next byte
i++
} else if p[i] == '*' && p[i+1] == '*' {
return true
}
}
return false
}
// Returns the index of the first unescaped meta character, or negative 1.
func indexMeta(s string) int {
var c byte
l := len(s)
for i := 0; i < l; i++ {
c = s[i]
if c == '*' || c == '?' || c == '[' || c == '{' {
return i
} else if c == '\\' {
// skip next byte
i++
}
}
return -1
}
// Returns the index of the last unescaped slash or closing alt (`}`) in the
// string, or negative 1.
func lastIndexSlashOrAlt(s string) int {
for i := len(s) - 1; i >= 0; i-- {
if (s[i] == '/' || s[i] == '}') && (i == 0 || s[i-1] != '\\') {
return i
}
}
return -1
}
// Returns the index of the last unescaped slash in the string, or negative 1.
func lastIndexSlash(s string) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '/' && (i == 0 || s[i-1] != '\\') {
return i
}
}
return -1
}
// Assuming the byte after the end of `s` is a closing `}`, this function will
// find the index of the matching `{`. That is, it'll skip over any nested `{}`
// and account for escaping.
func indexMatchedOpeningAlt(s string) int {
alts := 1
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '}' && (i == 0 || s[i-1] != '\\') {
alts++
} else if s[i] == '{' && (i == 0 || s[i-1] != '\\') {
if alts--; alts == 0 {
return i
}
}
}
return -1
}
// Returns true if the path exists
func exists(fsys fs.FS, name string) bool {
if _, err := fs.Stat(fsys, name); err != nil {
return false
}
return true
}
// Returns true if the path is a directory, or a symlink to a directory
func isPathDir(fsys fs.FS, name string) bool {
info, err := fs.Stat(fsys, name)
if err != nil {
return false
}
return info.IsDir()
}
// Returns whether or not the given DirEntry is a directory. If the DirEntry
// represents a symbolic link, the link is followed by running fs.Stat() on
// `path.Join(dir, name)`
func isDir(fsys fs.FS, dir string, name string, info fs.DirEntry) bool {
if (info.Type() & fs.ModeSymlink) > 0 {
finfo, err := fs.Stat(fsys, path.Join(dir, name))
if err != nil {
return false
}
return finfo.IsDir()
}
return info.IsDir()
}
// Builds a string from an alt
func buildAlt(prefix, pattern string, startIdx, openingIdx, currentIdx, nextIdx, afterIdx int) string {
// pattern:
// ignored/start{alts,go,here}remaining - len = 36
// | | | | ^--- afterIdx = 27
// | | | \--------- nextIdx = 21
// | | \----------- currentIdx = 19
// | \----------------- openingIdx = 13
// \---------------------- startIdx = 8
//
// result:
// prefix/startgoremaining - len = 7 + 5 + 2 + 9 = 23
var buf []byte
patLen := len(pattern)
size := (openingIdx - startIdx) + (nextIdx - currentIdx) + (patLen - afterIdx)
if prefix != "" {
buf = make([]byte, 0, size + len(prefix) + 1)
buf = append(buf, prefix...)
buf = append(buf, '/')
} else {
buf = make([]byte, 0, size)
}
buf = append(buf, pattern[startIdx:openingIdx]...)
buf = append(buf, pattern[currentIdx:nextIdx]...)
if afterIdx < patLen {
buf = append(buf, pattern[afterIdx:]...)
}
return string(buf)
}
// Running alts can produce results that are not sorted, and, worse, can cause
// duplicates (consider the trivial pattern `path/to/{a,*}`). Since we know
// each run of doGlob is sorted, we can basically do the "merge" step of a
// merge sort in-place.
func sortAndRemoveDups(matches []string, idx1, idx2, l int) []string {
var tmp string
for ; idx1 < idx2; idx1++ {
if matches[idx1] < matches[idx2] {
// order is correct
continue
} else if matches[idx1] > matches[idx2] {
// need to swap and then re-sort matches above idx2
tmp = matches[idx1]
matches[idx1] = matches[idx2]
shft := idx2 + 1
for ; shft < l && matches[shft] < tmp; shft++ {
matches[shft - 1] = matches[shft]
}
matches[shft - 1] = tmp
} else {
// duplicate - shift matches above idx2 down one and decrement l
for shft := idx2 + 1; shft < l; shft++ {
matches[shft - 1] = matches[shft]
}
if l--; idx2 == l {
// nothing left to do... matches[idx2:] must have been full of dups
break
}
}
}
return matches[:l]
}

275
vendor/github.com/bmatcuk/doublestar/v4/globwalk.go generated vendored Normal file
View file

@ -0,0 +1,275 @@
package doublestar
import (
"io/fs"
"path"
)
// Callback function for GlobWalk(). If the function returns an error, GlobWalk
// will end immediately and return the same error.
type GlobWalkFunc func(path string, d fs.DirEntry) error
// GlobWalk calls the callback function `fn` for every file matching pattern.
// The syntax of pattern is the same as in Match() and the behavior is the same
// as Glob(), with regard to limitations (such as patterns containing `/./`,
// `/../`, or starting with `/`). The pattern may describe hierarchical names
// such as usr/*/bin/ed.
//
// GlobWalk may have a small performance benefit over Glob if you do not need a
// slice of matches because it can avoid allocating memory for the matches.
// Additionally, GlobWalk gives you access to the `fs.DirEntry` objects for
// each match, and lets you quit early by returning a non-nil error from your
// callback function.
//
// GlobWalk ignores file system errors such as I/O errors reading directories.
// GlobWalk may return ErrBadPattern, reporting that the pattern is malformed.
// Additionally, if the callback function `fn` returns an error, GlobWalk will
// exit immediately and return that error.
//
// Like Glob(), this function assumes that your pattern uses `/` as the path
// separator even if that's not correct for your OS (like Windows). If you
// aren't sure if that's the case, you can use filepath.ToSlash() on your
// pattern before calling GlobWalk().
//
func GlobWalk(fsys fs.FS, pattern string, fn GlobWalkFunc) error {
if !ValidatePattern(pattern) {
return ErrBadPattern
}
return doGlobWalk(fsys, pattern, true, fn)
}
// Actually execute GlobWalk
func doGlobWalk(fsys fs.FS, pattern string, firstSegment bool, fn GlobWalkFunc) error {
patternStart := indexMeta(pattern)
if patternStart == -1 {
// pattern doesn't contain any meta characters - does a file matching the
// pattern exist?
info, err := fs.Stat(fsys, pattern)
if err == nil {
err = fn(pattern, dirEntryFromFileInfo(info))
return err
} else {
// ignore IO errors
return nil
}
}
dir := "."
splitIdx := lastIndexSlashOrAlt(pattern)
if splitIdx != -1 {
if pattern[splitIdx] == '}' {
openingIdx := indexMatchedOpeningAlt(pattern[:splitIdx])
if openingIdx == -1 {
// if there's no matching opening index, technically Match() will treat
// an unmatched `}` as nothing special, so... we will, too!
splitIdx = lastIndexSlash(pattern[:splitIdx])
} else {
// otherwise, we have to handle the alts:
return globAltsWalk(fsys, pattern, openingIdx, splitIdx, firstSegment, fn)
}
}
dir = pattern[:splitIdx]
pattern = pattern[splitIdx+1:]
}
// if `splitIdx` is less than `patternStart`, we know `dir` has no meta
// characters. They would be equal if they are both -1, which means `dir`
// will be ".", and we know that doesn't have meta characters either.
if splitIdx <= patternStart {
return globDirWalk(fsys, dir, pattern, firstSegment, fn)
}
return doGlobWalk(fsys, dir, false, func(p string, d fs.DirEntry) error {
if err := globDirWalk(fsys, p, pattern, firstSegment, fn); err != nil {
return err
}
return nil
})
}
// handle alts in the glob pattern - `openingIdx` and `closingIdx` are the
// indexes of `{` and `}`, respectively
func globAltsWalk(fsys fs.FS, pattern string, openingIdx, closingIdx int, firstSegment bool, fn GlobWalkFunc) (err error) {
var matches []DirEntryWithFullPath
startIdx := 0
afterIdx := closingIdx + 1
splitIdx := lastIndexSlashOrAlt(pattern[:openingIdx])
if splitIdx == -1 || pattern[splitIdx] == '}' {
// no common prefix
matches, err = doGlobAltsWalk(fsys, "", pattern, startIdx, openingIdx, closingIdx, afterIdx, firstSegment, matches)
if err != nil {
return
}
} else {
// our alts have a common prefix that we can process first
startIdx = splitIdx + 1
err = doGlobWalk(fsys, pattern[:splitIdx], false, func(p string, d fs.DirEntry) (e error) {
matches, e = doGlobAltsWalk(fsys, p, pattern, startIdx, openingIdx, closingIdx, afterIdx, firstSegment, matches)
return e
})
if err != nil {
return
}
}
for _, m := range matches {
if err = fn(m.Path, m.Entry); err != nil {
return
}
}
return
}
// runs actual matching for alts
func doGlobAltsWalk(fsys fs.FS, d, pattern string, startIdx, openingIdx, closingIdx, afterIdx int, firstSegment bool, m []DirEntryWithFullPath) (matches []DirEntryWithFullPath, err error) {
matches = m
matchesLen := len(m)
patIdx := openingIdx + 1
for patIdx < closingIdx {
nextIdx := indexNextAlt(pattern[patIdx:closingIdx], true)
if nextIdx == -1 {
nextIdx = closingIdx
} else {
nextIdx += patIdx
}
alt := buildAlt(d, pattern, startIdx, openingIdx, patIdx, nextIdx, afterIdx)
err = doGlobWalk(fsys, alt, firstSegment, func(p string, d fs.DirEntry) error {
// insertion sort, ignoring dups
insertIdx := matchesLen
for insertIdx > 0 && matches[insertIdx-1].Path > p {
insertIdx--
}
if insertIdx > 0 && matches[insertIdx-1].Path == p {
// dup
return nil
}
// append to grow the slice, then insert
entry := DirEntryWithFullPath{d, p}
matches = append(matches, entry)
for i := matchesLen; i > insertIdx; i-- {
matches[i] = matches[i-1]
}
matches[insertIdx] = entry
matchesLen++
return nil
})
if err != nil {
return
}
patIdx = nextIdx + 1
}
return
}
func globDirWalk(fsys fs.FS, dir, pattern string, canMatchFiles bool, fn GlobWalkFunc) (e error) {
if pattern == "" {
// pattern can be an empty string if the original pattern ended in a slash,
// in which case, we should just return dir, but only if it actually exists
// and it's a directory (or a symlink to a directory)
info, err := fs.Stat(fsys, dir)
if err != nil || !info.IsDir() {
return nil
}
return fn(dir, dirEntryFromFileInfo(info))
}
if pattern == "**" {
// `**` can match *this* dir
info, err := fs.Stat(fsys, dir)
if err != nil || !info.IsDir() {
return nil
}
if e = fn(dir, dirEntryFromFileInfo(info)); e != nil {
return
}
return globDoubleStarWalk(fsys, dir, canMatchFiles, fn)
}
dirs, err := fs.ReadDir(fsys, dir)
if err != nil {
// ignore IO errors
return nil
}
var matched bool
for _, info := range dirs {
name := info.Name()
if canMatchFiles || isDir(fsys, dir, name, info) {
matched, e = matchWithSeparator(pattern, name, '/', false)
if e != nil {
return
}
if matched {
if e = fn(path.Join(dir, name), info); e != nil {
return
}
}
}
}
return
}
func globDoubleStarWalk(fsys fs.FS, dir string, canMatchFiles bool, fn GlobWalkFunc) (e error) {
dirs, err := fs.ReadDir(fsys, dir)
if err != nil {
// ignore IO errors
return
}
// `**` can match *this* dir, so add it
for _, info := range dirs {
name := info.Name()
if isDir(fsys, dir, name, info) {
p := path.Join(dir, name)
if e = fn(p, info); e != nil {
return
}
if e = globDoubleStarWalk(fsys, p, canMatchFiles, fn); e != nil {
return
}
} else if canMatchFiles {
if e = fn(path.Join(dir, name), info); e != nil {
return
}
}
}
return
}
type DirEntryFromFileInfo struct {
fi fs.FileInfo
}
func (d *DirEntryFromFileInfo) Name() string {
return d.fi.Name()
}
func (d *DirEntryFromFileInfo) IsDir() bool {
return d.fi.IsDir()
}
func (d *DirEntryFromFileInfo) Type() fs.FileMode {
return d.fi.Mode().Type()
}
func (d *DirEntryFromFileInfo) Info() (fs.FileInfo, error) {
return d.fi, nil
}
func dirEntryFromFileInfo(fi fs.FileInfo) fs.DirEntry {
return &DirEntryFromFileInfo{fi}
}
type DirEntryWithFullPath struct {
Entry fs.DirEntry
Path string
}

3
vendor/github.com/bmatcuk/doublestar/v4/go.mod generated vendored Normal file
View file

@ -0,0 +1,3 @@
module github.com/bmatcuk/doublestar/v4
go 1.16

374
vendor/github.com/bmatcuk/doublestar/v4/match.go generated vendored Normal file
View file

@ -0,0 +1,374 @@
package doublestar
import (
"path/filepath"
"unicode/utf8"
)
// Match reports whether name matches the shell pattern.
// The pattern syntax is:
//
// pattern:
// { term }
// term:
// '*' matches any sequence of non-path-separators
// '/**/' matches zero or more directories
// '?' matches any single non-path-separator character
// '[' [ '^' '!' ] { character-range } ']'
// character class (must be non-empty)
// starting with `^` or `!` negates the class
// '{' { term } [ ',' { term } ... ] '}'
// alternatives
// c matches character c (c != '*', '?', '\\', '[')
// '\\' c matches character c
//
// character-range:
// c matches character c (c != '\\', '-', ']')
// '\\' c matches character c
// lo '-' hi matches character c for lo <= c <= hi
//
// Match returns true if `name` matches the file name `pattern`. `name` and
// `pattern` are split on forward slash (`/`) characters and may be relative or
// absolute.
//
// Match requires pattern to match all of name, not just a substring.
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
//
// A doublestar (`**`) should appear surrounded by path separators such as
// `/**/`. A mid-pattern doublestar (`**`) behaves like bash's globstar
// option: a pattern such as `path/to/**.txt` would return the same results as
// `path/to/*.txt`. The pattern you're looking for is `path/to/**/*.txt`.
//
// Note: this is meant as a drop-in replacement for path.Match() which
// always uses '/' as the path separator. If you want to support systems
// which use a different path separator (such as Windows), what you want
// is PathMatch(). Alternatively, you can run filepath.ToSlash() on both
// pattern and name and then use this function.
//
func Match(pattern, name string) (bool, error) {
return matchWithSeparator(pattern, name, '/', true)
}
// PathMatch returns true if `name` matches the file name `pattern`. The
// difference between Match and PathMatch is that PathMatch will automatically
// use your system's path separator to split `name` and `pattern`. On systems
// where the path separator is `'\'`, escaping will be disabled.
//
// Note: this is meant as a drop-in replacement for filepath.Match(). It
// assumes that both `pattern` and `name` are using the system's path
// separator. If you can't be sure of that, use filepath.ToSlash() on both
// `pattern` and `name`, and then use the Match() function instead.
//
func PathMatch(pattern, name string) (bool, error) {
return matchWithSeparator(pattern, name, filepath.Separator, true)
}
func matchWithSeparator(pattern, name string, separator rune, validate bool) (matched bool, err error) {
doublestarPatternBacktrack := -1
doublestarNameBacktrack := -1
starPatternBacktrack := -1
starNameBacktrack := -1
patIdx := 0
nameIdx := 0
patLen := len(pattern)
nameLen := len(name)
startOfSegment := true
MATCH:
for nameIdx < nameLen {
if patIdx < patLen {
switch pattern[patIdx] {
case '*':
if patIdx++; patIdx < patLen && pattern[patIdx] == '*' {
// doublestar - must begin with a path separator, otherwise we'll
// treat it like a single star like bash
patIdx++
if startOfSegment {
if patIdx >= patLen {
// pattern ends in `/**`: return true
return true, nil
}
// doublestar must also end with a path separator, otherwise we're
// just going to treat the doublestar as a single star like bash
patRune, patRuneLen := utf8.DecodeRuneInString(pattern[patIdx:])
if patRune == separator {
patIdx += patRuneLen
doublestarPatternBacktrack = patIdx
doublestarNameBacktrack = nameIdx
starPatternBacktrack = -1
starNameBacktrack = -1
continue
}
}
}
startOfSegment = false
starPatternBacktrack = patIdx
starNameBacktrack = nameIdx
continue
case '?':
startOfSegment = false
nameRune, nameRuneLen := utf8.DecodeRuneInString(name[nameIdx:])
if nameRune == separator {
// `?` cannot match the separator
break
}
patIdx++
nameIdx += nameRuneLen
continue
case '[':
startOfSegment = false
if patIdx++; patIdx >= patLen {
// class didn't end
return false, ErrBadPattern
}
nameRune, nameRuneLen := utf8.DecodeRuneInString(name[nameIdx:])
matched := false
negate := pattern[patIdx] == '!' || pattern[patIdx] == '^'
if negate {
patIdx++
}
if patIdx >= patLen || pattern[patIdx] == ']' {
// class didn't end or empty character class
return false, ErrBadPattern
}
last := utf8.MaxRune
for patIdx < patLen && pattern[patIdx] != ']' {
patRune, patRuneLen := utf8.DecodeRuneInString(pattern[patIdx:])
patIdx += patRuneLen
// match a range
if last < utf8.MaxRune && patRune == '-' && patIdx < patLen && pattern[patIdx] != ']' {
if pattern[patIdx] == '\\' {
// next character is escaped
patIdx++
}
patRune, patRuneLen = utf8.DecodeRuneInString(pattern[patIdx:])
patIdx += patRuneLen
if last <= nameRune && nameRune <= patRune {
matched = true
break
}
// didn't match range - reset `last`
last = utf8.MaxRune
continue
}
// not a range - check if the next rune is escaped
if patRune == '\\' {
patRune, patRuneLen = utf8.DecodeRuneInString(pattern[patIdx:])
patIdx += patRuneLen
}
// check if the rune matches
if patRune == nameRune {
matched = true
break
}
// no matches yet
last = patRune
}
if matched == negate {
// failed to match - if we reached the end of the pattern, that means
// we never found a closing `]`
if patIdx >= patLen {
return false, ErrBadPattern
}
break
}
closingIdx := indexUnescapedByte(pattern[patIdx:], ']', true)
if closingIdx == -1 {
// no closing `]`
return false, ErrBadPattern
}
patIdx += closingIdx + 1
nameIdx += nameRuneLen
continue
case '{':
startOfSegment = false
patIdx++
closingIdx := indexMatchedClosingAlt(pattern[patIdx:], separator != '\\')
if closingIdx == -1 {
// no closing `}`
return false, ErrBadPattern
}
closingIdx += patIdx
for ;; {
commaIdx := indexNextAlt(pattern[patIdx:closingIdx], separator != '\\')
if commaIdx == -1 {
break
}
commaIdx += patIdx
result, err := matchWithSeparator(pattern[patIdx:commaIdx] + pattern[closingIdx+1:], name[nameIdx:], separator, validate)
if result || err != nil {
return result, err
}
patIdx = commaIdx + 1
}
return matchWithSeparator(pattern[patIdx:closingIdx] + pattern[closingIdx+1:], name[nameIdx:], separator, validate)
case '\\':
if separator != '\\' {
// next rune is "escaped" in the pattern - literal match
if patIdx++; patIdx >= patLen {
// pattern ended
return false, ErrBadPattern
}
}
fallthrough
default:
patRune, patRuneLen := utf8.DecodeRuneInString(pattern[patIdx:])
nameRune, nameRuneLen := utf8.DecodeRuneInString(name[nameIdx:])
if patRune != nameRune {
if separator != '\\' && patIdx > 0 && pattern[patIdx-1] == '\\' {
// if this rune was meant to be escaped, we need to move patIdx
// back to the backslash before backtracking or validating below
patIdx--
}
break
}
patIdx += patRuneLen
nameIdx += nameRuneLen
startOfSegment = patRune == separator
continue
}
}
if starPatternBacktrack >= 0 {
// `*` backtrack, but only if the `name` rune isn't the separator
nameRune, nameRuneLen := utf8.DecodeRuneInString(name[starNameBacktrack:])
if nameRune != separator {
starNameBacktrack += nameRuneLen
patIdx = starPatternBacktrack
nameIdx = starNameBacktrack
startOfSegment = false
continue
}
}
if doublestarPatternBacktrack >= 0 {
// `**` backtrack, advance `name` past next separator
nameIdx = doublestarNameBacktrack
for nameIdx < nameLen {
nameRune, nameRuneLen := utf8.DecodeRuneInString(name[nameIdx:])
nameIdx += nameRuneLen
if nameRune == separator {
doublestarNameBacktrack = nameIdx
patIdx = doublestarPatternBacktrack
startOfSegment = true
continue MATCH
}
}
}
if validate && patIdx < patLen && !doValidatePattern(pattern[patIdx:], separator) {
return false, ErrBadPattern
}
return false, nil
}
if nameIdx < nameLen {
// we reached the end of `pattern` before the end of `name`
return false, nil
}
// we've reached the end of `name`; we've successfully matched if we've also
// reached the end of `pattern`, or if the rest of `pattern` can match a
// zero-length string
return isZeroLengthPattern(pattern[patIdx:], separator)
}
func isZeroLengthPattern(pattern string, separator rune) (ret bool, err error) {
// `/**` is a special case - a pattern such as `path/to/a/**` *should* match
// `path/to/a` because `a` might be a directory
if pattern == "" || pattern == "*" || pattern == "**" || pattern == string(separator) + "**" {
return true, nil
}
if pattern[0] == '{' {
closingIdx := indexMatchedClosingAlt(pattern[1:], separator != '\\')
if closingIdx == -1 {
// no closing '}'
return false, ErrBadPattern
}
closingIdx += 1
patIdx := 1
for ;; {
commaIdx := indexNextAlt(pattern[patIdx:closingIdx], separator != '\\')
if commaIdx == -1 {
break
}
commaIdx += patIdx
ret, err = isZeroLengthPattern(pattern[patIdx:commaIdx] + pattern[closingIdx+1:], separator)
if ret || err != nil {
return
}
patIdx = commaIdx + 1
}
return isZeroLengthPattern(pattern[patIdx:closingIdx] + pattern[closingIdx+1:], separator)
}
// no luck - validate the rest of the pattern
if !doValidatePattern(pattern, separator) {
return false, ErrBadPattern
}
return false, nil
}
// Finds the index of the first unescaped byte `c`, or negative 1.
func indexUnescapedByte(s string, c byte, allowEscaping bool) int {
l := len(s)
for i := 0; i < l; i++ {
if allowEscaping && s[i] == '\\' {
// skip next byte
i++
} else if s[i] == c {
return i
}
}
return -1
}
// Assuming the byte before the beginning of `s` is an opening `{`, this
// function will find the index of the matching `}`. That is, it'll skip over
// any nested `{}` and account for escaping
func indexMatchedClosingAlt(s string, allowEscaping bool) int {
alts := 1
l := len(s)
for i := 0; i < l; i++ {
if allowEscaping && s[i] == '\\' {
// skip next byte
i++
} else if s[i] == '{' {
alts++
} else if s[i] == '}' {
if alts--; alts == 0 {
return i
}
}
}
return -1
}

69
vendor/github.com/bmatcuk/doublestar/v4/utils.go generated vendored Normal file
View file

@ -0,0 +1,69 @@
package doublestar
// SplitPattern is a utility function. Given a pattern, SplitPattern will
// return two strings: the first string is everything up to the last slash
// (`/`) that appears _before_ any unescaped "meta" characters (ie, `*?[{`).
// The second string is everything after that slash. For example, given the
// pattern:
//
// ../../path/to/meta*/**
// ^----------- split here
//
// SplitPattern returns "../../path/to" and "meta*/**". This is useful for
// initializing os.DirFS() to call Glob() because Glob() will silently fail if
// your pattern includes `/./` or `/../`. For example:
//
// base, pattern := SplitPattern("../../path/to/meta*/**")
// fsys := os.DirFS(base)
// matches, err := Glob(fsys, pattern)
//
// If SplitPattern cannot find somewhere to split the pattern (for example,
// `meta*/**`), it will return "." and the unaltered pattern (`meta*/**` in
// this example).
//
// Of course, it is your responsibility to decide if the returned base path is
// "safe" in the context of your application. Perhaps you could use Match() to
// validate against a list of approved base directories?
//
func SplitPattern(p string) (base, pattern string) {
base = "."
pattern = p
splitIdx := -1
for i := 0; i < len(p); i++ {
c := p[i]
if c == '\\' {
i++
} else if c == '/' {
splitIdx = i
} else if c == '*' || c == '?' || c == '[' || c == '{' {
break
}
}
if splitIdx >= 0 {
return p[:splitIdx], p[splitIdx+1:]
}
return
}
// Finds the next comma, but ignores any commas that appear inside nested `{}`.
// Assumes that each opening bracket has a corresponding closing bracket.
func indexNextAlt(s string, allowEscaping bool) int {
alts := 1
l := len(s)
for i := 0; i < l; i++ {
if allowEscaping && s[i] == '\\' {
// skip next byte
i++
} else if s[i] == '{' {
alts++
} else if s[i] == '}' {
alts--
} else if s[i] == ',' && alts == 1 {
return i
}
}
return -1
}

82
vendor/github.com/bmatcuk/doublestar/v4/validate.go generated vendored Normal file
View file

@ -0,0 +1,82 @@
package doublestar
import "path/filepath"
// Validate a pattern. Patterns are validated while they run in Match(),
// PathMatch(), and Glob(), so, you normally wouldn't need to call this.
// However, there are cases where this might be useful: for example, if your
// program allows a user to enter a pattern that you'll run at a later time,
// you might want to validate it.
//
// ValidatePattern assumes your pattern uses '/' as the path separator.
//
func ValidatePattern(s string) bool {
return doValidatePattern(s, '/')
}
// Like ValidatePattern, only uses your OS path separator. In other words, use
// ValidatePattern if you would normally use Match() or Glob(). Use
// ValidatePathPattern if you would normally use PathMatch(). Keep in mind,
// Glob() requires '/' separators, even if your OS uses something else.
//
func ValidatePathPattern(s string) bool {
return doValidatePattern(s, filepath.Separator)
}
func doValidatePattern(s string, separator rune) bool {
altDepth := 0
l := len(s)
VALIDATE:
for i := 0; i < l; i++ {
switch s[i] {
case '\\':
if separator != '\\' {
// skip the next byte - return false if there is no next byte
if i++; i >= l {
return false
}
}
continue
case '[':
if i++; i >= l {
// class didn't end
return false
}
if s[i] == '^' || s[i] == '!' {
i++
}
if i >= l || s[i] == ']' {
// class didn't end or empty character class
return false
}
for ; i < l; i++ {
if separator != '\\' && s[i] == '\\' {
i++
} else if s[i] == ']' {
// looks good
continue VALIDATE
}
}
// class didn't end
return false
case '{':
altDepth++
continue
case '}':
if altDepth == 0 {
// alt end without a corresponding start
return false
}
altDepth--
continue
}
}
// valid as long as all alts are closed
return altDepth == 0
}

5
vendor/modules.txt vendored
View file

@ -33,6 +33,9 @@ github.com/beorn7/perks/quantile
# github.com/bmatcuk/doublestar v1.3.4
## explicit
github.com/bmatcuk/doublestar
# github.com/bmatcuk/doublestar/v4 v4.0.2
## explicit
github.com/bmatcuk/doublestar/v4
# github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74
## explicit
github.com/bradrydzewski/togo/parser
@ -174,6 +177,8 @@ github.com/mattn/go-isatty
# github.com/mattn/go-sqlite3 v2.0.3+incompatible
## explicit
github.com/mattn/go-sqlite3
# github.com/mattn/go-zglob v0.0.3
## explicit
# github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/matttproud/golang_protobuf_extensions/pbutil
# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd