mirror of
https://github.com/NotAShelf/batmon.git
synced 2024-11-27 07:36:47 +00:00
166 lines
3.9 KiB
Go
166 lines
3.9 KiB
Go
|
// Package finder looks for files and directories in an {fs.Fs} filesystem.
|
||
|
package locafero
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"io/fs"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/sourcegraph/conc/iter"
|
||
|
"github.com/spf13/afero"
|
||
|
)
|
||
|
|
||
|
// Finder looks for files and directories in an [afero.Fs] filesystem.
|
||
|
type Finder struct {
|
||
|
// Paths represents a list of locations that the [Finder] will search in.
|
||
|
//
|
||
|
// They are essentially the root directories or starting points for the search.
|
||
|
//
|
||
|
// Examples:
|
||
|
// - home/user
|
||
|
// - etc
|
||
|
Paths []string
|
||
|
|
||
|
// Names are specific entries that the [Finder] will look for within the given Paths.
|
||
|
//
|
||
|
// It provides the capability to search for entries with depth,
|
||
|
// meaning it can target deeper locations within the directory structure.
|
||
|
//
|
||
|
// It also supports glob syntax (as defined by [filepat.Match]), offering greater flexibility in search patterns.
|
||
|
//
|
||
|
// Examples:
|
||
|
// - config.yaml
|
||
|
// - home/*/config.yaml
|
||
|
// - home/*/config.*
|
||
|
Names []string
|
||
|
|
||
|
// Type restricts the kind of entries returned by the [Finder].
|
||
|
//
|
||
|
// This parameter helps in differentiating and filtering out files from directories or vice versa.
|
||
|
Type FileType
|
||
|
}
|
||
|
|
||
|
// Find looks for files and directories in an [afero.Fs] filesystem.
|
||
|
func (f Finder) Find(fsys afero.Fs) ([]string, error) {
|
||
|
// Arbitrary go routine limit (TODO: make this a parameter)
|
||
|
// pool := pool.NewWithResults[[]string]().WithMaxGoroutines(5).WithErrors().WithFirstError()
|
||
|
|
||
|
type searchItem struct {
|
||
|
path string
|
||
|
name string
|
||
|
}
|
||
|
|
||
|
var searchItems []searchItem
|
||
|
|
||
|
for _, searchPath := range f.Paths {
|
||
|
searchPath := searchPath
|
||
|
|
||
|
for _, searchName := range f.Names {
|
||
|
searchName := searchName
|
||
|
|
||
|
searchItems = append(searchItems, searchItem{searchPath, searchName})
|
||
|
|
||
|
// pool.Go(func() ([]string, error) {
|
||
|
// // If the name contains any glob character, perform a glob match
|
||
|
// if strings.ContainsAny(searchName, "*?[]\\^") {
|
||
|
// return globWalkSearch(fsys, searchPath, searchName, f.Type)
|
||
|
// }
|
||
|
//
|
||
|
// return statSearch(fsys, searchPath, searchName, f.Type)
|
||
|
// })
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// allResults, err := pool.Wait()
|
||
|
// if err != nil {
|
||
|
// return nil, err
|
||
|
// }
|
||
|
|
||
|
allResults, err := iter.MapErr(searchItems, func(item *searchItem) ([]string, error) {
|
||
|
// If the name contains any glob character, perform a glob match
|
||
|
if strings.ContainsAny(item.name, "*?[]\\^") {
|
||
|
return globWalkSearch(fsys, item.path, item.name, f.Type)
|
||
|
}
|
||
|
|
||
|
return statSearch(fsys, item.path, item.name, f.Type)
|
||
|
})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var results []string
|
||
|
|
||
|
for _, r := range allResults {
|
||
|
results = append(results, r...)
|
||
|
}
|
||
|
|
||
|
// Sort results in alphabetical order for now
|
||
|
// sort.Strings(results)
|
||
|
|
||
|
return results, nil
|
||
|
}
|
||
|
|
||
|
func globWalkSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
|
||
|
var results []string
|
||
|
|
||
|
err := afero.Walk(fsys, searchPath, func(p string, fileInfo fs.FileInfo, err error) error {
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Skip the root path
|
||
|
if p == searchPath {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var result error
|
||
|
|
||
|
// Stop reading subdirectories
|
||
|
// TODO: add depth detection here
|
||
|
if fileInfo.IsDir() && filepath.Dir(p) == searchPath {
|
||
|
result = fs.SkipDir
|
||
|
}
|
||
|
|
||
|
// Skip unmatching type
|
||
|
if !searchType.matchFileInfo(fileInfo) {
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
match, err := filepath.Match(searchName, fileInfo.Name())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if match {
|
||
|
results = append(results, p)
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
})
|
||
|
if err != nil {
|
||
|
return results, err
|
||
|
}
|
||
|
|
||
|
return results, nil
|
||
|
}
|
||
|
|
||
|
func statSearch(fsys afero.Fs, searchPath string, searchName string, searchType FileType) ([]string, error) {
|
||
|
filePath := filepath.Join(searchPath, searchName)
|
||
|
|
||
|
fileInfo, err := fsys.Stat(filePath)
|
||
|
if errors.Is(err, fs.ErrNotExist) {
|
||
|
return nil, nil
|
||
|
}
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Skip unmatching type
|
||
|
if !searchType.matchFileInfo(fileInfo) {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
return []string{filePath}, nil
|
||
|
}
|