···11# 🌲🌴 import-tree 🎄🌳
2233-> Helper functions for import of [Nixpkgs module system](https://nix.dev/tutorials/module-system/) modules under a directory recursively
33+> Recursively import [Nix modules](https://nix.dev/tutorials/module-system/) from a directory, with a simple, extensible API.
4455-- Flake callable; Easy to use, intuitive for the most common use case: `inputs.import-tree ./modules`
66-- Module class agnostic; can be used for NixOS, nix-darwin, home-manager, flake-parts, NixVim.
77-- Can be used outside flakes as a dependencies-free lib; Just import our `./default.nix`.
88-- Can be used to list other file types, not just `.nix`. See `.initFilter`, `.files` API.
99-- Extensible API. import-tree objects are customizable. See `.addAPI`.
1010-- Useful for implementing the [Dendritic Pattern](https://github.com/mightyiam/dendritic).
55+## Quick Start (flake-parts)
1161212-## Quick Usage (with flake-parts)
1313-1414-This example shows how to load all nix files inside `./modules`, on [Dendritic](https://vic.github.io/dendrix/Dendritic.html) setups. (see also [flake-file's dendritic template](https://github.com/vic/flake-file?tab=readme-ov-file#flakemodulesdendritic))
77+Import all nix files inside `./modules` in your flake:
158169```nix
1710{
1811 inputs.import-tree.url = "github:vic/import-tree";
1912 inputs.flake-parts.url = "github:hercules-ci/flake-parts";
20132121- outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules);
1414+ outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; }
1515+ (inputs.import-tree ./modules);
2216}
2317```
24182525-## Quick Usage (outside a modules evaluation)
1919+> By default, paths having `/_` are ignored.
26202727-If you want to get a list of nix files programmatically outside of a modules evaluation,
2828-you can use the import-tree API (read below for more).
2121+## Features
2222+2323+🌳 Works with NixOS, nix-darwin, home-manager, flake-parts, NixVim, etc.\
2424+🌲 Callable as a deps-free Flake or nix lib.\
2525+🌴 API for listing custom file types with filters and transformations.\
2626+🌵 Extensible: add your own API methods to tailor import-tree objects.\
2727+🎄 No dependencies outside flakes: just `import ./default.nix`.\
2828+🌿 Useful on [Dendritic Pattern](https://github.com/mightyiam/dendritic) setups.\
2929+🌱 [Growing](https://github.com/search?q=language%3ANix+import-tree&type=code) [community](https://vic.github.io/dendrix/Dendrix-Trees.html) [adoption](https://github.com/vic/flake-file)
3030+3131+## Other Usage (outside module evaluation)
3232+3333+Get a list of nix files programmatically:
29343035```nix
3131-(import-tree.withLib pkgs.lib).leafs ./modules # => list of .nix files
3636+(import-tree.withLib pkgs.lib).leafs ./modules
3237```
33383434-## Ignored files
3535-3636-By default, paths having a component that begins with an underscore (`/_`) are ignored.
3939+<details>
4040+<summary>Advanced Usage, API, and Rationale</summary>
37413838-This can be changed by using `.initFilter` API.
4242+### Ignored files
39434040-<details>
4141- <summary>
4444+By default, paths having a component that begins with an underscore (`/_`) are ignored. This can be changed by using `.initFilter` API.
42454343-## API usage
4646+### API usage
44474548The following goes recursively through `./modules` and imports all `.nix` files.
46494750```nix
4848-# Usage as part of any nix module system.
4951{config, ...} {
5052 imports = [ (import-tree ./modules) ];
5153}
···53555456For more advanced usage, `import-tree` can be configured via its extensible API.
55575656-</summary>
5858+______________________________________________________________________
57595858-## Obtaining the API
6060+#### Obtaining the API
59616060-When used as a flake, the flake outputs attrset is the primary callable.
6161-Otherwise, importing the `default.nix` that is at the root of this repository will evaluate into the same attrset.
6262-This callable attrset is referred to as `import-tree` in this documentation.
6262+When used as a flake, the flake outputs attrset is the primary callable. Otherwise, importing the `default.nix` at the root of this repository will evaluate into the same attrset. This callable attrset is referred to as `import-tree` in this documentation.
63636464-## `import-tree`
6464+#### `import-tree`
65656666-Takes a single argument: path or deeply nested list of path.
6767-Returns a module that imports the discovered files.
6868-For example, given the following file tree:
6666+Takes a single argument: path or deeply nested list of path. Returns a module that imports the discovered files. For example, given the following file tree:
69677068```
7169default.nix
···9896}
9997```
10098101101-If given a deeply nested list of paths the list will be flattened and results concatenated.
102102-The following is valid usage:
9999+If given a deeply nested list of paths the list will be flattened and results concatenated. The following is valid usage:
103100104101```nix
105102{lib, config, ...} {
···109106110107Other import-tree objects can also be given as arguments (or in lists) as if they were paths.
111108112112-As an special case, when the single argument given to an `import-tree` object is an
113113-attribute-set containing an `options` attribute, the `import-tree` object
114114-assumes it is being evaluated as a module. This way, a pre-configured `import-tree` can
115115-also be used directly in a list of module imports.
116116-117117-This is useful for authors exposing pre-configured `import-tree`s that users can directly
118118-add to their import list or continue configuring themselves using its API.
119119-120120-```nix
121121-let
122122- # imagine this configured tree comes from some author's flake or library.
123123- # library author can extend an import-tree with custom API methods
124124- # according to the library's directory and file naming conventions.
125125- configured-tree = import-tree.addAPI {
126126- # the knowledge of where modules are located inside the library structure
127127- # or which filters/regexes/transformations to apply are abstracted
128128- # from the user by the author providing a meaningful API.
129129- maximal = self: self.addPath ./modules;
130130- minimal = self: self.maximal.filter (lib.hasInfix "minimal");
131131- };
132132-in {
133133- # the library user can directly import or further configure an import-tree.
134134- imports = [ configured-tree.minimal ];
135135-}
136136-```
109109+As a special case, when the single argument given to an `import-tree` object is an attribute-set containing an `options` attribute, the `import-tree` object assumes it is being evaluated as a module. This way, a pre-configured `import-tree` object can also be used directly in a list of module imports.
137110138138-## Configurable behavior
111111+#### Configurable behavior
139112140140-`import-tree` objects with custom behavior can be obtained using a builder pattern.
141141-For example:
113113+`import-tree` objects with custom behavior can be obtained using a builder pattern. For example:
142114143115```nix
144116lib.pipe import-tree [
145145- (i: i.map lib.traceVal) # trace all paths. useful for debugging what is being imported.
146146- (i: i.filter (lib.hasInfix ".mod.")) # filter nix files by some predicate
147147- (i: i ./modules) # finally, call the configured import-tree with a path
117117+ (i: i.map lib.traceVal)
118118+ (i: i.filter (lib.hasInfix ".mod."))
119119+ (i: i ./modules)
148120]
149121```
150122151151-Here is a simpler but less readable equivalent:
123123+Or, in a simpler but less readable way:
152124153125```nix
154126((import-tree.map lib.traceVal).filter (lib.hasInfix ".mod.")) ./modules
155127```
156128157157-### `import-tree.filter` and `import-tree.filterNot`
158158-159159-`filter` takes a predicate function `path -> bool`. Only paths for which the filter returns `true` are selected:
129129+##### `import-tree.filter` and `import-tree.filterNot`
160130161161-> \[!NOTE\]
162162-> Only files with suffix `.nix` are candidates.
131131+`filter` takes a predicate function `path -> bool`. Only files with suffix `.nix` are candidates.
163132164133```nix
165165-# import-tree.filter : (path -> bool) -> import-tree
166166-167134import-tree.filter (lib.hasInfix ".mod.") ./some-dir
168135```
169136170170-`filter` can be applied multiple times, in which case only the files matching _all_ filters will be selected:
171171-172172-```nix
173173-lib.pipe import-tree [
174174- (i: i.filter (lib.hasInfix ".mod."))
175175- (i: i.filter (lib.hasSuffix "default.nix"))
176176- (i: i ./some-dir)
177177-]
178178-```
179179-180180-Or, in a simpler but less readable way:
181181-182182-```nix
183183-(import-tree.filter (lib.hasInfix ".mod.")).filter (lib.hasSuffix "default.nix") ./some-dir
184184-```
185185-186186-See also `import-tree.initFilter`.
137137+Multiple filters can be combined, results must match all of them.
187138188188-### `import-tree.match` and `import-tree.matchNot`
139139+##### `import-tree.match` and `import-tree.matchNot`
189140190141`match` takes a regular expression. The regex should match the full path for the path to be selected. Matching is done with `builtins.match`.
191142192143```nix
193193-# import-tree.match : regex -> import-tree
194194-195144import-tree.match ".*/[a-z]+@(foo|bar)\.nix" ./some-dir
196145```
197146198198-`match` can be applied multiple times, in which case only the paths matching _all_ regex patterns will be selected, and can be combined with any number of `filter`, in any order.
147147+Multiple match filters can be added, results must match all of them.
199148200200-### `import-tree.map`
149149+##### `import-tree.map`
201150202151`map` can be used to transform each path by providing a function.
203152204204-e.g. to convert the path into a module explicitly:
205205-206153```nix
207207-# import-tree.map : (path -> any) -> import-tree
208208-209209-import-tree.map (path: {
210210- imports = [ path ];
211211- # assuming such an option is declared
212212- automaticallyImportedPaths = [ path ];
213213-})
154154+# generate a custom module from path
155155+import-tree.map (path: { imports = [ path ]; })
214156```
215157216216-`map` can be applied multiple times, composing the transformations:
158158+Outside modules evaluation, you can transform paths into something else:
217159218160```nix
219161lib.pipe import-tree [
220220- (i: i.map (lib.removeSuffix ".nix"))
221221- (i: i.map builtins.stringLength)
222222-] ./some-dir
162162+ (i: i.map builtins.readFile)
163163+ (i: i.withLib lib)
164164+ (i: i.leafs ./dir)
165165+]
166166+# => list of contents of all files.
223167```
224168225225-The above example first removes the `.nix` suffix from all selected paths, then takes their lengths.
169169+##### `import-tree.addPath`
226170227227-Or, in a simpler but less readable way:
228228-229229-```nix
230230-((import-tree.map (lib.removeSuffix ".nix")).map builtins.stringLength) ./some-dir
231231-```
232232-233233-`map` can be combined with any number of `filter` and `match` calls, in any order, but the (composed) transformation is applied _after_ the filters, and only to the paths that match all of them.
234234-235235-### `import-tree.addPath`
236236-237237-`addPath` can be used to prepend paths to be filter as a setup for import-tree.
238238-This function can be applied multiple times.
171171+`addPath` can be used to prepend paths to be filtered as a setup for import-tree.
239172240173```nix
241241-# import-tree.addPath : (path_or_list_of_paths) -> import-tree
242242-243243-# Both of these result in the same imported files.
244244-# however, the first adds ./vendor as a *pre-configured* path.
245245-# and the final user can supply ./modules or [] empty.
246174(import-tree.addPath ./vendor) ./modules
247175import-tree [./vendor ./modules]
248176```
249177250250-### `import-tree.addAPI`
178178+##### `import-tree.addAPI`
251179252180`addAPI` extends the current import-tree object with new methods.
253253-The API is cumulative, meaning that this function can be called multiple times.
254254-255255-`addAPI` takes an attribute set of functions taking a single argument:
256256-`self` which is the current import-tree object.
257181258182```nix
259259-# import-tree.addAPI : api-attr-set -> import-tree
260260-261183import-tree.addAPI {
262184 maximal = self: self.addPath ./modules;
263263- feature = self: featureName: self.maximal.filter (lib.hasInfix feature);
185185+ feature = self: infix: self.maximal.filter (lib.hasInfix infix);
264186 minimal = self: self.feature "minimal";
265187}
266188```
267189268268-on the previous API, users can call `import-tree.feature "+vim"` or `import-tree.minimal`, etc.
190190+##### `import-tree.withLib`
269191270270-### `import-tree.withLib`
271271-272272-> \[!NOTE\]
273273-> `withLib` is required prior to invocation of any of `.leafs` or `.pipeTo`.
274274-> Because with the use of those functions the implementation does not have access to a `lib` that is provided as a module argument.
192192+`withLib` is required prior to invocation of any of `.leafs` or `.pipeTo` when not used as part of a nix modules evaluation.
275193276194```nix
277277-# import-tree.withLib : lib -> import-tree
278278-279195import-tree.withLib pkgs.lib
280196```
281197282282-### `import-tree.pipeTo`
198198+##### `import-tree.pipeTo`
283199284200`pipeTo` takes a function that will receive the list of paths.
285285-When configured with this, `import-tree` will not return a nix module but the result of the function being piped to.
286201287202```nix
288288-# import-tree.pipeTo : ([paths] -> any) -> import-tree
289289-290203import-tree.pipeTo lib.id # equivalent to `.leafs`
291204```
292205293293-### `import-tree.leafs`
206206+##### `import-tree.leafs`
294207295295-`leafs` takes no arguments, it is equivalent to calling `import-tree.pipeTo lib.id`. That is, instead of producing a nix module, just return the list of results.
208208+`leafs` takes no arguments, it is equivalent to calling `import-tree.pipeTo lib.id`.
296209297210```nix
298298-# import-tree.leafs : import-tree
299299-300211import-tree.leafs
301212```
302213303303-### `import-tree.new`
214214+##### `import-tree.new`
304215305305-Returns a fresh import-tree with empty state. If you previously had any path, lib, filter, etc,
306306-you might need to set them on the new empty tree.
216216+Returns a fresh import-tree with empty state.
307217308308-### `import-tree.initFilter`
218218+##### `import-tree.initFilter`
309219310220*Replaces* the initial filter which defaults to: Include files with `.nix` suffix and not having `/_` infix.
311221312312-_NOTE_: initFilter is non-accumulating and is the *first* filter to run before those accumulated via `filter`/`match`.
313313-314314-You can use this to make import-tree scan for other file types or change the ignore convention.
315315-316222```nix
317317-# import-tree.initFilter : (path -> bool) -> import-tree
318318-319319-import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/ignored/") # nix files not inside /ignored/
320320-import-tree.initFilter (lib.hasSuffix ".md") # scan for .md files everywhere, nothing ignored.
223223+import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/ignored/" p)
224224+import-tree.initFilter (lib.hasSuffix ".md")
321225```
322226323323-### `import-tree.files`
227227+##### `import-tree.files`
324228325229A shorthand for `import-tree.leafs.result`. Returns a list of matching files.
326230327327-This can be used when you don't want to import the tree, but just get a list of files from it.
328328-329329-Useful for listing files other than `.nix`, for example, for passing all `.js` files to a minifier:
330330-331331-_TIP_: remember to use `withLib` when *not* using import-tree as a module import.
332332-333231```nix
334334-# import-tree.files : [ <list-of-files> ]
335335-336336-# paths to give to uglify-js
337232lib.pipe import-tree [
338338- (i: i.initFilter (lib.hasSuffix ".js")) # look for .js files. ignore nothing.
339339- (i: i.addPath ./out) # under the typescript compiler outDir
340340- (i: i.withLib lib) # set lib since we are not importing modules.
233233+ (i: i.initFilter (lib.hasSuffix ".js"))
234234+ (i: i.addPath ./out)
235235+ (i: i.withLib lib)
341236 (i: i.files)
342237]
343343-# => list of all .js files
344238```
345239346346-### `import-tree.result`
240240+##### `import-tree.result`
347241348242Exactly the same as calling the import-tree object with an empty list `[ ]`.
349349-This is useful for import-tree objects that already have paths configured via `.addPath`.
350243351244```nix
352352-# import-tree.result : <module-or-piped-result>
353353-354354-# these two are exactly the same:
355245(import-tree.addPath ./modules).result
356246(import-tree.addPath ./modules) [ ]
357247```
358248359359-</details>
249249+______________________________________________________________________
360250361251## Why
362252···427317}
428318```
429319430430-</details>
320320+______________________________________________________________________
431321432322## Testing
433323···444334```sh
445335nix run github:vic/checkmate#fmt
446336```
337337+338338+</details>