﻿<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text" xml:lang="en">Conform</title>
  <link type="application/atom+xml" href="https://d.moonfire.us/tags/conform/atom.xml" rel="self" />
  <link type="text/html" href="https://d.moonfire.us/tags/conform/" rel="alternate" />
  <updated>2026-03-09T17:42:47Z</updated>
  <id>https://d.moonfire.us/tags/conform/</id>
  <author>
    <name>D. Moonfire</name>
  </author>
  <rights>Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</rights>
  <entry>
    <title>Enforcing Standards with NixOS</title>
    <link rel="alternate" href="https://d.moonfire.us/blog/2023/12/02/enforcing-standards-with-nixos/" />
    <updated>2023-12-02T06:00:00Z</updated>
    <id>https://d.moonfire.us/blog/2023/12/02/enforcing-standards-with-nixos/</id>
    <category term="development" scheme="https://d.moonfire.us/categories/" label="Development" />
    <category term="nixos" scheme="https://d.moonfire.us/tags/" label="NixOS" />
    <category term="conform" scheme="https://d.moonfire.us/tags/" label="Conform" />
    <category term="conventional-commits" scheme="https://d.moonfire.us/tags/" label="Conventional Commits" />
    <category term="editorconfig" scheme="https://d.moonfire.us/tags/" label="EditorConfig" />
    <category term="buck2" scheme="https://d.moonfire.us/tags/" label="Buck2" />
    <category term="direnv" scheme="https://d.moonfire.us/tags/" label="direnv" />
    <summary type="html">A way of using Nix and direnv to hook up standards for formatting and conventions.
</summary>
    <content type="html">&lt;p&gt;Some time ago, I stumbled into &lt;a href="https://github.com/divnix/std"&gt;std&lt;/a&gt;, a batteries-included development stuff. It looks like something I would really like to get into, mainly because it gave off notes of &lt;a href="https://buck2.build/"&gt;Buck2&lt;/a&gt;, which is something that interests me when dealing with microservice ecosystems and polyglot frameworks. And I know I love a polyglot solution to problems.&lt;/p&gt;
&lt;p&gt;There were a few things that I fought again. I didn't care for the menu system that always shows up (noise), it's instability (still alpha), and the difficulty getting it to work with my way of thinking. I could have worked on some of those and figure out how to accept what I couldn't change and alter what I needed to be productive.&lt;/p&gt;
&lt;p&gt;I don't really have that energy at the moment. I'm painfully aware that my time and attempt budget has been eroded by my family, drama, and the other things going on in my life. I find that I don't have the energy to do much and getting std to play with me was one of those things I decided to bump.&lt;/p&gt;
&lt;h2&gt;Automation Tools&lt;/h2&gt;
&lt;p&gt;However, there were some things I really liked about std that I wasn't aware of, namely &lt;a href="https://github.com/nix-community/nixago"&gt;Nixago&lt;/a&gt; which is a way of having the shell hook of a Nix setup automatically write out the various configuration files for things like &lt;a href="https://github.com/siderolabs/conform"&gt;Conform&lt;/a&gt; for Git messages (I like my conventional commits), &lt;a href="https://editorconfig.org/"&gt;EditorConfig&lt;/a&gt; for formatting, and &lt;a href="https://github.com/evilmartians/lefthook"&gt;Lefthook&lt;/a&gt; to make sure everything is honored. Standard also taught me about &lt;a href="https://github.com/numtide/treefmt"&gt;Treefmt&lt;/a&gt; which is a single command to reformat a command base.&lt;/p&gt;
&lt;p&gt;There was also a way of doing arbitrary configurations, such as maybe setting up my project configuration files or handling other things, but I couldn't figure out how with a cursory look.&lt;/p&gt;
&lt;p&gt;In short, all of those things I like to handle automatically instead of remembering all the little details.&lt;/p&gt;
&lt;p&gt;With me getting rid of std, I wanted to keep this. Ideally in a manner that I could eventually create a flake of my common configurations and then apply them to every story or programming project.&lt;/p&gt;
&lt;h2&gt;Necessity Calls&lt;/h2&gt;
&lt;p&gt;Getting rid of std meant I hand to figure it out. Last night, I sat down and went through the code with my growing skill at Nix (I still do not enjoy the language but I'm getting more fluent with it). I'm also messing with &lt;a href="https://flakehub.com/"&gt;FlakeHub&lt;/a&gt; so you'll see some elements from that library.&lt;/p&gt;
&lt;p&gt;I already use &lt;a href="https://direnv.net/"&gt;direnv&lt;/a&gt; for setting up my flakes as I enter directories. That is part of my normal tool set and I plan on using that for a great deal of time.&lt;/p&gt;
&lt;h2&gt;Layout&lt;/h2&gt;
&lt;p&gt;I like small, individual files. Naturally this means I would like every automated system to have its own file but grouped together in a folder to make it obvious how they are used. (Needless to say, I don't advocate &lt;a href="https://medium.com/lost-but-coding/in-programming-folder-structure-doesnt-matter-as-much-as-you-think-71deecca6028"&gt;coding without folders&lt;/a&gt; but that is also how I work/)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ find src -type d
src
src/configs
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Inputs&lt;/h2&gt;
&lt;p&gt;The first part is pulling in the inputs for Nixago and its extensions.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;# flake.nix
{
  inputs = {
    nixpkgs.url = &amp;quot;https://flakehub.com/f/NixOS/nixpkgs/*.tar.gz&amp;quot;;

    nixago.url = &amp;quot;github:jmgilman/nixago&amp;quot;;
    nixago.inputs.nixpkgs.follows = &amp;quot;nixpkgs&amp;quot;;

    nixago-exts.url = &amp;quot;github:nix-community/nixago-extensions&amp;quot;;
    nixago-exts.inputs.nixpkgs.follows = &amp;quot;nixpkgs&amp;quot;;
  };

  # Flake outputs that other flakes can use
  outputs = inputs @ { self, nixpkgs, nixago, nixago-exts }:
    let
      # This bit comes from Flakehub's init and seems to be a reasonable pattern.
      supportedSystems = [ &amp;quot;x86_64-linux&amp;quot; ];
      forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
        inherit system;
        pkgs = import nixpkgs { inherit system; };
      });
    in
    {
      devShells = forEachSupportedSystem ({ system, pkgs }:
        let
          # This pulls in the configurations from the configuration directory.
          configs = import ./src/configs/default.nix { inherit system pkgs nixago nixago-exts; };
        in
        {
          default = pkgs.mkShell {
            # Pinned packages available in the environment
            packages = with pkgs; [
              git         # Needed for life until I find something more awesome
              nixpkgs-fmt # Needed for Lefthook
              treefmt     # Needed for Lefthook
              lefthook    # Needed for Lefthook
            ];

            # Configuration setup
            shellHook = ''
              ${configs.shellHook}
              lefthook install
            '';
          };
        });
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configurations&lt;/h2&gt;
&lt;p&gt;The basic default is just so I have a single line to configure all the libraries. This just acts as an index file.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;# src/configs/default.nix
inputs:
inputs.nixago.lib.${inputs.system}.makeAll [
  (import ./conform.nix (inputs))
  (import ./editorconfig.nix (inputs))
  (import ./lefthook.nix (inputs))
  (import ./prettier.nix (inputs))
  (import ./treefmt.nix (inputs))
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Prettier&lt;/h3&gt;
&lt;p&gt;Prettier was the first one I used, since I have very little customization in it. The nixago-exts is an extension library that figures out a lot of the formats so I don't have to.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;# src/configs/prettier.nix
inputs @ { system, nixago, nixago-exts, ... }:
nixago-exts.prettier.${system} {
  data = {
    printWidth = 80;
    proseWrap = &amp;quot;always&amp;quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Lefthook&lt;/h3&gt;
&lt;p&gt;Lefthook's configuration is the same, but you'll notice there is no &lt;code&gt;data =&lt;/code&gt; element like most of the others. This threw me because it is different than the others. I also found that I had to add &lt;code&gt;&amp;amp;&amp;amp; git add {staged_files}&lt;/code&gt; from most of the examples I saw others when I commit, it would reformat the code but then leave them modified for the &lt;em&gt;next&lt;/em&gt; check in. Adding the files fixes that and keeps things relatively speedy.&lt;/p&gt;
&lt;p&gt;You also can see how I refer to specific paths for the executables while cleaning up the code.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;# src/configs/lefthook.nix
inputs @ { system, pkgs, nixago, nixago-exts, ... }:
nixago-exts.lefthook.${system} {
  commit-msg = {
    commands = {
      # Runs conform on commit-msg hook to ensure commit messages are
      # compliant.
      conform = {
        run = &amp;quot;${pkgs.conform}/bin/conform enforce --commit-msg-file {1}&amp;quot;;
      };
    };
  };
  pre-commit = {
    commands = {
      # Runs treefmt on pre-commit hook to ensure checked-in source code is
      # properly formatted.
      treefmt = {
        run = &amp;quot;${pkgs.treefmt}/bin/treefmt {staged_files} &amp;amp;&amp;amp; git add {staged_files}&amp;quot;;
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a side note, I have not found a single &lt;em&gt;fast&lt;/em&gt; C# reformatter that only handles a few files. It has been intensely frustrating because I really like ReSharper's &amp;ldquo;Silently Clean&amp;rdquo; feature and I don't have a way of doing it from the command line in a reasonable period of time.&lt;/p&gt;
&lt;h3&gt;Conform&lt;/h3&gt;
&lt;p&gt;Conform is nice because it enforces Git commit messages for conventional commits.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;# src/configs/conform.nix
inputs @ { system, nixago, nixago-exts, ... }:
nixago-exts.conform.${system} {
  commit = {
    header = { length = 89; };
    conventional = {
      # Only allow these types of conventional commits (inspired by Angular)
      types = [
        &amp;quot;build&amp;quot;
        &amp;quot;chore&amp;quot;
        &amp;quot;ci&amp;quot;
        &amp;quot;docs&amp;quot;
        &amp;quot;feat&amp;quot;
        &amp;quot;fix&amp;quot;
        &amp;quot;perf&amp;quot;
        &amp;quot;refactor&amp;quot;
        &amp;quot;style&amp;quot;
        &amp;quot;test&amp;quot;
      ];

      # If you want scopes, then add:
      #scopes = [&amp;quot;allows&amp;quot; &amp;quot;scopes&amp;quot; &amp;quot;here&amp;quot;];
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Treefmt&lt;/h2&gt;
&lt;p&gt;Cleaning up code is something that is tedious but really needs to be done to lower the bar of allowing others into the code. It is also something that can be &amp;ldquo;mostly&amp;rdquo; automated, which I'm also in favor of. Sadly, there are gaps in the tools that I want, like a C# or Rust formatter that will organize members (such as grouping public properties together and making them alphabetical), but I can live without those.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# src/configs/treefmt.nix
inputs @ { pkgs, ... }:
let
  data = {
    formatter = {
      prettier = {
        command = &amp;quot;${pkgs.nodePackages.prettier}/bin/prettier&amp;quot;;
        options = [ &amp;quot;--write&amp;quot; ];
        includes = [
          &amp;quot;*.css&amp;quot;
          &amp;quot;*.html&amp;quot;
          &amp;quot;*.js&amp;quot;
          &amp;quot;*.json&amp;quot;
          &amp;quot;*.jsx&amp;quot;
          &amp;quot;*.md&amp;quot;
          &amp;quot;*.mdx&amp;quot;
          &amp;quot;*.scss&amp;quot;
          &amp;quot;*.ts&amp;quot;
          &amp;quot;*.yaml&amp;quot;
          #&amp;quot;*.toml&amp;quot;
        ];
        excludes = [ &amp;quot;**.min.js&amp;quot; ];
      };

      nix = {
        command = &amp;quot;nixpkgs-fmt&amp;quot;;
        includes = [ &amp;quot;*.nix&amp;quot; ];
      };
    };
  };
in
{
  # I don't understand the reason why many Nix examples define
  # the data in the let section and then just inherit it here.
  inherit data;
  output = &amp;quot;treefmt.toml&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;EditorConfig&lt;/h3&gt;
&lt;p&gt;I think one of the best things that came out of the last decade or so of coding was a slow migration to having a semi-universal file for configuring line endings, trimming white space, and the others. Also, both Microsoft and Jetbrains have embraced EditorConfig so I can check in a file that reduces the trivial (but needed) rejects for pull requests. I want more tools to use this and extend them because I don't want to bother with line indents, tabs verses spaces (tabs lost, but I've accepted spaces now), and formatting rules (braces on new lines).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;# src/configs/editorconfig.nix
inputs: # I don't use the inputs, I just wanted all the calls in `default.nix` to be consistent.
let
  data = {
    root = true;

    &amp;quot;*&amp;quot; = {
      end_of_line = &amp;quot;lf&amp;quot;;
      insert_final_newline = true;
      trim_trailing_whitespace = true;
      charset = &amp;quot;utf-8&amp;quot;;
      indent_style = &amp;quot;space&amp;quot;;
      indent_size = 4;
      indent_brace_style = &amp;quot;K&amp;amp;R&amp;quot;;
      max_line_length = 80;
      tab_width = 4;
      curly_bracket_next_line = true;
    };

    &amp;quot;*.md&amp;quot; = {
      max_line_length = &amp;quot;off&amp;quot;;
    };

    &amp;quot;package.json&amp;quot; = {
      indent_style = &amp;quot;space&amp;quot;;
      indent_size = 2;
      tab_width = 2;
    };

    &amp;quot;{LICENSES/**,LICENSE}&amp;quot; = {
      end_of_line = &amp;quot;unset&amp;quot;;
      insert_final_newline = &amp;quot;unset&amp;quot;;
      trim_trailing_whitespace = &amp;quot;unset&amp;quot;;
      charset = &amp;quot;unset&amp;quot;;
      indent_style = &amp;quot;unset&amp;quot;;
      indent_size = &amp;quot;unset&amp;quot;;
    };
  };
in
{
  inherit data;
  hook.mode = &amp;quot;copy&amp;quot;;
  output = &amp;quot;.editorconfig&amp;quot;;
  format = &amp;quot;toml&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously, the C# version is huge with lots of settings to fit my standards.&lt;/p&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;p&gt;The nice part about this is all I have to do is change into the directory and direnv will automatically make sure all the files are correct and up to date. Since I'm mostly on the command line, this works out beautifully for me and how I work.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-shell"&gt;$ cd bakfu
direnv: loading ~/src/bakfu/.envrc
direnv: using flake
evaluating derivation 'git+file:///home/dmoonfire/src/bakfu#devShells.x86_64-linux.default'
nixago: updating repository files
nixago: '.conform.yaml' link updated
nixago: '.editorconfig' copy is up to date
nixago: 'lefthook.yml' link updated
nixago: '.prettierrc.json' link updated
nixago: 'treefmt.toml' link updated
direnv: export +AR +AS +CC +CONFIG_SHELL +CXX +HOST_PATH +IN_NIX_SHELL +LD +NIX_BINTOOLS +NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_BUILD_CORES +NIX_BUILD_TOP +NIX_CC +NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_CFLAGS_COMPILE +NIX_ENFORCE_NO_NATIVE +NIX_HARDENING_ENABLE +NIX_LDFLAGS +NIX_STORE +NM +OBJCOPY +OBJDUMP +RANLIB +READELF +SIZE +SOURCE_DATE_EPOCH +STRINGS +STRIP +TEMP +TEMPDIR +TMP +TMPDIR +__structuredAttrs +buildInputs +buildPhase +builder +cmakeFlags +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +dontAddDisableDepTrack +mesonFlags +name +nativeBuildInputs +out +outputs +patches +phases +preferLocalBuild +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system ~PATH ~XDG_DATA_DIRS
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Moving Parts&lt;/h2&gt;
&lt;p&gt;There is a problem with this, in that it is a lot of moving parts to basically write out a file that could easily be checked into code once and be done with. I fully admit that we are going through a lot to hoops that could easily be done with simple files with only one exception.&lt;/p&gt;
&lt;p&gt;The biggest exception is Lefthook. Someone needs to run &lt;code&gt;lefthook init&lt;/code&gt; after the repository is cloned to ensure the hooks are all configured so it enforces the commit messages and makes sure the code is formatted before committing. Since Git won't ever provide that, having the shell hook from the flake enforce it cuts out a tedious step that is easily overlooked.&lt;/p&gt;
&lt;p&gt;The other reason for going with this approach is my ability to update it. Each of my stories are in their own repository for a variety of reasons, but the structure and layout of them are typically manipulated en masse as I update a standard. I also do theme and style changes across the boards, such as finding a new font or fixing the ebook generation.&lt;/p&gt;
&lt;p&gt;With the flake setup, I could easily migrate these configurations to a dedicated flake that is shared across all of them, and then just update the lock for that file to enforce the latest iteration of an evolving standard.&lt;/p&gt;
&lt;p&gt;And standards are evolving. While I have common patterns (braces on newlines), how I format the code, organize files, or hook up patterns changes. Sometimes it is a little incremental change, sometimes it is a sweeping change as I switch build systems or introduce a standard format. Last year, I set it up so every project would generate a EPUB and PDF file and work with my Gitea and Woodpecker CI setup.&lt;/p&gt;
&lt;p&gt;If I can make those changes cut across all the projects, then it is less effort for me to get conformity but also let me work in an environment I'm comfortable with. Being able to make sure every tool I want is available (such as &lt;a href="/tags/author-intrusion/"&gt;Author Intrusion&lt;/a&gt; or &lt;a href="/tags/markdown/"&gt;markdowny&lt;/a&gt;) means I don't have to think about the plumbing and just do the part that is fun: write.&lt;/p&gt;
</content>
  </entry>
</feed>
