Skip to content

Commit

Permalink
New config discovery procedure
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
braydonk committed Feb 3, 2024
1 parent 176fffc commit c14a998
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 26 deletions.
66 changes: 49 additions & 17 deletions cmd/yamlfmt/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down
14 changes: 12 additions & 2 deletions cmd/yamlfmt/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package main
import (
"flag"
"fmt"
"runtime"
"strings"

"github.com/google/yamlfmt/command"
Expand All @@ -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.")
Expand Down Expand Up @@ -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()
}
Expand All @@ -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)
}
2 changes: 1 addition & 1 deletion cmd/yamlfmt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion docs/command-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 12 additions & 5 deletions docs/config-file.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down

0 comments on commit c14a998

Please sign in to comment.