Submodule Types¶
After learning about composed types, you can imagine that nesting can get deep quickly.
The logic for tracking each layer can become overwhelming.
This is where one of the most useful types included in type system comes into play: submodule
.
It allows you to define nested modules with their own option imports, declarations, and definitions.
First we will define our submodule.
In character.nix
we have something that looks very similar to what we built before.
We have name
, title
, and origin
as inputs and a greeting that combines all these as an output.
{
lib,
options,
config,
...
}: let
cfg = config;
opts = options;
in {
options = {
name = lib.mkOption {
type = lib.types.str;
};
title = lib.mkOption {
type = lib.types.str;
};
origin = lib.mkOption {
type = lib.types.str;
};
greeting = lib.mkOption {
type = lib.types.str;
};
};
config = {
greeting = (
lib.concatStringsSep
"\n"
(
[
"Hello"
]
++ (lib.optional opts.name.isDefined "My name is ${cfg.name}.")
++ (lib.optional opts.title.isDefined "I am a ${cfg.title}.")
++ (lib.optional opts.origin.isDefined "I am from ${cfg.origin}.")
)
);
};
}
In our options.nix
, we have a characters
option that is a list of submodule that points to our character.nix
file.
We have also defined a greeting
option that will take all greeting
definitions from our characters and join them into a single string.
Note: these two greeting
options are not the same; character.nix
and options.nix
each have their own greeting
option.
Note
We could have written the character.nix
submodule in place.
For the sake of readability, we gave it its own file.
Also, writing it in its own file allows us to use it elsewhere if desired.
{
lib,
config,
...
}: let
cfg = config;
in {
options = {
characters = lib.mkOption {
type = lib.types.listOf (lib.types.submodule ./character.nix);
};
greeting = lib.mkOption {
type = lib.types.str;
};
};
config = {
greeting =
lib.concatStringsSep
"\n\n"
(
builtins.map
(character: character.greeting)
cfg.characters
);
};
}
In our config.nix
, we define all of our characters.
The behavior of the submodule type is attr-like.
So inside our list we have attribute sets; each one can have a value for attributes name
, title
and origin
.
{...}: {
config = {
characters = [
{
name = "Boaty McBoatface";
origin = "England";
}
{
name = "djacu";
title = "Nix Enthusiast";
origin = "USA";
}
];
};
}
Setup an eval.nix
to evaluate our modules and return the config.greeting
attribute.
{pkgs}:
(
pkgs.lib.evalModules {
modules = [
./options.nix
./config.nix
];
}
)
.config
.greeting
Create a run.sh
run script to evaluate the eval.nix
file.
nix eval -f eval.nix \
--apply 'x: x {pkgs = import <nixpkgs> {};}' \
--json | nix run nixpkgs#jq -- -r
And if we run the script (./run.sh
), we have our configuration.
Hello
My name is Boaty McBoatface.
I am from England.
Hello
My name is djacu.
I am a Nix Enthusiast.
I am from USA.