fake.modules transposition for aspect-oriented Dendritic Nix. with cross-aspect dependencies. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ dendrix.oeiuwq.com/Dendritic.html
dendritic nix aspect oriented

docs

+51 -71
+51 -71
README.md
··· 7 7 <a href="LICENSE"> <img src="https://img.shields.io/github/license/vic/flake-aspects" alt="License"/> </a> 8 8 </p> 9 9 10 - # `<aspect>.<class>` transposition for Dendritic Nix 10 + # `<aspect>.<class>` Transposition for Dendritic Nix 11 + 12 + In [aspect-oriented](https://vic.github.io/dendrix/Dendritic.html) [Dendritic](https://github.com/mightyiam/dendritic) setups, it is common to expose modules using the structure `flake.modules.<class>.<aspect>`. 11 13 12 - On [aspect oriented](https://vic.github.io/dendrix/Dendritic.html) [Dendritic](https://github.com/mightyiam/dendritic) setups, it is common to expose modules using `flake.modules.<class>.<aspect>`. 13 - However, for humans, it might be more intuitive to use a transposed attrset `<aspect>.<class>`. Because it feels more natural to nest classes inside aspects than the other way around. 14 + However, for many users, a transposed attribute set, `<aspect>.<class>`, can be more intuitive. It often feels more natural to nest classes within aspects rather than the other way around. 14 15 15 - This project provides a [`transpose`](default.nix) primitive, small and powerful enough to implement [cross-aspect dependencies](aspects.nix) for *any* nix configuration class, and a [flake-parts module](flakeModule.nix) for turning `flake.aspects` into `flake.modules`. 16 + This project provides a small, dependency-free [`transpose`](default.nix) primitive that is powerful enough to implement [cross-aspect dependencies](aspects.nix) for any Nix configuration class. It also includes a [flake-parts module](flakeModule.nix) that transforms `flake.aspects` into `flake.modules`. 16 17 17 18 <table> 18 19 <tr> ··· 72 73 </tr> 73 74 </table> 74 75 75 - ## Usage 76 + ## Usage ⚙️ 76 77 77 - ### As a deps-free library from `./default.nix`: 78 + ### As a Dependency-Free Library (`./default.nix`) 📚 78 79 79 - Our [`transpose`](default.nix) library takes an optional `emit` function that 80 - can be used to ignore some items, modify them or produce many other items on its place. 80 + The [`transpose`](default.nix) library accepts an optional `emit` function that can be used to ignore items, modify them, or generate multiple items from a single input. 81 81 82 82 ```nix 83 83 let transpose = import ./default.nix { lib = pkgs.lib; }; in 84 84 transpose { a.b.c = 1; } # => { b.a.c = 1; } 85 85 ``` 86 86 87 - This `emit` function is used by our [`aspects`](aspects.nix) library 88 - (both libs are flakes-independent) to provide cross-aspects same-class module dependencies. 87 + This `emit` function is utilized by the [`aspects`](aspects.nix) library (both libraries are independent of flakes) to manage cross-aspect, same-class module dependencies. 89 88 90 - ### As a *Dendritic* flake-parts module that provides the `flake.aspects` option: 89 + ### As a Dendritic Flake-Parts Module (`flake.aspects` option) 🧩 91 90 92 - > `flake.aspects` transposes into `flake.modules`. 91 + The `flake.aspects` option is transposed into `flake.modules`. 93 92 94 93 ```nix 95 - # code in this example can (and should) be split into different dendritic modules. 94 + # The code in this example can (and should) be split into different Dendritic modules. 96 95 { inputs, ... }: { 97 96 imports = [ inputs.flake-aspects.flakeModule ]; 98 97 flake.aspects = { 99 98 100 99 sliding-desktop = { 101 - description = "nextgen tiling windowing"; 102 - nixos = { }; # configure Niri on Linux 103 - darwin = { }; # configure Paneru on MacOS 100 + description = "Next-generation tiling windowing"; 101 + nixos = { }; # Configure Niri on Linux 102 + darwin = { }; # Configure Paneru on macOS 104 103 }; 105 104 106 - 107 105 awesome-cli = { 108 - description = "enhances environment with best of cli an tui"; 109 - nixos = { }; # os services 110 - darwin = { }; # apps like ghostty, iterm2 111 - homeManager = { }; # fish aliases, tuis, etc. 112 - nixvim = { }; # plugins 106 + description = "Enhances the environment with the best of CLI and TUI"; 107 + nixos = { }; # OS services 108 + darwin = { }; # Apps like ghostty, iTerm2 109 + homeManager = { }; # Fish aliases, TUIs, etc. 110 + nixvim = { }; # Plugins 113 111 }; 114 112 115 113 work-network = { 116 - description = "work vpn and ssh access."; 117 - nixos = {}; # enable openssh 118 - darwin = {}; # enable MacOS ssh server 119 - terranix = {}; # provision vpn 120 - hjem = {}; # home link .ssh keys and configs. 121 - } 114 + description = "Work VPN and SSH access"; 115 + nixos = {}; # Enable OpenSSH 116 + darwin = {}; # Enable macOS SSH server 117 + terranix = {}; # Provision VPN 118 + hjem = {}; # Home: link .ssh keys and configs 119 + }; 122 120 123 121 }; 124 122 } 125 123 ``` 126 124 127 - #### Declaring cross-aspect dependencies 125 + #### Declaring Cross-Aspect Dependencies 🔗 128 126 129 - `flake.aspects` also allow **aspects** to declare dependencies between them. 127 + `flake.aspects` also allows aspects to declare dependencies among themselves. 130 128 131 - Of course each module can have its own `imports`, however aspect requirements 132 - are aspect-level instead of module-level. Dependencies will ultimately resolve to 133 - modules and get imported only when they exist. 129 + Each module can have its own `imports`, but aspect requirements are defined at the aspect level, not the module level. Dependencies are eventually resolved to modules and are imported only if they exist. 134 130 135 - In the following example, our `development-server` aspect can be applied into 136 - linux and macos hosts. 137 - Note that `alice` prefers to use `nixos`+`homeManager`, while `bob` likes `darwin`+`hjem`. 131 + In the example below, the `development-server` aspect can be applied to both Linux and macOS hosts. Note that `alice` uses `nixos` + `homeManager`, while `bob` uses `darwin` + `hjem`. 138 132 139 - The `development-server` aspect is a "_usability concern_", it configures the exact same 140 - development environment on two different OS. 141 - When applied to a NixOS machine, the `alice.nixos` module will likely 142 - configure the alice user, but there is no nixos user for `bob`. 133 + The `development-server` aspect addresses a usability concern by configuring the same development environment on different operating systems. When applied to a NixOS machine, the `alice.nixos` module will likely configure the `alice` user; there is no corresponding NixOS user for `bob`. 143 134 144 135 ```nix 145 136 { 146 - flake.aspects = {config, ...}: { 137 + flake.aspects = { config, ... }: { 147 138 development-server = { 148 139 requires = with config; [ alice bob ]; 149 140 150 - # without flake-aspects, you'd normally do: 141 + # Without flake-aspects, you would normally do: 151 142 # nixos.imports = [ inputs.self.modules.nixos.alice ]; 152 143 # darwin.imports = [ inputs.self.modules.darwin.bob ]; 153 144 }; 154 145 155 - alice = { 146 + alice = { 156 147 nixos = {}; 157 148 }; 158 149 ··· 163 154 } 164 155 ``` 165 156 166 - It is out of scope for this library to create OS configurations. 167 - As you might have guessed, exposing configurations would look like this: 157 + Creating OS configurations is outside the scope of this library. Exposing configurations might look like this: 168 158 169 159 ```nix 170 160 { inputs, ... }: ··· 175 165 }; 176 166 177 167 flake.darwinConfigurations.fooHost = inputs.darwin.lib.darwinSystem { 178 - system = "aarm64-darwin"; 168 + system = "aarch64-darwin"; 179 169 modules = [ inputs.self.modules.darwin.development-server ]; 180 170 }; 181 171 } 182 172 ``` 183 173 184 - #### Advanced aspect dependencies. 174 + #### Advanced Aspect Dependencies 🧭 185 175 186 - You have already seen that an `aspect` can have a `requires` list: 176 + An aspect can declare a `requires` list: 187 177 188 178 ```nix 189 - # A foo aspect that depends on aspects bar and baz. 179 + # A 'foo' aspect that depends on 'bar' and 'baz' aspects. 190 180 flake.aspects = { config, ... }: { 191 181 foo.requires = [ config.bar config.baz ]; 192 182 } 193 183 ``` 194 184 195 - cross-aspect requirements work like this: 185 + Cross-aspect requirements work as follows: 196 186 197 - When a module `flake.modules.nixos.foo` is requested (eg, included in a nixosConfiguration), 198 - a corresponding module will be computed from `flake.aspects.foo.nixos`. 187 + When a module like `flake.modules.nixos.foo` is requested (for example, included in a `nixosConfiguration`), a corresponding module is computed from `flake.aspects.foo.nixos`. 199 188 200 - `flake.aspects.foo.requires` is a list of functions (**providers**) 201 - that will be called with `{aspect = "foo"; class = "nixos"}` to obtain another aspect 202 - providing a module having the same `class` (`nixos` in our example). 189 + `flake.aspects.foo.requires` is a list of functions (providers) that are called with `{ aspect = "foo"; class = "nixos" }`. These providers return another aspect that provides a module of the same `class` (in this case, `nixos`). 203 190 204 - _providers_ are a way of asking: if I have a (`foo`, `nixos`) module what other 205 - aspects can you provide that have `nixos` modules to be imported in `foo`. 191 + Providers answer the question: given a (`foo`, `nixos`) module, what other aspects can provide `nixos` modules to be imported into `foo`? 206 192 207 - > This way, it is aspects *being included* who decide what configuration must 208 - > be used by its caller aspect. 193 + This means that the included aspect determines which configuration its caller should use. 209 194 210 - by default, all aspects have a `<aspect>.provides.itself` function that ignores its argument 211 - and always returns the `<aspect>` itself. 212 - This is why you can use the `with config; [ bar baz ]` syntax. 213 - They are actually `[ config.bar.provides.itself config.baz.provides.itself ]`. 195 + By default, all aspects include a `<aspect>.provides.itself` function that ignores its argument and returns the `<aspect>` itself. This is why `with config; [ bar baz ]` works: it is shorthand for `[ config.bar.provides.itself config.baz.provides.itself ]`. 214 196 215 - but you can also define custom providers that can inspect the argument's `aspect` and `class` 216 - and return some set of modules accordingly. you can also use this feature to have aspects that 217 - like as proxy/routers of dependencies. 197 + You can also define custom providers that inspect the `aspect` and `class` arguments and return a set of modules accordingly. This allows aspects to act as proxies or routers for dependencies. 218 198 219 199 ```nix 220 - flake.aspects.alice.provides.os-user = { aspect, class }: 221 - if someCondition aspect && class == "nixos" then { nixos = { ... }; } else { } 200 + flake.aspects.alice.provides.os-user = { aspect, class }: 201 + if someCondition aspect && class == "nixos" then { nixos = { ... }; } else { }; 222 202 ``` 223 203 224 - the `os-user` provider can be now included in a `requires` list: 204 + The `os-user` provider can then be included in a `requires` list: 225 205 226 206 ```nix 227 - flake.aspects = {config, ...}: { 207 + flake.aspects = { config, ... }: { 228 208 home-server.requires = [ config.alice.provides.os-user ]; 229 209 } 230 210 ``` 231 211 232 - ## Testing 212 + ## Testing ✅ 233 213 234 214 ```shell 235 215 nix run ./checkmate#fmt --override-input target .