predictable-yaml can lint (complain) about the order of the keys in YAML files. It can also fix them.
This is alpha, but ready for use, especially linting.
Several experimental features are enabled by default in the fixer. If robust reordering without human inspection of the results is desired, consider disabling the experiments.
- Robust reordering of keys.
- Always produces valid YAML.
- Removes empty lines, can change indentation for comments in undesired ways.
- Always sets the
-in lists indented from the parent node, never even with it. - Reinserts a
---document marker at beginning of file if it was there before reordering.
Experimental features attempt to make less of a diff after reordering. We welcome issues and contributions!
- Reduce list indentation
- For those who prefer the
-in lists even with the parent node. - Has potential to produce invalid YAML. Hopefully rarely, please submit an issue.
- May act slightly differently when combined or not with other experimental features.
- For those who prefer the
- Preserve Empty Lines
- Currently, associates empty lines to the YAML key following them. This is not perfect in every possible circumstance.
- Reinserts empty line(s) at end of file if they were there before reordering.
- Preserve Comments
- The inline comment fixing should be pretty robust.
- Replaces all comments and lead spaces with the original versions.
- May not work well when the fixer also changes the resulting indentation from the original. (Set
--indentation-leveland--reduce-list-indentation-bycorrectly first.)
# Run from local checkout
nix run .#predictable-yaml -- lint --help
# Run from GitHub
nix run github:snarlysodboxer/predictable-yaml -- lint --help
# Install to profile
nix profile install github:snarlysodboxer/predictable-yaml
# Development shell (includes Go, gopls, delve)
nix develop
# Build locally
nix build
./result/bin/predictable-yaml --helpDownload the latest release for your system and then move it in place.
mv ~/Downloads/predictable-yaml-darwin-amd64 /usr/local/bin/predictable-yaml
chmod ug+x /usr/local/bin/predictable-yaml
# on MacOS
sudo xattr -r -d com.apple.quarantine /usr/local/bin/predictable-yamlClone the repo and go install.
go run main.go --helpORgo build main.go -o predictable-yaml./predictable-yaml --helpORgo installpredictable-yaml --help- Setup a
.predictable-yamlconfig dir- Use your home directory, or a directory in a repo. See Config Files for more info.
mkdir ~/.predictable-yaml && cp ./example-configs/* ~/.predictable-yaml/
- Use the example configs in this repo:
predictable-yaml lint --config-dir example-configs test-data
- Search my-dir for yaml files, suppress success messages:
predictable-yaml lint --quiet my-dir
- Run with Docker:
docker run -it --rm -v $(pwd):/code -w /code snarlysodboxer/predictable-yaml:latest lint $(find my-dir -name '*.yaml')
- Use the example configs in this repo:
predictable-yaml fix --config-dir example-configs $(find test-data -name '*\.yaml')
- Disable all experimental features:
predictable-yaml fix -d test-data
- Use four spaces and more deeply indented lists:
predictable-yaml fix --indentation-level 4 --reduce-list-indentation-by 0 test-data
- Only prompt if the line count changes:
predictable-yaml fix --prompt-if-line-count-change test-data
- Run with Docker:
docker run -it --rm -v $(pwd):/code -w /code snarlysodboxer/predictable-yaml:latest fix test-data
- Uses one config schema file per target file schema. E.G. One config file for a Kubernetes
Deploymentis used to lint any Deployment target files.- Can be used with any YAML schema, not just Kubernetes ones.
- Indentation does not have to match between the config file and target files. Use another linter for that.
- Algorithm
- Lines in the target file that exist in the config file must be in the same order as the config file, but there can be any amount of lines inbetween them.
- Lines can be configured to be first, required, or to 'ditto' the configs under another line.
- Pass a directory path to search it recursively for yaml files, or file paths to just test those files, or any combination of the two.
- Supports multiple config schema files. Place them in the config directory.
- Config type can be configured with the comment
# predictable-yaml: kind=my-schema, but if this is not found, we'll attempt to get it from the Kubernetes-esqkind: my-schema, value. If neither are found, an error will be thrown. - Target file type will be determined in the same way, however if a matching config is not found, a warning will be output.
- Config type can be configured with the comment
- If the
--config-dirflag is specified, configs from only that directory will be loaded. - If the
--config-dirflag is not specified:- Search up the directory tree looking for
.predictable-yamldirectories in parent directories, loading configs for any schema types it does not already know about. - Search down the directory tree looking for
.predictable-yamldirectories in sub directories matching the given file paths, overriding configs for any files whose path match.- For example, when running
predictable-yaml lint my-dir, any configs found inmy-dir/my-subdir/.predictable-yamlwill override any found in parent dirs, for themy-dir/my-subdir/deployment.yamlfile, but not for themy-dir/other/deployment.yamlfile.
- For example, when running
- Search up the directory tree looking for
- Config files must:
- Not have comments other than the configuration ones specific to this program.
- Not have more than one entry in each sequence.
- The first entry in a sequence in the config will be used for each entry in the target file.
- Not have null nodes, and node types must match what's expected in the target file.
- Good:
initContainers: [] # ditto=.spec.containers containers: [] # required
- Not good:
initContainers: # ditto=.spec.containers containers: [] # required
- Good:
- Set
# firstto throw errors if a key is not first. - Set
# requiredto throw errors if a key is not found. - Set
# ditto=[kind].<path>to setup a key and sub-keys with the same configs as another key and sub-keys.- Point to a key in the same config file by starting with a
., like:ditto=.spec.template.spec.containers. - Point to a key in a different config file by starting with a schema type, then the path to the node, like:
ditto=Pod.spec.
- Point to a key in the same config file by starting with a
- Set any combination of these like:
# first, required, ditto=Pod.spec. - See example-configs for examples.
Config comments in target files must be before, inline, or after the first line of yaml (not just at the top of the file.)
- File type can be configured or overridden with the comment
# predictable-yaml: kind=my-schema. - A file can be skipped with the comment
# predictable-yaml: ignore. - A required lines can be ignored with the comment
# predictable-yaml: ignore-requireds. - These can be combined:
# predictable-yaml: kind=my-schema, ignore-requireds.
- Caviats
- The fixer has three experimental features enabled by default, list de-indentation, preservation of empty lines, and preservation of spaces before comments. Disable them if you get unexpected results.
- Whitespace preservation is not a perfect system as it involves guessing at what the original writer intended by am empty line. Disable with
--preserve-empty-lines=false - Support for fixing is limited by Golang yaml.v3's ability to record and reproduce comments.
- It is possible to loose comments altogether with the fixer, if the comment is not a header, footer, or line comment. (Empty lines around the comment define these.)
- Recommend starting with a clean Git tree so changes can easily be undone if the results are undesired. (Or use the diff to re-add any dropped comments, which will hopefully be rare-ish.)
- Prompting and not prompting for confirmation are both supported, as well as only prompting if the number of lines in the file would change.
- By default, yaml nodes in the target file that are not in the config file will be moved to the end of the map they're in.
- Change to the begining with the flag
--unmatched-to-beginning.
- Change to the begining with the flag
- Missing required keys will be added unless
ignore-requiredsis set as a per file override. - Lines can be set to
# preferredto allow linting to pass without them, but make the fixer add them when fixing, and the--add-preferredflag is set.
- Run with
go test ./....
dlv test pkg/compare/compare* -- -test.run TestWalkAndCompare