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

feat: initFilter allows looking other file types, ignore other than /_. (#19)

* feat: initFilter allows looking other file types, ignore other than /_.

* Update README.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

authored by oeiuwq.com

Copilot and committed by
GitHub
21c639dc 234b5bb6

+83 -12
+41 -2
README.md
··· 2 2 3 3 > Helper functions for import of [Nixpkgs module system](https://nix.dev/tutorials/module-system/) modules under a directory recursively 4 4 5 - Module class agnostic; can be used for NixOS, nix-darwin, home-manager, flake-parts, NixVim. 5 + - Flake callable; Easy to use, intuitive for the most common use case: `inputs.import-tree ./modules` 6 + - Module class agnostic; can be used for NixOS, nix-darwin, home-manager, flake-parts, NixVim. 7 + - Can be used outside flakes as a dependencies-free lib; Just import our `./default.nix`. 8 + - Can be used to list other file types, not just `.nix`. See `.initFilter`, `.files` API. 9 + - Extensible API. import-tree objects are customizable. See `.addAPI`. 6 10 7 11 ## Quick Usage (with flake-parts) 8 12 ··· 29 33 30 34 ## Ignored files 31 35 32 - Paths that have a component that begins with an underscore are ignored. 36 + By default, paths having a component that begins with an underscore (`/_`) are ignored. 37 + 38 + This can be changed by using `.initFilter` API. 33 39 34 40 <details> 35 41 <summary> ··· 177 183 (import-tree.filter (lib.hasInfix ".mod.")).filter (lib.hasSuffix "default.nix") ./some-dir 178 184 ``` 179 185 186 + See also `import-tree.initFilter`. 187 + 180 188 ### `import-tree.match` and `import-tree.matchNot` 181 189 182 190 `match` takes a regular expression. The regex should match the full path for the path to be selected. Matching is done with `builtins.match`. ··· 296 304 297 305 Returns a fresh import-tree with empty state. If you previously had any path, lib, filter, etc, 298 306 you might need to set them on the new empty tree. 307 + 308 + ### `import-tree.initFilter` 309 + 310 + *Replaces* the initial filter which defaults to: Include files with `.nix` suffix and not having `/_` infix. 311 + 312 + _NOTE_: initFilter is non-accumulating and is the *first* filter to run before those accumulated via `filter`/`match`. 313 + 314 + You can use this to make import-tree scan for other file types or change the ignore convention. 315 + 316 + ```nix 317 + # import-tree.initFilter : (path -> bool) -> import-tree 318 + 319 + import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/ignored/") # nix files not inside /ignored/ 320 + import-tree.initFilter (lib.hasSuffix ".md") # scan for .md files everywhere, nothing ignored. 321 + ``` 322 + 323 + ### `import-tree.files` 324 + 325 + A shorthand for `import-tree.leafs.result`. Returns a list of matching files. 326 + 327 + This can be used when you don't want to import the tree, but just get a list of files from it. 328 + 329 + Useful for listing files other than `.nix`, for example, for passing all `.js` files to a minifier: 330 + 331 + _TIP_: remember to use `withLib` when *not* using import-tree as a module import. 332 + 333 + ```nix 334 + # import-tree.files : [ <list-of-files> ] 335 + 336 + ((import-tree.withLib lib).initFilter (lib.hasSuffix ".js")).files # => list of all .js files 337 + ``` 299 338 300 339 ### `import-tree.result` 301 340
+32 -7
checkmate.nix
··· 92 92 }; 93 93 94 94 addPath."test `addPath` prepends a path to filter" = { 95 - expr = (lit.addPath ./tree/x).leafs.result; 95 + expr = (lit.addPath ./tree/x).files; 96 96 expected = [ ./tree/x/y.nix ]; 97 97 }; 98 98 99 99 addPath."test `addPath` can be called multiple times" = { 100 - expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).leafs.result; 100 + expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).files; 101 101 expected = [ 102 102 ./tree/x/y.nix 103 103 ./tree/a/b/b_a.nix ··· 106 106 }; 107 107 108 108 addPath."test `addPath` identity" = { 109 - expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).leafs.result; 109 + expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).files; 110 110 expected = lit.leafs [ 111 111 ./tree/x 112 112 ./tree/a/b 113 113 ]; 114 114 }; 115 115 116 - reset."test `new` returns a clear state" = { 116 + new."test `new` returns a clear state" = { 117 117 expr = lib.pipe lit [ 118 118 (i: i.addPath ./tree/x) 119 119 (i: i.addPath ./tree/a/b) 120 120 (i: i.new) 121 121 (i: i.addPath ./tree/modules/hello-world) 122 122 (i: i.withLib lib) 123 - (i: i.leafs.result) 123 + (i: i.files) 124 124 ]; 125 125 expected = [ ./tree/modules/hello-world/mod.nix ]; 126 126 }; 127 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 + 128 153 addAPI."test extends the API available on an import-tree object" = { 129 154 expr = 130 155 let 131 156 extended = lit.addAPI { helloOption = self: self.addPath ./tree/modules/hello-option; }; 132 157 in 133 - extended.helloOption.leafs.result; 158 + extended.helloOption.files; 134 159 expected = [ ./tree/modules/hello-option/mod.nix ]; 135 160 }; 136 161 ··· 139 164 let 140 165 first = lit.addAPI { helloOption = self: self.addPath ./tree/modules/hello-option; }; 141 166 second = first.addAPI { helloWorld = self: self.addPath ./tree/modules/hello-world; }; 142 - extended = second.addAPI { res = self: self.helloOption.leafs.result; }; 167 + extended = second.addAPI { res = self: self.helloOption.files; }; 143 168 in 144 169 extended.res; 145 170 expected = [ ./tree/modules/hello-option/mod.nix ];
+10 -3
default.nix
··· 3 3 { 4 4 lib ? null, 5 5 pipef ? null, 6 + initf ? null, 6 7 filterf, 7 8 mapf, 8 9 paths, ··· 28 29 leafs = 29 30 lib: root: 30 31 let 31 - initialFilter = andNot (lib.hasInfix "/_") (lib.hasSuffix ".nix"); 32 + treeFiles = t: (t.withLib lib).files; 32 33 listFilesRecursive = 33 34 x: 34 35 if isImportTree x then ··· 39 40 lib.filesystem.listFilesRecursive x 40 41 else 41 42 [ x ]; 42 - treeFiles = t: (t.withLib lib).leafs.result; 43 + nixFilter = andNot (lib.hasInfix "/_") (lib.hasSuffix ".nix"); 44 + initialFilter = if initf != null then initf else nixFilter; 43 45 pathFilter = compose (and filterf initialFilter) toString; 44 - filter = x: if isPathLike x then pathFilter x else filterf x; 46 + otherFilter = and filterf (if initf != null then initf else (_: true)); 47 + filter = x: if isPathLike x then pathFilter x else otherFilter x; 45 48 in 46 49 lib.pipe 47 50 [ paths root ] ··· 118 121 119 122 # Configuration updates (non-accumulating) 120 123 withLib = lib: mergeAttrs { inherit lib; }; 124 + initFilter = initf: mergeAttrs { inherit initf; }; 121 125 pipeTo = pipef: mergeAttrs { inherit pipef; }; 122 126 leafs = mergeAttrs { pipef = (i: i); }; 123 127 124 128 # Applies empty (for already path-configured trees) 125 129 result = (self f) [ ]; 130 + 131 + # Return a list of all filtered files. 132 + files = (self f).leafs.result; 126 133 127 134 # returns the original empty state 128 135 new = callable;