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
at main 171 lines 5.1 kB view raw
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}