From dfb28a39909aabf778bb97ff3b8f289cc96191dd Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Tue, 17 Feb 2026 14:06:50 +0000 Subject: [PATCH] feat(lib): allow multiple files This makes handling libs more comfortable. Also adds docs for lib-tests automatic check. --- .../getting-started/folder_structure.md | 62 ++++++++++++++++--- lib/default.nix | 45 +++++++++++++- 2 files changed, 96 insertions(+), 11 deletions(-) diff --git a/docs/content/getting-started/folder_structure.md b/docs/content/getting-started/folder_structure.md index 9115f66..51e9208 100644 --- a/docs/content/getting-started/folder_structure.md +++ b/docs/content/getting-started/folder_structure.md @@ -324,26 +324,68 @@ Because the username is part of the path to the configuration, the `home.usernam ## **lib/** for Nix functions. -### `lib/default.nix` +All `.nix` files in `lib/` are automatically imported and exposed as `lib.`. -Loaded if it exists. +### Structure -Inputs: +``` +lib/ +├── default.nix # Optional: merged into lib +├── strings.nix # Available as lib.strings +└── math.nix # Available as lib.math +``` -* `flake` -* `inputs` +### Examples -Flake outputs: +```nix +# lib/add.nix - Simple function +a: b: a + b + +# lib/greet.nix - Function with inputs +{ inputs, flake, ... }: +name: "Hello, ${name}!" + +# lib/strings.nix - Attrset of functions +{ inputs, flake, ... }: +{ + capitalize = str: /* ... */; + kebabToCamel = str: /* ... */; +} +``` + +### Usage + +```nix +inputs.myflake.lib.add 1 2 # => 3 +inputs.myflake.lib.greet "World" # => "Hello, World!" +inputs.myflake.lib.strings.capitalize "hello" # => "Hello" +``` + +Files can optionally receive `{ inputs, flake, ... }` - blueprint auto-detects this. -* `lib` - contains the return value of `lib/default.nix` +`lib/default.nix` exports are merged and take precedence over auto-imported files. -Eg: +### Testing + +Export a `tests` attribute from your `lib/` to run [nix-unit](https://github.com/nix-community/nix-unit) tests: ```nix -{ flake, inputs }: -{ } +# lib/default.nix +{ inputs, flake, ... }: +{ + add = a: b: a + b; + + tests = { + testAdd = { + expr = (import ./default.nix { inherit inputs flake; }).add 1 2; + expected = 3; + }; + }; +} ``` +Tests run automatically with `nix flake check` as `checks..lib-tests`. + ## **`modules/`** for NixOS and other modules. ### `modules//(|.nix)` diff --git a/lib/default.nix b/lib/default.nix index 83ec3eb..b9b2cf3 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -538,7 +538,50 @@ let perSystem.self.formatter or pkgs.nixfmt-tree ); - lib = tryImport (src + "/lib") specialArgs; + lib = + let + # Try to load lib/default.nix (legacy behavior) + defaultLib = tryImport (src + "/lib") specialArgs; + + # Smart import: check if function expects specialArgs + smartImport = path: + let + module = import path; + in + if builtins.isFunction module then + let + # Get the function's expected arguments + args = builtins.functionArgs module; + # Check if it expects any of the specialArgs keys or has ... + expectsSpecialArgs = + (builtins.length (builtins.attrNames args) > 0) || + (args ? inputs) || + (args ? flake) || + (args ? self); + in + if expectsSpecialArgs then + # Call with specialArgs + module specialArgs + else + # Return function as-is (doesn't expect specialArgs) + module + else + # Not a function, return as-is + module; + + # Auto-import lib/* files as an attrset + libFiles = optionalPathAttrs (src + "/lib") ( + path: + let + entries = importDir path lib.id; + # Filter out default.nix to avoid duplication + nonDefaultEntries = builtins.removeAttrs entries [ "default" ]; + in + lib.mapAttrs (_name: { path, ... }: smartImport path) nonDefaultEntries + ); + in + # Merge: lib/default.nix takes precedence, then add auto-imported files + libFiles // defaultLib; # expose the functor to the top-level # FIXME: only if it exists