The nix configuration language is very powerful and contrary to other opinions I find it quite easy to read, once you get the hang of it. Since you can interpolate variables in strings, it can easily be used as a rudimentary templating system on its own. However this has the drawback that your configuration will contain the whole "template", which can get get very long and hard to read.
The problem
Imagine you want to use
home-manager's
xdg.configFile
option to write a file containing some variables. This example
is taken from my
Neovim
configuration. I use the same color definitions in multiple places around the
system and want to write a lua file which I can then base the editors
colorscheme on. The colors are defined in ./vars.nix
, a simple file containing
only an attribute set with the hex codes
{
colors = {
Black = "24283B";
DarkGrey = "313547";
Grey = "393f59";
BrightGrey = "4b4f5e";
# more colors...
};
}
The simple approach is to just interpolate the values. This is okay for short snippets, but get's annoying soon. For the file above the code would look something like this
{
vars = import ../vars.nix;
xdg = {
enable = true;
configFile = {
nvim_lua_nixcolors = {
target = "nvim/lua/nixcolors.lua";
text = ''
local M = {}
M.Black = "#vars.colors.Black"
M.DarkGrey = "#vars.colors.DarkGrey"
M.Grey = "#vars.colors.Grey"
-- More colors ... --
M.Magenta = "#vars.colors.Magenta"
M.BrightMagenta = "#vars.colors.BrightMagenta"
return M
'';
};
};
};
}
We can't keep doing that, ansible users will make fun of us. It would be better to use a real templating features and have the data, templates and the configuration in separate files. You don't need a templating framework for that, a few lines of simple code will do, here is how.
The solution
To further specify the
Mustache is one of the most used templating languages, let's use that one. The idea is simple:
- Write our templates in the mustache format
- Convert the nix attribute set containing the input data to JSON
- Render the template where we want it declaratively
Conversion is easy. Nix already has builtin function for it, builtins.toJSON
.
It accepts an attribute set and returns a string containing JSON. Perfect.
To avoid escaping issues, we will pass the data and template as file paths
function instead of strings. stddenv.mkDerivation
has some less known
extended
attributes, which
allow passing in temporary files to the derivation.
passAsFile = [ "jsonData" ];
jsonData = builtins.toJSON data;
This sets an environment variable called $jsonDataPath
inside the derivation,
pointing to the temporary file. The path to the mustache template will be passed
directly as another function argument.
To do the actual rendering, any mustache parser can be used. I went for
mustache-go because it is already in
packaged in nixpkgs
and has minimal dependencies. It's command line interface
is simple:
And that's already most of the code. Our derivation will have call that in the
buildPhase
and use the installPhase
to copy the file to the store, returning
the path to it.
{
buildPhase = ''
pkgs.mustache-go/bin/mustache $jsonDataPath template > rendered_file
'';
installPhase = ''
cp rendered_file $out
'';
}
You just build your own templating engine using nix! For completeness, here is the full code of the function
{
vars = import ../vars.nix;
templateFile = name: template: data:
pkgs.stdenv.mkDerivation {
name = "name";
nativeBuildInpts = [ pkgs.mustache-go ];
# Pass Json as file to avoid escaping
passAsFile = [ "jsonData" ];
jsonData = builtins.toJSON data;
# Disable phases which are not needed. In particular the unpackPhase will
# fail, if no src attribute is set
phases = [ "buildPhase" "installPhase" ];
buildPhase = ''
pkgs.mustache-go/bin/mustache $jsonDataPath template > rendered_file
'';
installPhase = ''
cp rendered_file $out
'';
};
}
Now to use it, just rewrite the code of the beginning to use it, passing name
,
the path to the template and an attribute set containing the data as parameters.
{
# ...
xdg = {
enable = true;
configFile = {
nixcolors-lua = {
target = "nvim/lua/nixcolors.lua";
source =
templateFile "nixcolors.lua" ./nixcolors.lua.mustache vars.colors;
};
};
};
}
After rebuilding your configuration, a symlink will be placed in
~/.config/nvim/lua/nixcolors.lua
containing the rendered file.