Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/

New Intent-oriented and Incremental contexts. (#57)

( Closes #56, #55 ) -- (Pending #58)

This PR introduces a fundamental refactoring of the aspect dependency and context management system. The primary goal is to provide a more robust, explicit, and powerful way to handle how different parts of the configuration (hosts, users, home-manager) interact with each other. This leads to clearer and more predictable evaluations, but also introduces significant breaking changes from previous unified context `userToHost` and `hostToUser`.

> [!IMPORTANT]
> The previous context objects `userToHost` and `hostToUser` prove to be harder to use and have been **completely removed**.

Now contexts are incremental and context now are used for **configuration intent**. For example, a NixOS configuration starts with context `{ OS }` but later expands to `{ OS, user, host }` as soon as the OS has information of which users and hosts it is being applied to. See the examples on aspects.nix for how this replaces `userToHost` and `hostToUser`.

> [!IMPORTANT]
> Since contexts are now _incremental_, they are also no longer _exact_ by default.

What this means: In previous releases, a function `{ host }: { }` context had to match exactly. For example if applied to `{ host = ...; foo = ... }` it would not apply since `foo` is unexpected. This was as to avoid `function called with unexpectd "foo"` errors. However now contexts can be incremental.

A function `{ host, ... }: { }` provides anytime a `host` is in context.
A function `{ host, user, ... }: { }` provides when at least both are in context.

For example, you can use `{ user, ... }: ...` to provide configurations based on user data anytime it is in context.

**OS/HM Intent Contexts**

OS and HM configurations now start with a single `{ OS }` and `{ HM }` contexts. They are higher-level contexts used to describe the **Intent** of the configuration being produced. This, in hand with incremental contexts, allows to get rid of `hostToUser` and `userToHost`.

For example a context `{ OS, user, host }` is explicit about it being a configuration depending on host/user data but also that it is **being used** for providing NixOS/Darwin configuration specifically.

`OS`/`HM` are not used in our provided batteries at all. Instead the batteries rely only on `host`/`user`/`home` data being in context, fixing the complexity that was introduced by previous `hostToUser` and `userToHost`.

`OS`/`HM` are useful for opt-in bi-directional dependencies, this is how we avoid duplicating settings now. Since a function `{ user, host, ...}: { }` can be installed in `den.default`, and `den.default` is included for any User and Host. That function would produce duplicate configurations - this is correct since the function is **explicitly set in den.default** which is included twice: once for the user and once for the host. To avoid this, either install the function on a specific `den.aspect.<host/user>.includes` or use a higher **intent context** like `OS`/`HM`. See how our aspects.nix examples were updated for this.


## refactor: Dependency Resolution Engine

The core dependency resolution logic in `modules/aspects/dependencies.nix` has been rewritten to be more explicit and prevent recursion.

- It now uses `take.exactly` with the new `OS` and `HM` contexts to ensure that dependency-gathering functions run only once at the appropriate level, preventing evaluation loops.
- The logic for how users contribute to a host's configuration (`osIncludesFromUsers`) and how a host contributes to a user's home-manager configuration (`hmIncludesFromHost`) is now clearly defined and separated.
- The new engine correctly passes the intent `OS` and `HM` contexts down to the aspects being resolved.

## refactor: `provides` Modules Updated for New Context

All built-in `provides` modules have been updated to use the new intent-based context system.

- `provides/define-user`: Now uses generic data-specific `{ host, user, ... }` and `{ home, ... }` contexts directly, removing the need for the intermediate `userToHostContext`.
- `provides/home-manager`: Now passes the `{ HM }` **intent** context when resolving a user's `homeManager` aspect.
- `provides/primary-user`: Logic simplified to expect `{ user, host, ... }` instead of `userToHost`.
- `provides/user-shell`: The logic has been unified. A single `userShell` function now correctly applies shell configuration to `nixos`, `darwin`, and `homeManager` attributes based on the context it receives (`{ user, ... }` or `{ home, ... }`).

## test: Enhanced `canTake` Tests

The test suite for `function_can_take.nix` has been expanded to include assertions for the new `takes.exactly` functionality, ensuring that it correctly identifies functions that take an exact set of arguments.

## feat: Granular Context Control with `take` and `parametric`

A new set of library functions, `den.lib.take` and a redesigned `den.lib.parametric`, have been introduced to give aspect authors fine-grained control over how and when their functions are applied.

- **`den.lib.take`**: This is a new functor that allows a function to declare precisely what context it expects.
- `take.atLeast`: The function is applied if the context provides *at least* the required arguments. This is the default behavior for most parametric functions.
- `take.exactly`: The function is applied only if the context provides the *exact* set of required arguments, with no extras. This is crucial for preventing infinite recursion in dependency resolution.
- `take.unused`: A utility to signal which arguments from the context are "consumed" by the function, allowing for clearer composition.

- **`den.lib.parametric`**: The `parametric` functor has been completely overhauled to leverage `take`.
- It now offers `atLeast` and `exactly` variants to create aspects that apply their includes based on these context matching rules.
- The syntax `parametric true` is now an alias for `parametric.atLeast`.

This new system replaces the older, less precise `canTake` logic, providing a more declarative and safer way to write parametric aspects.

## BREAKING CHANGE: Intent Context (`OS` and `HM`)

The context passed to parametric aspects is no longer a flat attribute set. It has been restructured into namespaced contexts to clearly separate concerns between operating system configuration and home-manager configuration.

- **`OS` Context**: When evaluating for a host's operating system configuration (NixOS or `nix-darwin`), aspects now receive an `OS` attribute in their context. The original `host` object is available as `OS.host`.
- **`HM` Context**: When evaluating for a user's `home-manager` configuration, aspects receive an `HM` attribute. This context contains both the `user` and the `host` they belong to (`HM.user` and `HM.host`).

The previous context objects `userToHost` and `hostToUser` proven to be harder to use and have been **completely removed**. And now contexts are incremental. For example, a NixOS configuration starts with context `{ OS }` but later expands to `{ OS, user, host }` as soon as the OS has information of which users and hosts it is being applied to. See the examples on aspects.nix for how this replaces `userToHost` and `hostToUser`.

### Migration Guide

All parametric aspects must be updated to expect the new context structure.

**Before:**
```nix
my-aspect = { userToHost, ... }: {
${userToHost.host.class}.services.foo.enable = true;
};

my-other-aspect = { hostToUser, ... }: {
homeManager.programs.bar.enable = true;
};
```

**After:**
```nix
# OS context is needed only if my-aspect is installed at den.default
my-aspect = { OS, user, host, ... }: {
${host.class}.services.foo.enable = true;
};

# HM intent context is needed only if installed into den.default.
my-other-aspect = { HM, user, host, ... }: {
homeManager.programs.bar.enable = true;
};
```

authored by oeiuwq.com and committed by

GitHub 721c34b1 4f5653b0

+264 -186
+30 -13
checkmate/tests/aspect-functor.nix
··· 5 5 ... 6 6 }: 7 7 let 8 - __functor = (inputs.target.lib { inherit lib inputs config; }).parametric true; 8 + den.lib = inputs.target.lib { inherit lib inputs config; }; 9 + 10 + inherit (den.lib) parametric canTake; 9 11 10 12 aspect-example = { 11 - inherit __functor; 13 + __functor = parametric true; 12 14 nixos.foo = 99; 13 15 includes = [ 14 16 { nixos.static = 100; } ··· 28 30 } 29 31 ) 30 32 ( 31 - { userToHost, ... }: 32 33 { 33 - nixos.user-to-host = [ 34 - userToHost.host 35 - userToHost.user 34 + OS, 35 + user, 36 + host, 37 + ... 38 + }: 39 + { 40 + nixos.os-user-host = [ 41 + OS 42 + user 43 + host 36 44 ]; 37 45 } 38 46 ) ··· 44 52 ) 45 53 ( 46 54 { user, ... }@ctx: 47 - if builtins.length (builtins.attrNames ctx) == 1 then 55 + if canTake.exactly ctx ({ user }: user) then 48 56 { 49 57 nixos.user-only = user; 50 58 } ··· 153 161 }; 154 162 }; 155 163 156 - flake.tests."test functor applied with userToHost" = { 164 + flake.tests."test functor applied with host/user/OS" = { 157 165 expr = ( 158 166 aspect-example { 159 - userToHost = { 160 - user = 2; 161 - host = 1; 162 - }; 167 + OS = 0; 168 + user = 2; 169 + host = 1; 163 170 } 164 171 ); 165 172 expected = { 166 173 includes = [ 174 + { nixos.host = 1; } 167 175 { 168 - nixos.user-to-host = [ 176 + nixos.host-user = [ 169 177 1 170 178 2 171 179 ]; 172 180 } 181 + { 182 + nixos.os-user-host = [ 183 + 0 184 + 2 185 + 1 186 + ]; 187 + } 188 + { nixos.user = 2; } 189 + { nixos.user-only = false; } 173 190 { nixos.any = 10; } 174 191 ]; 175 192 };
+15 -1
checkmate/tests/function_can_take.nix
··· 5 5 ... 6 6 }: 7 7 let 8 - takes = (inputs.target.lib { inherit inputs lib config; }).canTake; 8 + den.lib = inputs.target.lib { inherit inputs lib config; }; 9 + takes = den.lib.canTake; 10 + 11 + flake.tests."test exactly fails" = { 12 + expr = takes.exactly { 13 + a = 1; 14 + b = 2; 15 + } ({ a }: a); 16 + expected = false; 17 + }; 18 + 19 + flake.tests."test exactly succeeds" = { 20 + expr = takes.exactly { a = 1; } ({ a }: a); 21 + expected = true; 22 + }; 9 23 10 24 flake.tests."test function with no named arguments can take anything" = { 11 25 expr = takes { } (x: x);
+1 -1
modules/aspects/definition.nix
··· 19 19 ${host.aspect} = { 20 20 ${host.class} = { }; 21 21 includes = [ den.default ]; 22 - __functor = den.lib.parametric { inherit host; }; 22 + __functor = den.lib.parametric { OS = { inherit host; }; }; 23 23 }; 24 24 }; 25 25
+56 -47
modules/aspects/dependencies.nix
··· 1 1 { 2 2 den, 3 + lib, 3 4 ... 4 5 }: 5 6 let 6 - inherit (den.lib) owned statics; 7 + inherit (den.lib) 8 + owned 9 + statics 10 + parametric 11 + take 12 + ; 7 13 8 14 dependencies = [ 9 - # owned attributes: <aspect>.<class> 10 - ({ home, ... }: owned home.class den.aspects.${home.aspect}) 11 - ({ host, ... }: owned host.class den.aspects.${host.aspect}) 12 - ({ user, ... }: owned user.class den.aspects.${user.aspect}) 15 + ({ home, ... }: baseDeps home) 16 + ({ host, ... }: baseDeps host) 17 + ({ user, ... }: baseDeps user) 18 + (os osIncludesFromUsers) 19 + (hm hmIncludesFromHost) 20 + ]; 13 21 14 - # defaults: owned from den.default.<class> 15 - ({ home, ... }: owned home.class den.default) 16 - ({ host, ... }: owned host.class den.default) 17 - ({ user, ... }: owned user.class den.default) 22 + # deadnix: skip # exact { OS } to avoid recursion 23 + os = fn: { OS, ... }@ctx: take.exactly ctx fn; 24 + # deadnix: skip # exact { HM } to avoid recursion 25 + hm = fn: { HM, ... }@ctx: take.exactly ctx fn; 18 26 19 - # static (non-parametric) from <aspect>.includes 20 - ({ home, ... }: statics den.aspects.${home.aspect}) 21 - ({ host, ... }: statics den.aspects.${host.aspect}) 22 - ({ user, ... }: statics den.aspects.${user.aspect}) 27 + baseDeps = 28 + from: 29 + let 30 + exists = from ? aspect && builtins.hasAttr from.aspect den.aspects; 31 + aspect = den.aspects.${from.aspect}; 32 + in 33 + { 34 + includes = lib.optionals exists [ 35 + (statics den.default) 36 + (statics aspect) 37 + (owned den.default) 38 + (owned aspect) 39 + ]; 40 + }; 23 41 24 - # user-to-host context 25 - ({ userToHost, ... }: owned userToHost.host.class den.aspects.${userToHost.user.aspect}) 26 - # host-to-user context 27 - ({ hostToUser, ... }: owned hostToUser.user.class den.aspects.${hostToUser.host.aspect}) 28 - 29 - # { host } => [ { userToHost } ] 30 - (hostIncludesFromUsers) 31 - 32 - # { user, host } => { hostToUser } 33 - (userIncludesFromHost) 34 - ]; 35 - 36 - hostIncludesFromUsers = 37 - { host, ... }: 42 + osIncludesFromUsers = 43 + { OS }: 44 + let 45 + inherit (OS) host; 46 + users = builtins.attrValues host.users; 47 + userDeps = user: parametric { inherit OS user host; } den.aspects.${user.aspect}; 48 + userContribs.includes = map userDeps users; 49 + hostDeps = user: parametric { inherit user host; } den.aspects.${host.aspect}; 50 + hostContribs.includes = map hostDeps users; 51 + in 38 52 { 39 - includes = 40 - let 41 - users = builtins.attrValues host.users; 42 - context = user: { 43 - userToHost = { 44 - inherit user host; 45 - }; 46 - }; 47 - contrib = user: den.aspects.${user.aspect} (context user); 48 - in 49 - map contrib users; 53 + includes = [ 54 + hostContribs 55 + userContribs 56 + ]; 50 57 }; 51 58 52 - userIncludesFromHost = 53 - { user, host }: 59 + hmIncludesFromHost = 60 + { HM }: 61 + let 62 + inherit (HM) user host; 63 + userDeps = parametric { inherit HM user host; } den.aspects.${user.aspect}; 64 + hostDeps = parametric { inherit HM user host; } den.aspects.${host.aspect}; 65 + in 54 66 { 55 - includes = den.aspects.${host.aspect}.includes; 56 - __functor = den.lib.parametric { 57 - hostToUser = { 58 - inherit host user; 59 - }; 60 - }; 67 + includes = [ 68 + userDeps 69 + hostDeps 70 + ]; 61 71 }; 62 - 63 72 in 64 73 { 65 74 den.default.includes = dependencies;
+3 -12
modules/aspects/provides/define-user.nix
··· 22 22 host: user: 23 23 if lib.hasSuffix "darwin" host.system then "/Users/${user.userName}" else "/home/${user.userName}"; 24 24 25 - userToHostContext = 26 - { userToHost, ... }: 27 - let 28 - inherit (userToHost) host user; 29 - in 25 + userContext = 26 + { host, user, ... }: 30 27 { 31 28 nixos.users.users.${user.userName}.isNormalUser = true; 32 29 darwin.users.users.${user.userName} = { 33 30 name = user.userName; 34 31 home = homeDir host user; 35 32 }; 36 - }; 37 - 38 - userContext = 39 - { host, user }: 40 - { 41 33 homeManager = { 42 34 home.username = user.userName; 43 35 home.homeDirectory = homeDir host user; ··· 45 37 }; 46 38 47 39 hmContext = 48 - { home }: 40 + { home, ... }: 49 41 userContext { 50 42 host.system = home.system; 51 43 user.userName = home.userName; ··· 55 47 den.provides.define-user = { 56 48 inherit description; 57 49 includes = [ 58 - userToHostContext 59 50 userContext 60 51 hmContext 61 52 ];
+23 -17
modules/aspects/provides/home-manager.nix
··· 4 4 den, 5 5 ... 6 6 }: 7 - { 8 - den.provides.home-manager = { 9 - description = '' 10 - integrates home-manager into nixos/darwin OS classes. 7 + let 8 + description = '' 9 + integrates home-manager into nixos/darwin OS classes. 11 10 12 - usage: 11 + usage: 13 12 14 - for using home-manager in just a particular host: 13 + for using home-manager in just a particular host: 15 14 16 - den.aspects.my-laptop.includes = [ den._.home-manager ]; 15 + den.aspects.my-laptop.includes = [ den._.home-manager ]; 17 16 18 - for enabling home-manager by default on all hosts: 17 + for enabling home-manager by default on all hosts: 19 18 20 - den.default.includes = [ den._.home-manager ]; 19 + den.default.includes = [ den._.home-manager ]; 21 20 22 - Does nothing for hosts that have no users with `homeManager` class. 23 - Expects `inputs.home-manager` to exist. If `<host>.hm-module` exists 24 - it is the home-manager.{nixos/darwin}Modules.home-manager. 21 + Does nothing for hosts that have no users with `homeManager` class. 22 + Expects `inputs.home-manager` to exist. If `<host>.hm-module` exists 23 + it is the home-manager.{nixos/darwin}Modules.home-manager. 25 24 26 - For each user resolves den.aspects.''${user.aspect} and imports its homeManager class module. 27 - ''; 25 + For each user resolves den.aspects.''${user.aspect} and imports its homeManager class module. 26 + ''; 27 + in 28 + { 29 + den.provides.home-manager = { 30 + inherit description; 28 31 29 32 __functor = 30 33 _: 31 34 { host, ... }: 32 35 { class, aspect-chain }: 33 36 let 34 - hmUsers = builtins.filter (u: u.class == "homeManager") (lib.attrValues host.users); 37 + hmClass = "homeManager"; 38 + hmUsers = builtins.filter (u: u.class == hmClass) (lib.attrValues host.users); 35 39 36 40 hmUserModule = 37 41 user: 38 42 let 39 - aspect = den.aspects.${user.aspect} { inherit user host; }; 40 43 ctx = { 41 44 inherit aspect-chain; 42 - class = user.class; 45 + class = hmClass; 46 + }; 47 + aspect = den.aspects.${user.aspect} { 48 + HM = { inherit host user; }; 43 49 }; 44 50 in 45 51 aspect.resolve ctx;
+2 -2
modules/aspects/provides/import-tree.nix
··· 50 50 # load from ./hosts/<host>/_nixos 51 51 den.default.includes = [ (den._.import-tree._.host ./hosts) ]; 52 52 53 - # load from ./users/<user>@<host>/{_homeManager, _nixos} 53 + # load from ./users/<user>/{_homeManager, _nixos} 54 54 den.default.includes = [ (den._.import-tree._.user ./users) ]; 55 55 56 56 # load from ./homes/<home>/_homeManager ··· 72 72 den._.import-tree.provides = { 73 73 host = root: { host, ... }: den._.import-tree "${toString root}/${host.name}"; 74 74 home = root: { home, ... }: den._.import-tree "${toString root}/${home.name}"; 75 - user = root: { host, user, ... }: den._.import-tree "${toString root}/${user.name}@${host.name}"; 75 + user = root: { user, ... }: den._.import-tree "${toString root}/${user.name}"; 76 76 }; 77 77 }
+1 -2
modules/aspects/provides/primary-user.nix
··· 14 14 ''; 15 15 16 16 userToHostContext = 17 - { userToHost, ... }: 17 + { user, host, ... }: 18 18 let 19 - inherit (userToHost) host user; 20 19 on-wsl.nixos.wsl.defaultUser = user.userName; 21 20 in 22 21 {
+7 -15
modules/aspects/provides/user-shell.nix
··· 12 12 ]; 13 13 ''; 14 14 15 - userContext = 16 - { shell }: 17 - { host }: 18 - { 19 - homeManager.programs.${shell}.enable = true; 20 - }; 21 - 22 - userToHostContext = 23 - { shell }: 24 - { userToHost, ... }: 15 + userShell = 16 + shell: user: 25 17 let 26 - inherit (userToHost) user; 27 18 nixos = 28 19 { pkgs, ... }: 29 20 { ··· 31 22 users.users.${user.userName}.shell = pkgs.${shell}; 32 23 }; 33 24 darwin = nixos; 25 + homeManager.programs.${shell}.enable = true; 34 26 in 35 27 { 36 - inherit nixos darwin; 28 + inherit nixos darwin homeManager; 37 29 }; 38 30 39 31 in ··· 41 33 den.provides.user-shell = shell: { 42 34 inherit description; 43 35 __functor = den.lib.parametric true; 44 - includes = map (f: f { inherit shell; }) [ 45 - userContext 46 - userToHostContext 36 + includes = [ 37 + ({ user, ... }: userShell shell user) 38 + ({ home, ... }: userShell shell home) 47 39 ]; 48 40 }; 49 41 }
+22 -11
nix/fn-can-take.nix
··· 1 - lib: param: f: 1 + lib: 2 2 let 3 - args = lib.mapAttrsToList (name: optional: { inherit name optional; }) (lib.functionArgs f); 4 - 5 - givenAttrs = (builtins.isAttrs param) && !param ? __functor; 6 - 7 - required = map (x: x.name) (lib.filter (x: !x.optional) args); 8 - provided = lib.attrNames param; 9 - 10 - intersection = lib.intersectLists required provided; 11 - satisfied = lib.length required == lib.length intersection; 3 + check = 4 + params: func: 5 + let 6 + givenArgs = builtins.isAttrs params; 7 + fargs = lib.functionArgs func; 8 + provided = builtins.attrNames params; 9 + args = lib.mapAttrsToList (name: optional: { inherit name optional; }) fargs; 10 + required = map (x: x.name) (lib.filter (x: !x.optional) args); 11 + intersection = lib.intersectLists required provided; 12 + satisfied = givenArgs && lib.length required == lib.length intersection; 13 + noExtras = lib.length required == lib.length provided; 14 + exactly = satisfied && noExtras; 15 + in 16 + { 17 + inherit satisfied exactly; 18 + }; 12 19 in 13 - givenAttrs && satisfied 20 + { 21 + __functor = self: self.atLeast; 22 + atLeast = params: func: (check params func).satisfied; 23 + exactly = params: func: (check params func).exactly; 24 + }
+64 -40
nix/lib.nix
··· 5 5 ... 6 6 }: 7 7 let 8 + 9 + # "Just Give 'Em One of These" - Moe Szyslak 10 + # A __functor that applies context to parametric includes (functions) 11 + funk = 12 + apply: aspect: 13 + aspect 14 + // { 15 + __functor = self: ctx: { 16 + includes = builtins.filter (x: x != { }) (map (apply ctx) (builtins.filter isFn self.includes)); 17 + }; 18 + }; 19 + 8 20 isFn = f: (builtins.isFunction f) || (f ? __functor); 9 21 canTake = import ./fn-can-take.nix lib; 10 - inCtx = ctx: f: isFn f && canTake ctx f; 11 - 12 - isAspect = canTake { 13 - class = ""; 14 - aspect-chain = [ ]; 15 - }; 16 22 17 23 # creates an aspect that inherits class from fromAspect. 18 - owned = class: fromAspect: { ${class} = fromAspect.${class} or { }; }; 24 + owned = 25 + aspect: 26 + aspect 27 + // { 28 + includes = [ ]; 29 + __functor = 30 + self: 31 + # deadnix: skip 32 + { class, aspect-chain }: 33 + self; 34 + }; 19 35 20 36 # only static includes from an aspect. 21 37 statics = 22 38 aspect: 23 - # deadnix: skip 24 - { class, aspect-chain }: 25 - { 26 - includes = 27 - let 28 - include = 29 - f: 30 - if !isFn f then 31 - f 32 - else if isAspect f then 33 - f { inherit class aspect-chain; } 34 - else 35 - { }; 36 - in 37 - map include aspect.includes; 39 + aspect 40 + // { 41 + __functor = 42 + self: 43 + # deadnix: skip 44 + { class, aspect-chain }@ctx: 45 + funk applyStatics self ctx; 38 46 }; 39 47 40 - # "Just Give 'Em One of These" - Moe Szyslak 41 - # a __functor that **only** considers parametric includes 42 - # that **exactly** match the given context. 43 - funk = 44 - aspect: ctx: 45 - let 46 - fns = builtins.filter (inCtx ctx) aspect.includes; 47 - includes = map (f: f ctx) fns; 48 - in 49 - { 50 - inherit includes; 51 - }; 48 + applyStatics = 49 + ctx: f: 50 + if isStatic f then 51 + f ctx 52 + else if !isFn f then 53 + f 54 + else 55 + { }; 52 56 53 - parametric = 54 - param: aspect: 55 - if param == true then 56 - funk aspect 57 + isStatic = canTake { 58 + class = ""; 59 + aspect-chain = [ ]; 60 + }; 61 + 62 + take.unused = _unused: used: used; 63 + take.exactly = take canTake.exactly; 64 + take.atLeast = take canTake.atLeast; 65 + take.__functor = 66 + _: takes: ctx: fn: 67 + if takes ctx fn then fn ctx else { }; 68 + 69 + parametric.atLeast = funk take.atLeast; 70 + parametric.exactly = funk take.exactly; 71 + parametric.context = lib.flip parametric.atLeast; 72 + parametric.expands = attrs: funk (ctx: take.atLeast (attrs // ctx)); 73 + parametric.__functor = 74 + self: ctx: 75 + if ctx == true then 76 + self.atLeast 77 + else if ctx == false then 78 + self.exactly 79 + else if isFn ctx then 80 + funk ctx 57 81 else 58 - # deadnix: skip 59 - { class, aspect-chain }: funk aspect param; 82 + self.context ctx; 60 83 61 84 aspects = inputs.flake-aspects.lib lib; 62 85 ··· 71 94 owned 72 95 isFn 73 96 canTake 97 + take 74 98 ; 75 99 }; 76 100 in
+6 -3
templates/bogus/modules/bug.nix
··· 16 16 tux = inputs.self.nixosConfigurations.igloo.config.users.users.tux; 17 17 18 18 expr.len = lib.length tux.packages; 19 - expr.name = lib.getName (lib.head tux.packages); 19 + expr.names = map lib.getName tux.packages; 20 20 21 - expected.len = 1; 22 - expected.name = "hello"; 21 + expected.len = 2; 22 + expected.names = [ 23 + "hello" 24 + "hello" 25 + ]; 23 26 in 24 27 { 25 28 inherit expr expected;
+34 -22
templates/default/modules/_example/aspects.nix
··· 31 31 ${host.class}.networking.hostName = host.name; 32 32 }; 33 33 34 - # Example: installed on den.defaults for each user contribute into host. 35 - one-hello-package-for-each-user = 36 - { userToHost, ... }: 37 - { 38 - ${userToHost.host.class} = 39 - { pkgs, ... }: 40 - { 41 - users.users.${userToHost.user.userName}.packages = [ pkgs.hello ]; 42 - }; 43 - }; 44 - 45 - # Example: configuration that depends on both host and user. provides to the host. 34 + # Example: configuration that depends on both host and user. provides anytime { user, host } is in context. 46 35 user-to-host-conditional = 47 - { userToHost, ... }: 48 - if userToHost.user.userName == "alice" && !lib.hasSuffix "darwin" userToHost.host.system then 36 + { user, host, ... }: 37 + if user.userName == "alice" && !lib.hasSuffix "darwin" host.system then 49 38 { 50 39 nixos.programs.tmux.enable = true; 51 40 } 52 41 else 53 42 { }; 54 43 55 - # Example: configuration that depends on both host and user. provides to the host. 44 + # Example: adds hello into each user. provides only to OS. 45 + one-hello-package-for-each-user = 46 + { 47 + OS, 48 + user, 49 + host, 50 + ... 51 + }: 52 + den.lib.take.unused [ OS ] { 53 + ${host.class} = 54 + { pkgs, ... }: 55 + { 56 + users.users.${user.userName}.packages = [ pkgs.hello ]; 57 + }; 58 + }; 59 + 60 + # Example: configuration that depends on both host and user. provides only to HM. 56 61 host-to-user-conditional = 57 - { hostToUser, ... }: 58 - if hostToUser.user.userName == "alice" && !lib.hasSuffix "darwin" hostToUser.host.system then 59 - { 60 - homeManager.programs.git.enable = true; 61 - } 62 - else 63 - { }; 62 + { 63 + HM, 64 + user, 65 + host, 66 + ... 67 + }: 68 + den.lib.take.unused [ HM ] ( 69 + if user.userName == "alice" && !lib.hasSuffix "darwin" host.system then 70 + { 71 + homeManager.programs.git.enable = true; 72 + } 73 + else 74 + { } 75 + ); 64 76 65 77 # Example: luke standalone home-manager has access to rockhopper osConfig specialArg. 66 78 os-conditional-hm =