...

Source file src/github.com/sagikazarmark/locafero/finder.go

Documentation: github.com/sagikazarmark/locafero

     1  // Package finder looks for files and directories in an {fs.Fs} filesystem.
     2  package locafero
     3  
     4  import (
     5  	"errors"
     6  	"io/fs"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/sourcegraph/conc/iter"
    11  	"github.com/spf13/afero"
    12  )
    13  
    14  // Finder looks for files and directories in an [afero.Fs] filesystem.
    15  type Finder struct {
    16  	// Paths represents a list of locations that the [Finder] will search in.
    17  	//
    18  	// They are essentially the root directories or starting points for the search.
    19  	//
    20  	// Examples:
    21  	//   - home/user
    22  	//   - etc
    23  	Paths []string
    24  
    25  	// Names are specific entries that the [Finder] will look for within the given Paths.
    26  	//
    27  	// It provides the capability to search for entries with depth,
    28  	// meaning it can target deeper locations within the directory structure.
    29  	//
    30  	// It also supports glob syntax (as defined by [filepat.Match]), offering greater flexibility in search patterns.
    31  	//
    32  	// Examples:
    33  	//   - config.yaml
    34  	//   - home/*/config.yaml
    35  	//   - home/*/config.*
    36  	Names []string
    37  
    38  	// Type restricts the kind of entries returned by the [Finder].
    39  	//
    40  	// This parameter helps in differentiating and filtering out files from directories or vice versa.
    41  	Type FileType
    42  }
    43  
    44  // Find looks for files and directories in an [afero.Fs] filesystem.
    45  func (f Finder) Find(fsys afero.Fs) ([]string, error) {
    46  	// Arbitrary go routine limit (TODO: make this a parameter)
    47  	// pool := pool.NewWithResults[[]string]().WithMaxGoroutines(5).WithErrors().WithFirstError()
    48  
    49  	type searchItem struct {
    50  		path string
    51  		name string
    52  	}
    53  
    54  	var searchItems []searchItem
    55  
    56  	for _, searchPath := range f.Paths {
    57  		searchPath := searchPath
    58  
    59  		for _, searchName := range f.Names {
    60  			searchName := searchName
    61  
    62  			searchItems = append(searchItems, searchItem{searchPath, searchName})
    63  
    64  			// pool.Go(func() ([]string, error) {
    65  			// 	// If the name contains any glob character, perform a glob match
    66  			// 	if strings.ContainsAny(searchName, "*?[]\\^") {
    67  			// 		return globWalkSearch(fsys, searchPath, searchName, f.Type)
    68  			// 	}
    69  			//
    70  			// 	return statSearch(fsys, searchPath, searchName, f.Type)
    71  			// })
    72  		}
    73  	}
    74  
    75  	// allResults, err := pool.Wait()
    76  	// if err != nil {
    77  	// 	return nil, err
    78  	// }
    79  
    80  	allResults, err := iter.MapErr(searchItems, func(item *searchItem) ([]string, error) {
    81  		// If the name contains any glob character, perform a glob match
    82  		if strings.ContainsAny(item.name, "*?[]\\^") {
    83  			return globWalkSearch(fsys, item.path, item.name, f.Type)
    84  		}
    85  
    86  		return statSearch(fsys, item.path, item.name, f.Type)
    87  	})
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	var results []string
    93  
    94  	for _, r := range allResults {
    95  		results = append(results, r...)
    96  	}
    97  
    98  	// Sort results in alphabetical order for now
    99  	// sort.Strings(results)
   100  
   101  	return results, nil
   102  }
   103  
   104  func globWalkSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
   105  	var results []string
   106  
   107  	err := afero.Walk(fsys, searchPath, func(p string, fileInfo fs.FileInfo, err error) error {
   108  		if err != nil {
   109  			return err
   110  		}
   111  
   112  		// Skip the root path
   113  		if p == searchPath {
   114  			return nil
   115  		}
   116  
   117  		var result error
   118  
   119  		// Stop reading subdirectories
   120  		// TODO: add depth detection here
   121  		if fileInfo.IsDir() && filepath.Dir(p) == searchPath {
   122  			result = fs.SkipDir
   123  		}
   124  
   125  		// Skip unmatching type
   126  		if !searchType.matchFileInfo(fileInfo) {
   127  			return result
   128  		}
   129  
   130  		match, err := filepath.Match(searchName, fileInfo.Name())
   131  		if err != nil {
   132  			return err
   133  		}
   134  
   135  		if match {
   136  			results = append(results, p)
   137  		}
   138  
   139  		return result
   140  	})
   141  	if err != nil {
   142  		return results, err
   143  	}
   144  
   145  	return results, nil
   146  }
   147  
   148  func statSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
   149  	filePath := filepath.Join(searchPath, searchName)
   150  
   151  	fileInfo, err := fsys.Stat(filePath)
   152  	if errors.Is(err, fs.ErrNotExist) {
   153  		return nil, nil
   154  	}
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	// Skip unmatching type
   160  	if !searchType.matchFileInfo(fileInfo) {
   161  		return nil, nil
   162  	}
   163  
   164  	return []string{filePath}, nil
   165  }
   166  

View as plain text