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

contexts are not strict now - preparation for incremental contexts. (#52)

This is in preparation for contexts being incremental,

an aspect can add (merge) additional data to an existing context.

for example, a host configuration starts with { host } but later for each of it users it merges currentContext // { user } leading to { host, user } augmented context.

This allows contexts to be incremental, so parametric-aspects (functions) can specify what is the context they need:

{ user, ...} - At least user data in context.
{ user, host, gaming } - Need the three of them.

Will invalidate and close #51.
Breaking Change

Your existing functions taking context like { host } will likely need to be { host, ... } now. See this PR diff for how batteries were updated.

authored by oeiuwq.com and committed by

GitHub e4a7c7c8 891c5c58

+25 -23
+5 -1
checkmate/tests/aspect-functor.nix
··· 40 40 nixos.user-only = user; 41 41 } 42 42 else 43 - { } 43 + { nixos.user-only = false; } 44 44 ) 45 45 ( 46 46 { home, ... }: ··· 100 100 ); 101 101 expected = { 102 102 includes = [ 103 + { nixos.home = 2; } 103 104 { nixos.any = 10; } 104 105 ]; 105 106 }; ··· 129 130 ); 130 131 expected = { 131 132 includes = [ 133 + { nixos.host = 1; } 132 134 { 133 135 nixos.host-user = [ 134 136 1 135 137 2 136 138 ]; 137 139 } # host user 140 + { nixos.user = 2; } 141 + { nixos.user-only = false; } 138 142 { nixos.any = 10; } 139 143 ]; 140 144 };
+1 -1
checkmate/tests/function_can_take.nix
··· 62 62 user 63 63 ] 64 64 ); 65 - expected = false; 65 + expected = true; 66 66 }; 67 67 68 68 in
+10 -10
modules/aspects/dependencies.nix
··· 7 7 8 8 dependencies = [ 9 9 # owned attributes: <aspect>.<class> 10 - ({ home }: owned home.class den.aspects.${home.aspect}) 11 - ({ host }: owned host.class den.aspects.${host.aspect}) 12 - ({ user, host }: owned user.class den.aspects.${user.aspect}) 10 + ({ home, ... }: owned home.class den.aspects.${home.aspect}) 11 + ({ host, ... }: owned host.class den.aspects.${host.aspect}) 12 + ({ user, ... }: owned user.class den.aspects.${user.aspect}) 13 13 14 14 # defaults: owned from den.default.<class> 15 - ({ home }: owned home.class den.default) 16 - ({ host }: owned host.class den.default) 17 - ({ user, host }: owned user.class den.default) 15 + ({ home, ... }: owned home.class den.default) 16 + ({ host, ... }: owned host.class den.default) 17 + ({ user, ... }: owned user.class den.default) 18 18 19 19 # static (non-parametric) from <aspect>.includes 20 - ({ home }: statics den.aspects.${home.aspect}) 21 - ({ host }: statics den.aspects.${host.aspect}) 22 - ({ user, host }: statics den.aspects.${user.aspect}) 20 + ({ home, ... }: statics den.aspects.${home.aspect}) 21 + ({ host, ... }: statics den.aspects.${host.aspect}) 22 + ({ user, ... }: statics den.aspects.${user.aspect}) 23 23 24 24 # user-to-host context 25 25 ({ fromUser, toHost }: owned toHost.class den.aspects.${fromUser.aspect}) ··· 34 34 ]; 35 35 36 36 hostIncludesFromUsers = 37 - { host }: 37 + { host, ... }: 38 38 { 39 39 includes = 40 40 let
+1 -1
modules/aspects/provides/home-manager.nix
··· 28 28 29 29 __functor = 30 30 _: 31 - { host }: 31 + { host, ... }: 32 32 { class, aspect-chain }: 33 33 let 34 34 hmUsers = builtins.filter (u: u.class == "homeManager") (lib.attrValues host.users);
+5 -4
modules/aspects/provides/import-tree.nix
··· 61 61 62 62 den._.import-tree.__functor = 63 63 _: root: 64 - { class, ... }: 64 + # deadnix: skip 65 + { class, aspect-chain }: 65 66 let 66 67 path = "${toString root}/_${class}"; 67 68 aspect.${class}.imports = [ (inputs.import-tree path) ]; ··· 69 70 if builtins.pathExists path then aspect else { }; 70 71 71 72 den._.import-tree.provides = { 72 - host = root: { host }: den._.import-tree "${toString root}/${host.name}"; 73 - user = root: { host, user }: den._.import-tree "${toString root}/${user.name}@${host.name}"; 74 - home = root: { home }: den._.import-tree "${toString root}/${home.name}"; 73 + host = root: { host, ... }: den._.import-tree "${toString root}/${host.name}"; 74 + home = root: { home, ... }: den._.import-tree "${toString root}/${home.name}"; 75 + user = root: { host, user, ... }: den._.import-tree "${toString root}/${user.name}@${host.name}"; 75 76 }; 76 77 }
+1 -4
nix/fn-can-take.nix
··· 1 1 lib: param: f: 2 2 let 3 3 args = lib.mapAttrsToList (name: optional: { inherit name optional; }) (lib.functionArgs f); 4 - empty = lib.length args == 0; 5 4 6 5 givenAttrs = (builtins.isAttrs param) && !param ? __functor; 7 6 ··· 10 9 11 10 intersection = lib.intersectLists required provided; 12 11 satisfied = lib.length required == lib.length intersection; 13 - 14 - noExtas = lib.length required == lib.length provided; 15 12 in 16 - empty || (givenAttrs && satisfied && noExtas) 13 + givenAttrs && satisfied
+1 -1
templates/default/modules/_example/aspects.nix
··· 26 26 27 27 # Example: parametric host aspect to automatically set hostName on any host. 28 28 set-host-name = 29 - { host }: 29 + { host, ... }: 30 30 { 31 31 ${host.class}.networking.hostName = host.name; 32 32 };
+1 -1
templates/default/modules/_profile/profiles.nix
··· 11 11 pro.profiles = { 12 12 __functor = den.lib.parametric true; 13 13 includes = [ 14 - ({ host }: pro.${host.system} or { }) 14 + ({ host, ... }: pro.${host.system} or { }) 15 15 # add other routes according to context. 16 16 ]; 17 17 };