Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

> Helper functions for import of [Nixpkgs module system](https://nix.dev/tutorials/module-system/) modules under a directory recursively

Module class agnostic; can be used for NixOS, nix-darwin, home-manager, flake-parts, NixVim.
- Flake callable; Easy to use, intuitive for the most common use case: `inputs.import-tree ./modules`
- Module class agnostic; can be used for NixOS, nix-darwin, home-manager, flake-parts, NixVim.
- Can be used outside flakes as a dependencies-free lib; Just import our `./default.nix`.
- Can be used to list other file types, not just `.nix`. See `.initFilter`, `.files` API.
- Extensible API. import-tree objects are customizable. See `.addAPI`.

## Quick Usage (with flake-parts)

Expand All @@ -29,7 +33,9 @@ you can use the import-tree API (read below for more).

## Ignored files

Paths that have a component that begins with an underscore are ignored.
By default, paths having a component that begins with an underscore (`/_`) are ignored.

This can be changed by using `.initFilter` API.

<details>
<summary>
Expand Down Expand Up @@ -177,6 +183,8 @@ Or, in a simpler but less readable way:
(import-tree.filter (lib.hasInfix ".mod.")).filter (lib.hasSuffix "default.nix") ./some-dir
```

See also `import-tree.initFilter`.

### `import-tree.match` and `import-tree.matchNot`

`match` takes a regular expression. The regex should match the full path for the path to be selected. Matching is done with `builtins.match`.
Expand Down Expand Up @@ -297,6 +305,37 @@ import-tree.leafs
Returns a fresh import-tree with empty state. If you previously had any path, lib, filter, etc,
you might need to set them on the new empty tree.

### `import-tree.initFilter`

*Replaces* the initial filter which defaults to: Include files with `.nix` suffix and not having `/_` infix.

_NOTE_: initFilter is non-accumulating and is the *first* filter to run before those accumulated via `filter`/`match`.

You can use this to make import-tree scan for other file types or change the ignore convention.

```nix
# import-tree.initFilter : (path -> bool) -> import-tree

import-tree.initFilter (p: lib.hasSuffix ".nix" p && !lib.hasInfix "/ignored/") # nix files not inside /ignored/
import-tree.initFilter (lib.hasSuffix ".md") # scan for .md files everywhere, nothing ignored.
```

### `import-tree.files`

A shorthand for `import-tree.leafs.result`. Returns a list of matching files.

This can be used when you don't want to import the tree, but just get a list of files from it.

Useful for listing files other than `.nix`, for example, for passing all `.js` files to a minifier:

_TIP_: remember to use `withLib` when *not* using import-tree as a module import.

```nix
# import-tree.files : [ <list-of-files> ]

((import-tree.withLib lib).initFilter (lib.hasSuffix ".js")).files # => list of all .js files
```

### `import-tree.result`

Exactly the same as calling the import-tree object with an empty list `[ ]`.
Expand Down
39 changes: 32 additions & 7 deletions checkmate.nix
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ in
};

addPath."test `addPath` prepends a path to filter" = {
expr = (lit.addPath ./tree/x).leafs.result;
expr = (lit.addPath ./tree/x).files;
expected = [ ./tree/x/y.nix ];
};

addPath."test `addPath` can be called multiple times" = {
expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).leafs.result;
expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).files;
expected = [
./tree/x/y.nix
./tree/a/b/b_a.nix
Expand All @@ -106,31 +106,56 @@ in
};

addPath."test `addPath` identity" = {
expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).leafs.result;
expr = ((lit.addPath ./tree/x).addPath ./tree/a/b).files;
expected = lit.leafs [
./tree/x
./tree/a/b
];
};

reset."test `new` returns a clear state" = {
new."test `new` returns a clear state" = {
expr = lib.pipe lit [
(i: i.addPath ./tree/x)
(i: i.addPath ./tree/a/b)
(i: i.new)
(i: i.addPath ./tree/modules/hello-world)
(i: i.withLib lib)
(i: i.leafs.result)
(i: i.files)
];
expected = [ ./tree/modules/hello-world/mod.nix ];
};

initFilter."test can change the initial filter to look for other file types" = {
expr = (lit.initFilter (p: lib.hasSuffix ".txt" p)).leafs [ ./tree/a ];
expected = [ ./tree/a/a.txt ];
};

initFilter."test initf does filter non-paths" = {
expr =
let
mod = (it.initFilter (x: !(x ? config.boom))) [
{
options.hello = lib.mkOption {
default = "world";
type = lib.types.str;
};
}
{
config.boom = "boom";
}
];
res = lib.modules.evalModules { modules = [ mod ]; };
in
res.config.hello;
expected = "world";
};

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;
extended.helloOption.files;
expected = [ ./tree/modules/hello-option/mod.nix ];
};

Expand All @@ -139,7 +164,7 @@ in
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; };
extended = second.addAPI { res = self: self.helloOption.files; };
in
extended.res;
expected = [ ./tree/modules/hello-option/mod.nix ];
Expand Down
13 changes: 10 additions & 3 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ let
{
lib ? null,
pipef ? null,
initf ? null,
filterf,
mapf,
paths,
Expand All @@ -28,7 +29,7 @@ let
leafs =
lib: root:
let
initialFilter = andNot (lib.hasInfix "/_") (lib.hasSuffix ".nix");
treeFiles = t: (t.withLib lib).files;
listFilesRecursive =
x:
if isImportTree x then
Expand All @@ -39,9 +40,11 @@ let
lib.filesystem.listFilesRecursive x
else
[ x ];
treeFiles = t: (t.withLib lib).leafs.result;
nixFilter = andNot (lib.hasInfix "/_") (lib.hasSuffix ".nix");
initialFilter = if initf != null then initf else nixFilter;
pathFilter = compose (and filterf initialFilter) toString;
filter = x: if isPathLike x then pathFilter x else filterf x;
otherFilter = and filterf (if initf != null then initf else (_: true));
filter = x: if isPathLike x then pathFilter x else otherFilter x;
in
lib.pipe
[ paths root ]
Expand Down Expand Up @@ -118,12 +121,16 @@ let

# Configuration updates (non-accumulating)
withLib = lib: mergeAttrs { inherit lib; };
initFilter = initf: mergeAttrs { inherit initf; };
pipeTo = pipef: mergeAttrs { inherit pipef; };
leafs = mergeAttrs { pipef = (i: i); };

# Applies empty (for already path-configured trees)
result = (self f) [ ];

# Return a list of all filtered files.
files = (self f).leafs.result;

# returns the original empty state
new = callable;
};
Expand Down