mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-02-01 04:02:22 +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:
|
||||
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/**"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
1
go.mod
|
@ -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
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/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
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
|
||||
## 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
|
||||
|
|
Loading…
Reference in a new issue