mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-02-12 17:35:15 +00:00
Support recursive glob for path conditions (#327)
* Support glob with doublestar for path conditions * Update docs and pipeline
This commit is contained in:
parent
d802fdb26d
commit
96828b16c1
19 changed files with 1659 additions and 13 deletions
|
@ -27,7 +27,7 @@ pipeline:
|
||||||
when:
|
when:
|
||||||
event: push
|
event: push
|
||||||
branch: master
|
branch: master
|
||||||
path: "docs/*"
|
path: "docs/**"
|
||||||
|
|
||||||
build-docs-pr:
|
build-docs-pr:
|
||||||
image: node:14-alpine
|
image: node:14-alpine
|
||||||
|
@ -37,4 +37,4 @@ pipeline:
|
||||||
- yarn build
|
- yarn build
|
||||||
when:
|
when:
|
||||||
event: [pull_request]
|
event: [pull_request]
|
||||||
path: "docs/*"
|
path: "docs/**"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
doublestar "github.com/bmatcuk/doublestar/v4"
|
||||||
libcompose "github.com/docker/libcompose/yaml"
|
libcompose "github.com/docker/libcompose/yaml"
|
||||||
"github.com/woodpecker-ci/woodpecker/cncd/pipeline/pipeline/frontend"
|
"github.com/woodpecker-ci/woodpecker/cncd/pipeline/pipeline/frontend"
|
||||||
"github.com/woodpecker-ci/woodpecker/cncd/pipeline/pipeline/frontend/yaml/types"
|
"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 {
|
func (c *ConstraintPath) Includes(v []string) bool {
|
||||||
for _, pattern := range c.Include {
|
for _, pattern := range c.Include {
|
||||||
for _, file := range v {
|
for _, file := range v {
|
||||||
if ok, _ := filepath.Match(pattern, file); ok {
|
if ok, _ := doublestar.Match(pattern, file); ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +240,7 @@ func (c *ConstraintPath) Includes(v []string) bool {
|
||||||
func (c *ConstraintPath) Excludes(v []string) bool {
|
func (c *ConstraintPath) Excludes(v []string) bool {
|
||||||
for _, pattern := range c.Exclude {
|
for _, pattern := range c.Exclude {
|
||||||
for _, file := range v {
|
for _, file := range v {
|
||||||
if ok, _ := filepath.Match(pattern, file); ok {
|
if ok, _ := doublestar.Match(pattern, file); ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,26 @@ func TestConstraintList(t *testing.T) {
|
||||||
with: []string{"CHANGELOG.md", "README.md"},
|
with: []string{"CHANGELOG.md", "README.md"},
|
||||||
want: true,
|
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 ] }",
|
conf: "{ include: [ README.md ] }",
|
||||||
with: []string{"CHANGELOG.md"},
|
with: []string{"CHANGELOG.md"},
|
||||||
|
@ -233,7 +253,7 @@ func TestConstraintList(t *testing.T) {
|
||||||
c := parseConstraintPath(test.conf)
|
c := parseConstraintPath(test.conf)
|
||||||
got, want := c.Match(test.with, test.message), test.want
|
got, want := c.Match(test.with, test.message), test.want
|
||||||
if got != 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,28 +302,26 @@ when:
|
||||||
|
|
||||||
### `path`
|
### `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
|
```diff
|
||||||
when:
|
when:
|
||||||
path: "src/*"
|
path: "src/*"
|
||||||
```
|
```
|
||||||
|
|
||||||
Execute a step only on commit excluding certain files added/removed/modified:
|
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`.
|
||||||
|
|
||||||
|
|
||||||
> NOTE: Feature is only available for GitHub and Gitea repositories.
|
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
when:
|
when:
|
||||||
path:
|
path:
|
||||||
exclude: [ '*.md', '*.ini' ]
|
include: [ '.woodpecker/*.yml', '*.ini' ]
|
||||||
|
exclude: [ '*.md', 'docs/**' ]
|
||||||
ignore_message: "[ALL]"
|
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
|
## Step `group` - Parallel execution
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
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/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 // indirect
|
||||||
github.com/bmatcuk/doublestar v1.3.4
|
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/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
github.com/dgrijalva/jwt-go v0.0.0-20150904212456-c1da56349675
|
github.com/dgrijalva/jwt-go v0.0.0-20150904212456-c1da56349675
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -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/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 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
|
||||||
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
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 h1:fE72rAOk9gpizJL3mNv+Ez+3yt/GCoZWtkKEPLTZvvM=
|
||||||
github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74/go.mod h1:+zgWTTgi3saXD5N9SSA+LYteMbFoIJKJ9WEPXoV0jQA=
|
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=
|
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
32
vendor/github.com/bmatcuk/doublestar/v4/.gitignore
generated
vendored
Normal 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
18
vendor/github.com/bmatcuk/doublestar/v4/.travis.yml
generated
vendored
Normal 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
22
vendor/github.com/bmatcuk/doublestar/v4/LICENSE
generated
vendored
Normal 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
278
vendor/github.com/bmatcuk/doublestar/v4/README.md
generated
vendored
Normal 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
63
vendor/github.com/bmatcuk/doublestar/v4/UPGRADING.md
generated
vendored
Normal 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/
|
8
vendor/github.com/bmatcuk/doublestar/v4/doublestar.go
generated
vendored
Normal file
8
vendor/github.com/bmatcuk/doublestar/v4/doublestar.go
generated
vendored
Normal 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
395
vendor/github.com/bmatcuk/doublestar/v4/glob.go
generated
vendored
Normal 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
275
vendor/github.com/bmatcuk/doublestar/v4/globwalk.go
generated
vendored
Normal 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
3
vendor/github.com/bmatcuk/doublestar/v4/go.mod
generated
vendored
Normal 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
374
vendor/github.com/bmatcuk/doublestar/v4/match.go
generated
vendored
Normal 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
69
vendor/github.com/bmatcuk/doublestar/v4/utils.go
generated
vendored
Normal 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
82
vendor/github.com/bmatcuk/doublestar/v4/validate.go
generated
vendored
Normal 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
5
vendor/modules.txt
vendored
|
@ -33,6 +33,9 @@ github.com/beorn7/perks/quantile
|
||||||
# github.com/bmatcuk/doublestar v1.3.4
|
# github.com/bmatcuk/doublestar v1.3.4
|
||||||
## explicit
|
## explicit
|
||||||
github.com/bmatcuk/doublestar
|
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
|
# github.com/bradrydzewski/togo v0.0.0-20180401185031-50a0e4726e74
|
||||||
## explicit
|
## explicit
|
||||||
github.com/bradrydzewski/togo/parser
|
github.com/bradrydzewski/togo/parser
|
||||||
|
@ -174,6 +177,8 @@ github.com/mattn/go-isatty
|
||||||
# github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
# github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
github.com/mattn/go-sqlite3
|
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 v1.0.1
|
||||||
github.com/matttproud/golang_protobuf_extensions/pbutil
|
github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||||
# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
||||||
|
|
Loading…
Reference in a new issue