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
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(core): `aspect.meta` allow attaching freeform attributes to aspects (#376)

This can be used for aspect introspection or for passing metadata
attached to aspects themselves.

---
@drupol would love your input on this one, does this look good to you?

authored by

Victor Borja and committed by
GitHub
7e84abfd aa99f5eb

+222 -13
+48
docs/src/content/docs/guides/configure-aspects.mdx
··· 185 185 Parametric functions in `den.default.includes` are evaluated at every context 186 186 stage. Use `den.lib.take.exactly` if a function should only run in specific contexts. 187 187 </Aside> 188 + 189 + 190 + ## Aspect Meta-Data 191 + 192 + Aspects have a `meta` submodule that can be used to attach meta-data 193 + at the aspect-level. 194 + 195 + This is useful for introspection, for example, using some key to filter out aspects 196 + or communicate information about aspects to other tooling like graph generators. 197 + 198 + Since `.meta` is a freeformType you can add any custom attribute or import a module on it. 199 + 200 + The following are default `meta.*` attributes defined by Den: 201 + 202 + ```nix 203 + aspect.name # "den.aspects.igloo" 204 + meta.loc # the location [ "den" "aspects" "igloo" ] from where name is derived. 205 + meta.file # the first file where this aspect was defined. 206 + meta.self # a reference to the aspect module `config`. 207 + ``` 208 + 209 + You can acess meta values by referencing an aspect: 210 + 211 + ```nix 212 + den.aspects.igloo.meta.name 213 + ``` 214 + 215 + Or if an aspect needs to access its own meta data as part of its parametric functor, 216 + it can do something like: 217 + 218 + 219 + ```nix 220 + den.aspects.foo = 221 + { config, lib, ... }: # module args to access `config.meta` 222 + { 223 + meta.foo = "bar"; 224 + __functor = self: ctx: 225 + if config.meta.foo == "bar" 226 + then { } # do nothing, skip. 227 + { 228 + includes = filter (i: someCondition i) self.includes; 229 + }; 230 + } 231 + 232 + ``` 233 + 234 + 235 +
+21 -8
nix/lib/aspects/resolve.nix
··· 3 3 4 4 inherit (den.lib) canTake take; 5 5 6 + apply = 7 + provided: args: 8 + let 9 + res = if canTake.upTo args provided then take.upTo provided args else provided; 10 + mod = 11 + den.lib.aspects.types.aspectType.merge 12 + [ ] 13 + [ 14 + { 15 + file = "<curried>"; 16 + value = res; 17 + } 18 + ]; 19 + in 20 + if lib.isFunction res then mod else res; 21 + 6 22 resolve = 7 23 class: prev-chain: provided: 8 24 let 9 - aspect = if canTake.upTo args provided then take.upTo provided args else provided; 25 + aspect = apply provided { inherit class aspect-chain; }; 10 26 11 27 aspect-chain = prev-chain ++ [ provided ] ++ (lib.optional (provided != aspect) aspect); 12 28 13 - args = { 14 - inherit aspect class aspect-chain; 15 - }; 16 - 17 - imports = 18 - (lib.optional (aspect ? ${class}) aspect.${class}) 19 - ++ (map (resolve class aspect-chain) (aspect.includes or [ ])); 29 + classModule = lib.optional (aspect ? ${class}) ( 30 + lib.setDefaultModuleLocation "${class}@${aspect.name}" aspect.${class} 31 + ); 20 32 33 + imports = classModule ++ (map (resolve class aspect-chain) (aspect.includes or [ ])); 21 34 in 22 35 { 23 36 inherit imports;
+35 -5
nix/lib/aspects/types.nix
··· 9 9 }; 10 10 11 11 isProviderFn = canTake.upTo { 12 - aspect = true; 13 12 aspect-chain = true; 14 13 class = true; 15 14 }; ··· 48 47 let 49 48 sub = aspectSubmodule cnf; 50 49 in 51 - sub 52 - // { 53 - merge = loc: defs: sub.merge loc defs; 50 + sub // { merge = mergeWithAspectMeta sub; }; 51 + 52 + mergeWithAspectMeta = 53 + sub: loc: defs: 54 + sub.merge loc ( 55 + defs 56 + ++ [ 57 + { 58 + file = (lib.head defs).file; 59 + value = aspectMeta loc defs; 60 + } 61 + ] 62 + ); 63 + 64 + aspectMeta = 65 + loc: defs: 66 + { config, ... }: 67 + let 68 + names = map (x: if builtins.isString x then x else "<anon>") loc; 69 + nameFromLoc = lib.concatStringsSep "." names; 70 + in 71 + { 72 + name = nameFromLoc; 73 + meta.file = (lib.last defs).file; 74 + meta.loc = loc; 54 75 }; 55 76 56 77 aspectSubmodule = ··· 59 80 { name, config, ... }: 60 81 { 61 82 freeformType = lib.types.lazyAttrsOf lib.types.deferredModule; 62 - config._module.args.aspect = config; 63 83 imports = [ (lib.mkAliasOptionModule [ "_" ] [ "provides" ]) ]; 64 84 65 85 options = { ··· 75 95 defaultText = lib.literalExpression "name"; 76 96 default = "Aspect ${name}"; 77 97 type = lib.types.str; 98 + }; 99 + 100 + meta = lib.mkOption { 101 + description = "Aspect attached meta data"; 102 + type = lib.types.submodule { 103 + freeformType = lib.types.lazyAttrsOf lib.types.unspecified; 104 + self = config; 105 + }; 106 + defaultText = lib.literalExpression "{ }"; 107 + default = { }; 78 108 }; 79 109 80 110 includes = lib.mkOption {
+118
templates/ci/modules/features/aspect-meta.nix
··· 1 + { denTest, ... }: 2 + { 3 + flake.tests.aspect-meta = { 4 + 5 + test-meta-can-be-referenced = denTest ( 6 + { den, funnyNames, ... }: 7 + { 8 + den.aspects.foo = { 9 + funny.names = [ den.aspects.foo.meta.nick ]; 10 + meta.nick = "McLoving"; 11 + }; 12 + 13 + expr = funnyNames den.aspects.foo; 14 + expected = [ "McLoving" ]; 15 + } 16 + ); 17 + 18 + test-meta-can-be-self-referenced = denTest ( 19 + { den, funnyNames, ... }: 20 + { 21 + den.aspects.foo = 22 + { config, ... }: 23 + { 24 + funny.names = [ config.meta.nick ]; 25 + meta.nick = "McLoving"; 26 + }; 27 + 28 + expr = funnyNames den.aspects.foo; 29 + expected = [ "McLoving" ]; 30 + } 31 + ); 32 + 33 + test-name-can-be-referenced = denTest ( 34 + { den, funnyNames, ... }: 35 + { 36 + den.aspects.foo = { 37 + funny.names = [ den.aspects.foo.name ]; 38 + }; 39 + 40 + expr = funnyNames den.aspects.foo; 41 + expected = [ "den.aspects.foo" ]; 42 + } 43 + ); 44 + 45 + test-name-can-be-self-referenced = denTest ( 46 + { den, funnyNames, ... }: 47 + { 48 + den.aspects.foo = 49 + { config, ... }: 50 + { 51 + funny.names = [ config.name ]; 52 + }; 53 + 54 + expr = funnyNames den.aspects.foo; 55 + expected = [ "den.aspects.foo" ]; 56 + } 57 + ); 58 + 59 + test-meta-keys-at-host-aspect = denTest ( 60 + { 61 + den, 62 + igloo, 63 + lib, 64 + ... 65 + }: 66 + { 67 + den.hosts.x86_64-linux.igloo = { }; 68 + den.aspects.igloo.includes = [ den.aspects.foo ]; 69 + 70 + den.aspects.foo = 71 + { host }: 72 + { 73 + nixos.environment.sessionVariables = { 74 + KEYS = lib.attrNames den.aspects.foo.meta.self.meta; 75 + }; 76 + }; 77 + 78 + expr = { 79 + inherit (igloo.environment.sessionVariables) 80 + KEYS 81 + ; 82 + }; 83 + expected.KEYS = "file:loc:self"; 84 + } 85 + ); 86 + 87 + test-meta-keys-at-host-fixpoint = denTest ( 88 + { 89 + den, 90 + igloo, 91 + lib, 92 + ... 93 + }: 94 + { 95 + den.hosts.x86_64-linux.igloo = { }; 96 + den.aspects.igloo.includes = [ den.aspects.foo ]; 97 + 98 + den.aspects.foo = 99 + { host }: 100 + { config, lib, ... }: 101 + { 102 + nixos.environment.sessionVariables = { 103 + KEYS = lib.attrNames config.meta; 104 + }; 105 + meta.foo = 12; 106 + }; 107 + 108 + expr = { 109 + inherit (igloo.environment.sessionVariables) 110 + KEYS 111 + ; 112 + }; 113 + expected.KEYS = "file:foo:loc:self"; 114 + } 115 + ); 116 + 117 + }; 118 + }