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(ctx): Allow transformations into nested contexts (#260)

In preparation for denful nested contexts.


Previously, in `den.ctx`:

We had simple one-flat level transformations:

```nix
den.ctx.foo.into.bar = fooDataTransformIntoBar;

den.ctx.bar._.bar = aspectResponsibleToConfigureBar;

den.ctx.foo._.bar = aspectContributionFromFoo;
```

Denful has nested aspect scopes, since `den.ctx` are an specialized kind
of aspect, it
now supports nested context transformations (in adition to flat one we
currently use in hm/hjem/maid)

```nix
den.ctx.foo.into = fooCtx: { x.y.z = fooDataTramsformIntoZ fooCtx; };

den.ctx.x.y.z._.z = aspectConfiguringZ;

den.ctx.foo._."x.y.z" = aspectContributionFromFoo;
```

authored by

Victor Borja and committed by
GitHub
1751a01e 76966206

+300 -31
+50 -18
nix/ctx-apply.nix
··· 16 16 17 17 cleanCtx = self: builtins.removeAttrs self ctxKeys; 18 18 19 + # Flatten nested into result to [ { path = [str]; into = [ctx_value]; } ]. 20 + # Leaf nodes are lists; intermediate nodes recurse deeper. 21 + flattenInto = 22 + attrset: prefix: 23 + lib.concatLists ( 24 + lib.mapAttrsToList ( 25 + name: v: 26 + let 27 + path = prefix ++ [ name ]; 28 + in 29 + if builtins.isList v then 30 + [ 31 + { 32 + inherit path; 33 + into = v; 34 + } 35 + ] 36 + else 37 + flattenInto v path 38 + ) attrset 39 + ); 40 + 19 41 transformAll = 20 - source: self: ctx: 42 + source: self: ctx: key: 21 43 [ 22 44 { 23 - inherit ctx source; 45 + inherit ctx source key; 24 46 ctxDef = self; 25 47 } 26 48 ] 27 49 ++ lib.concatLists ( 28 - lib.mapAttrsToList ( 29 - name: into: 30 - if den.ctx ? ${name} then 31 - lib.concatMap (transformAll self den.ctx.${name}) into 32 - else if self.provides ? ${name} then 33 - lib.concatMap (transformAll self { 34 - inherit name; 35 - into = _: { }; 36 - }) into 50 + map ( 51 + { path, into }: 52 + let 53 + target = lib.attrByPath path null den.ctx; 54 + tkey = lib.concatStringsSep "." path; 55 + in 56 + if target != null then 57 + lib.concatMap (v: transformAll self target v tkey) into 58 + else if builtins.length path == 1 && self.provides ? ${lib.head path} then 59 + let 60 + name = lib.head path; 61 + in 62 + lib.concatMap ( 63 + v: 64 + transformAll self { 65 + inherit name; 66 + into = _: { }; 67 + } v name 68 + ) into 37 69 else 38 70 [ ] 39 - ) (self.into ctx) 71 + ) (flattenInto (self.into ctx) [ ]) 40 72 ); 41 73 42 74 noop = _: { }; 43 75 44 - crossProvider = p: if p.source == null then noop else p.source.provides.${p.ctxDef.name} or noop; 76 + crossProvider = p: if p.source == null then noop else p.source.provides.${p.key} or noop; 45 77 46 78 dedupIncludes = 47 79 let ··· 53 85 let 54 86 p = builtins.head remaining; 55 87 rest = builtins.tail remaining; 56 - name = p.ctxDef.name; 88 + key = p.key; 57 89 clean = cleanCtx p.ctxDef; 58 - isFirst = !(acc.seen ? ${name}); 59 - selfFun = p.ctxDef.provides.${name} or noop; 90 + isFirst = !(acc.seen ? ${key}); 91 + selfFun = p.ctxDef.provides.${p.ctxDef.name} or noop; 60 92 crossFun = crossProvider p; 61 93 items = [ 62 94 (if isFirst then parametric.fixedTo p.ctx clean else parametric.atLeast clean p.ctx) ··· 66 98 in 67 99 go { 68 100 seen = acc.seen // { 69 - ${name} = true; 101 + ${key} = true; 70 102 }; 71 103 result = acc.result ++ items; 72 104 } rest; ··· 77 109 }; 78 110 79 111 ctxApply = self: ctx: { 80 - includes = dedupIncludes (transformAll null self ctx); 112 + includes = dedupIncludes (transformAll null self ctx self.name); 81 113 }; 82 114 83 115 in
+37 -13
nix/nixModule/ctx.nix
··· 2 2 let 3 3 inherit (config) den; 4 4 inherit (den.lib.aspects.types) aspectSubmodule; 5 - inherit (den.lib) ctxApply; 5 + inherit (den.lib) ctxApply take; 6 6 7 7 intoType = 8 8 let 9 - # into = { x = {ctx}: []; y = {ctx}: []}; } 10 - intoAttrsType = lib.types.lazyAttrsOf (lib.types.functionTo (lib.types.listOf lib.types.raw)); 9 + # into = { x = ctx→[]; y = ctx→[]; } 10 + # Also supports nested namespace keys: into."foo.bar" = fn 11 + intoAttrsType = lib.types.lazyAttrsOf lib.types.raw; 11 12 12 - # into = {ctx}: { x = []; y = []; } 13 + # into = ctx → { x = []; y = []; } 13 14 intoFnType = lib.types.functionTo (lib.types.lazyAttrsOf lib.types.raw); 14 15 in 15 16 lib.types.either intoFnType intoAttrsType; 16 17 17 - normalizeInto = 18 - value: 19 - if lib.isFunction value then 20 - value 18 + # Recrusively apply ctx to leaf functions in a (possibly nested) attrset. 19 + applyIntoNode = 20 + ctx: v: 21 + if lib.isFunction v then 22 + (take.atLeast v) ctx 23 + else if builtins.isAttrs v then 24 + lib.mapAttrs (_: applyIntoNode ctx) v 21 25 else 22 - ctx: lib.mapAttrs (n: v: (den.lib.take.atLeast v) ctx) value; 26 + v; 23 27 24 - # a context-definiton is an aspect extended with into.* transformations 25 - # and a fixed functor to apply them. 28 + normalizeInto = 29 + value: if lib.isFunction value then value else ctx: lib.mapAttrs (_: applyIntoNode ctx) value; 30 + 26 31 ctxSubmodule = lib.types.submodule ( 27 - { config, ... }: 32 + { ... }: 28 33 { 29 34 imports = aspectSubmodule.getSubModules; 30 35 options.into = lib.mkOption { ··· 37 42 } 38 43 ); 39 44 45 + # A ctx tree node: either a leaf ctx definition (has `into`/`__functor`) 46 + # or a namespace container holding more nodes. Detected at merge time, 47 + # so the self-reference in the namespace branch is lazily evaluated. 48 + ctxTreeType = lib.types.mkOptionType { 49 + name = "ctxTree"; 50 + description = "ctx definition or namespace"; 51 + check = lib.isAttrs; 52 + merge = 53 + loc: defs: 54 + let 55 + hasKey = x: x ? into || x ? provides || x ? _ || x ? includes || x ? __functor || x ? _module; 56 + isLeaf = lib.any (d: hasKey d.value) defs; 57 + in 58 + if isLeaf then ctxSubmodule.merge loc defs else (lib.types.lazyAttrsOf ctxTreeType).merge loc defs; 59 + emptyValue = { 60 + value = { }; 61 + }; 62 + }; 63 + 40 64 in 41 65 { 42 66 options.den.ctx = lib.mkOption { 43 67 default = { }; 44 - type = lib.types.lazyAttrsOf ctxSubmodule; 68 + type = lib.types.lazyAttrsOf ctxTreeType; 45 69 }; 46 70 }
+88
templates/ci/modules/features/context/nested-ctx-providers.nix
··· 1 + { denTest, lib, ... }: 2 + { 3 + flake.tests.ctx-nested-providers = { 4 + 5 + # Cross-providers for nested ctx should use the FULL PATH, not local name. 6 + # root.provides.ns.inner targets ctx at den.ctx.ns.inner specifically. 7 + test-nested-cross-provider = denTest ( 8 + { den, funnyNames, ... }: 9 + { 10 + den.ctx.ns.inner._.inner = 11 + { z }: 12 + { 13 + funny.names = [ "inner-${z}" ]; 14 + }; 15 + 16 + den.ctx.root.into = 17 + { z }: 18 + { 19 + ns.inner = [ { inherit z; } ]; 20 + }; 21 + 22 + den.ctx.root.provides.${"ns.inner"} = 23 + { z }: 24 + { 25 + funny.names = [ "root-for-inner-${z}" ]; 26 + }; 27 + 28 + expr = funnyNames (den.ctx.root { z = "x"; }); 29 + expected = [ 30 + "inner-x" 31 + "root-for-inner-x" 32 + ]; 33 + } 34 + ); 35 + 36 + # Two nested contexts with the same local name must get independent cross-providers. 37 + test-no-cross-provider-collision = denTest ( 38 + { den, funnyNames, ... }: 39 + { 40 + den.ctx.a.leaf._.leaf = 41 + { v }: 42 + { 43 + funny.names = [ "a-${v}" ]; 44 + }; 45 + den.ctx.b.leaf._.leaf = 46 + { v }: 47 + { 48 + funny.names = [ "b-${v}" ]; 49 + }; 50 + 51 + den.ctx.root.into = _: { 52 + a.leaf = [ { v = "x"; } ]; 53 + b.leaf = [ { v = "y"; } ]; 54 + }; 55 + 56 + den.ctx.root.provides.${"a.leaf"} = 57 + { v }: 58 + { 59 + funny.names = [ "cross-a-${v}" ]; 60 + }; 61 + 62 + expr = funnyNames (den.ctx.root { }); 63 + expected = [ 64 + "a-x" 65 + "b-y" 66 + "cross-a-x" 67 + ]; 68 + } 69 + ); 70 + 71 + # Attrset-form into with nested keys: into.ns.inner = fn 72 + test-nested-attrset-into = denTest ( 73 + { den, funnyNames, ... }: 74 + { 75 + den.ctx.ns.inner._.inner = 76 + { z }: 77 + { 78 + funny.names = [ "inner-${z}" ]; 79 + }; 80 + 81 + den.ctx.root.into.ns.inner = lib.singleton; 82 + 83 + expr = funnyNames (den.ctx.root { z = "q"; }); 84 + expected = [ "inner-q" ]; 85 + } 86 + ); 87 + }; 88 + }
+125
templates/ci/modules/features/context/nested-ctx.nix
··· 1 + { denTest, lib, ... }: 2 + { 3 + flake.tests.ctx-nested = { 4 + 5 + test-two-level-nesting = denTest ( 6 + { den, funnyNames, ... }: 7 + { 8 + den.ctx.ns.inner._.inner = 9 + { z }: 10 + { 11 + funny.names = [ "inner-${z}" ]; 12 + }; 13 + 14 + den.ctx.root._.root = 15 + { v }: 16 + { 17 + funny.names = [ v ]; 18 + }; 19 + den.ctx.root.into = 20 + { v }: 21 + { 22 + ns.inner = [ { z = v; } ]; 23 + }; 24 + 25 + expr = funnyNames (den.ctx.root { v = "hello"; }); 26 + expected = [ 27 + "hello" 28 + "inner-hello" 29 + ]; 30 + } 31 + ); 32 + 33 + test-three-level-nesting = denTest ( 34 + { den, funnyNames, ... }: 35 + { 36 + den.ctx.a.b.c._.c = 37 + { z }: 38 + { 39 + funny.names = [ "abc-${z}" ]; 40 + }; 41 + 42 + den.ctx.start.into = 43 + { z }: 44 + { 45 + a.b.c = [ { z = z; } ]; 46 + }; 47 + 48 + expr = funnyNames (den.ctx.start { z = "deep"; }); 49 + expected = [ "abc-deep" ]; 50 + } 51 + ); 52 + 53 + test-dedup-by-full-path = denTest ( 54 + { den, funnyNames, ... }: 55 + { 56 + den.ctx.a.leaf._.leaf = 57 + { v }: 58 + { 59 + funny.names = [ "a-${v}" ]; 60 + }; 61 + den.ctx.b.leaf._.leaf = 62 + { v }: 63 + { 64 + funny.names = [ "b-${v}" ]; 65 + }; 66 + 67 + den.ctx.root.into = _: { 68 + a.leaf = [ { v = "x"; } ]; 69 + b.leaf = [ { v = "y"; } ]; 70 + }; 71 + 72 + expr = funnyNames (den.ctx.root { }); 73 + expected = [ 74 + "a-x" 75 + "b-y" 76 + ]; 77 + } 78 + ); 79 + 80 + test-flat-still-works = denTest ( 81 + { den, funnyNames, ... }: 82 + { 83 + den.ctx.flat._.flat = 84 + { x }: 85 + { 86 + funny.names = [ x ]; 87 + }; 88 + 89 + den.ctx.root.into.flat = lib.singleton; 90 + 91 + expr = funnyNames (den.ctx.root { x = "hi"; }); 92 + expected = [ "hi" ]; 93 + } 94 + ); 95 + 96 + test-into-mixed-flat-and-nested = denTest ( 97 + { den, funnyNames, ... }: 98 + { 99 + den.ctx.ns.deep._.deep = 100 + { k }: 101 + { 102 + funny.names = [ "deep-${k}" ]; 103 + }; 104 + den.ctx.flat._.flat = 105 + { k }: 106 + { 107 + funny.names = [ "flat-${k}" ]; 108 + }; 109 + 110 + den.ctx.root.into = 111 + { k }: 112 + { 113 + flat = [ { inherit k; } ]; 114 + ns.deep = [ { inherit k; } ]; 115 + }; 116 + 117 + expr = funnyNames (den.ctx.root { k = "v"; }); 118 + expected = [ 119 + "deep-v" 120 + "flat-v" 121 + ]; 122 + } 123 + ); 124 + }; 125 + }