diff --git a/README.md b/README.md index aacfdf1..03c932d 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,7 @@ The following goes recursively through `./modules` and imports all `.nix` files. } ``` -For more advanced usage, `import-tree` can be configured via its builder API. -This means that the result of calling a function on an `import-tree` object -is itself another `import-tree` object. +For more advanced usage, `import-tree` can be configured via its extensible API. @@ -94,20 +92,28 @@ The following is valid usage: ``` As an special case, when the single argument given to an `import-tree` object is an -attribute-set *meaning it is _NOT_ a path or list of paths*, the `import-tree` object -assumes it is being imported as a module. This way, a pre-configured `import-tree` can +attribute-set *-it is _NOT_ a path or list of paths-*, the `import-tree` object +assumes it is being evaluated as a module. This way, a pre-configured `import-tree` can also be used directly in a list of module imports. This is useful for authors exposing pre-configured `import-tree`s that users can directly -add to their import list or continue configuring themselves using the API. +add to their import list or continue configuring themselves using its API. ```nix let - # imagine this configured tree is actually provided by some flake or library. - # users can directly import it or continue using API methods on it. - configured-tree = import-tree.addPath [./a [./b]]; # paths are configured by library author. + # imagine this configured tree comes from some author's flake or library. + # library author can extend an import-tree with custom API methods + # according to the library's directory and file naming conventions. + configured-tree = import-tree.addAPI { + # the knowledge of where modules are located inside the library structure + # or which filters/regexes/transformations to apply are abstracted + # from the user by the author providing a meaningful API. + maximal = self: self.addPath ./modules; + minimal = self: self.maximal.filtered (lib.hasInfix "minimal"); + }; in { - imports = [ configured-tree ]; # but then imported or further configured by the library user. + # the library user can directly import or further configure an import-tree. + imports = [ configured-tree.minimal ]; } ``` @@ -221,6 +227,26 @@ This function can be applied multiple times. import-tree [./vendor ./modules] ``` +### `import-tree.addAPI` + +`addAPI` extends the current import-tree object with new methods. +The API is cumulative, meaning that this function can be called multiple times. + +`addAPI` takes an attribute set of functions taking a single argument: +`self` which is the current import-tree object. + +```nix +# import-tree.addAPI : api-attr-set -> import-tree + +import-tree.addAPI { + maximal = self: self.addPath ./modules; + feature = self: featureName: self.maximal.filtered (lib.hasInfix feature); + minimal = self: self.feature "minimal"; +} +``` + +on the previous API, users can call `import-tree.feature "+vim"` or `import-tree.minimal`, etc. + ### `import-tree.withLib` > \[!NOTE\] @@ -283,26 +309,42 @@ to learn about the Dendritic pattern advantages. ### Sharing pre-configured subtrees of modules -Since the import-tree API lets you prepend paths and filter which files to include, -you could have flakes that output different sets of pre-configured trees. +
+ + +Since the import-tree API is _extensible_ and lets you add paths or +filters at configuration time, library authors can provide pre-configured +import-trees and custom API methods for their particular needs. + + This would allow us to have community-driven *sets* of configurations, much like those popular for editors: spacemacs/lazy-vim distributions. -Imagine an editor distribution exposing the following `lib.trees` flake output: +Imagine an editor distribution exposing the following flake output: ```nix # editor-distro's flakeModule {inputs, lib, ...}: let - flake.lib.trees = { - inherit root on off xor; - }; + flake.lib.modules-tree = lib.pipe import-tree [ + (i: i.addPath ./modules) + (i: i.addAPI { inherit hadDirMatching on off exclusive; }) + (i: i.addAPI { ruby = self: self.on "ruby"; }) + (i: i.addAPI { python = self: self.on "python"; }) + (i: i.addAPI { old-school = self: self.off "copilot"; }) + (i: i.addAPI { vim-btw = self: self.exclusive "vim" "emacs"; }) + ]; + + hasDirMatching = self: re: self.matching ".*/.*?${re}.*/.*"; - root = inputs.import-tree.addPath ./modules; + on = self: flagName: self.hasDirMatching "\+${flagName}"; + off = self: flagName: self.hasDirMatching "\-${flagName}"; - on = flag: tree: tree.filter (lib.hasInfix "/+${flag}/"); - off = flag: tree: tree.filter (lib.hasInfix "/-${flag}/"); + exclusive = self: onFlag: offFlag: lib.pipe self [ + (self: self.on onFlag) + (self: self.off offFlag) + ]; in { inherit flake; @@ -314,15 +356,16 @@ Users of such distribution can do: ```nix # consumer flakeModule {inputs, lib, ...}: let - inherit (inputs.editor-distro.lib.trees) on off root; + ed-tree = inputs.editor-distro.lib.modules-tree; in { imports = [ - # files inside +vim -emacs directories - (lib.pipe root [(on "vim") (off "emacs") (i: i.result)]) + (ed-tree.vim-btw.old-school.on "rust") ]; } ``` +
+ ## Testing `import-tree` uses [`checkmate`](https://github.com/vic/checkmate) for testing. diff --git a/checkmate.nix b/checkmate.nix index 883aa39..03fa790 100644 --- a/checkmate.nix +++ b/checkmate.nix @@ -106,6 +106,26 @@ in ]; }; + addAPI."test extends the API available on an import-tree object" = { + expr = + let + extended = lit.addAPI { helloOption = self: self.addPath ./tree/modules/hello-option; }; + in + extended.helloOption.leafs.result; + expected = [ ./tree/modules/hello-option/mod.nix ]; + }; + + addAPI."test preserves previous API extensions on an import-tree object" = { + expr = + let + first = lit.addAPI { helloOption = self: self.addPath ./tree/modules/hello-option; }; + second = first.addAPI { helloWorld = self: self.addPath ./tree/modules/hello-world; }; + extended = second.addAPI { res = self: self.helloOption.leafs.result; }; + in + extended.res; + expected = [ ./tree/modules/hello-option/mod.nix ]; + }; + pipeTo."test pipes list into a function" = { expr = (lit.mapWith lib.pathType).pipeTo (lib.length) ./tree/x; expected = 1; diff --git a/default.nix b/default.nix index 346dd79..cd923e2 100644 --- a/default.nix +++ b/default.nix @@ -73,28 +73,36 @@ let let __config = { # Accumulated configuration + api = { }; mapf = (i: i); filterf = _: true; paths = [ ]; - __functor = self: f: { - __config = (f self); - __functor = functor; + __functor = + self: f: + let + __config = (f self); + in + __config.api + // { + inherit __config; + __functor = functor; - # Configuration updates (accumulating) - filtered = filterf: self (c: mapAttr (f c) "filterf" (and filterf)); - matching = regex: self (c: mapAttr (f c) "filterf" (and (matchesRegex regex))); - mapWith = mapf: self (c: mapAttr (f c) "mapf" (compose mapf)); - addPath = path: self (c: mapAttr (f c) "paths" (p: p ++ [ path ])); + # Configuration updates (accumulating) + filtered = filterf: self (c: mapAttr (f c) "filterf" (and filterf)); + matching = regex: self (c: mapAttr (f c) "filterf" (and (matchesRegex regex))); + mapWith = mapf: self (c: mapAttr (f c) "mapf" (compose mapf)); + addPath = path: self (c: mapAttr (f c) "paths" (p: p ++ [ path ])); + addAPI = api: self (c: mapAttr (f c) "api" (a: a // builtins.mapAttrs (_: g: g (self f)) api)); - # Configuration updates (non-accumulating) - withLib = lib: self (c: (f c) // { inherit lib; }); - pipeTo = pipef: self (c: (f c) // { inherit pipef; }); - leafs = self (c: (f c) // { pipef = (i: i); }); + # Configuration updates (non-accumulating) + withLib = lib: self (c: (f c) // { inherit lib; }); + pipeTo = pipef: self (c: (f c) // { inherit pipef; }); + leafs = self (c: (f c) // { pipef = (i: i); }); - # Applies empty (for already path-configured trees) - result = (self f) [ ]; - }; + # Applies empty (for already path-configured trees) + result = (self f) [ ]; + }; }; in __config (c: c);