Import all nix files in a directory tree. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ dendrix.oeiuwq.com/Dendritic.html
dendritic inputs

Compare changes

Choose any two refs to compare.

+341 -311
+12
.tangled/workflows/mirror.yml
··· 1 + when: 2 + - event: ["push"] 3 + branch: ["*"] 4 + engine: "nixery" 5 + clone: 6 + skip: true 7 + dependencies: 8 + nixpkgs: 9 + - gh 10 + steps: 11 + - name: mirror 12 + command: gh -R vic/vic issue comment 3 -b import-tree
+18 -7
README.md
··· 1 + <p align="right"> 2 + <a href="https://github.com/sponsors/vic"><img src="https://img.shields.io/badge/sponsor-vic-white?logo=githubsponsors&logoColor=white&labelColor=%23FF0000" alt="Sponsor Vic"/> 3 + </a> 4 + <a href="https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries"> <img src="https://img.shields.io/badge/Dendritic-Nix-informational?logo=nixos&logoColor=white" alt="Dendritic Nix"/> </a> 5 + <a href="LICENSE"> <img src="https://img.shields.io/github/license/vic/import-tree" alt="License"/> </a> 6 + <a href="https://github.com/vic/import-tree/actions"> 7 + <img src="https://github.com/vic/import-tree/actions/workflows/test.yml/badge.svg" alt="CI Status"/> </a> 8 + </p> 9 + 10 + > `import-tree` and [vic](https://bsky.app/profile/oeiuwq.bsky.social)'s [dendritic libs](https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries) made for you with Love++ and AI--. If you like my work, consider [sponsoring](https://github.com/sponsors/vic) 11 + 1 12 # ๐ŸŒฒ๐ŸŒด import-tree ๐ŸŽ„๐ŸŒณ 2 13 3 14 > Recursively import [Nix modules](https://nix.dev/tutorials/module-system/) from a directory, with a simple, extensible API. ··· 55 66 56 67 For more advanced usage, `import-tree` can be configured via its extensible API. 57 68 58 - ______________________________________________________________________ 69 + --- 59 70 60 71 #### Obtaining the API 61 72 ··· 217 228 218 229 ##### ๐ŸŒณ `import-tree.initFilter` 219 230 220 - *Replaces* the initial filter which defaults to: Include files with `.nix` suffix and not having `/_` infix. 231 + _Replaces_ the initial filter which defaults to: Include files with `.nix` suffix and not having `/_` infix. 221 232 222 233 ```nix 223 234 import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/ignored/" p) ··· 246 257 (import-tree.addPath ./modules) [ ] 247 258 ``` 248 259 249 - ______________________________________________________________________ 260 + --- 250 261 251 262 ## Why 252 263 ··· 269 280 270 281 @vic is using this on [Dendrix](https://github.com/vic/dendrix) for [community conventions](https://github.com/vic/dendrix/blob/main/dev/modules/community/_pipeline.nix) on tagging files. 271 282 272 - This would allow us to have community-driven *sets* of configurations, 283 + This would allow us to have community-driven _sets_ of configurations, 273 284 much like those popular for editors: spacemacs/lazy-vim distributions. 274 285 275 286 Imagine an editor distribution exposing the following flake output: ··· 277 288 ```nix 278 289 # editor-distro's flakeModule 279 290 {inputs, lib, ...}: 280 - let 291 + let 281 292 flake.lib.modules-tree = lib.pipe inputs.import-tree [ 282 293 (i: i.addPath ./modules) 283 294 (i: i.addAPI { inherit on off exclusive; }) ··· 312 323 } 313 324 ``` 314 325 315 - ______________________________________________________________________ 326 + --- 316 327 317 328 ## Testing 318 329 ··· 321 332 The test suite can be found in [`checkmate.nix`](checkmate.nix). To run it locally: 322 333 323 334 ```sh 324 - nix flake check path:checkmate --override-input target path:. 335 + nix flake check github:vic/checkmate --override-input target path:. 325 336 ``` 326 337 327 338 Run the following to format files:
-1
checkmate/.gitignore
··· 1 - flake.lock
-6
checkmate/flake.nix
··· 1 - { 2 - inputs.target.url = "path:.."; 3 - inputs.checkmate.url = "github:vic/checkmate"; 4 - inputs.checkmate.inputs.target.follows = "target"; 5 - outputs = inputs: inputs.checkmate.lib.newFlake; 6 - }
+3
checkmate/modules/formatter.nix
··· 1 + { 2 + perSystem.treefmt.settings.global.excludes = [ "checkmate/tree/*" ]; 3 + }
+267
checkmate/modules/tests.nix
··· 1 + # If formatting fails, run 2 + # nix run github:vic/checkmate#checkmate-treefmt 3 + # 4 + { inputs, lib, ... }: 5 + let 6 + # since we are tested by github:vic/checkmate 7 + it = inputs.target; 8 + lit = it.withLib lib; 9 + in 10 + { 11 + perSystem = ( 12 + { ... }: 13 + { 14 + nix-unit.tests = { 15 + leafs."test fails if no lib has been set" = { 16 + expr = it.leafs ../tree; 17 + expectedError.type = "ThrownError"; 18 + }; 19 + 20 + leafs."test succeeds when lib has been set" = { 21 + expr = (it.withLib lib).leafs ../tree/hello; 22 + expected = [ ]; 23 + }; 24 + 25 + leafs."test only returns nix non-ignored files" = { 26 + expr = lit.leafs ../tree/a; 27 + expected = [ 28 + ../tree/a/a_b.nix 29 + ../tree/a/b/b_a.nix 30 + ../tree/a/b/m.nix 31 + ]; 32 + }; 33 + 34 + filter."test returns empty if no nix files with true predicate" = { 35 + expr = (lit.filter (_: false)).leafs ../tree; 36 + expected = [ ]; 37 + }; 38 + 39 + filter."test only returns nix files with true predicate" = { 40 + expr = (lit.filter (lib.hasSuffix "m.nix")).leafs ../tree; 41 + expected = [ ../tree/a/b/m.nix ]; 42 + }; 43 + 44 + filter."test multiple `filter`s compose" = { 45 + expr = ((lit.filter (lib.hasInfix "b/")).filter (lib.hasInfix "_")).leafs ../tree; 46 + expected = [ ../tree/a/b/b_a.nix ]; 47 + }; 48 + 49 + match."test returns empty if no files match regex" = { 50 + expr = (lit.match "badregex").leafs ../tree; 51 + expected = [ ]; 52 + }; 53 + 54 + match."test returns files matching regex" = { 55 + expr = (lit.match ".*/[^/]+_[^/]+\.nix").leafs ../tree; 56 + expected = [ 57 + ../tree/a/a_b.nix 58 + ../tree/a/b/b_a.nix 59 + ]; 60 + }; 61 + 62 + matchNot."test returns files not matching regex" = { 63 + expr = (lit.matchNot ".*/[^/]+_[^/]+\.nix").leafs ../tree/a/b; 64 + expected = [ 65 + ../tree/a/b/m.nix 66 + ]; 67 + }; 68 + 69 + match."test `match` composes with `filter`" = { 70 + expr = ((lit.match ".*a_b.nix").filter (lib.hasInfix "/a/")).leafs ../tree; 71 + expected = [ ../tree/a/a_b.nix ]; 72 + }; 73 + 74 + match."test multiple `match`s compose" = { 75 + expr = ((lit.match ".*/[^/]+_[^/]+\.nix").match ".*b\.nix").leafs ../tree; 76 + expected = [ ../tree/a/a_b.nix ]; 77 + }; 78 + 79 + map."test transforms each matching file with function" = { 80 + expr = (lit.map import).leafs ../tree/x; 81 + expected = [ "z" ]; 82 + }; 83 + 84 + map."test `map` composes with `filter`" = { 85 + expr = ((lit.filter (lib.hasInfix "/x")).map import).leafs ../tree; 86 + expected = [ "z" ]; 87 + }; 88 + 89 + map."test multiple `map`s compose" = { 90 + expr = ((lit.map import).map builtins.stringLength).leafs ../tree/x; 91 + expected = [ 1 ]; 92 + }; 93 + 94 + addPath."test `addPath` prepends a path to filter" = { 95 + expr = (lit.addPath ../tree/x).files; 96 + expected = [ ../tree/x/y.nix ]; 97 + }; 98 + 99 + addPath."test `addPath` can be called multiple times" = { 100 + expr = ((lit.addPath ../tree/x).addPath ../tree/a/b).files; 101 + expected = [ 102 + ../tree/x/y.nix 103 + ../tree/a/b/b_a.nix 104 + ../tree/a/b/m.nix 105 + ]; 106 + }; 107 + 108 + addPath."test `addPath` identity" = { 109 + expr = ((lit.addPath ../tree/x).addPath ../tree/a/b).files; 110 + expected = lit.leafs [ 111 + ../tree/x 112 + ../tree/a/b 113 + ]; 114 + }; 115 + 116 + new."test `new` returns a clear state" = { 117 + expr = lib.pipe lit [ 118 + (i: i.addPath ../tree/x) 119 + (i: i.addPath ../tree/a/b) 120 + (i: i.new) 121 + (i: i.addPath ../tree/modules/hello-world) 122 + (i: i.withLib lib) 123 + (i: i.files) 124 + ]; 125 + expected = [ ../tree/modules/hello-world/mod.nix ]; 126 + }; 127 + 128 + initFilter."test can change the initial filter to look for other file types" = { 129 + expr = (lit.initFilter (p: lib.hasSuffix ".txt" p)).leafs [ ../tree/a ]; 130 + expected = [ ../tree/a/a.txt ]; 131 + }; 132 + 133 + initFilter."test initf does filter non-paths" = { 134 + expr = 135 + let 136 + mod = (it.initFilter (x: !(x ? config.boom))) [ 137 + { 138 + options.hello = lib.mkOption { 139 + default = "world"; 140 + type = lib.types.str; 141 + }; 142 + } 143 + { 144 + config.boom = "boom"; 145 + } 146 + ]; 147 + res = lib.modules.evalModules { modules = [ mod ]; }; 148 + in 149 + res.config.hello; 150 + expected = "world"; 151 + }; 152 + 153 + addAPI."test extends the API available on an import-tree object" = { 154 + expr = 155 + let 156 + extended = lit.addAPI { helloOption = self: self.addPath ../tree/modules/hello-option; }; 157 + in 158 + extended.helloOption.files; 159 + expected = [ ../tree/modules/hello-option/mod.nix ]; 160 + }; 161 + 162 + addAPI."test preserves previous API extensions on an import-tree object" = { 163 + expr = 164 + let 165 + first = lit.addAPI { helloOption = self: self.addPath ../tree/modules/hello-option; }; 166 + second = first.addAPI { helloWorld = self: self.addPath ../tree/modules/hello-world; }; 167 + extended = second.addAPI { res = self: self.helloOption.files; }; 168 + in 169 + extended.res; 170 + expected = [ ../tree/modules/hello-option/mod.nix ]; 171 + }; 172 + 173 + addAPI."test API extensions are late bound" = { 174 + expr = 175 + let 176 + first = lit.addAPI { res = self: self.late; }; 177 + extended = first.addAPI { late = _self: "hello"; }; 178 + in 179 + extended.res; 180 + expected = "hello"; 181 + }; 182 + 183 + pipeTo."test pipes list into a function" = { 184 + expr = (lit.map lib.pathType).pipeTo (lib.length) ../tree/x; 185 + expected = 1; 186 + }; 187 + 188 + import-tree."test does not break if given a path to a file instead of a directory." = { 189 + expr = lit.leafs ../tree/x/y.nix; 190 + expected = [ ../tree/x/y.nix ]; 191 + }; 192 + 193 + import-tree."test returns a module with a single imported nested module having leafs" = { 194 + expr = 195 + let 196 + oneElement = arr: if lib.length arr == 1 then lib.elemAt arr 0 else throw "Expected one element"; 197 + module = it ../tree/x; 198 + inner = (oneElement module.imports) { inherit lib; }; 199 + in 200 + oneElement inner.imports; 201 + expected = ../tree/x/y.nix; 202 + }; 203 + 204 + import-tree."test evaluates returned module as part of module-eval" = { 205 + expr = 206 + let 207 + res = lib.modules.evalModules { modules = [ (it ../tree/modules) ]; }; 208 + in 209 + res.config.hello; 210 + expected = "world"; 211 + }; 212 + 213 + import-tree."test can itself be used as a module" = { 214 + expr = 215 + let 216 + res = lib.modules.evalModules { modules = [ (it.addPath ../tree/modules) ]; }; 217 + in 218 + res.config.hello; 219 + expected = "world"; 220 + }; 221 + 222 + import-tree."test take as arg anything path convertible" = { 223 + expr = lit.leafs [ 224 + { 225 + outPath = ../tree/modules/hello-world; 226 + } 227 + ]; 228 + expected = [ ../tree/modules/hello-world/mod.nix ]; 229 + }; 230 + 231 + import-tree."test passes non-paths without string conversion" = { 232 + expr = 233 + let 234 + mod = it [ 235 + { 236 + options.hello = lib.mkOption { 237 + default = "world"; 238 + type = lib.types.str; 239 + }; 240 + } 241 + ]; 242 + res = lib.modules.evalModules { modules = [ mod ]; }; 243 + in 244 + res.config.hello; 245 + expected = "world"; 246 + }; 247 + 248 + import-tree."test can take other import-trees as if they were paths" = { 249 + expr = (lit.filter (lib.hasInfix "mod")).leafs [ 250 + (it.addPath ../tree/modules/hello-option) 251 + ../tree/modules/hello-world 252 + ]; 253 + expected = [ 254 + ../tree/modules/hello-option/mod.nix 255 + ../tree/modules/hello-world/mod.nix 256 + ]; 257 + }; 258 + 259 + leafs."test loads from hidden directory but excludes sub-hidden" = { 260 + expr = lit.leafs ../tree/a/b/_c; 261 + expected = [ ../tree/a/b/_c/d/e.nix ]; 262 + }; 263 + }; 264 + 265 + } 266 + ); 267 + }
+1
checkmate/tree/a/a.txt
··· 1 + hello
+1
checkmate/tree/a/a_b.nix
··· 1 + { }
+1
checkmate/tree/a/b/_c/d/_f.nix
··· 1 + true
+1
checkmate/tree/a/b/_c/d/e.nix
··· 1 + { }
+1
checkmate/tree/a/b/_n.nix
··· 1 + { }
+1
checkmate/tree/a/b/b_a.nix
··· 1 + { }
+1
checkmate/tree/a/b/m.nix
··· 1 + { }
+1
checkmate/tree/hello/world
··· 1 + hola
+7
checkmate/tree/modules/hello-option/mod.nix
··· 1 + { lib, ... }: 2 + { 3 + options.hello = lib.mkOption { 4 + type = lib.types.str; 5 + default = "goodbye"; 6 + }; 7 + }
+3
checkmate/tree/modules/hello-world/mod.nix
··· 1 + { 2 + hello = "world"; 3 + }
+1
checkmate/tree/x/y.nix
··· 1 + "z"
-267
checkmate.nix
··· 1 - # If formatting fails, run 2 - # nix run github:vic/checkmate#checkmate-treefmt 3 - # 4 - { inputs, lib, ... }: 5 - let 6 - # since we are tested by github:vic/checkmate 7 - it = inputs.target; 8 - lit = it.withLib lib; 9 - in 10 - { 11 - perSystem = ( 12 - { ... }: 13 - { 14 - nix-unit.tests = { 15 - leafs."test fails if no lib has been set" = { 16 - expr = it.leafs ./trees; 17 - expectedError.type = "ThrownError"; 18 - }; 19 - 20 - leafs."test succeeds when lib has been set" = { 21 - expr = (it.withLib lib).leafs ./tree/hello; 22 - expected = [ ]; 23 - }; 24 - 25 - leafs."test only returns nix non-ignored files" = { 26 - expr = lit.leafs ./tree/a; 27 - expected = [ 28 - ./tree/a/a_b.nix 29 - ./tree/a/b/b_a.nix 30 - ./tree/a/b/m.nix 31 - ]; 32 - }; 33 - 34 - filter."test returns empty if no nix files with true predicate" = { 35 - expr = (lit.filter (_: false)).leafs ./tree; 36 - expected = [ ]; 37 - }; 38 - 39 - filter."test only returns nix files with true predicate" = { 40 - expr = (lit.filter (lib.hasSuffix "m.nix")).leafs ./tree; 41 - expected = [ ./tree/a/b/m.nix ]; 42 - }; 43 - 44 - filter."test multiple `filter`s compose" = { 45 - expr = ((lit.filter (lib.hasInfix "b/")).filter (lib.hasInfix "_")).leafs ./tree; 46 - expected = [ ./tree/a/b/b_a.nix ]; 47 - }; 48 - 49 - match."test returns empty if no files match regex" = { 50 - expr = (lit.match "badregex").leafs ./tree; 51 - expected = [ ]; 52 - }; 53 - 54 - match."test returns files matching regex" = { 55 - expr = (lit.match ".*/[^/]+_[^/]+\.nix").leafs ./tree; 56 - expected = [ 57 - ./tree/a/a_b.nix 58 - ./tree/a/b/b_a.nix 59 - ]; 60 - }; 61 - 62 - matchNot."test returns files not matching regex" = { 63 - expr = (lit.matchNot ".*/[^/]+_[^/]+\.nix").leafs ./tree/a/b; 64 - expected = [ 65 - ./tree/a/b/m.nix 66 - ]; 67 - }; 68 - 69 - match."test `match` composes with `filter`" = { 70 - expr = ((lit.match ".*a_b.nix").filter (lib.hasInfix "/a/")).leafs ./tree; 71 - expected = [ ./tree/a/a_b.nix ]; 72 - }; 73 - 74 - match."test multiple `match`s compose" = { 75 - expr = ((lit.match ".*/[^/]+_[^/]+\.nix").match ".*b\.nix").leafs ./tree; 76 - expected = [ ./tree/a/a_b.nix ]; 77 - }; 78 - 79 - map."test transforms each matching file with function" = { 80 - expr = (lit.map import).leafs ./tree/x; 81 - expected = [ "z" ]; 82 - }; 83 - 84 - map."test `map` composes with `filter`" = { 85 - expr = ((lit.filter (lib.hasInfix "/x")).map import).leafs ./tree; 86 - expected = [ "z" ]; 87 - }; 88 - 89 - map."test multiple `map`s compose" = { 90 - expr = ((lit.map import).map builtins.stringLength).leafs ./tree/x; 91 - expected = [ 1 ]; 92 - }; 93 - 94 - addPath."test `addPath` prepends a path to filter" = { 95 - expr = (lit.addPath ./tree/x).files; 96 - expected = [ ./tree/x/y.nix ]; 97 - }; 98 - 99 - addPath."test `addPath` can be called multiple times" = { 100 - expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).files; 101 - expected = [ 102 - ./tree/x/y.nix 103 - ./tree/a/b/b_a.nix 104 - ./tree/a/b/m.nix 105 - ]; 106 - }; 107 - 108 - addPath."test `addPath` identity" = { 109 - expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).files; 110 - expected = lit.leafs [ 111 - ./tree/x 112 - ./tree/a/b 113 - ]; 114 - }; 115 - 116 - new."test `new` returns a clear state" = { 117 - expr = lib.pipe lit [ 118 - (i: i.addPath ./tree/x) 119 - (i: i.addPath ./tree/a/b) 120 - (i: i.new) 121 - (i: i.addPath ./tree/modules/hello-world) 122 - (i: i.withLib lib) 123 - (i: i.files) 124 - ]; 125 - expected = [ ./tree/modules/hello-world/mod.nix ]; 126 - }; 127 - 128 - initFilter."test can change the initial filter to look for other file types" = { 129 - expr = (lit.initFilter (p: lib.hasSuffix ".txt" p)).leafs [ ./tree/a ]; 130 - expected = [ ./tree/a/a.txt ]; 131 - }; 132 - 133 - initFilter."test initf does filter non-paths" = { 134 - expr = 135 - let 136 - mod = (it.initFilter (x: !(x ? config.boom))) [ 137 - { 138 - options.hello = lib.mkOption { 139 - default = "world"; 140 - type = lib.types.str; 141 - }; 142 - } 143 - { 144 - config.boom = "boom"; 145 - } 146 - ]; 147 - res = lib.modules.evalModules { modules = [ mod ]; }; 148 - in 149 - res.config.hello; 150 - expected = "world"; 151 - }; 152 - 153 - addAPI."test extends the API available on an import-tree object" = { 154 - expr = 155 - let 156 - extended = lit.addAPI { helloOption = self: self.addPath ./tree/modules/hello-option; }; 157 - in 158 - extended.helloOption.files; 159 - expected = [ ./tree/modules/hello-option/mod.nix ]; 160 - }; 161 - 162 - addAPI."test preserves previous API extensions on an import-tree object" = { 163 - expr = 164 - let 165 - first = lit.addAPI { helloOption = self: self.addPath ./tree/modules/hello-option; }; 166 - second = first.addAPI { helloWorld = self: self.addPath ./tree/modules/hello-world; }; 167 - extended = second.addAPI { res = self: self.helloOption.files; }; 168 - in 169 - extended.res; 170 - expected = [ ./tree/modules/hello-option/mod.nix ]; 171 - }; 172 - 173 - addAPI."test API extensions are late bound" = { 174 - expr = 175 - let 176 - first = lit.addAPI { res = self: self.late; }; 177 - extended = first.addAPI { late = _self: "hello"; }; 178 - in 179 - extended.res; 180 - expected = "hello"; 181 - }; 182 - 183 - pipeTo."test pipes list into a function" = { 184 - expr = (lit.map lib.pathType).pipeTo (lib.length) ./tree/x; 185 - expected = 1; 186 - }; 187 - 188 - import-tree."test does not break if given a path to a file instead of a directory." = { 189 - expr = lit.leafs ./tree/x/y.nix; 190 - expected = [ ./tree/x/y.nix ]; 191 - }; 192 - 193 - import-tree."test returns a module with a single imported nested module having leafs" = { 194 - expr = 195 - let 196 - oneElement = arr: if lib.length arr == 1 then lib.elemAt arr 0 else throw "Expected one element"; 197 - module = it ./tree/x; 198 - inner = (oneElement module.imports) { inherit lib; }; 199 - in 200 - oneElement inner.imports; 201 - expected = ./tree/x/y.nix; 202 - }; 203 - 204 - import-tree."test evaluates returned module as part of module-eval" = { 205 - expr = 206 - let 207 - res = lib.modules.evalModules { modules = [ (it ./tree/modules) ]; }; 208 - in 209 - res.config.hello; 210 - expected = "world"; 211 - }; 212 - 213 - import-tree."test can itself be used as a module" = { 214 - expr = 215 - let 216 - res = lib.modules.evalModules { modules = [ (it.addPath ./tree/modules) ]; }; 217 - in 218 - res.config.hello; 219 - expected = "world"; 220 - }; 221 - 222 - import-tree."test take as arg anything path convertible" = { 223 - expr = lit.leafs [ 224 - { 225 - outPath = ./tree/modules/hello-world; 226 - } 227 - ]; 228 - expected = [ ./tree/modules/hello-world/mod.nix ]; 229 - }; 230 - 231 - import-tree."test passes non-paths without string conversion" = { 232 - expr = 233 - let 234 - mod = it [ 235 - { 236 - options.hello = lib.mkOption { 237 - default = "world"; 238 - type = lib.types.str; 239 - }; 240 - } 241 - ]; 242 - res = lib.modules.evalModules { modules = [ mod ]; }; 243 - in 244 - res.config.hello; 245 - expected = "world"; 246 - }; 247 - 248 - import-tree."test can take other import-trees as if they were paths" = { 249 - expr = (lit.filter (lib.hasInfix "mod")).leafs [ 250 - (it.addPath ./tree/modules/hello-option) 251 - ./tree/modules/hello-world 252 - ]; 253 - expected = [ 254 - ./tree/modules/hello-option/mod.nix 255 - ./tree/modules/hello-world/mod.nix 256 - ]; 257 - }; 258 - 259 - leafs."test loads from hidden directory but excludes sub-hidden" = { 260 - expr = lit.leafs ./tree/a/b/_c; 261 - expected = [ ./tree/a/b/_c/d/e.nix ]; 262 - }; 263 - }; 264 - 265 - } 266 - ); 267 - }
+22 -11
default.nix
··· 138 138 139 139 callable = 140 140 let 141 - __config = { 141 + initial = { 142 142 # Accumulated configuration 143 143 api = { }; 144 144 mapf = (i: i); 145 145 filterf = _: true; 146 146 paths = [ ]; 147 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. 148 152 __functor = 149 - self: f: 153 + config: update: 150 154 let 151 - __config = (f self); 152 - boundAPI = builtins.mapAttrs (_: g: g (self f)) __config.api; 153 - accAttr = attrName: acc: self (c: mapAttr (f c) attrName acc); 154 - mergeAttrs = attrs: self (c: (f c) // attrs); 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); 155 166 in 156 167 boundAPI 157 168 // { 158 - inherit __config; 159 - __functor = functor; 169 + __config = updated; 170 + __functor = functor; # user-facing callable 160 171 161 172 # Configuration updates (accumulating) 162 173 filter = filterf: accAttr "filterf" (and filterf); ··· 174 185 leafs = mergeAttrs { pipef = (i: i); }; 175 186 176 187 # Applies empty (for already path-configured trees) 177 - result = (self f) [ ]; 188 + result = current [ ]; 178 189 179 190 # Return a list of all filtered files. 180 - files = (self f).leafs.result; 191 + files = current.leafs.result; 181 192 182 193 # returns the original empty state 183 194 new = callable; 184 195 }; 185 196 }; 186 197 in 187 - __config (c: c); 198 + initial (config: config); 188 199 189 200 in 190 201 callable
-1
tree/a/a.txt
··· 1 - hello
-1
tree/a/a_b.nix
··· 1 - { }
-1
tree/a/b/_c/d/_f.nix
··· 1 - true
-1
tree/a/b/_c/d/e.nix
··· 1 - { }
-1
tree/a/b/_n.nix
··· 1 - { }
-1
tree/a/b/b_a.nix
··· 1 - { }
-1
tree/a/b/m.nix
··· 1 - { }
-1
tree/hello/world
··· 1 - hola
-7
tree/modules/hello-option/mod.nix
··· 1 - { lib, ... }: 2 - { 3 - options.hello = lib.mkOption { 4 - type = lib.types.str; 5 - default = "goodbye"; 6 - }; 7 - }
-3
tree/modules/hello-world/mod.nix
··· 1 - { 2 - hello = "world"; 3 - }
-1
tree/x/y.nix
··· 1 - "z"