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
1# Core type system for aspect-oriented configuration
2
3lib:
4let
5 resolve = import ./resolve.nix lib;
6
7 # Type for computed values that only exist during evaluation
8 ignoredType = lib.types.mkOptionType {
9 name = "ignored type";
10 description = "ignored values";
11 merge = _loc: _defs: null;
12 check = _: true;
13 };
14
15 # Create internal read-only option with custom apply function
16 mkInternal =
17 desc: type: fn:
18 lib.mkOption {
19 internal = true;
20 visible = false;
21 readOnly = true;
22 description = desc;
23 inherit type;
24 apply = fn;
25 };
26
27 # Like lib.types.functionTo, but it does not merges all definitions, and keeps
28 # just the last one.
29 functorType = lib.mkOptionType {
30 name = "aspectFunctor";
31 description = "aspect functor function";
32 check = lib.isFunction;
33 merge =
34 loc: defs:
35 let
36 # Use only the last definition to avoid duplication from
37 # functionTo merging all definitions with the same args.
38 # All definitions receive the same merged `self`, so they
39 # produce equivalent results - picking one is correct.
40 lastDef = lib.last defs;
41 innerType = providerType;
42 in
43 {
44 __functionArgs = lib.functionArgs lastDef.value;
45 __functor =
46 _: callerArgs:
47 (lib.modules.mergeDefinitions (loc ++ [ "<function body>" ]) innerType [
48 {
49 inherit (lastDef) file;
50 value = lastDef.value callerArgs;
51 }
52 ]).mergedValue;
53 };
54 };
55
56 # Check if function has submodule-style arguments
57 isSubmoduleFn =
58 m:
59 lib.length (
60 lib.intersectLists [ "lib" "config" "options" "aspect" ] (lib.attrNames (lib.functionArgs m))
61 ) > 0;
62
63 # Check if function accepts { class } and/or { aspect-chain }
64 isProviderFn =
65 f:
66 let
67 args = lib.functionArgs f;
68 n = lib.length (lib.attrNames args);
69 in
70 (args ? class && n == 1)
71 || (args ? aspect-chain && n == 1)
72 || (args ? class && args ? aspect-chain && n == 2);
73
74 # Direct provider function: ({ class, aspect-chain }) → aspect
75 directProviderFn = lib.types.addCheck (lib.types.functionTo aspectSubmodule) isProviderFn;
76
77 # Curried provider function: (params) → provider (enables parametrization)
78 curriedProviderFn = lib.types.addCheck (lib.types.functionTo providerType) (
79 f:
80 builtins.isFunction f
81 || lib.isAttrs f && lib.subtractLists [ "__functor" "__functionArgs" ] (lib.attrNames f) == [ ]
82 );
83
84 # Any provider function: direct or curried
85 providerFn = lib.types.either directProviderFn curriedProviderFn;
86
87 # Provider type: function or aspect that can provide configurations
88 providerType = lib.types.either providerFn aspectSubmodule;
89
90 # Core aspect submodule with all aspect properties
91 aspectSubmodule = lib.types.submodule (
92 { name, config, ... }:
93 {
94 freeformType = lib.types.lazyAttrsOf lib.types.deferredModule;
95 config._module.args.aspect = config;
96 imports = [ (lib.mkAliasOptionModule [ "_" ] [ "provides" ]) ];
97
98 options = {
99 name = lib.mkOption {
100 description = "Aspect name";
101 default = name;
102 type = lib.types.str;
103 };
104
105 description = lib.mkOption {
106 description = "Aspect description";
107 default = "Aspect ${name}";
108 type = lib.types.str;
109 };
110
111 includes = lib.mkOption {
112 description = "Providers to ask aspects from";
113 type = lib.types.listOf providerType;
114 default = [ ];
115 };
116
117 provides = lib.mkOption {
118 description = "Providers of aspect for other aspects";
119 default = { };
120 type = lib.types.submodule (
121 { config, ... }:
122 {
123 freeformType = lib.types.lazyAttrsOf providerType;
124 config._module.args.aspects = config;
125 }
126 );
127 };
128
129 __functor = lib.mkOption {
130 internal = true;
131 visible = false;
132 description = "Functor to default provider";
133 type = functorType;
134 default = aspect: { class, aspect-chain }: if true || (class aspect-chain) then aspect else aspect;
135 };
136
137 modules = mkInternal "resolved modules from this aspect" ignoredType (
138 _: lib.mapAttrs (class: _: config.resolve { inherit class; }) config
139 );
140
141 resolve = mkInternal "function to resolve a module from this aspect" ignoredType (
142 _:
143 {
144 class,
145 aspect-chain ? [ ],
146 }:
147 resolve class aspect-chain (config {
148 inherit class aspect-chain;
149 })
150 );
151 };
152 }
153 );
154
155in
156{
157 # Top-level aspects container with fixpoint semantics
158 aspectsType = lib.types.submodule (
159 { config, ... }:
160 {
161 freeformType = lib.types.lazyAttrsOf (
162 lib.types.either (lib.types.addCheck aspectSubmodule (
163 m: (!builtins.isFunction m) || isSubmoduleFn m
164 )) providerType
165 );
166 config._module.args.aspects = config;
167 }
168 );
169
170 inherit aspectSubmodule providerType;
171}