Modular, context-aware and aspect-oriented dendritic Nix configurations.
Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/
den.oeiuwq.com
configurations
den
dendritic
nix
aspect
oriented
1{ den, lib, ... }:
2let
3 description = ''
4 An aspect that imports all modules defined for `from` class
5 into a target `into` submodule.
6
7 This can be used to create custom Nix classes that help
8 people separating concerns on huge module hierarchies.
9
10 For example, using a new `user` class that forwards all its
11 settings into `users.users.<userName>` allows:
12
13 den.aspects.alice.nixos.users.users.alice.isNormalUser = true;
14
15 to become:
16
17 den.aspects.alice.user.isNormalUser = true;
18
19
20 This is exactly how `homeManager` class support is implemented in Den.
21 See home-manager/hm-integration.nix.
22
23 Den also provides the mentioned `user` class (`den._.os-user`) for setting
24 NixOS/Darwin options under `users.users.<userName>` at os-level.
25
26 Any other user-environments like `nix-maid` or `hjem` or user-custom classes
27 are easily implemented using `den._.forward`.
28
29 Note: `den._.forward` is a high-level aspect, its result
30 is another aspect that needs to be included for the new class to exist.
31
32 See templates/ci/modules/forward.nix for usage example.
33 See also: https://github.com/vic/den/issues/160, https://github.com/vic/flake-aspects/pull/31
34 '';
35
36 forwardEach = fwd: {
37 includes = map (item: forwardOne (fwd // { each = [ item ]; })) fwd.each;
38 };
39
40 forwardOne =
41 {
42 guard ? null,
43 adaptArgs ? null,
44 adapterModule ? null,
45 ...
46 }@fwd:
47 let
48 clean = builtins.removeAttrs fwd [
49 "guard"
50 "adaptArgs"
51 "adapterModule"
52 ];
53 fromClass = fwd.fromClass (lib.head fwd.each);
54 intoClass = fwd.intoClass (lib.head fwd.each);
55 intoPath = fwd.intoPath (lib.head fwd.each);
56 freeformMod = {
57 config._module.freeformType = lib.types.lazyAttrsOf lib.types.unspecified;
58 };
59 adapterKey = lib.concatStringsSep "_" (
60 [
61 fromClass
62 intoClass
63 ]
64 ++ intoPath
65 );
66 adapter = {
67 includes = [
68 (den.lib.aspects.forward (
69 clean
70 // {
71 intoPath = _: [
72 "den"
73 "fwd"
74 adapterKey
75 ];
76 }
77 ))
78 ];
79 ${intoClass} = args: {
80 options.den.fwd.${adapterKey} = lib.mkOption {
81 default = { };
82 type = lib.types.submoduleWith {
83 specialArgs = if adaptArgs == null then args else adaptArgs args;
84 modules = if adapterModule == null then [ freeformMod ] else [ adapterModule ];
85 };
86 };
87 config = lib.optionalAttrs (guard == null || guard args) (
88 lib.setAttrByPath intoPath args.config.den.fwd.${adapterKey}
89 );
90 };
91 };
92
93 needsAdapter = guard != null || adaptArgs != null || adapterModule != null;
94 forwarded = den.lib.aspects.forward clean;
95 in
96 if needsAdapter then adapter else forwarded;
97
98in
99{
100 den.provides.forward = {
101 inherit description;
102 __functor = _self: forwardEach;
103 };
104}