github.com/Jeffail/benthos/v3@v3.65.0/internal/filepath/glob.go (about)

     1  package filepath
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"runtime"
     7  	"strings"
     8  )
     9  
    10  // hasMeta reports whether path contains any of the magic characters
    11  // recognized by Match.
    12  //
    13  // Taken from path/filepath/match.go
    14  func hasMeta(path string) bool {
    15  	magicChars := `*?[`
    16  	if runtime.GOOS != "windows" {
    17  		magicChars = `*?[\`
    18  	}
    19  	return strings.ContainsAny(path, magicChars)
    20  }
    21  
    22  // Globs attempts to expand a list of paths, which may include glob patterns, to
    23  // a list of explicit file paths. The paths are de-duplicated but are not
    24  // sorted.
    25  func Globs(paths []string) ([]string, error) {
    26  	var expandedPaths []string
    27  	seenPaths := map[string]struct{}{}
    28  
    29  	for _, path := range paths {
    30  		var globbed []string
    31  		var err error
    32  		if segments := strings.Split(path, "**"); len(segments) == 1 {
    33  			globbed, err = filepath.Glob(path)
    34  		} else {
    35  			globbed, err = superGlobs(segments)
    36  		}
    37  		if err != nil {
    38  			return nil, err
    39  		}
    40  		for _, gPath := range globbed {
    41  			if _, seen := seenPaths[gPath]; !seen {
    42  				expandedPaths = append(expandedPaths, gPath)
    43  				seenPaths[gPath] = struct{}{}
    44  			}
    45  		}
    46  		if len(globbed) == 0 && !hasMeta(path) {
    47  			if _, seen := seenPaths[path]; !seen {
    48  				expandedPaths = append(expandedPaths, path)
    49  				seenPaths[path] = struct{}{}
    50  			}
    51  		}
    52  	}
    53  
    54  	return expandedPaths, nil
    55  }
    56  
    57  // Inspired by https://github.com/yargevad/filepathx/blob/master/filepathx.go
    58  func superGlobs(segments []string) ([]string, error) {
    59  	matches := map[string]struct{}{"": {}}
    60  
    61  	for i, segment := range segments {
    62  		newMatches := map[string]struct{}{}
    63  		lastSegment := (len(segments) - 1) == i
    64  
    65  		for match := range matches {
    66  			paths, err := filepath.Glob(match + segment)
    67  			if err != nil {
    68  				return nil, err
    69  			}
    70  			for _, path := range paths {
    71  				if err := filepath.Walk(path, func(newPath string, info os.FileInfo, err error) error {
    72  					if err != nil {
    73  						return err
    74  					}
    75  					if lastSegment && info.IsDir() {
    76  						return nil
    77  					}
    78  					newMatches[newPath] = struct{}{}
    79  					return nil
    80  				}); err != nil {
    81  					return nil, err
    82  				}
    83  			}
    84  		}
    85  
    86  		matches = newMatches
    87  	}
    88  
    89  	matchSlice := make([]string, 0, len(matches))
    90  	for path := range matches {
    91  		matchSlice = append(matchSlice, path)
    92  	}
    93  	return matchSlice, nil
    94  }