diff --git a/go.mod b/go.mod index 7699ba1b..524e62d3 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/buger/goterm v1.0.4 // indirect github.com/bugsnag/bugsnag-go v2.1.2+incompatible // indirect github.com/bugsnag/panicwrap v1.3.4 // indirect diff --git a/go.sum b/go.sum index 1e9821e9..57800eb0 100644 --- a/go.sum +++ b/go.sum @@ -281,6 +281,8 @@ github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +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/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= diff --git a/pkg/filepathutil/list.go b/pkg/filepathutil/list.go index 414c9c3f..997b9949 100644 --- a/pkg/filepathutil/list.go +++ b/pkg/filepathutil/list.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/yargevad/filepathx" + "github.com/benchkram/bob/pkg/filepathxx" ) // DefaultIgnores @@ -50,7 +50,7 @@ func ListRecursive(inp string) (all []string, err error) { if s, err := os.Stat(inp); err != nil || !s.IsDir() { // File // Use glob for unknowns (wildcard-paths) and existing files (non-dirs) - matches, err := filepathx.Glob(inp) + matches, err := filepathxx.Glob(inp) if err != nil { return nil, fmt.Errorf("failed to glob %q: %w", inp, err) } @@ -85,11 +85,7 @@ func ListRecursive(inp string) (all []string, err error) { return all, nil } -var WalkedDirs = map[string]int{} - func listDir(path string) ([]string, error) { - times := WalkedDirs[path] - WalkedDirs[path] = times + 1 var all []string if err := filepath.WalkDir(path, func(p string, fi fs.DirEntry, err error) error { diff --git a/pkg/filepathxx/filepathxx.go b/pkg/filepathxx/filepathxx.go new file mode 100644 index 00000000..13d44aff --- /dev/null +++ b/pkg/filepathxx/filepathxx.go @@ -0,0 +1,123 @@ +// From https://github.com/klauspost/filepathx/blob/master/filepathx.go +// +// Package filepathx adds double-star globbing support to the Glob function from the core path/filepath package. +// You might recognize "**" recursive globs from things like your .gitignore file, and zsh. +// The "**" glob represents a recursive wildcard matching zero-or-more directory levels deep. +package filepathxx + +import ( + "io/fs" + "path/filepath" + "runtime" + "strings" +) + +// Globs represents one filepath glob, with its elements joined by "**". +type Globs []string + +// Glob adds double-star support to the core path/filepath Glob function. +// It's useful when your globs might have double-stars, but you're not sure. +func Glob(pattern string) ([]string, error) { + if !strings.Contains(pattern, "**") { + // passthru to core package if no double-star + return filepath.Glob(pattern) + } + return Globs(strings.Split(pattern, "**")).Expand() +} + +const replaceIfAny = "[]*" + +var replacements [][2]string + +func init() { + // Escape `filepath.Match` syntax. + // On Unix escaping works with `\\`, + // on Windows it is disabled, therefore + // replace it by '?' := any character. + if runtime.GOOS == "windows" { + replacements = [][2]string{ + // Windows cannot have * in file names. + {"[", "?"}, + {"]", "?"}, + } + } else { + replacements = [][2]string{ + {"*", "\\*"}, + {"[", "\\["}, + {"]", "\\]"}, + } + } +} + +// Expand finds matches for the provided Globs. +func (globs Globs) Expand() ([]string, error) { + var matches = []string{""} // accumulate here + for _, glob := range globs { + if glob == "" { + // If the glob is empty string that means it was ** + // By setting this to . patterns like **/*.txt are supported + glob = "." + } + var hits []string + //var hitMap = map[string]bool{} + for _, match := range matches { + if strings.ContainsAny(match, replaceIfAny) { + for _, sr := range replacements { + match = strings.ReplaceAll(match, sr[0], sr[1]) + } + } + + paths, err := filepath.Glob(filepath.Join(match, glob)) + if err != nil { + return nil, err + } + + for _, path := range paths { + err = filepath.WalkDir(path, func(path string, _ fs.DirEntry, err error) error { + if err != nil { + return err + } + // save deduped match from current iteration + // if _, ok := hitMap[path]; !ok { + // hits = append(hits, path) + // hitMap[path] = true + // } + hits = appendUnique(hits, path) + // if !contains(path, hits) { + // hits = append(hits, path) + // } + return nil + }) + if err != nil { + return nil, err + } + } + } + matches = hits + } + + // fix up return value for nil input + if globs == nil && len(matches) > 0 && matches[0] == "" { + matches = matches[1:] + } + + return matches, nil +} + +func contains(entry string, arr []string) bool { + for _, e := range arr { + if entry == e { + return true + } + } + return false +} + +func appendUnique(a []string, x string) []string { + for _, y := range a { + if x == y { + return a + } + } + return append(a, x) +}