github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/pkg/filepathxx/filepathxx.go (about) 1 // From https://github.com/klauspost/filepathx/blob/master/filepathx.go 2 // 3 // Package filepathx adds double-star globbing support to the Glob function from the core path/filepath package. 4 // You might recognize "**" recursive globs from things like your .gitignore file, and zsh. 5 // The "**" glob represents a recursive wildcard matching zero-or-more directory levels deep. 6 package filepathxx 7 8 import ( 9 "io/fs" 10 "path/filepath" 11 "runtime" 12 "strings" 13 ) 14 15 // Globs represents one filepath glob, with its elements joined by "**". 16 type Globs []string 17 18 // Glob adds double-star support to the core path/filepath Glob function. 19 // It's useful when your globs might have double-stars, but you're not sure. 20 func Glob(pattern string) ([]string, error) { 21 if !strings.Contains(pattern, "**") { 22 // passthru to core package if no double-star 23 return filepath.Glob(pattern) 24 } 25 return Globs(strings.Split(pattern, "**")).Expand() 26 } 27 28 const replaceIfAny = "[]*" 29 30 var replacements [][2]string 31 32 func init() { 33 // Escape `filepath.Match` syntax. 34 // On Unix escaping works with `\\`, 35 // on Windows it is disabled, therefore 36 // replace it by '?' := any character. 37 if runtime.GOOS == "windows" { 38 replacements = [][2]string{ 39 // Windows cannot have * in file names. 40 {"[", "?"}, 41 {"]", "?"}, 42 } 43 } else { 44 replacements = [][2]string{ 45 {"*", "\\*"}, 46 {"[", "\\["}, 47 {"]", "\\]"}, 48 } 49 } 50 } 51 52 // Expand finds matches for the provided Globs. 53 func (globs Globs) Expand() ([]string, error) { 54 var matches = []string{""} // accumulate here 55 for _, glob := range globs { 56 if glob == "" { 57 // If the glob is empty string that means it was ** 58 // By setting this to . patterns like **/*.txt are supported 59 glob = "." 60 } 61 var hits []string 62 //var hitMap = map[string]bool{} 63 for _, match := range matches { 64 if strings.ContainsAny(match, replaceIfAny) { 65 for _, sr := range replacements { 66 match = strings.ReplaceAll(match, sr[0], sr[1]) 67 } 68 } 69 70 paths, err := filepath.Glob(filepath.Join(match, glob)) 71 if err != nil { 72 return nil, err 73 } 74 75 for _, path := range paths { 76 err = filepath.WalkDir(path, func(path string, _ fs.DirEntry, err error) error { 77 if err != nil { 78 return err 79 } 80 // save deduped match from current iteration 81 // if _, ok := hitMap[path]; !ok { 82 // hits = append(hits, path) 83 // hitMap[path] = true 84 // } 85 hits = appendUnique(hits, path) 86 // if !contains(path, hits) { 87 // hits = append(hits, path) 88 // } 89 return nil 90 }) 91 if err != nil { 92 return nil, err 93 } 94 } 95 } 96 matches = hits 97 } 98 99 // fix up return value for nil input 100 if globs == nil && len(matches) > 0 && matches[0] == "" { 101 matches = matches[1:] 102 } 103 104 return matches, nil 105 } 106 107 func appendUnique(a []string, x string) []string { 108 for _, y := range a { 109 if x == y { 110 return a 111 } 112 } 113 return append(a, x) 114 }