From c14a9985221951f59b556a9a5e0919377f27be9c Mon Sep 17 00:00:00 2001 From: braydonk Date: Sat, 3 Feb 2024 18:29:07 +0000 Subject: [PATCH 1/2] New config discovery procedure Config discovery is changing, but it should be non-breaking. * The search for a config now starts at the root directory and then searches up the tree to finding the lowest level one. This should help in larger monorepo setups when you want different configs in different places, or to fall back to some default higher in the tree. I am not 100% sure whether this will break some workflows but it will be pretty easy to either change back or provide a short-circuit if necessary. * Added a new flag `-global_conf` that will force the usage of the config from `XDG_CONFIG_HOME`/`LOCALAPPDATA` * The yamlfmt config can now be in `yamlfmt.yaml` or `yamlfmt.yml` instead of just `.yamlfmt` --- cmd/yamlfmt/config.go | 66 ++++++++++++++++++++++++++++++++----------- cmd/yamlfmt/flags.go | 14 +++++++-- cmd/yamlfmt/main.go | 2 +- docs/command-usage.md | 3 +- docs/config-file.md | 17 +++++++---- 5 files changed, 76 insertions(+), 26 deletions(-) diff --git a/cmd/yamlfmt/config.go b/cmd/yamlfmt/config.go index 07b722f..4ba279a 100644 --- a/cmd/yamlfmt/config.go +++ b/cmd/yamlfmt/config.go @@ -17,10 +17,13 @@ import ( "github.com/mitchellh/mapstructure" ) -const ( - configFileName string = ".yamlfmt" - configHomeDir string = "yamlfmt" -) +var configFileNames = collections.Set[string]{ + ".yamlfmt": {}, + "yamlfmt.yml": {}, + "yamlfmt.yaml": {}, +} + +const configHomeDir string = "yamlfmt" var ( errNoConfFlag = errors.New("config path not specified in --conf") @@ -80,17 +83,17 @@ func getConfigPath() (string, error) { } // Second priority: in the working directory - configPath, err = getConfigPathFromWd() - // In this scenario, no error constitutes a failure state, - // so we continue to the next fallback. + configPath, err = getConfigPathFromDirTree() + // In this scenario, no errors are considered a failure state, + // so we continue to the next fallback if there are no errors. if err == nil { return configPath, nil } // Third priority: in home config directory configPath, err = getConfigPathFromConfigHome() - // In this scenario, no error constitutes a failure state, - // so we continue to the next fallback. + // In this scenario, no errors are considered a failure state, + // so we continue to the next fallback if there are no errors. if err == nil { return configPath, nil } @@ -101,20 +104,49 @@ func getConfigPath() (string, error) { } func getConfigPathFromFlag() (string, error) { + // If there is a path specified in the conf flag, that takes precedence configPath := *flagConf if configPath == "" { return configPath, errNoConfFlag } - return configPath, validatePath(configPath) + // Then we check if we want the global config + if *flagGlobalConf { + return getConfigPathFromXdgConfigHome() + } + + return "", validatePath(configPath) } -func getConfigPathFromWd() (string, error) { +// This function searches up the directory tree until it finds +// a config file. +func getConfigPathFromDirTree() (string, error) { wd, err := os.Getwd() if err != nil { return "", err } - configPath := filepath.Join(wd, configFileName) - return configPath, validatePath(configPath) + absPath, err := filepath.Abs(wd) + if err != nil { + return "", err + } + dir := absPath + for dir != filepath.Dir(dir) { + configPath, err := getConfigPathFromDir(dir) + if err == nil { + return configPath, nil + } + dir = filepath.Dir(dir) + } + return "", errConfPathNotExist +} + +func getConfigPathFromDir(dir string) (string, error) { + for filename := range configFileNames { + configPath := filepath.Join(dir, filename) + if err := validatePath(configPath); err == nil { + return configPath, nil + } + } + return "", errConfPathNotExist } func getConfigPathFromConfigHome() (string, error) { @@ -137,8 +169,8 @@ func getConfigPathFromXdgConfigHome() (string, error) { } configHome = filepath.Join(home, ".config") } - homeConfigPath := filepath.Join(configHome, configHomeDir, configFileName) - return homeConfigPath, validatePath(homeConfigPath) + homeConfigPath := filepath.Join(configHome, configHomeDir) + return getConfigPathFromDir(homeConfigPath) } func getConfigPathFromAppDataLocal() (string, error) { @@ -148,8 +180,8 @@ func getConfigPathFromAppDataLocal() (string, error) { // so this should only happen to sickos with broken setups. return "", errNoConfigHome } - homeConfigPath := filepath.Join(configHome, configHomeDir, configFileName) - return homeConfigPath, validatePath(homeConfigPath) + homeConfigPath := filepath.Join(configHome, configHomeDir) + return getConfigPathFromDir(homeConfigPath) } func validatePath(path string) error { diff --git a/cmd/yamlfmt/flags.go b/cmd/yamlfmt/flags.go index cb7aac6..dda3647 100644 --- a/cmd/yamlfmt/flags.go +++ b/cmd/yamlfmt/flags.go @@ -17,6 +17,7 @@ package main import ( "flag" "fmt" + "runtime" "strings" "github.com/google/yamlfmt/command" @@ -30,6 +31,7 @@ operation without performing it.`) flagIn *bool = flag.Bool("in", false, "Format yaml read from stdin and output to stdout") flagVersion *bool = flag.Bool("version", false, "Print yamlfmt version") flagConf *string = flag.String("conf", "", "Read yamlfmt config from this path") + flagGlobalConf *bool = flag.Bool("global_conf", false, globalConfFlagMessage()) flagDoublestar *bool = flag.Bool("dstar", false, "Use doublestar globs for include and exclude") flagQuiet *bool = flag.Bool("quiet", false, "Print minimal output to stdout") flagContinueOnError *bool = flag.Bool("continue_on_error", false, "Continue to format files that didn't fail instead of exiting with code 1.") @@ -67,12 +69,12 @@ func configureHelp() { Arguments: Glob paths to yaml files - Send any number of paths to yaml files specified in doublestar glob format (see: https://github.com/bmatcuk/doublestar). + Send any number of paths to yaml files specified in doublestar glob format (see: https://github.com/bmatcuk/doublestar). Any flags must be specified before the paths. - or /dev/stdin Passing in a single - or /dev/stdin will read the yaml from stdin and output the formatted result to stdout - + Flags:`) flag.PrintDefaults() } @@ -98,3 +100,11 @@ func isStdinArg() bool { arg := flag.Args()[0] return arg == "-" || arg == "/dev/stdin" } + +func globalConfFlagMessage() string { + varName := "XDG_CONFIG_HOME" + if runtime.GOOS == "windows" { + varName = "LOCALAPPDATA" + } + return fmt.Sprintf("Use global yamlfmt config from %s", varName) +} diff --git a/cmd/yamlfmt/main.go b/cmd/yamlfmt/main.go index e2ba177..877049b 100644 --- a/cmd/yamlfmt/main.go +++ b/cmd/yamlfmt/main.go @@ -24,7 +24,7 @@ import ( "github.com/google/yamlfmt/formatters/basic" ) -var version string = "0.10.0" +var version string = "0.11.0" func main() { if err := run(); err != nil { diff --git a/docs/command-usage.md b/docs/command-usage.md index 9611e9d..f6c3354 100644 --- a/docs/command-usage.md +++ b/docs/command-usage.md @@ -77,9 +77,10 @@ The string array flags can be a bit confusing. See the [String Array Flags](#str | Name | Flag | Type | Example | Description | |:-----------------|:--------------|:---------|:----------------------------------------------------------|:------------| | Config File Path | `-conf` | string | `yamlfmt -conf ./config/.yamlfmt` | Specify a path to read a [configuration file](./config-file.md) from. | +| Global Config | `-global_conf`| bool | `yamlfmt -global_conf ./config/.yamlfmt` | Force yamlfmt to use the configuration file from the system config directory. | | Doublstar | `-dstar` | boolean | `yamlfmt -dstar "**/*.yaml"` | Enable [Doublstar](./paths.md#doublestar) path collection mode. | | Exclude | `-exclude` | []string | `yamlfmt -exclude ./not/,these_paths.yaml` | Patterns to exclude from path collection. These add to exclude patterns specified in the [config file](./config-file.md) | -| Extensions | `-extensions` | []string | `yamlfmt -extensions yaml,yml` | Extensions to use in standard path collection. Has no effect in Doublestar mode. These add to extensions specified in the [config file](./config-file.md) +| Extensions | `-extensions` | []string | `yamlfmt -extensions yaml,yml` | Extensions to use in standard path collection. Has no effect in Doublestar mode. These add to extensions specified in the [config file](./config-file.md) | Formatter Config | `-formatter` | []string | `yamlfmt -formatter indent=2,include_document_start=true` | Provide configuration values for the formatter. See [Formatter Configuration Options](./config-file.md#basic-formatter) for options. Each field is specified as `configkey=value`. | #### String Array Flags diff --git a/docs/config-file.md b/docs/config-file.md index c5acd77..0d609ba 100644 --- a/docs/config-file.md +++ b/docs/config-file.md @@ -1,15 +1,22 @@ -# Configuration With .yamlfmt +# Configuration With .yamlfmt, yamlfmt.yaml, and yamlfmt.yml ## Config File Discovery -The config file is discovered in the following priority order: +The config file is a file named `.yamlfmt`, `yamlfmt.yaml`, or `yamlfmt.yml` that contains a valid yamlfmt configuration. The config file is discovered in the following priority order: -1. Specified in the `--conf` flag (if this is an invalid path or doesn't exist, the tool will fail) -2. A `.yamlfmt` file in the current working directory -3. A `yamlfmt` folder with a `.yamlfmt` file in the system config directory (`$XDG_CONFIG_HOME`, `$HOME/.config`, `%LOCALAPPDATA%`) e.g. `$HOME/.config/yamlfmt/.yamlfmt` +1. Specified in the `-conf` flag (if this is an invalid path or doesn't exist, the tool will fail) +1. A config file in the current working directory +1. The first config file found up the tree step by step from the current working directory +1. A `yamlfmt` folder with a config file in the system config directory (`$XDG_CONFIG_HOME`, `$HOME/.config`, `%LOCALAPPDATA%`) e.g. `$HOME/.config/yamlfmt/.yamlfmt` If none of these are found, the tool's default configuration will be used. +### Config File Discovery Caveats + +If the flag `-global_conf` is passed, all other steps will be circumvented and the config file will be discovered from the system config directory. See [the command line flag docs](./command-usage.md#configuration-flags). + +In the `-conf` flag, the config file will be named anything. As long as it's valid yaml, yamlfmt will read it as a config file. This can be useful for applying unique configs to different directories in a project. The automatic discovery paths do need to use one of the known names. + ## Command The command package defines the main command engine that `cmd/yamlfmt` uses. It uses the top level configuration that any run of the yamlfmt command will use. From 84edcb8711dae23c6e890faf5fed4a03d137d796 Mon Sep 17 00:00:00 2001 From: braydonk Date: Sat, 3 Feb 2024 18:57:53 +0000 Subject: [PATCH 2/2] I like this doc title better --- docs/config-file.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config-file.md b/docs/config-file.md index 0d609ba..ed0f704 100644 --- a/docs/config-file.md +++ b/docs/config-file.md @@ -1,4 +1,4 @@ -# Configuration With .yamlfmt, yamlfmt.yaml, and yamlfmt.yml +# Configuration File ## Config File Discovery