fake.modules transposition for aspect-oriented Dendritic Nix. with cross-aspect dependencies. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ dendrix.oeiuwq.com/Dendritic.html
dendritic nix aspect oriented

providers take a list of aspect names (#7)

This allows providers an opportunity to inspect
the chain of aspects that caused they to be
included and can return accordingly.

authored by oeiuwq.com and committed by

GitHub c45a4ad1 f5d1ff4c

+29 -19
+7 -5
README.md
··· 186 186 187 187 When a module like `flake.modules.nixos.foo` is requested (for example, included in a `nixosConfiguration`), a corresponding module is computed from `flake.aspects.foo.nixos`. 188 188 189 - `flake.aspects.foo.includes` is a list of functions (providers) that are called with `{ aspect = "foo"; class = "nixos" }`. These providers return another aspect that provides a module of the same `class` (in this case, `nixos`). 189 + `flake.aspects.foo.includes` is a list of functions (providers) that are called with `{ aspect-chain = ["foo"]; class = "nixos" }`. These providers return another aspect that provides a module of the same `class` (in this case, `nixos`). 190 190 191 - Providers answer the question: given a (`foo`, `nixos`) module, what other aspects can provide `nixos` modules to be imported into `foo`? 191 + Providers answer the question: given we have `nixos` modules from `[foo]` aspects, what other aspects can provide `nixos` modules that need to be imported? 192 192 193 193 This means that the included aspect determines which configuration its caller should use. 194 194 195 195 By default, all aspects have a `<aspect>.provides.itself` provider function that ignores its argument and returns the `<aspect>` itself. This is why `with aspects; [ bar baz ]` works: it is shorthand for `[ aspects.bar.provides.itself aspects.baz.provides.itself ]`. 196 196 197 - You can also define custom providers that inspect the `aspect` and `class` arguments and return a set of modules accordingly. This allows providers to act as proxies or routers for dependencies. 197 + You can also define custom providers that inspect the `aspect-chain` and `class` arguments and return a set of modules accordingly. This allows providers to act as proxies or routers for dependencies. 198 198 199 199 ```nix 200 - flake.aspects.alice.provides.os-user = { aspect, class }: 201 - if someCondition aspect && class == "nixos" then { nixos = { ... }; } else { }; 200 + flake.aspects.alice.provides.os-user = { aspect-chain, class }: 201 + if someCondition aspect-chain && class == "nixos" then { nixos = { ... }; } else { }; 202 202 ``` 203 203 204 204 The `os-user` provider can then be included in a `includes` list: ··· 208 208 home-server.includes = [ aspects.alice.provides.os-user ]; 209 209 } 210 210 ``` 211 + 212 + See `aspects."test provides"` [checkmate tests](checkmate.nix) for more examples on chained providers. 211 213 212 214 ## Testing 213 215
+7 -6
aspects.nix
··· 4 4 emit = transposed: [ 5 5 { 6 6 inherit (transposed) parent child; 7 - value = aspectModule transposed.child transposed.parent aspects.${transposed.child}; 7 + value = aspectModule [ transposed.child ] transposed.parent aspects.${transposed.child}; 8 8 } 9 9 ]; 10 10 11 11 include = 12 - aspect: class: f: 12 + aspect-chain: class: f: 13 13 let 14 - asp = f { inherit aspect class; }; 14 + asp = f { inherit aspect-chain class; }; 15 + new-chain = aspect-chain ++ [ asp.name ]; 15 16 in 16 - aspectModule asp.name class asp; 17 + aspectModule new-chain class asp; 17 18 18 19 aspectModule = 19 - aspect: class: asp: 20 + aspect-chain: class: asp: 20 21 let 21 22 module.imports = lib.flatten [ 22 23 (asp.${class} or { }) 23 - (lib.map (include aspect class) asp.includes) 24 + (lib.map (include aspect-chain class) asp.includes) 24 25 ]; 25 26 in 26 27 module;
+14 -5
checkmate.nix
··· 144 144 inherit expr expected; 145 145 }; 146 146 147 - aspects."test provider arguments" = 147 + aspects."test provides" = 148 148 let 149 149 flake = mkFlake ({ 150 150 flake.aspects = ··· 154 154 foo 155 155 bar 156 156 ]; 157 - aspectOne.classOne = { }; # included for mixing dependencies. 157 + aspectOne.classOne = { }; # must be present for mixing dependencies. 158 158 aspectTwo = { 159 159 classOne.bar = [ "class one not included" ]; 160 160 classTwo.bar = [ "class two not included" ]; 161 161 provides.foo = 162 - { class, aspect }: 162 + { class, aspect-chain }: 163 163 { 164 - classOne.bar = [ "foo:${aspect}:${class}" ]; 164 + name = "aspectTwo.foo"; 165 + description = "aspectTwo foo provided"; 166 + includes = [ aspects.aspectThree.provides.moo ]; 167 + classOne.bar = [ "two:${class}:${lib.concatStringsSep "/" aspect-chain}" ]; 165 168 classTwo.bar = [ "foo class two not included" ]; 166 169 }; 167 170 provides.bar = _: { ··· 169 172 classTwo.bar = [ "bar class two not included" ]; 170 173 }; 171 174 }; 175 + aspectThree.provides.moo = 176 + { aspect-chain, class }: 177 + { 178 + classOne.bar = [ "three:${class}:${lib.concatStringsSep "/" aspect-chain}" ]; 179 + }; 172 180 }; 173 181 }); 174 182 expr = lib.sort (a: b: a < b) (evalMod "classOne" flake.modules.classOne.aspectOne).bar; 175 183 expected = [ 176 - "foo:aspectOne:classOne" 184 + "three:classOne:aspectOne/aspectTwo.foo" 185 + "two:classOne:aspectOne" 177 186 ]; 178 187 in 179 188 {
+1 -3
types.nix
··· 12 12 { 13 13 freeformType = lib.types.lazyAttrsOf lib.types.deferredModule; 14 14 options.name = lib.mkOption { 15 - readOnly = true; 16 15 description = "Aspect name"; 17 16 default = name; 18 17 type = lib.types.str; ··· 42 41 }; 43 42 options.__functor = lib.mkOption { 44 43 internal = true; 45 - readOnly = true; 46 44 visible = false; 47 45 description = "Functor to default provider"; 48 - type = lib.types.unspecified; 46 + type = lib.types.functionTo lib.types.unspecified; 49 47 default = _: config.provides.itself; 50 48 }; 51 49 }