cli: search for the tree root by default

Restore the treefmt 1.x behaviour where it would search for the tree
root by recursively searching for the treefmt.toml file up the
filesystem, starting from the current directory.

The `--tree-root-file` option will be useful to remove this bash wrapper: 2fba33a182/module-options.nix (L116-L135)

Fixes #308
This commit is contained in:
zimbatm 2024-05-30 15:43:26 +02:00
parent 022398399f
commit bd32d36a33
2 changed files with 90 additions and 2 deletions

View File

@ -13,10 +13,11 @@ type Format struct {
WorkingDirectory kong.ChangeDirFlag `default:"." short:"C" help:"Run as if treefmt was started in the specified working directory instead of the current working directory."`
NoCache bool `help:"Ignore the evaluation cache entirely. Useful for CI."`
ClearCache bool `short:"c" help:"Reset the evaluation cache. Use in case the cache is not precise enough."`
ConfigFile string `type:"existingfile" default:"./treefmt.toml" help:"The config file to use."`
ConfigFile string `type:"path" help:"Load the config file from the given path (defaults to finding treefmt.toml up)."`
FailOnChange bool `help:"Exit with error if any changes were made. Useful for CI."`
Formatters []string `short:"f" help:"Specify formatters to apply. Defaults to all formatters."`
TreeRoot string `type:"existingdir" default:"." help:"The root directory from which treefmt will start walking the filesystem."`
TreeRoot string `type:"path" help:"The root directory from which treefmt will start walking the filesystem (defaults to the directory containing the config file)."`
TreeRootFile string `type:"path" help:"File to search for to find the project root (if --tree-root is not passed)."`
Walk walk.Type `enum:"auto,git,filesystem" default:"auto" help:"The method used to traverse the files within --tree-root. Currently supports 'auto', 'git' or 'filesystem'."`
Verbosity int `name:"verbose" short:"v" type:"counter" default:"0" env:"LOG_LEVEL" help:"Set the verbosity of logs e.g. -vv."`
Version bool `name:"version" short:"V" help:"Print version."`

View File

@ -69,6 +69,40 @@ func (f *Format) Run() (err error) {
}
}()
// find the config file unless specified
if Cli.ConfigFile == "" {
pwd, err := os.Getwd()
if err != nil {
return err
}
Cli.ConfigFile, _, err = findUp(pwd, "treefmt.toml")
if err != nil {
return err
}
}
// search for the project root unless specified
if Cli.TreeRoot == "" {
// use the location of the treefmt.toml file by default
dir := filepath.Dir(Cli.ConfigFile)
// search using the --tree-root-file if specified
if Cli.TreeRootFile != "" {
pwd, err := os.Getwd()
if err != nil {
return err
}
_, dir, err = findUp(pwd, Cli.TreeRootFile)
if err != nil {
return err
}
}
Cli.TreeRoot = dir
}
log.Debugf("config-file=%s tree-root=%s", Cli.ConfigFile, Cli.TreeRoot)
// read config
cfg, err := config.ReadFile(Cli.ConfigFile, Cli.Formatters)
if err != nil {
@ -384,3 +418,56 @@ func applyFormatters(ctx context.Context) func() error {
return nil
}
}
func findUp(searchDir string, fileName string) (path string, dir string, err error) {
for _, dir := range eachDir(searchDir) {
path := filepath.Join(dir, fileName)
if fileExists(path) {
return path, dir, nil
}
}
return "", "", fmt.Errorf("could not find %s in %s", fileName, searchDir)
}
func eachDir(path string) (paths []string) {
path, err := filepath.Abs(path)
if err != nil {
return
}
paths = []string{path}
if path == "/" {
return
}
for i := len(path) - 1; i >= 0; i-- {
if path[i] == os.PathSeparator {
path = path[:i]
if path == "" {
path = "/"
}
paths = append(paths, path)
}
}
return
}
func fileExists(path string) bool {
// Some broken filesystems like SSHFS return file information on stat() but
// then cannot open the file. So we use os.Open.
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
// Next, check that the file is a regular file.
fi, err := f.Stat()
if err != nil {
return false
}
return fi.Mode().IsRegular()
}