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..ed0f704 100644 --- a/docs/config-file.md +++ b/docs/config-file.md @@ -1,15 +1,22 @@ -# Configuration With .yamlfmt +# Configuration File ## 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.