Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: search for the tree root by default #309

Merged
merged 7 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)."`
zimbatm marked this conversation as resolved.
Show resolved Hide resolved
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)."`
zimbatm marked this conversation as resolved.
Show resolved Hide resolved
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."`
Expand Down
87 changes: 87 additions & 0 deletions cli/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
zimbatm marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this handle symlinks? Would it follow a directory symlink that might "escape" into a completely different directory tree?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eachDir is only decomposing the /path/ string, removing one component after the other. I don't like to follow symlinks because it can lead to infinite loops and other weirdnesses.

fileExists will return true if the file is readable. It can be a symlink to another file, that's fine.

}
}
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()
}