From d490e9813bd395d2102f2563231ecf42c26efd29 Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Wed, 6 Nov 2024 12:46:42 +0000 Subject: [PATCH] fix: use PRJ_ROOT to find config Improves the tests for config file resolution. Signed-off-by: Brian McGee --- cmd/root.go | 22 +++++++-- cmd/root_test.go | 126 ++++++++++++++++++++++++++++++++++++++--------- config/config.go | 19 +++++-- 3 files changed, 136 insertions(+), 31 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 194c2701..6d5d1709 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -107,13 +107,27 @@ func runE(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, args []string) configFile = os.Getenv("TREEFMT_CONFIG") } - // find the config file if one was not specified + filenames := []string{"treefmt.toml", ".treefmt.toml"} + + // look in PRJ_ROOT if set + if prjRoot := os.Getenv("PRJ_ROOT"); configFile == "" && prjRoot != "" { + configFile, _ = config.Find(prjRoot, filenames...) + } + + // search up from the working directory if configFile == "" { - if configFile, _, err = config.FindUp(workingDir, "treefmt.toml", ".treefmt.toml"); err != nil { - return fmt.Errorf("failed to find treefmt config file: %w", err) - } + configFile, _, err = config.FindUp(workingDir, filenames...) + } + + // error out if we couldn't find the config file + if err != nil { + cmd.SilenceUsage = true + + return fmt.Errorf("failed to find treefmt config file: %w", err) } + log.Infof("using config file: %s", configFile) + // read in the config v.SetConfigFile(configFile) diff --git a/cmd/root_test.go b/cmd/root_test.go index db5c7cf9..ee588398 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -427,30 +427,93 @@ func TestIncludesAndExcludes(t *testing.T) { ) } -func TestPrjRootEnvVariable(t *testing.T) { - tempDir := test.TempExamples(t) - configPath := filepath.Join(tempDir, "treefmt.toml") +func TestConfigFile(t *testing.T) { + as := require.New(t) - t.Setenv("PRJ_ROOT", tempDir) + for _, name := range []string{"treefmt.toml", ".treefmt.toml"} { + t.Run(name, func(t *testing.T) { + tempDir := test.TempExamples(t) - treefmt(t, - withConfig(configPath, &config.Config{ - FormatterConfigs: map[string]*config.Formatter{ - "echo": { - Command: "echo", - Includes: []string{"*"}, - }, - }, - }), - withArgs("--config-file", configPath), - withNoError(t), - withStats(t, map[stats.Type]int{ - stats.Traversed: 32, - stats.Matched: 32, - stats.Formatted: 32, - stats.Changed: 0, - }), - ) + // use a config file in a different temp directory + configPath := filepath.Join(t.TempDir(), name) + + // if we don't specify a tree root, we default to the directory containing the config file + treefmt(t, + withConfig(configPath, &config.Config{ + FormatterConfigs: map[string]*config.Formatter{ + "echo": { + Command: "echo", + Includes: []string{"*"}, + }, + }, + }), + withArgs("--config-file", configPath), + withNoError(t), + withStats(t, map[stats.Type]int{ + stats.Traversed: 1, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 0, + }), + ) + + treefmt(t, + withArgs("--config-file", configPath, "--tree-root", tempDir), + withNoError(t), + withStats(t, map[stats.Type]int{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }), + ) + + // use env variable + treefmt(t, + withEnv(map[string]string{ + // TREEFMT_CONFIG takes precedence + "TREEFMT_CONFIG": configPath, + "PRJ_ROOT": tempDir, + }), + withNoError(t), + withStats(t, map[stats.Type]int{ + stats.Traversed: 1, + stats.Matched: 1, + stats.Formatted: 0, + stats.Changed: 0, + }), + ) + + // should fallback to PRJ_ROOT + treefmt(t, + withArgs("--tree-root", tempDir), + withEnv(map[string]string{ + "PRJ_ROOT": filepath.Dir(configPath), + }), + withNoError(t), + withStats(t, map[stats.Type]int{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 0, + stats.Changed: 0, + }), + ) + + // should not search upwards if using PRJ_ROOT + configSubDir := filepath.Join(filepath.Dir(configPath), "sub") + as.NoError(os.MkdirAll(configSubDir, 0o600)) + + treefmt(t, + withArgs("--tree-root", tempDir), + withEnv(map[string]string{ + "PRJ_ROOT": configSubDir, + }), + withError(func(err error) { + as.ErrorContains(err, "failed to find treefmt config file") + }), + ) + }) + } } func TestCache(t *testing.T) { @@ -1729,6 +1792,7 @@ func TestRunInSubdir(t *testing.T) { type options struct { args []string + env map[string]string config struct { path string @@ -1754,6 +1818,12 @@ func withArgs(args ...string) option { } } +func withEnv(env map[string]string) option { + return func(o *options) { + o.env = env + } +} + func withConfig(path string, cfg *config.Config) option { return func(o *options) { o.config.path = path @@ -1818,6 +1888,18 @@ func treefmt( option(opts) } + // set env + for k, v := range opts.env { + t.Setenv(k, v) + } + + defer func() { + // unset env variables after executing + for k := range opts.env { + t.Setenv(k, "") + } + }() + // default args if nil // we must pass an empty array otherwise cobra with use os.Args[1:] args := opts.args diff --git a/config/config.go b/config/config.go index 906e9030..46051eeb 100644 --- a/config/config.go +++ b/config/config.go @@ -238,13 +238,22 @@ func FromViper(v *viper.Viper) (*Config, error) { return cfg, nil } +func Find(searchDir string, fileNames ...string) (path string, err error) { + for _, f := range fileNames { + path := filepath.Join(searchDir, f) + if fileExists(path) { + return path, nil + } + } + + return "", fmt.Errorf("could not find %s in %s", fileNames, searchDir) +} + func FindUp(searchDir string, fileNames ...string) (path string, dir string, err error) { for _, dir := range eachDir(searchDir) { - for _, f := range fileNames { - path := filepath.Join(dir, f) - if fileExists(path) { - return path, dir, nil - } + path, err := Find(dir, fileNames...) + if err == nil { + return path, dir, nil } }