mirror of https://github.com/databricks/cli.git
135 lines
3.2 KiB
Go
135 lines
3.2 KiB
Go
package fileset
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
pathlib "path"
|
|
"path/filepath"
|
|
"slices"
|
|
|
|
"github.com/databricks/cli/libs/vfs"
|
|
)
|
|
|
|
// FileSet facilitates recursive file listing for paths rooted at a given directory.
|
|
// It optionally takes into account ignore rules through the [Ignorer] interface.
|
|
type FileSet struct {
|
|
// Root path of the fileset.
|
|
root vfs.Path
|
|
|
|
// Paths to include in the fileset.
|
|
// Files are included as-is (if not ignored) and directories are traversed recursively.
|
|
// Defaults to []string{"."} if not specified.
|
|
paths []string
|
|
|
|
// Ignorer interface to check if a file or directory should be ignored.
|
|
ignore Ignorer
|
|
}
|
|
|
|
// New returns a [FileSet] for the given root path.
|
|
// It optionally accepts a list of paths relative to the root to include in the fileset.
|
|
// If not specified, it defaults to including all files in the root path.
|
|
func New(root vfs.Path, args ...[]string) *FileSet {
|
|
// Default to including all files in the root path.
|
|
if len(args) == 0 {
|
|
args = [][]string{{"."}}
|
|
}
|
|
|
|
// Collect list of normalized and cleaned paths.
|
|
var paths []string
|
|
for _, arg := range args {
|
|
for _, path := range arg {
|
|
path = filepath.ToSlash(path)
|
|
path = pathlib.Clean(path)
|
|
|
|
// Skip path if it's already in the list.
|
|
if slices.Contains(paths, path) {
|
|
continue
|
|
}
|
|
|
|
paths = append(paths, path)
|
|
}
|
|
}
|
|
|
|
return &FileSet{
|
|
root: root,
|
|
paths: paths,
|
|
ignore: nopIgnorer{},
|
|
}
|
|
}
|
|
|
|
// Ignorer returns the [FileSet]'s current ignorer.
|
|
func (w *FileSet) Ignorer() Ignorer {
|
|
return w.ignore
|
|
}
|
|
|
|
// SetIgnorer sets the [Ignorer] interface for this [FileSet].
|
|
func (w *FileSet) SetIgnorer(ignore Ignorer) {
|
|
w.ignore = ignore
|
|
}
|
|
|
|
// Files returns performs recursive listing on all configured paths and returns
|
|
// the collection of files it finds (and are not ignored).
|
|
// The returned slice does not contain duplicates.
|
|
// The order of files in the slice is stable.
|
|
func (w *FileSet) Files() (out []File, err error) {
|
|
seen := make(map[string]struct{})
|
|
for _, p := range w.paths {
|
|
files, err := w.recursiveListFiles(p, seen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, files...)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Recursively traverses dir in a depth first manner and returns a list of all files
|
|
// that are being tracked in the FileSet (ie not being ignored for matching one of the
|
|
// patterns in w.ignore)
|
|
func (w *FileSet) recursiveListFiles(path string, seen map[string]struct{}) (out []File, err error) {
|
|
err = fs.WalkDir(w.root, path, func(name string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case info.Mode().IsDir():
|
|
ign, err := w.ignore.IgnoreDirectory(name)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot check if %s should be ignored: %w", name, err)
|
|
}
|
|
if ign {
|
|
return fs.SkipDir
|
|
}
|
|
|
|
case info.Mode().IsRegular():
|
|
ign, err := w.ignore.IgnoreFile(name)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot check if %s should be ignored: %w", name, err)
|
|
}
|
|
if ign {
|
|
return nil
|
|
}
|
|
|
|
// Skip duplicates
|
|
if _, ok := seen[name]; ok {
|
|
return nil
|
|
}
|
|
|
|
seen[name] = struct{}{}
|
|
out = append(out, NewFile(w.root, d, name))
|
|
|
|
default:
|
|
// Skip non-regular files (e.g. symlinks).
|
|
}
|
|
|
|
return nil
|
|
})
|
|
return
|
|
}
|