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  }