aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/doublestar/glob.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/doublestar/glob.go')
-rw-r--r--cli/internal/doublestar/glob.go393
1 files changed, 0 insertions, 393 deletions
diff --git a/cli/internal/doublestar/glob.go b/cli/internal/doublestar/glob.go
deleted file mode 100644
index eee8920..0000000
--- a/cli/internal/doublestar/glob.go
+++ /dev/null
@@ -1,393 +0,0 @@
-// Package doublestar is adapted from https://github.com/bmatcuk/doublestar
-// Copyright Bob Matcuk. All Rights Reserved.
-// SPDX-License-Identifier: MIT
-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) ([]string, 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 matches, 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 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
- var err error
- dirs, err = doGlob(fsys, dir, matches, false)
- if err != nil {
- return nil, err
- }
- for _, d := range dirs {
- matches, err = globDir(fsys, d, pattern, matches, firstSegment)
- if err != nil {
- return nil, err
- }
- }
-
- return matches, nil
-}
-
-// 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) ([]string, 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
- var err error
- dirs, err = doGlob(fsys, pattern[:splitIdx], matches, false)
- if err != nil {
- return nil, err
- }
-
- 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)
- var err error
- matches, err = doGlob(fsys, alt, matches, firstSegment)
- if err != nil {
- return nil, err
- }
-
- 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 matches, nil
-}
-
-// find files/subdirectories in the given `dir` that match `pattern`
-func globDir(fsys fs.FS, dir, pattern string, matches []string, canMatchFiles bool) ([]string, 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 m, nil
- }
-
- if pattern == "**" {
- m = globDoubleStar(fsys, dir, m, canMatchFiles)
- return m, nil
- }
-
- dirs, err := fs.ReadDir(fsys, dir)
- if err != nil {
- // ignore IO errors
- return m, nil
- }
-
- var matched bool
- for _, info := range dirs {
- name := info.Name()
- if canMatchFiles || isDir(fsys, dir, name, info) {
- matched, err = matchWithSeparator(pattern, name, '/', false)
- if err != nil {
- return nil, err
- }
- if matched {
- m = append(m, path.Join(dir, name))
- }
- }
- }
-
- return m, nil
-}
-
-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, return false
-func isDir(fsys fs.FS, dir string, name string, info fs.DirEntry) bool {
- if (info.Type() & fs.ModeSymlink) > 0 {
- return false
- }
- 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]
-}