numtide / treefmt-nix

treefmt nix configuration
https://numtide.github.io/treefmt/
MIT License
268 stars 83 forks source link
buildbot-numtide flake-parts nix treefmt
# treefmt-nix **Fast and convenient multi-file formatting with Nix** _A numtide project._

Static Badge

treefmt combines file formatters for multiple programming languages so that you can format all your project files with a single command. With treefmt-nix you can specify treefmt build options, dependencies and config in one place, conveniently managed by Nix.

treefmt-nix automatically installs and configures the desired formatters as well as treefmt for you and integrates nicely into your Nix development environments. It comes with sane, pre-crafted formatter-configs maintained by the community; each config corresponds to a section that you would normally add to the treefmt config file treefmt.toml.

Take a look at the already supported formatters for Python, Rust, Go, Haskell and more.

Integration into Nix

Nix classic without flakes

To run treefmt-nix with nix-classic, import the repo using niv:

$ niv add numtide/treefmt-nix

Alternatively, you can download the source and run nix-build in the project root directory:

$ nix-build

The command will return the helper functions which will be later used to produce a derivation from the specified treefmt-nix configuration.

After you installed treefmt-nix, specify the formatter configuration. For instance, this one is for formatting terraform files:

# myfile.nix
{ system ? builtins.currentSystem }:
let
  nixpkgsSrc = builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/refs/heads/nixos-unstable.tar.gz";
  treefmt-nixSrc = builtins.fetchTarball "https://github.com/numtide/treefmt-nix/archive/refs/heads/master.tar.gz";
  nixpkgs = import nixpkgsSrc { inherit system; };
  treefmt-nix = import treefmt-nixSrc;
in
treefmt-nix.mkWrapper nixpkgs {
  # Used to find the project root
  projectRootFile = ".git/config";
  # Enable the terraform formatter
  programs.terraform.enable = true;
  # Override the default package
  programs.terraform.package = nixpkgs.terraform_1;
  # Override the default settings generated by the above option
  settings.formatter.terraform.excludes = [ "hello.tf" ];
}

It's a good practice to place the configuration file in the project root directory.

Next, execute this command:

$ nix-build myfile.nix

This command returns a derivation that contains a treefmt binary at ./result/bin/treefmt in your current directory. The file is actually a symlink to the artifact in /nix/store.

treefmt.toml in this case isn't generated: the binary is wrapped with the config.

Flakes

Running treefmt-nix with flakes isn't hard. The library is exposed as the lib attribute:

# flake.nix
{
  inputs.treefmt-nix.url = "github:numtide/treefmt-nix";

  outputs = { self, nixpkgs, systems, treefmt-nix }:
    let
      # Small tool to iterate over each systems
      eachSystem = f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system});

      # Eval the treefmt modules from ./treefmt.nix
      treefmtEval = eachSystem (pkgs: treefmt-nix.lib.evalModule pkgs ./treefmt.nix);
    in
    {
      # for `nix fmt`
      formatter = eachSystem (pkgs: treefmtEval.config.build.wrapper);
      # for `nix flake check`
      checks = eachSystem (pkgs: {
        formatting = treefmtEval.config.build.check self;
      });
    };
}

And also add the treefmt.nix file (or put the content inline if you prefer):

# treefmt.nix
{ pkgs, ... }:
{
  # Used to find the project root
  projectRootFile = "flake.nix";
  # Enable the terraform formatter
  programs.terraform.enable = true;
  # Override the default package
  programs.terraform.package = pkgs.terraform_1;
  # Override the default settings generated by the above option
  settings.formatter.terraform.excludes = [ "hello.tf" ];
}

This file is also the place to define all the treefmt parameters like includes, excludes and formatter options.

After specifying the flake, run nix fmt:

$ nix fmt

Nix-fmt is a tool to format all nix files in the project, but with the specified flake, it starts treefmt-nix and formats your project.

You can also run nix flake check (eg: in CI) to validate that the project's code is properly formatted.

Flake-parts

This flake exposes a flake-parts module as well. To use it:

  1. Add inputs.treefmt-nix.flakeModule to the imports list of your flake-parts call.

  2. Add treefmt = { .. } (containing the configuration above) to your perSystem.

  3. Add config.treefmt.build.wrapper to the nativeBuildInputs of your devShell. This will make the treefmt command available in the shell using the specified configuration.

    You can also use config.treefmt.build.programs to get access to the individual programs, which could be useful to provide them to your IDE or editor.

    For an example, see haskell-template's flake.nix.

See this page for a detailed walkthrough.

Configuration

While dealing with treefmt outside of nix, the formatter configuration is specified in a toml format. On the contrary, with nix, you write in with a nix syntax like this:

# Used to find the project root
projectRootFile = ".git/config";
# Enable the terraform formatter
programs.terraform.enable = true;
# Override the default package
programs.terraform.package = nixpkgs.terraform_1;
# Override the default settings generated by the above option
settings.formatter.terraform.excludes = [ "hello.tf" ];

Options:

programs.terraform.enable = true;
programs.gofmt.enable = true;

For detailed description of the options, refer to the treefmt documentation.

Project structure

This repo contains a top-level default.nix that returns the library helper functions.

Supported programs

treefmt-nix currently supports the following formatters:

For non-Nix users, you can also find the generated examples in the ./examples folder.

Using a custom formatter

It is also possible to use custom formatters with treefmt-nix. For example, the following custom formatter formats JSON files using yq-go:

settings.formatter = {
  "yq-json" = {
    command = "${pkgs.bash}/bin/bash";
    options = [
      "-euc"
      ''
        for file in "$@"; do
          ${lib.getExe yq-go} -i --output-format=json $file
        done
      ''
      "--" # bash swallows the second argument when using -c
    ];
    includes = [ "*.json" ];
  };
};

Adding new formatters

PRs to add new formatters are welcome!

In order to add a new formatter do the following things:

  1. Create a new entry in the ./programs/ folder.
  2. Consider adding yourself as the meta.maintainer (see below).
  3. Run ./examples.sh to update the ./examples folder.
  4. To test the program:
    1. Extend the project's ./treefmt.nix file (temporarily) to enable the new formatter and configure it in whatever manner is appropriate.
    2. Add a bunch of pertinent sources in this repo -- for instance, if the new formatter is meant to format *.foo files, add a number of *.foo files, some well-formatted (and therefore expected to be exempt from modification by treefmt) and some badly-formatted.
    3. Run nix fmt. Confirm that well-formatted files are unchanged and that badly-formatted files are flagged as such. Re-run nix fmt and confirm that no additional changes were made.
    4. Once this is good, revert those changes.
  5. Submit the PR!

Definition of a meta.maintainer

You can register your desire to help with a specific formatter by adding your GitHub handle to the module's meta.maintainers list.

That mostly means, for the given formatter:

Commercial support

Looking for help or customization?

Get in touch with Numtide to get a quote. We make it easy for companies to work with Open Source projects: https://numtide.com/contact

License

All the code and documentation is licensed with the MIT license.