Import all nix files in a directory tree. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ dendrix.oeiuwq.com/Dendritic.html
dendritic inputs
at main 201 lines 5.8 kB view raw
1let 2 perform = 3 { 4 lib ? null, 5 pipef ? null, 6 initf ? null, 7 filterf, 8 mapf, 9 paths, 10 ... 11 }: 12 path: 13 let 14 result = 15 if pipef == null then 16 { imports = [ module ]; } 17 else if lib == null then 18 throw "You need to call withLib before trying to read the tree." 19 else 20 pipef (leafs lib path); 21 22 # module exists so we delay access to lib til we are part of the module system. 23 module = 24 { lib, ... }: 25 { 26 imports = leafs lib path; 27 }; 28 29 leafs = 30 lib: 31 let 32 treeFiles = t: (t.withLib lib).files; 33 34 listFilesRecursive = 35 x: 36 if isImportTree x then 37 treeFiles x 38 else if hasOutPath x then 39 listFilesRecursive x.outPath 40 else if isDirectory x then 41 lib.filesystem.listFilesRecursive x 42 else 43 [ x ]; 44 45 nixFilter = andNot (lib.hasInfix "/_") (lib.hasSuffix ".nix"); 46 47 initialFilter = if initf != null then initf else nixFilter; 48 49 pathFilter = compose (and filterf initialFilter) toString; 50 51 otherFilter = and filterf (if initf != null then initf else (_: true)); 52 53 filter = x: if isPathLike x then pathFilter x else otherFilter x; 54 55 isFileRelative = 56 root: 57 { file, rel }: 58 if file != null && lib.hasPrefix root file then 59 { 60 file = null; 61 rel = lib.removePrefix root file; 62 } 63 else 64 { inherit file rel; }; 65 getFileRelative = { file, rel }: if rel == null then file else rel; 66 67 makeRelative = 68 roots: 69 lib.pipe roots [ 70 (lib.lists.flatten) 71 (builtins.filter isDirectory) 72 (builtins.map builtins.toString) 73 (builtins.map isFileRelative) 74 (fx: fx ++ [ getFileRelative ]) 75 ( 76 fx: file: 77 lib.pipe { 78 file = builtins.toString file; 79 rel = null; 80 } fx 81 ) 82 ]; 83 84 rootRelative = 85 roots: 86 let 87 mkRel = makeRelative roots; 88 in 89 x: if isPathLike x then mkRel x else x; 90 in 91 root: 92 lib.pipe 93 [ paths root ] 94 [ 95 (lib.lists.flatten) 96 (map listFilesRecursive) 97 (lib.lists.flatten) 98 (builtins.filter ( 99 compose filter (rootRelative [ 100 paths 101 root 102 ]) 103 )) 104 (map mapf) 105 ]; 106 107 in 108 result; 109 110 compose = 111 g: f: x: 112 g (f x); 113 114 # Applies the second filter first, to allow partial application when building the configuration. 115 and = 116 g: f: x: 117 f x && g x; 118 119 andNot = g: and (x: !(g x)); 120 121 matchesRegex = re: p: builtins.match re p != null; 122 123 mapAttr = 124 attrs: k: f: 125 attrs // { ${k} = f attrs.${k}; }; 126 127 isDirectory = and (x: builtins.readFileType x == "directory") isPathLike; 128 129 isPathLike = x: builtins.isPath x || builtins.isString x || hasOutPath x; 130 131 hasOutPath = and (x: x ? outPath) builtins.isAttrs; 132 133 isImportTree = and (x: x ? __config.__functor) builtins.isAttrs; 134 135 inModuleEval = and (x: x ? options) builtins.isAttrs; 136 137 functor = self: arg: perform self.__config (if inModuleEval arg then [ ] else arg); 138 139 callable = 140 let 141 initial = { 142 # Accumulated configuration 143 api = { }; 144 mapf = (i: i); 145 filterf = _: true; 146 paths = [ ]; 147 148 # config is our state (initial at first). this functor allows it 149 # to work as if it was a function, taking an update function 150 # that will return a new state. for example: 151 # in mergeAttrs: `config (c: c // x)` will merge x into new config. 152 __functor = 153 config: update: 154 let 155 # updated is another config 156 updated = update config; 157 158 # current is the result of this functor. 159 # it is not a config, but an import-tree object containing a __config. 160 current = config update; 161 boundAPI = builtins.mapAttrs (_: g: g current) updated.api; 162 163 # these two helpers are used to **append** aggregated configs. 164 accAttr = attrName: acc: config (c: mapAttr (update c) attrName acc); 165 mergeAttrs = attrs: config (c: (update c) // attrs); 166 in 167 boundAPI 168 // { 169 __config = updated; 170 __functor = functor; # user-facing callable 171 172 # Configuration updates (accumulating) 173 filter = filterf: accAttr "filterf" (and filterf); 174 filterNot = filterf: accAttr "filterf" (andNot filterf); 175 match = regex: accAttr "filterf" (and (matchesRegex regex)); 176 matchNot = regex: accAttr "filterf" (andNot (matchesRegex regex)); 177 map = mapf: accAttr "mapf" (compose mapf); 178 addPath = path: accAttr "paths" (p: p ++ [ path ]); 179 addAPI = api: accAttr "api" (a: a // api); 180 181 # Configuration updates (non-accumulating) 182 withLib = lib: mergeAttrs { inherit lib; }; 183 initFilter = initf: mergeAttrs { inherit initf; }; 184 pipeTo = pipef: mergeAttrs { inherit pipef; }; 185 leafs = mergeAttrs { pipef = (i: i); }; 186 187 # Applies empty (for already path-configured trees) 188 result = current [ ]; 189 190 # Return a list of all filtered files. 191 files = current.leafs.result; 192 193 # returns the original empty state 194 new = callable; 195 }; 196 }; 197 in 198 initial (config: config); 199 200in 201callable