1
0
mirror of https://github.com/go-task/task.git synced 2025-11-23 22:24:45 +02:00
Files
task/internal/fsext/fs.go
Pete Davison f89c12ddf0 feat: XDG taskrc config (#2380)
Co-authored-by: Valentin Maerten <maerten.valentin@gmail.com>
2025-08-18 22:43:36 +02:00

203 lines
6.7 KiB
Go

package fsext
import (
"os"
"path/filepath"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/sysinfo"
)
// DefaultDir will return the default directory given an entrypoint or
// directory. If the directory is set, it will ensure it is an absolute path and
// return it. If the entrypoint is set, but the directory is not, it will leave
// the directory blank. If both are empty, it will default the directory to the
// current working directory.
func DefaultDir(entrypoint, dir string) string {
// If the directory is set, ensure it is an absolute path
if dir != "" {
var err error
dir, err = filepath.Abs(dir)
if err != nil {
return ""
}
return dir
}
// If the entrypoint and dir are empty, we default the directory to the current working directory
if entrypoint == "" {
wd, err := os.Getwd()
if err != nil {
return ""
}
return wd
}
// If the entrypoint is set, but the directory is not, we leave the directory blank
return ""
}
// ResolveDir returns an absolute path to the directory that the task should be
// run in. If the entrypoint and dir are BOTH set, then the Taskfile will not
// sit inside the directory specified by dir and we should ensure that the dir
// is absolute. Otherwise, the dir will always be the parent directory of the
// resolved entrypoint, so we should return that parent directory.
func ResolveDir(entrypoint, resolvedEntrypoint, dir string) (string, error) {
if entrypoint != "" && dir != "" {
return filepath.Abs(dir)
}
return filepath.Dir(resolvedEntrypoint), nil
}
// Search looks for files with the given possible filenames using the given
// entrypoint and directory. If the entrypoint is set, it checks if the
// entrypoint matches a file or if it matches a directory containing one of the
// possible filenames. Otherwise, it walks up the file tree starting at the
// given directory and performs a search in each directory for the possible
// filenames until it finds a match or reaches the root directory. If the
// entrypoint and directory are both empty, it defaults the directory to the
// current working directory and performs a recursive search starting there. If
// a match is found, the absolute path to the file is returned with its
// directory. If no match is found, an error is returned.
func Search(entrypoint, dir string, possibleFilenames []string) (string, error) {
var err error
if entrypoint != "" {
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
if err != nil {
return "", err
}
return entrypoint, nil
}
if dir == "" {
dir, err = os.Getwd()
if err != nil {
return "", err
}
}
entrypoint, err = SearchPathRecursively(dir, possibleFilenames)
if err != nil {
return "", err
}
return entrypoint, nil
}
// SearchAll looks for files with the given possible filenames using the given
// entrypoint and directory. If the entrypoint is set, it checks if the
// entrypoint matches a file or if it matches a directory containing one of the
// possible filenames and add it to a list of matches. It then walks up the file
// tree starting at the given directory and performs a search in each directory
// for the possible filenames until it finds a match or reaches the root
// directory. If the entrypoint and directory are both empty, it defaults the
// directory to the current working directory and performs a recursive search
// starting there. If matches are found, the absolute path to each file is added
// to the list and returned.
func SearchAll(entrypoint, dir string, possibleFilenames []string) ([]string, error) {
var err error
var entrypoints []string
if entrypoint != "" {
entrypoint, err = SearchPath(entrypoint, possibleFilenames)
if err != nil {
return nil, err
}
entrypoints = append(entrypoints, entrypoint)
}
if dir == "" {
dir, err = os.Getwd()
if err != nil {
return nil, err
}
}
paths, err := SearchNPathRecursively(dir, possibleFilenames, -1)
if err != nil {
return nil, err
}
return append(entrypoints, paths...), nil
}
// SearchPath will check if a file at the given path exists or not. If it does,
// it will return the path to it. If it does not, it will search for any files
// at the given path with any of the given possible names. If any of these match
// a file, the first matching path will be returned. If no files are found, an
// error will be returned.
func SearchPath(path string, possibleFilenames []string) (string, error) {
// Get file info about the path
fi, err := os.Stat(path)
if err != nil {
return "", err
}
// If the path exists and is a regular file, device, symlink, or named pipe,
// return the absolute path to it
if fi.Mode().IsRegular() ||
fi.Mode()&os.ModeDevice != 0 ||
fi.Mode()&os.ModeSymlink != 0 ||
fi.Mode()&os.ModeNamedPipe != 0 {
return filepath.Abs(path)
}
// If the path is a directory, check if any of the possible names exist
// in that directory
for _, filename := range possibleFilenames {
alt := filepathext.SmartJoin(path, filename)
if _, err := os.Stat(alt); err == nil {
return filepath.Abs(alt)
}
}
return "", os.ErrNotExist
}
// SearchPathRecursively walks up the directory tree starting at the given
// path, calling the Search function in each directory until it finds a matching
// file or reaches the root directory. On supported operating systems, it will
// also check if the user ID of the directory changes and abort if it does.
func SearchPathRecursively(path string, possibleFilenames []string) (string, error) {
paths, err := SearchNPathRecursively(path, possibleFilenames, 1)
if err != nil {
return "", err
}
if len(paths) == 0 {
return "", os.ErrNotExist
}
return paths[0], nil
}
// SearchNPathRecursively walks up the directory tree starting at the given
// path, calling the Search function in each directory and adding each matching
// file that it finds to a list until it reaches the root directory or the
// length of the list exceeds n. On supported operating systems, it will also
// check if the user ID of the directory changes and abort if it does.
func SearchNPathRecursively(path string, possibleFilenames []string, n int) ([]string, error) {
var paths []string
owner, err := sysinfo.Owner(path)
if err != nil {
return nil, err
}
for n == -1 || len(paths) < n {
fpath, err := SearchPath(path, possibleFilenames)
if err == nil {
paths = append(paths, fpath)
}
// Get the parent path/user id
parentPath := filepath.Dir(path)
parentOwner, err := sysinfo.Owner(parentPath)
if err != nil {
return nil, err
}
// Error if we reached the root directory and still haven't found a file
// OR if the user id of the directory changes
if path == parentPath || (parentOwner != owner) {
return paths, nil
}
owner = parentOwner
path = parentPath
}
return paths, nil
}