Precious - One Code Quality Tool to Rule Them All
Who doesn't love linters and tidiers? I sure love them. I love them so much that in many of my projects I might easily have five or ten of them enabled!
Wouldn't it be great if you could run all of them with just one command? Wouldn't it be great if that command just had one config file to define what tools to run on each part of your project? Wouldn't it be great if Sauron were our ruler?
Now with Precious you can say "yes" to all of those questions.
In all seriousness, managing code quality tools can be a bit of a pain. It becomes much more painful when you have a multi-language project. You may have multiple tools per language, each of which runs on some subset of your codebase. Then you need to hook these tools into your commit hooks and CI system.
With Precious you can configure all of your code quality tool rules in one
place and easily run
precious from your commit hooks and in CI.
There are several ways to install this tool.
The easiest way to install it is to grab a binary release from the releases page. Simply put this somewhere in your path and you're good to go.
You can also install this via
cargo by running
cargo install precious. See
for the rules on where the binary is installed.
Precious is configured via a single
precious.toml file that lives in your
project root. The file is in TOML format.
There is just one key that can be set in the top level table of the config file:
||array of strings||no||Each array member is a pattern that will be matched against potential files when
All other configuration is on a per-filter basis. A filter is something that either tidies (aka pretty prints or beautifies) or lints your code (or both). Currently all filters are defined as commands, external programs which precious will execute as needed.
Each filter should be defined in a block named something like
[commands.filter-name]. Each name after the
commands. prefix must be
unique. Note that you can have multiple filters defined for the same
executable as long as each one has a unique name.
The keys that are allowed for each command are as follows:
||strings||yes||all||This must be either
||array of strings||yes||all||Each array member is a gitignore file style pattern that tells
||array of strings||no||all||Each array member is a gitignore file style pattern that tells
||array of strings||yes||all||This is the executable to be run followed by any arguments that should always be passed.|
||array of strings||no||combined linter & tidier||If a command is both a linter and tidier than it may take extra flags to operate in linting mode. This is how you set that flag.|
||array of strings||no||combined linter & tidier||If a command is both a linter and tidier than it may take extra flags to operate in tidying mode. This is how you set that flag.|
||boolean||no||all||false||If this is true, then the command is run once per matched directory rather than per file.|
||boolean||no||all||false||If this is true, then the command is only run once from the root directory no matter how many files match.|
||boolean||no||all||false||If this is true, then the command will be run with a chdir to the relevant path. If the command operates on files,
||array of integers||yes||all||Any exit code that does not indicate an abnormal exit should be here. For most commands this is just
||array of integers||no||linters||If the command is a linter then these are the status codes that indicate a lint failure. These need to be specified so
Referencing the Project Root
For tools that can be run from a subdirectory, you may need to specify config
files in terms of the project root. You can do this by using the string
$PRECIOUS_ROOT in any element of the
cmd configuration key. So for example
you might write something like this:
cmd = ["some-tidier", "--config", "$PRECIOUS_ROOT/some-tidier.conf"]
$PRECIOUS_ROOT string will be replaced by the absolute path to the
To get help run
The root command takes the following options:
||Prints help information|
||Suppresses most output|
||Prints version information|
||Enable verbose output|
||Enable debugging output|
||Enable tracing output (maximum logging)|
||Replace super-fun Unicode symbols with terribly boring ASCII|
||Path to config file|
precious command has two subcommands,
tidy. You must always
specify one of these. These subcommands take the same options, all of which
are for selecting paths to operate on.
Selecting Paths to Operate On
When you run
precious you must tell it what paths to operate on. Precious
supports several ways of setting these via command line arguments:
||Run on all paths in the project.|
|Modified files according to git||
||Run on all files that git reports as having been modified.|
|Staged files according to git||
||Run on all files that git reports as having been staged. This will stash unstaged changes while it runs and pop the stash at the end. This ensures that filters only run against the staged version of your codebase.|
|Paths given on CLI||If you don't pass any of the above flags then
When selecting paths
precious always respects your ignore files. Right now
it only knows how this works for git, and it will respect all of the following
- Global gitignore globs, usually found in
This is implemented using the rust
crate, so adding support for other VCS
systems should be proposed there.
In addition, you can specify excludes for all filters by setting a global
Finally, you can specify per-filter
precious runs it does the following to determine which filters apply to
- The base paths are selected based on the command line option specified.
- VCS ignore rules are applied to remove paths from this list.
- Each filter is given either the files or directories from the list of paths,
depending on the
on_dirsetting for that filter.
- Except for
run_oncefilters, which will get all of the files in all directories and will use those to determine whether to run or not. These filters are always run exactly once.
- Except for
- The filter will check its include and exclude rules. The path must match at
least one include rule and not match any exclude rules to be accepted.
- If the filter is per-file, it matches each path against its rules as is.
- If the filter is per-directory, it matches the files in the directory against its include and exclude rules. If any of the files match the filter is run. If none of the files match the filter is not run.
Here are some example command configurations:
[commands.rustfmt] type = "both" include = "**/*.rs" cmd = ["rustfmt"] lint_flags = "--check" ok_exit_codes =  lint_failure_exit_codes = 
[commands.clippy] type = "lint" include = "**/*.rs" on_dir = true chdir = true run_once = true cmd = ["cargo", "clippy", "-q", "--", "-D", "clippy::all"] ok_exit_codes =  lint_failure_exit_codes = 
[commands.goimports] type = "tidy" include = "**/*.go" cmd = ["goimports", "-w"] ok_exit_codes = 0
There are some configuration scenarios that you may need to handle. Here are some examples:
Linter runs just once for the entire source tree
Some linters, such as rust-clippy, expect to run just once across the entire source tree, rather than once per file or directory.
In order to make that happen you should use the following config:
include = "." on_dir = true run_once = true
This combination of flags will cause
precious to run the command exactly
once in the project root.
Linter runs in the same directory as the files it lints and does not accept path as arguments
If you want to run the command without passing the path being operated on to
the command, add the
include = "**/*.rs" on_dir = true chdir = true
You will probably want to set the
on_dir flag to true in such cases, but
these two flags are independent in case there are tools where setting just
chdir makes sense.
You want a command to exclude an entire directory (tree) except for one file
There's no good way to do this with a single filter's
excluding a directory means that any attempt to
include a file under
that directory will be ignored. Instead, you can configure the same command
[commands.rustfmt-most] type = "both" include = "**/*.rs" exclude = "path/to/dir" cmd = ["rustfmt"] lint_flags = "--check" ok_exit_codes =  lint_failure_exit_codes =  [commands.rustfmt-that-file] type = "both" include = "path/to/dir/that.rs" cmd = ["rustfmt"] lint_flags = "--check" ok_exit_codes =  lint_failure_exit_codes = 
You want to run Precious as a commit hook
precious lint -s in your hook. It will exit with a non-zero
status if any of the lint filters indicate a linting problem.