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

New config discovery procedure #156

Merged
merged 2 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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 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.
Expand Down
Loading