├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── flake.lock ├── flake.nix ├── namaka.toml ├── nix ├── _formats │ ├── json │ │ ├── parse.nix │ │ └── serialize.nix │ ├── pretty │ │ └── serialize.nix │ └── string │ │ └── serialize.nix └── load.nix ├── rustfmt.toml ├── src ├── cfg.rs ├── cli.rs ├── cmd │ ├── check.rs │ ├── clean.rs │ ├── mod.rs │ ├── review.rs │ └── run.rs ├── main.rs └── proto │ ├── mod.rs │ ├── snapshot.rs │ └── test.rs ├── templates ├── default │ ├── flake.nix │ ├── src │ │ └── answer.nix │ └── tests │ │ └── works │ │ └── expr.nix ├── minimal │ ├── flake.nix │ ├── src │ │ └── answer.nix │ └── tests │ │ └── works │ │ └── expr.nix └── subflake │ ├── dev │ ├── flake.nix │ └── tests │ │ └── works │ │ └── expr.nix │ ├── flake.nix │ ├── namaka.toml │ └── src │ └── answer.nix └── tests ├── _snapshots ├── implicit ├── json ├── pretty └── string ├── implicit └── expr.nix ├── json └── expr.nix ├── pretty ├── expr.nix └── format.nix └── string ├── expr.nix └── format.nix /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: cargo 5 | directory: / 6 | schedule: 7 | interval: daily 8 | 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: daily 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | check: 11 | name: check 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [macos-latest, ubuntu-latest] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Install nix 22 | uses: cachix/install-nix-action@v30 23 | 24 | - name: Set up cachix 25 | uses: cachix/cachix-action@v15 26 | with: 27 | name: nix-community 28 | authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} 29 | 30 | - name: Run checks 31 | run: nix run . check 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.[0-9]+.[0-9]+ 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: The existing tag to publish to FlakeHub 11 | type: string 12 | required: true 13 | 14 | jobs: 15 | release: 16 | runs-on: ubuntu-latest 17 | if: github.event_name != 'workflow_dispatch' 18 | steps: 19 | - uses: softprops/action-gh-release@v2 20 | with: 21 | body: "[CHANGELOG.md](https://github.com/nix-community/namaka/blob/main/CHANGELOG.md)" 22 | 23 | flakehub: 24 | runs-on: ubuntu-latest 25 | permissions: 26 | id-token: write 27 | contents: read 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | ref: ${{ inputs.tag != null && format('refs/tags/{0}', inputs.tag) || '' }} 32 | - uses: DeterminateSystems/nix-installer-action@v16 33 | - uses: DeterminateSystems/flakehub-push@v5 34 | with: 35 | visibility: public 36 | tag: ${{ inputs.tag }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /result 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.2.1 - 2024-11-16 4 | 5 | ### Fixes 6 | 7 | - review: reset the terminal when interrupted 8 | 9 | ## v0.2.0 - 2023-06-02 10 | 11 | ### Breaking Changes 12 | 13 | - `namaka review` now runs `nix eval .#checks` by default instead of `nix flake check`. 14 | You can override this behavior by editing `namaka.toml` or using the `--cmd` flag. 15 | - When provided a directory, namaka now changes the working directory to it, 16 | instead of modifying the `nix` commands it runs. 17 | This can make a difference if you specify both the directory and `--cmd`. 18 | 19 | ### Features 20 | 21 | - Local configuration with `namaka.toml` 22 | - `namaka clean` to remove unused and pending snapshots 23 | - Add `subflake` template for using namaka in a subflake 24 | 25 | ### Improvements 26 | 27 | - `namaka review` now runs faster by default, 28 | since the default command has been changed to `nix eval .#checks`. 29 | 30 | ## v0.1.1 - 2023-05-02 31 | 32 | ### Features 33 | 34 | - load: accept `src` 35 | - cli: allow running commands other than `nix flake check` 36 | 37 | ### Changes 38 | 39 | - load: deprecate `flake` and `dir` 40 | 41 | ### Fixes 42 | 43 | - load: fix json format when values are implicitly casted 44 | - canonicalize path before running `nix flake check` 45 | 46 | ## v0.1.0 - 2023-04-09 47 | 48 | First release 49 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "adler2" 22 | version = "2.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 25 | 26 | [[package]] 27 | name = "aho-corasick" 28 | version = "1.1.3" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 31 | dependencies = [ 32 | "memchr", 33 | ] 34 | 35 | [[package]] 36 | name = "ansi_colours" 37 | version = "1.2.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "14eec43e0298190790f41679fe69ef7a829d2a2ddd78c8c00339e84710e435fe" 40 | dependencies = [ 41 | "rgb", 42 | ] 43 | 44 | [[package]] 45 | name = "anstream" 46 | version = "0.6.18" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 49 | dependencies = [ 50 | "anstyle", 51 | "anstyle-parse", 52 | "anstyle-query", 53 | "anstyle-wincon", 54 | "colorchoice", 55 | "is_terminal_polyfill", 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle" 61 | version = "1.0.10" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 64 | 65 | [[package]] 66 | name = "anstyle-parse" 67 | version = "0.2.6" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 70 | dependencies = [ 71 | "utf8parse", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-query" 76 | version = "1.1.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 79 | dependencies = [ 80 | "windows-sys 0.59.0", 81 | ] 82 | 83 | [[package]] 84 | name = "anstyle-wincon" 85 | version = "3.0.6" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 88 | dependencies = [ 89 | "anstyle", 90 | "windows-sys 0.59.0", 91 | ] 92 | 93 | [[package]] 94 | name = "backtrace" 95 | version = "0.3.71" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 98 | dependencies = [ 99 | "addr2line", 100 | "cc", 101 | "cfg-if", 102 | "libc", 103 | "miniz_oxide 0.7.4", 104 | "object", 105 | "rustc-demangle", 106 | ] 107 | 108 | [[package]] 109 | name = "base64" 110 | version = "0.22.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 113 | 114 | [[package]] 115 | name = "bat" 116 | version = "0.24.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "9dcc9e5637c2330d8eb7b920f2aa5d9e184446c258466f825ea1412c7614cc86" 119 | dependencies = [ 120 | "ansi_colours", 121 | "bincode", 122 | "bytesize", 123 | "clircle", 124 | "console", 125 | "content_inspector", 126 | "encoding_rs", 127 | "flate2", 128 | "globset", 129 | "home", 130 | "nu-ansi-term", 131 | "once_cell", 132 | "path_abs", 133 | "plist", 134 | "semver", 135 | "serde", 136 | "serde_yaml", 137 | "syntect", 138 | "thiserror", 139 | "unicode-width 0.1.14", 140 | ] 141 | 142 | [[package]] 143 | name = "bincode" 144 | version = "1.3.3" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 147 | dependencies = [ 148 | "serde", 149 | ] 150 | 151 | [[package]] 152 | name = "bitflags" 153 | version = "1.3.2" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 156 | 157 | [[package]] 158 | name = "bitflags" 159 | version = "2.6.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 162 | 163 | [[package]] 164 | name = "bstr" 165 | version = "1.11.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" 168 | dependencies = [ 169 | "memchr", 170 | "regex-automata", 171 | "serde", 172 | ] 173 | 174 | [[package]] 175 | name = "bytemuck" 176 | version = "1.19.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" 179 | 180 | [[package]] 181 | name = "bytesize" 182 | version = "1.3.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" 185 | 186 | [[package]] 187 | name = "cc" 188 | version = "1.2.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" 191 | dependencies = [ 192 | "shlex", 193 | ] 194 | 195 | [[package]] 196 | name = "cfg-if" 197 | version = "1.0.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 200 | 201 | [[package]] 202 | name = "cfg_aliases" 203 | version = "0.2.1" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 206 | 207 | [[package]] 208 | name = "clap" 209 | version = "4.5.21" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 212 | dependencies = [ 213 | "clap_builder", 214 | "clap_derive", 215 | ] 216 | 217 | [[package]] 218 | name = "clap_builder" 219 | version = "4.5.21" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 222 | dependencies = [ 223 | "anstream", 224 | "anstyle", 225 | "clap_lex", 226 | "strsim", 227 | "terminal_size", 228 | "unicase", 229 | "unicode-width 0.2.0", 230 | ] 231 | 232 | [[package]] 233 | name = "clap_complete" 234 | version = "4.5.38" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" 237 | dependencies = [ 238 | "clap", 239 | ] 240 | 241 | [[package]] 242 | name = "clap_derive" 243 | version = "4.5.18" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 246 | dependencies = [ 247 | "heck", 248 | "proc-macro2", 249 | "quote", 250 | "syn", 251 | ] 252 | 253 | [[package]] 254 | name = "clap_lex" 255 | version = "0.7.3" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 258 | 259 | [[package]] 260 | name = "clap_mangen" 261 | version = "0.2.24" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" 264 | dependencies = [ 265 | "clap", 266 | "roff", 267 | ] 268 | 269 | [[package]] 270 | name = "clircle" 271 | version = "0.4.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "c8e87cbed5354f17bd8ca8821a097fb62599787fe8f611743fad7ee156a0a600" 274 | dependencies = [ 275 | "cfg-if", 276 | "libc", 277 | "serde", 278 | "winapi", 279 | ] 280 | 281 | [[package]] 282 | name = "color-eyre" 283 | version = "0.6.3" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 286 | dependencies = [ 287 | "backtrace", 288 | "color-spantrace", 289 | "eyre", 290 | "indenter", 291 | "once_cell", 292 | "owo-colors 3.5.0", 293 | "tracing-error", 294 | ] 295 | 296 | [[package]] 297 | name = "color-spantrace" 298 | version = "0.2.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 301 | dependencies = [ 302 | "once_cell", 303 | "owo-colors 3.5.0", 304 | "tracing-core", 305 | "tracing-error", 306 | ] 307 | 308 | [[package]] 309 | name = "colorchoice" 310 | version = "1.0.3" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 313 | 314 | [[package]] 315 | name = "console" 316 | version = "0.15.8" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 319 | dependencies = [ 320 | "encode_unicode", 321 | "lazy_static", 322 | "libc", 323 | "unicode-width 0.1.14", 324 | "windows-sys 0.52.0", 325 | ] 326 | 327 | [[package]] 328 | name = "content_inspector" 329 | version = "0.2.4" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" 332 | dependencies = [ 333 | "memchr", 334 | ] 335 | 336 | [[package]] 337 | name = "crc32fast" 338 | version = "1.4.2" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 341 | dependencies = [ 342 | "cfg-if", 343 | ] 344 | 345 | [[package]] 346 | name = "ctrlc" 347 | version = "3.4.5" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" 350 | dependencies = [ 351 | "nix", 352 | "windows-sys 0.59.0", 353 | ] 354 | 355 | [[package]] 356 | name = "deranged" 357 | version = "0.3.11" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 360 | dependencies = [ 361 | "powerfmt", 362 | ] 363 | 364 | [[package]] 365 | name = "dialoguer" 366 | version = "0.11.0" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" 369 | dependencies = [ 370 | "console", 371 | "shell-words", 372 | "tempfile", 373 | "thiserror", 374 | "zeroize", 375 | ] 376 | 377 | [[package]] 378 | name = "encode_unicode" 379 | version = "0.3.6" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 382 | 383 | [[package]] 384 | name = "encoding_rs" 385 | version = "0.8.35" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 388 | dependencies = [ 389 | "cfg-if", 390 | ] 391 | 392 | [[package]] 393 | name = "equivalent" 394 | version = "1.0.1" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 397 | 398 | [[package]] 399 | name = "errno" 400 | version = "0.3.9" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 403 | dependencies = [ 404 | "libc", 405 | "windows-sys 0.52.0", 406 | ] 407 | 408 | [[package]] 409 | name = "eyre" 410 | version = "0.6.12" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 413 | dependencies = [ 414 | "indenter", 415 | "once_cell", 416 | ] 417 | 418 | [[package]] 419 | name = "fastrand" 420 | version = "2.2.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" 423 | 424 | [[package]] 425 | name = "flate2" 426 | version = "1.0.34" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" 429 | dependencies = [ 430 | "crc32fast", 431 | "miniz_oxide 0.8.0", 432 | ] 433 | 434 | [[package]] 435 | name = "fnv" 436 | version = "1.0.7" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 439 | 440 | [[package]] 441 | name = "gimli" 442 | version = "0.28.1" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 445 | 446 | [[package]] 447 | name = "globset" 448 | version = "0.4.15" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" 451 | dependencies = [ 452 | "aho-corasick", 453 | "bstr", 454 | "log", 455 | "regex-automata", 456 | "regex-syntax", 457 | ] 458 | 459 | [[package]] 460 | name = "hashbrown" 461 | version = "0.15.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 464 | 465 | [[package]] 466 | name = "heck" 467 | version = "0.5.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 470 | 471 | [[package]] 472 | name = "home" 473 | version = "0.5.9" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 476 | dependencies = [ 477 | "windows-sys 0.52.0", 478 | ] 479 | 480 | [[package]] 481 | name = "indenter" 482 | version = "0.3.3" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 485 | 486 | [[package]] 487 | name = "indexmap" 488 | version = "2.6.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 491 | dependencies = [ 492 | "equivalent", 493 | "hashbrown", 494 | ] 495 | 496 | [[package]] 497 | name = "is_terminal_polyfill" 498 | version = "1.70.1" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 501 | 502 | [[package]] 503 | name = "itoa" 504 | version = "1.0.11" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 507 | 508 | [[package]] 509 | name = "lazy_static" 510 | version = "1.5.0" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 513 | 514 | [[package]] 515 | name = "libc" 516 | version = "0.2.162" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 519 | 520 | [[package]] 521 | name = "linux-raw-sys" 522 | version = "0.4.14" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 525 | 526 | [[package]] 527 | name = "log" 528 | version = "0.4.22" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 531 | 532 | [[package]] 533 | name = "memchr" 534 | version = "2.7.4" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 537 | 538 | [[package]] 539 | name = "miniz_oxide" 540 | version = "0.7.4" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 543 | dependencies = [ 544 | "adler", 545 | ] 546 | 547 | [[package]] 548 | name = "miniz_oxide" 549 | version = "0.8.0" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 552 | dependencies = [ 553 | "adler2", 554 | ] 555 | 556 | [[package]] 557 | name = "monostate" 558 | version = "0.1.13" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "0d208407d7552cd041d8cdb69a1bc3303e029c598738177a3d87082004dc0e1e" 561 | dependencies = [ 562 | "monostate-impl", 563 | "serde", 564 | ] 565 | 566 | [[package]] 567 | name = "monostate-impl" 568 | version = "0.1.13" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "a7ce64b975ed4f123575d11afd9491f2e37bbd5813fbfbc0f09ae1fbddea74e0" 571 | dependencies = [ 572 | "proc-macro2", 573 | "quote", 574 | "syn", 575 | ] 576 | 577 | [[package]] 578 | name = "namaka" 579 | version = "0.2.1" 580 | dependencies = [ 581 | "bat", 582 | "bstr", 583 | "clap", 584 | "clap_complete", 585 | "clap_mangen", 586 | "color-eyre", 587 | "ctrlc", 588 | "dialoguer", 589 | "eyre", 590 | "monostate", 591 | "owo-colors 4.1.0", 592 | "rustc-hash", 593 | "serde", 594 | "serde_json", 595 | "similar", 596 | "toml", 597 | ] 598 | 599 | [[package]] 600 | name = "nix" 601 | version = "0.29.0" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 604 | dependencies = [ 605 | "bitflags 2.6.0", 606 | "cfg-if", 607 | "cfg_aliases", 608 | "libc", 609 | ] 610 | 611 | [[package]] 612 | name = "nu-ansi-term" 613 | version = "0.49.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" 616 | dependencies = [ 617 | "windows-sys 0.48.0", 618 | ] 619 | 620 | [[package]] 621 | name = "num-conv" 622 | version = "0.1.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 625 | 626 | [[package]] 627 | name = "object" 628 | version = "0.32.2" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 631 | dependencies = [ 632 | "memchr", 633 | ] 634 | 635 | [[package]] 636 | name = "once_cell" 637 | version = "1.20.2" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 640 | 641 | [[package]] 642 | name = "onig" 643 | version = "6.4.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" 646 | dependencies = [ 647 | "bitflags 1.3.2", 648 | "libc", 649 | "once_cell", 650 | "onig_sys", 651 | ] 652 | 653 | [[package]] 654 | name = "onig_sys" 655 | version = "69.8.1" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" 658 | dependencies = [ 659 | "cc", 660 | "pkg-config", 661 | ] 662 | 663 | [[package]] 664 | name = "owo-colors" 665 | version = "3.5.0" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 668 | 669 | [[package]] 670 | name = "owo-colors" 671 | version = "4.1.0" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" 674 | 675 | [[package]] 676 | name = "path_abs" 677 | version = "0.5.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "05ef02f6342ac01d8a93b65f96db53fe68a92a15f41144f97fb00a9e669633c3" 680 | dependencies = [ 681 | "std_prelude", 682 | ] 683 | 684 | [[package]] 685 | name = "pin-project-lite" 686 | version = "0.2.15" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 689 | 690 | [[package]] 691 | name = "pkg-config" 692 | version = "0.3.31" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 695 | 696 | [[package]] 697 | name = "plist" 698 | version = "1.7.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" 701 | dependencies = [ 702 | "base64", 703 | "indexmap", 704 | "quick-xml", 705 | "serde", 706 | "time", 707 | ] 708 | 709 | [[package]] 710 | name = "powerfmt" 711 | version = "0.2.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 714 | 715 | [[package]] 716 | name = "proc-macro2" 717 | version = "1.0.89" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 720 | dependencies = [ 721 | "unicode-ident", 722 | ] 723 | 724 | [[package]] 725 | name = "quick-xml" 726 | version = "0.32.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" 729 | dependencies = [ 730 | "memchr", 731 | ] 732 | 733 | [[package]] 734 | name = "quote" 735 | version = "1.0.37" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 738 | dependencies = [ 739 | "proc-macro2", 740 | ] 741 | 742 | [[package]] 743 | name = "regex-automata" 744 | version = "0.4.9" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 747 | dependencies = [ 748 | "aho-corasick", 749 | "memchr", 750 | "regex-syntax", 751 | ] 752 | 753 | [[package]] 754 | name = "regex-syntax" 755 | version = "0.8.5" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 758 | 759 | [[package]] 760 | name = "rgb" 761 | version = "0.8.50" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" 764 | dependencies = [ 765 | "bytemuck", 766 | ] 767 | 768 | [[package]] 769 | name = "roff" 770 | version = "0.2.2" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" 773 | 774 | [[package]] 775 | name = "rustc-demangle" 776 | version = "0.1.24" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 779 | 780 | [[package]] 781 | name = "rustc-hash" 782 | version = "2.0.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" 785 | 786 | [[package]] 787 | name = "rustix" 788 | version = "0.38.40" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" 791 | dependencies = [ 792 | "bitflags 2.6.0", 793 | "errno", 794 | "libc", 795 | "linux-raw-sys", 796 | "windows-sys 0.52.0", 797 | ] 798 | 799 | [[package]] 800 | name = "ryu" 801 | version = "1.0.18" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 804 | 805 | [[package]] 806 | name = "same-file" 807 | version = "1.0.6" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 810 | dependencies = [ 811 | "winapi-util", 812 | ] 813 | 814 | [[package]] 815 | name = "semver" 816 | version = "1.0.23" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 819 | 820 | [[package]] 821 | name = "serde" 822 | version = "1.0.215" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 825 | dependencies = [ 826 | "serde_derive", 827 | ] 828 | 829 | [[package]] 830 | name = "serde_derive" 831 | version = "1.0.215" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 834 | dependencies = [ 835 | "proc-macro2", 836 | "quote", 837 | "syn", 838 | ] 839 | 840 | [[package]] 841 | name = "serde_json" 842 | version = "1.0.132" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 845 | dependencies = [ 846 | "itoa", 847 | "memchr", 848 | "ryu", 849 | "serde", 850 | ] 851 | 852 | [[package]] 853 | name = "serde_spanned" 854 | version = "0.6.8" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 857 | dependencies = [ 858 | "serde", 859 | ] 860 | 861 | [[package]] 862 | name = "serde_yaml" 863 | version = "0.9.34+deprecated" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 866 | dependencies = [ 867 | "indexmap", 868 | "itoa", 869 | "ryu", 870 | "serde", 871 | "unsafe-libyaml", 872 | ] 873 | 874 | [[package]] 875 | name = "sharded-slab" 876 | version = "0.1.7" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 879 | dependencies = [ 880 | "lazy_static", 881 | ] 882 | 883 | [[package]] 884 | name = "shell-words" 885 | version = "1.1.0" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 888 | 889 | [[package]] 890 | name = "shlex" 891 | version = "1.3.0" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 894 | 895 | [[package]] 896 | name = "similar" 897 | version = "2.6.0" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" 900 | dependencies = [ 901 | "bstr", 902 | "unicode-segmentation", 903 | ] 904 | 905 | [[package]] 906 | name = "std_prelude" 907 | version = "0.2.12" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" 910 | 911 | [[package]] 912 | name = "strsim" 913 | version = "0.11.1" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 916 | 917 | [[package]] 918 | name = "syn" 919 | version = "2.0.87" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 922 | dependencies = [ 923 | "proc-macro2", 924 | "quote", 925 | "unicode-ident", 926 | ] 927 | 928 | [[package]] 929 | name = "syntect" 930 | version = "5.2.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" 933 | dependencies = [ 934 | "bincode", 935 | "bitflags 1.3.2", 936 | "flate2", 937 | "fnv", 938 | "once_cell", 939 | "onig", 940 | "regex-syntax", 941 | "serde", 942 | "serde_derive", 943 | "serde_json", 944 | "thiserror", 945 | "walkdir", 946 | ] 947 | 948 | [[package]] 949 | name = "tempfile" 950 | version = "3.14.0" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 953 | dependencies = [ 954 | "cfg-if", 955 | "fastrand", 956 | "once_cell", 957 | "rustix", 958 | "windows-sys 0.59.0", 959 | ] 960 | 961 | [[package]] 962 | name = "terminal_size" 963 | version = "0.4.0" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" 966 | dependencies = [ 967 | "rustix", 968 | "windows-sys 0.59.0", 969 | ] 970 | 971 | [[package]] 972 | name = "thiserror" 973 | version = "1.0.69" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 976 | dependencies = [ 977 | "thiserror-impl", 978 | ] 979 | 980 | [[package]] 981 | name = "thiserror-impl" 982 | version = "1.0.69" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 985 | dependencies = [ 986 | "proc-macro2", 987 | "quote", 988 | "syn", 989 | ] 990 | 991 | [[package]] 992 | name = "thread_local" 993 | version = "1.1.8" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 996 | dependencies = [ 997 | "cfg-if", 998 | "once_cell", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "time" 1003 | version = "0.3.36" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1006 | dependencies = [ 1007 | "deranged", 1008 | "itoa", 1009 | "num-conv", 1010 | "powerfmt", 1011 | "serde", 1012 | "time-core", 1013 | "time-macros", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "time-core" 1018 | version = "0.1.2" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1021 | 1022 | [[package]] 1023 | name = "time-macros" 1024 | version = "0.2.18" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1027 | dependencies = [ 1028 | "num-conv", 1029 | "time-core", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "toml" 1034 | version = "0.8.19" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 1037 | dependencies = [ 1038 | "serde", 1039 | "serde_spanned", 1040 | "toml_datetime", 1041 | "toml_edit", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "toml_datetime" 1046 | version = "0.6.8" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1049 | dependencies = [ 1050 | "serde", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "toml_edit" 1055 | version = "0.22.22" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 1058 | dependencies = [ 1059 | "indexmap", 1060 | "serde", 1061 | "serde_spanned", 1062 | "toml_datetime", 1063 | "winnow", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "tracing" 1068 | version = "0.1.40" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1071 | dependencies = [ 1072 | "pin-project-lite", 1073 | "tracing-core", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "tracing-core" 1078 | version = "0.1.32" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1081 | dependencies = [ 1082 | "once_cell", 1083 | "valuable", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "tracing-error" 1088 | version = "0.2.0" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 1091 | dependencies = [ 1092 | "tracing", 1093 | "tracing-subscriber", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "tracing-subscriber" 1098 | version = "0.3.18" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1101 | dependencies = [ 1102 | "sharded-slab", 1103 | "thread_local", 1104 | "tracing-core", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "unicase" 1109 | version = "2.8.0" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" 1112 | 1113 | [[package]] 1114 | name = "unicode-ident" 1115 | version = "1.0.13" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1118 | 1119 | [[package]] 1120 | name = "unicode-segmentation" 1121 | version = "1.12.0" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1124 | 1125 | [[package]] 1126 | name = "unicode-width" 1127 | version = "0.1.14" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1130 | 1131 | [[package]] 1132 | name = "unicode-width" 1133 | version = "0.2.0" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1136 | 1137 | [[package]] 1138 | name = "unsafe-libyaml" 1139 | version = "0.2.11" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1142 | 1143 | [[package]] 1144 | name = "utf8parse" 1145 | version = "0.2.2" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1148 | 1149 | [[package]] 1150 | name = "valuable" 1151 | version = "0.1.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1154 | 1155 | [[package]] 1156 | name = "walkdir" 1157 | version = "2.5.0" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1160 | dependencies = [ 1161 | "same-file", 1162 | "winapi-util", 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "winapi" 1167 | version = "0.3.9" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1170 | dependencies = [ 1171 | "winapi-i686-pc-windows-gnu", 1172 | "winapi-x86_64-pc-windows-gnu", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "winapi-i686-pc-windows-gnu" 1177 | version = "0.4.0" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1180 | 1181 | [[package]] 1182 | name = "winapi-util" 1183 | version = "0.1.9" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1186 | dependencies = [ 1187 | "windows-sys 0.59.0", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "winapi-x86_64-pc-windows-gnu" 1192 | version = "0.4.0" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1195 | 1196 | [[package]] 1197 | name = "windows-sys" 1198 | version = "0.48.0" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1201 | dependencies = [ 1202 | "windows-targets 0.48.5", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "windows-sys" 1207 | version = "0.52.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1210 | dependencies = [ 1211 | "windows-targets 0.52.6", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "windows-sys" 1216 | version = "0.59.0" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1219 | dependencies = [ 1220 | "windows-targets 0.52.6", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "windows-targets" 1225 | version = "0.48.5" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1228 | dependencies = [ 1229 | "windows_aarch64_gnullvm 0.48.5", 1230 | "windows_aarch64_msvc 0.48.5", 1231 | "windows_i686_gnu 0.48.5", 1232 | "windows_i686_msvc 0.48.5", 1233 | "windows_x86_64_gnu 0.48.5", 1234 | "windows_x86_64_gnullvm 0.48.5", 1235 | "windows_x86_64_msvc 0.48.5", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "windows-targets" 1240 | version = "0.52.6" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1243 | dependencies = [ 1244 | "windows_aarch64_gnullvm 0.52.6", 1245 | "windows_aarch64_msvc 0.52.6", 1246 | "windows_i686_gnu 0.52.6", 1247 | "windows_i686_gnullvm", 1248 | "windows_i686_msvc 0.52.6", 1249 | "windows_x86_64_gnu 0.52.6", 1250 | "windows_x86_64_gnullvm 0.52.6", 1251 | "windows_x86_64_msvc 0.52.6", 1252 | ] 1253 | 1254 | [[package]] 1255 | name = "windows_aarch64_gnullvm" 1256 | version = "0.48.5" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1259 | 1260 | [[package]] 1261 | name = "windows_aarch64_gnullvm" 1262 | version = "0.52.6" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1265 | 1266 | [[package]] 1267 | name = "windows_aarch64_msvc" 1268 | version = "0.48.5" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1271 | 1272 | [[package]] 1273 | name = "windows_aarch64_msvc" 1274 | version = "0.52.6" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1277 | 1278 | [[package]] 1279 | name = "windows_i686_gnu" 1280 | version = "0.48.5" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1283 | 1284 | [[package]] 1285 | name = "windows_i686_gnu" 1286 | version = "0.52.6" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1289 | 1290 | [[package]] 1291 | name = "windows_i686_gnullvm" 1292 | version = "0.52.6" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1295 | 1296 | [[package]] 1297 | name = "windows_i686_msvc" 1298 | version = "0.48.5" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1301 | 1302 | [[package]] 1303 | name = "windows_i686_msvc" 1304 | version = "0.52.6" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1307 | 1308 | [[package]] 1309 | name = "windows_x86_64_gnu" 1310 | version = "0.48.5" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1313 | 1314 | [[package]] 1315 | name = "windows_x86_64_gnu" 1316 | version = "0.52.6" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1319 | 1320 | [[package]] 1321 | name = "windows_x86_64_gnullvm" 1322 | version = "0.48.5" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1325 | 1326 | [[package]] 1327 | name = "windows_x86_64_gnullvm" 1328 | version = "0.52.6" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1331 | 1332 | [[package]] 1333 | name = "windows_x86_64_msvc" 1334 | version = "0.48.5" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1337 | 1338 | [[package]] 1339 | name = "windows_x86_64_msvc" 1340 | version = "0.52.6" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1343 | 1344 | [[package]] 1345 | name = "winnow" 1346 | version = "0.6.20" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 1349 | dependencies = [ 1350 | "memchr", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "zeroize" 1355 | version = "1.8.1" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1358 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "namaka" 3 | version = "0.2.1" 4 | authors = ["figsoda "] 5 | edition = "2021" 6 | description = "Snapshot testing for Nix based on haumea" 7 | readme = "README.md" 8 | homepage = "https://github.com/figsoda/namaka" 9 | repository = "https://github.com/figsoda/namaka" 10 | license = "MPL-2.0" 11 | keywords = ["assert", "cli", "nix", "snapshot", "testing"] 12 | categories = ["command-line-utilities", "development-tools::testing"] 13 | 14 | [dependencies] 15 | bstr = "1.11.0" 16 | color-eyre = "0.6.3" 17 | ctrlc = "3.4.5" 18 | dialoguer = "0.11.0" 19 | eyre = "0.6.12" 20 | monostate = "0.1.13" 21 | owo-colors = "4.1.0" 22 | rustc-hash = "2.0.0" 23 | serde = { version = "1.0.215", features = ["derive"] } 24 | serde_json = "1.0.132" 25 | similar = { version = "2.6.0", features = ["unicode"] } 26 | toml = "0.8.19" 27 | 28 | [dependencies.bat] 29 | version = "0.24.0" 30 | default-features = false 31 | features = ["regex-onig"] 32 | 33 | [dependencies.clap] 34 | version = "4.5.21" 35 | features = ["cargo", "derive", "unicode", "wrap_help"] 36 | 37 | [build-dependencies] 38 | clap = { version = "4.5.21", features = ["derive", "string"] } 39 | clap_complete = "4.5.38" 40 | clap_mangen = "0.2.24" 41 | 42 | [profile.release] 43 | lto = true 44 | panic = "abort" 45 | codegen-units = 1 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # namaka 2 | 3 | [![release](https://img.shields.io/github/v/release/nix-community/namaka?logo=github&style=flat-square)](https://github.com/nix-community/namaka/releases) 4 | [![version](https://img.shields.io/crates/v/namaka?logo=rust&style=flat-square)](https://crates.io/crates/namaka) 5 | [![deps](https://deps.rs/repo/github/nix-community/namaka/status.svg?style=flat-square&compact=true)](https://deps.rs/repo/github/nix-community/namaka) 6 | [![license](https://img.shields.io/badge/license-MPL--2.0-blue?style=flat-square)](https://www.mozilla.org/en-US/MPL/2.0) 7 | [![ci](https://img.shields.io/github/actions/workflow/status/nix-community/namaka/ci.yml?label=ci&logo=github-actions&style=flat-square)](https://github.com/nix-community/namaka/actions/workflows/ci.yml) 8 | 9 | [Snapshot testing](#snapshot-testing) for Nix based on [haumea] 10 | 11 | ![](https://user-images.githubusercontent.com/40620903/230751675-b1eb1076-bcd8-4c21-a420-f4c914716bb9.gif) 12 | 13 | ## Quick Start 14 | 15 | ```bash 16 | nix flake init -t github:nix-community/namaka 17 | nix develop # add namaka to the environment 18 | namaka check # run checks 19 | namaka review # review pending snapshots 20 | ``` 21 | 22 | ## Versioning 23 | 24 | Namaka follows [semantic versioning](https://semver.org). 25 | Breaking changes can happen in main branch at any time, 26 | so it is recommended to pin namaka to a specific tag. 27 | A list of available versions can be found on the 28 | [releases](https://github.com/nix-community/namaka/releases) page. 29 | 30 | ## Usage 31 | 32 | ``` 33 | Usage: namaka [OPTIONS] [DIR] 34 | 35 | Commands: 36 | check Wrapper around `nix flake check` to prepare snapshots for failed tests [aliases: c] 37 | clean Remove unused and pending snapshots [aliases: cl] 38 | review Review pending snapshots and selectively accept or reject them [aliases: r] 39 | help Print this message or the help of the given subcommand(s) 40 | 41 | Arguments: 42 | [DIR] Change to this working directory 43 | 44 | Options: 45 | -c, --cmd ... Command to run instead of `nix flake check` 46 | -h, --help Print help (see more with '--help') 47 | -V, --version Print version 48 | ``` 49 | 50 | ### [`load`](nix/load.nix) 51 | 52 | Type: `{ ... } -> { }` 53 | 54 | Wrapper around [`haumea.load`] to load snapshot tests from a directory. 55 | 56 | It throws an error if any of the tests fail, otherwise it always returns `{ }`. 57 | `load { src = ./tests; }` will load tests from the `tests` directory, 58 | which should be structured like this: 59 | 60 | ``` 61 | tests 62 | ├─ foo/ 63 | │ ├─ expr.nix 64 | └─ bar/ 65 | ├─ expr.nix 66 | └─ format.nix (optional) 67 | ``` 68 | 69 | `expr.nix` contains the Nix expression you want to test. 70 | 71 | `format.nix` contains a Nix string specifying how the expression should be serialized. 72 | Here are the possible values: 73 | 74 | - `"json"` serializes the expression using `builtins.toJSON` (default) 75 | - `"pretty"` serializes the expression using `lib.generators.toPretty { }` 76 | - `"string"` serializes the string as is 77 | 78 | See the [tests](tests) directory or one of the templates for an example. 79 | Ignore the `_snapshots` directory, as it is automatically generated by namaka. 80 | 81 | The rest of the available options are documented in [`haumea.load`], 82 | as they will function exactly the same. 83 | 84 | ## Configuration 85 | 86 | `namaka` will try to read `namaka.toml` in the working directory. 87 | Here is an example configuration file (all fields are optional): 88 | 89 | ```toml 90 | # namaka.toml 91 | 92 | # change the working directory 93 | # this stacks with the command line option 94 | #`namaka check bar` will read `bar/namaka.toml` and change the working directory to `bar/foo` 95 | dir = "foo" 96 | 97 | [check] 98 | # the command to run with `namaka check` 99 | # defaults to `nix flake check --extra-experimental-features "flakes nix-command"` 100 | cmd = ["nix", "eval", "./dev#checks"] 101 | 102 | [eval] 103 | # the command to run with `namaka review` and `namaka clean` 104 | # defaults to `nix eval ./checks --extra-experimental-features "flakes nix-command"` 105 | cmd = ["nix", "flake", "check"] 106 | ``` 107 | 108 | ## Snapshot Testing 109 | 110 | Snapshot testing is a strategy that allows you to write tests without manually writing reference values. 111 | Instead of `assert foo == bar;`, you only need to have `foo`, 112 | and namaka will store `bar` in a snapshot file and ask you to update it with `namaka review`. 113 | 114 | To start, you can follow the [Quick Start](#quick-start) guide, 115 | or refer to [load](#load) for more detailed documentation. 116 | 117 | ## Related Tools 118 | 119 | - Namaka is largely inspired by [insta](https://github.com/mitsuhiko/insta), 120 | a snapshot testing library for Rust. 121 | - Namaka is based on [haumea], which also has some testing functionalities. 122 | 123 | [haumea]: https://github.com/nix-community/haumea 124 | [`haumea.load`]: https://github.com/nix-community/haumea#load 125 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | fs::{create_dir_all, File}, 4 | path::Path, 5 | }; 6 | 7 | use clap::{CommandFactory, ValueEnum}; 8 | use clap_complete::{generate_to, Shell}; 9 | use clap_mangen::Man; 10 | 11 | mod cli { 12 | include!("src/cli.rs"); 13 | } 14 | 15 | fn main() { 16 | println!("cargo:rerun-if-env-changed=GEN_ARTIFACTS"); 17 | 18 | if let Some(dir) = env::var_os("GEN_ARTIFACTS") { 19 | let out = &Path::new(&dir); 20 | create_dir_all(out).unwrap(); 21 | let cmd = &mut cli::Opts::command(); 22 | 23 | Man::new(cmd.clone()) 24 | .render(&mut File::create(out.join("namaka.1")).unwrap()) 25 | .unwrap(); 26 | 27 | for subcmd in cmd.get_subcommands() { 28 | let name = format!("namaka-{}", subcmd.get_name()); 29 | Man::new(subcmd.clone().name(&name)) 30 | .render(&mut File::create(out.join(format!("{name}.1"))).unwrap()) 31 | .unwrap(); 32 | } 33 | 34 | for shell in Shell::value_variants() { 35 | generate_to(*shell, cmd, "namaka", out).unwrap(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "haumea": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1685133229, 11 | "narHash": "sha256-FePm/Gi9PBSNwiDFq3N+DWdfxFq0UKsVVTJS3cQPn94=", 12 | "owner": "nix-community", 13 | "repo": "haumea", 14 | "rev": "34dd58385092a23018748b50f9b23de6266dffc2", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "nix-community", 19 | "ref": "v0.2.2", 20 | "repo": "haumea", 21 | "type": "github" 22 | } 23 | }, 24 | "nixpkgs": { 25 | "locked": { 26 | "lastModified": 1731139594, 27 | "narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=", 28 | "owner": "nixos", 29 | "repo": "nixpkgs", 30 | "rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "nixos", 35 | "ref": "nixos-unstable", 36 | "repo": "nixpkgs", 37 | "type": "github" 38 | } 39 | }, 40 | "root": { 41 | "inputs": { 42 | "haumea": "haumea", 43 | "nixpkgs": "nixpkgs" 44 | } 45 | } 46 | }, 47 | "root": "root", 48 | "version": 7 49 | } 50 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Snapshot testing for Nix based on haumea"; 3 | 4 | inputs = { 5 | haumea = { 6 | url = "github:nix-community/haumea/v0.2.2"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 10 | }; 11 | 12 | outputs = { self, haumea, nixpkgs }: 13 | let 14 | inherit (nixpkgs) lib; 15 | inherit (lib) 16 | genAttrs 17 | importTOML 18 | licenses 19 | maintainers 20 | sourceByRegex 21 | ; 22 | 23 | eachSystem = f: genAttrs 24 | [ 25 | "aarch64-darwin" 26 | "aarch64-linux" 27 | "x86_64-darwin" 28 | "x86_64-linux" 29 | ] 30 | (system: f nixpkgs.legacyPackages.${system}); 31 | 32 | src = sourceByRegex self [ 33 | "src(/.*)?" 34 | ''Cargo\.(toml|lock)'' 35 | ''build\.rs'' 36 | ]; 37 | in 38 | { 39 | checks = self.lib.load { 40 | src = ./tests; 41 | inputs = { 42 | namaka = self.lib; 43 | }; 44 | }; 45 | 46 | formatter = eachSystem (pkgs: pkgs.nixpkgs-fmt); 47 | 48 | lib = haumea.lib.load { 49 | src = ./nix; 50 | inputs = { 51 | inherit lib; 52 | haumea = haumea.lib; 53 | }; 54 | }; 55 | 56 | packages = eachSystem (pkgs: 57 | let 58 | inherit (pkgs) 59 | installShellFiles 60 | oniguruma 61 | pkg-config 62 | rustPlatform 63 | ; 64 | 65 | inherit (importTOML (src + "/Cargo.toml")) package; 66 | in 67 | { 68 | default = rustPlatform.buildRustPackage { 69 | pname = package.name; 70 | inherit (package) version; 71 | 72 | inherit src; 73 | 74 | cargoLock = { 75 | lockFile = src + "/Cargo.lock"; 76 | }; 77 | 78 | nativeBuildInputs = [ 79 | installShellFiles 80 | pkg-config 81 | ]; 82 | 83 | buildInputs = [ 84 | oniguruma 85 | ]; 86 | 87 | env = { 88 | GEN_ARTIFACTS = "artifacts"; 89 | RUSTONIG_SYSTEM_LIBONIG = true; 90 | }; 91 | 92 | postInstall = '' 93 | installManPage artifacts/*.1 94 | installShellCompletion artifacts/namaka.{bash,fish} --zsh artifacts/_namaka 95 | ''; 96 | 97 | meta = { 98 | inherit (package) description; 99 | license = licenses.mpl20; 100 | maintainers = with maintainers; [ figsoda ]; 101 | }; 102 | }; 103 | }); 104 | 105 | templates = { 106 | default = { 107 | path = ./templates/default; 108 | description = "A Nix library"; 109 | }; 110 | minimal = { 111 | path = ./templates/minimal; 112 | description = "A Nix library that uses nixpkgs.lib instead of the entire nixpkgs"; 113 | }; 114 | subflake = { 115 | path = ./templates/subflake; 116 | description = "A Nix library that uses namaka in a subflake"; 117 | }; 118 | }; 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /namaka.toml: -------------------------------------------------------------------------------- 1 | [check] 2 | cmd = ["nix", "eval", ".#checks"] 3 | -------------------------------------------------------------------------------- /nix/_formats/json/parse.nix: -------------------------------------------------------------------------------- 1 | _: 2 | 3 | builtins.fromJSON 4 | -------------------------------------------------------------------------------- /nix/_formats/json/serialize.nix: -------------------------------------------------------------------------------- 1 | _: 2 | 3 | x: builtins.fromJSON (builtins.toJSON x) 4 | -------------------------------------------------------------------------------- /nix/_formats/pretty/serialize.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | 3 | lib.generators.toPretty { } 4 | -------------------------------------------------------------------------------- /nix/_formats/string/serialize.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | 3 | value: 4 | assert lib.isStringLike value; 5 | value 6 | -------------------------------------------------------------------------------- /nix/load.nix: -------------------------------------------------------------------------------- 1 | { root, lib, haumea }: 2 | 3 | let 4 | inherit (builtins) 5 | attrNames 6 | concatStringsSep 7 | filter 8 | mapAttrs 9 | pathExists 10 | readFile 11 | storeDir 12 | tail 13 | toJSON 14 | trace 15 | tryEval 16 | ; 17 | inherit (lib) 18 | filterAttrs 19 | flip 20 | hasPrefix 21 | id 22 | pipe 23 | removePrefix 24 | splitString 25 | warn 26 | ; 27 | in 28 | 29 | args: 30 | 31 | let 32 | src = toString ( 33 | args.src or (warn 34 | "namaka.load: `flake` and `dir` have been deprecated, use `src` directly instead" 35 | (args.flake + "/${args.dir or "tests"}")) 36 | ); 37 | 38 | tests = haumea.load (removeAttrs args [ "flake" "dir" ] // { 39 | inherit src; 40 | }); 41 | 42 | results = flip mapAttrs tests (name: { format ? "json", expr }: 43 | assert hasPrefix "." name 44 | -> throw "invalid snapshot '${name}', names should not start with '.'"; 45 | 46 | let 47 | path = "${src}/_snapshots/${name}"; 48 | old = pathExists path; 49 | snap = readFile path; 50 | prefix = "#${format}\n"; 51 | f = root.formats.${format}; 52 | value = (f.serialize or id) expr; 53 | expected = (f.parse or id) (removePrefix prefix snap); 54 | in 55 | 56 | if old && hasPrefix prefix snap 57 | && tryEval expected == { inherit value; success = true; } then 58 | true 59 | else { 60 | inherit format value old; 61 | }); 62 | 63 | msg = { 64 | dir = pipe src [ 65 | (removePrefix storeDir) 66 | (splitString "/") 67 | (filter (x: x != "")) 68 | tail 69 | (concatStringsSep "/") 70 | ]; 71 | inherit results; 72 | }; 73 | 74 | failures = attrNames (filterAttrs (_: res: res ? value) results); 75 | in 76 | 77 | assert trace "namaka=${toJSON msg}" true; 78 | 79 | if failures == [ ] then 80 | { } 81 | else 82 | throw "the following tests failed: ${concatStringsSep "," failures}" 83 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | 3 | condense_wildcard_suffixes = true 4 | format_code_in_doc_comments = true 5 | group_imports = "StdExternalCrate" 6 | imports_granularity = "Crate" 7 | newline_style = "Unix" 8 | normalize_comments = true 9 | normalize_doc_attributes = true 10 | reorder_impl_items = true 11 | spaces_around_ranges = true 12 | use_field_init_shorthand = true 13 | use_try_shorthand = true 14 | -------------------------------------------------------------------------------- /src/cfg.rs: -------------------------------------------------------------------------------- 1 | use std::{env::set_current_dir, fs::File, io::Read, path::PathBuf}; 2 | 3 | use eyre::Result; 4 | use serde::Deserialize; 5 | 6 | #[derive(Deserialize)] 7 | pub struct Config { 8 | pub dir: Option, 9 | pub check: Option, 10 | pub eval: Option, 11 | } 12 | 13 | #[derive(Deserialize)] 14 | pub struct Check { 15 | pub cmd: Option>, 16 | } 17 | 18 | #[derive(Deserialize)] 19 | pub struct Eval { 20 | pub cmd: Option>, 21 | } 22 | 23 | pub fn load() -> Result> { 24 | let Ok(mut file) = File::open("namaka.toml") else { 25 | return Ok(None); 26 | }; 27 | 28 | let mut buf = String::with_capacity( 29 | file.metadata() 30 | .map(|metadata| metadata.len() as usize) 31 | .unwrap_or_default(), 32 | ); 33 | file.read_to_string(&mut buf)?; 34 | 35 | let cfg: Config = toml::from_str(&buf)?; 36 | 37 | if let Some(ref dir) = cfg.dir { 38 | set_current_dir(dir)?; 39 | } 40 | 41 | Ok(Some(cfg)) 42 | } 43 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::OsString, path::PathBuf}; 2 | 3 | use clap::Parser; 4 | 5 | /// Snapshot testing for Nix based on haumea 6 | /// https://github.com/nix-community/namaka 7 | #[derive(Parser)] 8 | #[command(version, verbatim_doc_comment)] 9 | pub struct Opts { 10 | #[command(subcommand)] 11 | pub subcmd: Subcommand, 12 | 13 | /// Command to run instead of `nix flake check` 14 | /// 15 | /// Example: namaka check -c nix eval .#checks 16 | #[arg(short, long, num_args = 1 .., global = true)] 17 | pub cmd: Option>, 18 | 19 | /// Change to this working directory 20 | #[arg(global = true)] 21 | pub dir: Option, 22 | } 23 | 24 | #[derive(clap::Subcommand)] 25 | pub enum Subcommand { 26 | /// Wrapper around `nix flake check` to prepare snapshots for failed tests 27 | #[command(visible_alias = "c")] 28 | Check, 29 | /// Remove unused and pending snapshots 30 | #[command(visible_alias = "cl")] 31 | Clean, 32 | /// Review pending snapshots and selectively accept or reject them 33 | #[command(visible_alias = "r")] 34 | Review, 35 | } 36 | -------------------------------------------------------------------------------- /src/cmd/check.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{self, create_dir_all, remove_dir_all, File}, 3 | io::{stderr, BufRead, Write}, 4 | process::exit, 5 | }; 6 | 7 | use eyre::{eyre, Result}; 8 | use owo_colors::OwoColorize; 9 | 10 | use crate::{ 11 | cfg::Config, 12 | cli::Opts, 13 | cmd::run::nix_check, 14 | proto::{TestOutput, TestResult}, 15 | }; 16 | 17 | pub fn check(opts: Opts, cfg: Option) -> Result<()> { 18 | let output = nix_check(opts, cfg)?; 19 | let success = output.status.success(); 20 | 21 | for line in output.stderr.lines() { 22 | let line = line?; 23 | let Some(line) = line.strip_prefix("trace: namaka=") else { 24 | continue; 25 | }; 26 | 27 | let output = serde_json::from_str::(line)?; 28 | 29 | let pending = output.dir.join("_snapshots").join(".pending"); 30 | let _ = remove_dir_all(&pending); 31 | create_dir_all(&pending)?; 32 | fs::write(pending.join(".gitignore"), "*")?; 33 | 34 | let total = output.results.len(); 35 | let mut failures = 0; 36 | for (name, res) in output.results { 37 | let new = pending.join(&name); 38 | match res { 39 | TestResult::Success(_) => { 40 | println!("{} {name}", "✔".green()); 41 | } 42 | 43 | TestResult::Failure { snapshot, old } => { 44 | failures += 1; 45 | println!("{} {name}", if old { "✘" } else { "🞥" }.red()); 46 | snapshot.to_writer(File::create(new)?)?; 47 | } 48 | } 49 | } 50 | 51 | if failures == 0 { 52 | if success { 53 | eprintln!("All {total} tests succeeded"); 54 | return Ok(()); 55 | } else { 56 | break; 57 | } 58 | } else { 59 | eprintln!("{failures} out of {total} tests failed"); 60 | eprintln!("run `namaka review` to review the pending snapshots"); 61 | exit(1); 62 | } 63 | } 64 | 65 | stderr().write_all(&output.stderr)?; 66 | Err(eyre!("unknown error")) 67 | } 68 | -------------------------------------------------------------------------------- /src/cmd/clean.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::{read_dir, remove_dir_all, remove_file}, 3 | io::{stderr, BufRead, Write}, 4 | }; 5 | 6 | use eyre::{eyre, Result}; 7 | use owo_colors::OwoColorize; 8 | 9 | use crate::{cfg::Config, cli::Opts, cmd::run::nix_eval, proto::TestOutput}; 10 | 11 | pub fn clean(opts: Opts, cfg: Option) -> Result<()> { 12 | let output = nix_eval(opts, cfg)?; 13 | 14 | for line in output.stderr.lines() { 15 | let line = line?; 16 | let Some(line) = line.strip_prefix("trace: namaka=") else { 17 | continue; 18 | }; 19 | 20 | let mut out = stderr().lock(); 21 | let output = serde_json::from_str::(line)?; 22 | let snapshots = output.dir.join("_snapshots"); 23 | 24 | for entry in read_dir(snapshots)? { 25 | let entry = entry?; 26 | let name = entry.file_name(); 27 | let name = name 28 | .to_str() 29 | .ok_or_else(|| eyre!("invalid unicode in file name {}", name.to_string_lossy()))?; 30 | 31 | if !output.results.contains_key(name) { 32 | let path = entry.path(); 33 | writeln!(out, "{} {}", "removing".yellow(), path.to_string_lossy())?; 34 | if path.is_dir() { 35 | remove_dir_all(path)?; 36 | } else { 37 | remove_file(path)?; 38 | } 39 | } 40 | } 41 | } 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | mod check; 2 | mod clean; 3 | mod review; 4 | pub mod run; 5 | 6 | pub use check::check; 7 | pub use clean::clean; 8 | pub use review::review; 9 | -------------------------------------------------------------------------------- /src/cmd/review.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::OsStr, 3 | fs::{read_dir, remove_file, rename, File}, 4 | io::{self, stderr, BufRead, Write}, 5 | path::Path, 6 | process::exit, 7 | thread::sleep, 8 | time::Duration, 9 | }; 10 | 11 | use dialoguer::{console::Term, theme::ColorfulTheme, Select}; 12 | use eyre::{eyre, Result}; 13 | use owo_colors::OwoColorize; 14 | use similar::{ChangeTag, TextDiff}; 15 | 16 | use crate::{ 17 | cfg::Config, 18 | cli::Opts, 19 | cmd::run::nix_eval, 20 | proto::{Snapshot, TestOutput}, 21 | }; 22 | 23 | pub fn review(opts: Opts, cfg: Option) -> Result<()> { 24 | let output = nix_eval(opts, cfg)?; 25 | let _ = ctrlc::set_handler(|| { 26 | let mut term = Term::stderr(); 27 | let _ = term.show_cursor(); 28 | let _ = writeln!(term, "interrupted"); 29 | exit(0); 30 | }); 31 | 32 | for line in output.stderr.lines() { 33 | let line = line?; 34 | let Some(line) = line.strip_prefix("trace: namaka=") else { 35 | continue; 36 | }; 37 | 38 | let output = serde_json::from_str::(line)?; 39 | let snapshots = output.dir.join("_snapshots"); 40 | 41 | for entry in read_dir(snapshots.join(".pending"))? { 42 | use bstr::ByteSlice; 43 | 44 | let entry = entry?; 45 | let name = entry.file_name(); 46 | 47 | if <[u8] as ByteSlice>::from_os_str(&name).map_or(false, |name| name.starts_with(b".")) 48 | { 49 | continue; 50 | }; 51 | 52 | let old = snapshots.join(&name); 53 | let new = entry.path(); 54 | let new_snap = Snapshot::parse(File::open(&new)?)?; 55 | println!(); 56 | 57 | if let Ok(old_snap) = File::open(&old) { 58 | match (Snapshot::parse(old_snap), new_snap) { 59 | (Ok(Snapshot::Json(old_value)), Snapshot::Json(new_value)) => { 60 | print_diff( 61 | "json", 62 | &serde_json::to_string_pretty(&old_value)?, 63 | &serde_json::to_string_pretty(&new_value)?, 64 | )?; 65 | } 66 | (Ok(Snapshot::Pretty(old_value)), Snapshot::Pretty(new_value)) => { 67 | print_diff("pretty", &old_value, &new_value)?; 68 | } 69 | (Ok(Snapshot::String(old_value)), Snapshot::String(new_value)) => { 70 | print_diff("string", &old_value, &new_value)?; 71 | } 72 | (Ok(old_snap), new_snap) => { 73 | old_snap.print_old()?; 74 | new_snap.print_new()?; 75 | } 76 | (Err(e), new_snap) => { 77 | println!(" {} failed to parse: {e}", "old".bold().red()); 78 | new_snap.print_new()?; 79 | } 80 | } 81 | ask(&name, &old, &new)?; 82 | } else { 83 | println!(" {}: N/A", "old".bold().red()); 84 | new_snap.print_new()?; 85 | ask(&name, &old, &new)?; 86 | } 87 | } 88 | 89 | return Ok(()); 90 | } 91 | 92 | stderr().write_all(&output.stderr)?; 93 | Err(eyre!("unknown error")) 94 | } 95 | 96 | fn print_diff(fmt: &'static str, old: &str, new: &str) -> Result<()> { 97 | let diff = TextDiff::from_graphemes(old, new); 98 | for change in diff.iter_all_changes() { 99 | let tag = change.tag(); 100 | let change = change.to_string_lossy(); 101 | match tag { 102 | ChangeTag::Equal => print!("{change}"), 103 | ChangeTag::Delete => print!("{}", change.bold().red()), 104 | ChangeTag::Insert => print!("{}", change.bold().green()), 105 | } 106 | } 107 | 108 | if !diff.newline_terminated() { 109 | println!("⏎"); 110 | } 111 | 112 | println!( 113 | "{}\n---", 114 | format_args!("({fmt}) {} | {}", "old".red(), "new".green()).bold(), 115 | ); 116 | 117 | Ok(()) 118 | } 119 | 120 | fn ask(name: &OsStr, old: &Path, new: &Path) -> Result<()> { 121 | let choice = Select::with_theme(&ColorfulTheme::default()) 122 | .item("accept".green()) 123 | .item("reject".red()) 124 | .item("skip".blue()) 125 | .default(0) 126 | .with_prompt(format!("Review {}", name.to_string_lossy())) 127 | .interact() 128 | .inspect_err(|dialoguer::Error::IO(e)| { 129 | if e.kind() == io::ErrorKind::Interrupted { 130 | // make sure ctrlc handler has reset the terminal 131 | sleep(Duration::from_millis(16)); 132 | exit(0); 133 | } 134 | })?; 135 | 136 | match choice { 137 | 0 => rename(new, old).map_err(Into::into), 138 | 1 => remove_file(new).map_err(Into::into), 139 | 2 => Ok(()), 140 | _ => Err(eyre!("invalid selection")), 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/cmd/run.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{OsStr, OsString}, 3 | process::{Command, Output}, 4 | }; 5 | 6 | use eyre::{eyre, Result}; 7 | 8 | use crate::{cfg::Config, cli::Opts}; 9 | 10 | pub fn nix_check(opts: Opts, cfg: Option) -> Result { 11 | run( 12 | opts.cmd, 13 | (|| cfg?.check?.cmd)(), 14 | "nix", 15 | [ 16 | "flake", 17 | "check", 18 | "--extra-experimental-features", 19 | "flakes nix-command", 20 | ], 21 | ) 22 | } 23 | 24 | pub fn nix_eval(opts: Opts, cfg: Option) -> Result { 25 | run( 26 | opts.cmd, 27 | (|| cfg?.eval?.cmd)(), 28 | "nix", 29 | [ 30 | "eval", 31 | ".#checks", 32 | "--extra-experimental-features", 33 | "flakes nix-command", 34 | ], 35 | ) 36 | } 37 | 38 | fn run( 39 | cli_cmd: Option>, 40 | cfg_cmd: Option>, 41 | default_cmd: &str, 42 | default_args: [&str; N], 43 | ) -> Result { 44 | let mut cmd = if let Some(cli_cmd) = cli_cmd { 45 | let mut args = cli_cmd.iter(); 46 | cmd(args.next().ok_or_else(|| eyre!("no command"))?, args) 47 | } else if let Some(cfg_cmd) = cfg_cmd { 48 | let mut args = cfg_cmd.iter(); 49 | cmd(args.next().ok_or_else(|| eyre!("no command"))?, args) 50 | } else { 51 | cmd(default_cmd, default_args) 52 | }; 53 | cmd.output().map_err(Into::into) 54 | } 55 | 56 | fn cmd(cmd: impl AsRef, args: impl IntoIterator>) -> Command { 57 | let mut cmd = Command::new(cmd); 58 | cmd.args(args); 59 | cmd 60 | } 61 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod cfg; 2 | mod cli; 3 | mod cmd; 4 | mod proto; 5 | 6 | use std::env::set_current_dir; 7 | 8 | use clap::Parser; 9 | use eyre::Result; 10 | 11 | use crate::{ 12 | cli::{Opts, Subcommand}, 13 | cmd::{check, clean, review}, 14 | }; 15 | 16 | fn main() -> Result<()> { 17 | let _ = color_eyre::install(); 18 | 19 | let opts = Opts::parse(); 20 | if let Some(dir) = &opts.dir { 21 | set_current_dir(dir)?; 22 | } 23 | 24 | let cfg = cfg::load()?; 25 | 26 | match opts.subcmd { 27 | Subcommand::Check => check(opts, cfg), 28 | Subcommand::Clean => clean(opts, cfg), 29 | Subcommand::Review => review(opts, cfg), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | mod snapshot; 2 | mod test; 3 | 4 | pub use snapshot::Snapshot; 5 | pub use test::{TestOutput, TestResult}; 6 | -------------------------------------------------------------------------------- /src/proto/snapshot.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{BufRead, BufReader, Read, Write}, 4 | }; 5 | 6 | use bat::PrettyPrinter; 7 | use eyre::{eyre, Result}; 8 | use owo_colors::OwoColorize; 9 | use serde::Deserialize; 10 | use serde_json::Value; 11 | 12 | #[derive(Deserialize, Debug)] 13 | #[serde(rename_all = "kebab-case", tag = "format", content = "value")] 14 | pub enum Snapshot { 15 | Json(Value), 16 | Pretty(String), 17 | String(String), 18 | } 19 | 20 | impl Snapshot { 21 | pub fn parse(file: File) -> Result { 22 | let file = &mut BufReader::new(file); 23 | let header = file 24 | .lines() 25 | .next() 26 | .ok_or_else(|| eyre!("failed to parse"))??; 27 | 28 | match header.as_str() { 29 | "#json" => Ok(Self::Json(serde_json::from_reader(file)?)), 30 | "#pretty" => { 31 | let mut value = String::new(); 32 | file.read_to_string(&mut value)?; 33 | Ok(Self::Pretty(value)) 34 | } 35 | "#string" => { 36 | let mut value = String::new(); 37 | file.read_to_string(&mut value)?; 38 | Ok(Self::String(value)) 39 | } 40 | _ => Err(eyre!("invalid header {header}")), 41 | } 42 | } 43 | 44 | pub fn print_old(self) -> Result<()> { 45 | println!("---"); 46 | let fmt = self.format(); 47 | self.print()?; 48 | println!("{} ({fmt})", "↑ old".bold().red()); 49 | Ok(()) 50 | } 51 | 52 | pub fn print_new(self) -> Result<()> { 53 | println!("{} ({})", "↓ new".bold().green(), self.format()); 54 | self.print()?; 55 | println!("---"); 56 | Ok(()) 57 | } 58 | 59 | pub fn to_writer(&self, mut w: impl Write) -> Result<()> { 60 | writeln!(w, "#{}", self.format())?; 61 | match self { 62 | Self::Json(value) => serde_json::to_writer(w, &value).map_err(Into::into), 63 | Self::Pretty(value) => write!(w, "{value}").map_err(Into::into), 64 | Self::String(value) => write!(w, "{value}").map_err(Into::into), 65 | } 66 | } 67 | 68 | fn format(&self) -> &'static str { 69 | match self { 70 | Self::Json(_) => "json", 71 | Self::Pretty(_) => "pretty", 72 | Self::String(_) => "string", 73 | } 74 | } 75 | 76 | fn print(self) -> Result<()> { 77 | let newline = match self { 78 | Self::Json(value) => { 79 | let value = serde_json::to_vec_pretty(&value)?; 80 | PrettyPrinter::new() 81 | .language("json") 82 | .input_from_bytes(&value) 83 | .print()?; 84 | value.ends_with(b"\n") 85 | } 86 | Self::Pretty(value) => { 87 | PrettyPrinter::new() 88 | .language("nix") 89 | .input_from_bytes(value.as_bytes()) 90 | .print()?; 91 | value.ends_with('\n') 92 | } 93 | Self::String(value) => { 94 | PrettyPrinter::new() 95 | .input_from_bytes(value.as_bytes()) 96 | .print()?; 97 | value.ends_with('\n') 98 | } 99 | }; 100 | 101 | if !newline { 102 | println!("⏎"); 103 | } 104 | 105 | Ok(()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/proto/test.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use monostate::MustBe; 4 | use rustc_hash::FxHashMap; 5 | use serde::Deserialize; 6 | 7 | use crate::proto::Snapshot; 8 | 9 | #[derive(Deserialize, Debug)] 10 | pub struct TestOutput { 11 | pub dir: PathBuf, 12 | pub results: FxHashMap, 13 | } 14 | 15 | #[derive(Deserialize, Debug)] 16 | #[serde(untagged)] 17 | pub enum TestResult { 18 | Success(MustBe!(true)), 19 | Failure { 20 | #[serde(flatten)] 21 | snapshot: Snapshot, 22 | old: bool, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /templates/default/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | haumea = { 4 | url = "github:nix-community/haumea/v0.2.2"; 5 | inputs.nixpkgs.follows = "nixpkgs"; 6 | }; 7 | namaka = { 8 | url = "github:nix-community/namaka/v0.2.1"; 9 | inputs = { 10 | haumea.follows = "haumea"; 11 | nixpkgs.follows = "nixpkgs"; 12 | }; 13 | }; 14 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 15 | }; 16 | 17 | outputs = { self, haumea, namaka, nixpkgs }: 18 | let 19 | inherit (nixpkgs) lib; 20 | inherit (lib) 21 | genAttrs 22 | ; 23 | 24 | eachSystem = genAttrs [ 25 | "aarch64-darwin" 26 | "aarch64-linux" 27 | "x86_64-darwin" 28 | "x86_64-linux" 29 | ]; 30 | in 31 | { 32 | checks = namaka.lib.load { 33 | src = ./tests; 34 | inputs = { 35 | inherit lib; 36 | foo = self.lib; 37 | }; 38 | }; 39 | 40 | devShells = eachSystem (system: 41 | let 42 | inherit (nixpkgs.legacyPackages.${system}) 43 | mkShell 44 | ; 45 | in 46 | { 47 | default = mkShell { 48 | packages = [ 49 | namaka.packages.${system}.default 50 | ]; 51 | }; 52 | }); 53 | 54 | lib = haumea.lib.load { 55 | src = ./src; 56 | inputs = { 57 | inherit lib; 58 | }; 59 | }; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /templates/default/src/answer.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | 3 | lib.id 42 4 | -------------------------------------------------------------------------------- /templates/default/tests/works/expr.nix: -------------------------------------------------------------------------------- 1 | { foo }: 2 | 3 | foo.answer * 2 4 | -------------------------------------------------------------------------------- /templates/minimal/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | haumea = { 4 | url = "github:nix-community/haumea/v0.2.2"; 5 | inputs.nixpkgs.follows = "nixpkgs"; 6 | }; 7 | namaka = { 8 | url = "github:nix-community/namaka/v0.2.1"; 9 | inputs = { 10 | haumea.follows = "haumea"; 11 | nixpkgs.follows = "nixpkgs"; 12 | }; 13 | }; 14 | nixpkgs.url = "github:nix-community/nixpkgs.lib"; 15 | }; 16 | 17 | outputs = { self, haumea, namaka, nixpkgs }: { 18 | checks = namaka.lib.load { 19 | src = ./tests; 20 | inputs = { 21 | inherit (nixpkgs) lib; 22 | foo = self.lib; 23 | }; 24 | }; 25 | lib = haumea.lib.load { 26 | src = ./src; 27 | inputs = { 28 | inherit (nixpkgs) lib; 29 | }; 30 | }; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /templates/minimal/src/answer.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | 3 | lib.id 42 4 | -------------------------------------------------------------------------------- /templates/minimal/tests/works/expr.nix: -------------------------------------------------------------------------------- 1 | { foo }: 2 | 3 | foo.answer * 2 4 | -------------------------------------------------------------------------------- /templates/subflake/dev/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | call-flake.url = "github:divnix/call-flake"; 4 | flake-parts = { 5 | url = "github:hercules-ci/flake-parts"; 6 | inputs.nixpkgs-lib.follows = "nixpkgs"; 7 | }; 8 | namaka = { 9 | url = "github:nix-community/namaka/v0.2.1"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | }; 12 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 13 | }; 14 | 15 | outputs = inputs@{ call-flake, flake-parts, namaka, ... }: 16 | flake-parts.lib.mkFlake { inherit inputs; } { 17 | flake.checks = namaka.lib.load { 18 | src = ./tests; 19 | inputs = { 20 | foo = (call-flake ../.).lib; 21 | }; 22 | }; 23 | 24 | systems = [ 25 | "aarch64-darwin" 26 | "aarch64-linux" 27 | "x86_64-darwin" 28 | "x86_64-linux" 29 | ]; 30 | 31 | perSystem = { inputs', pkgs, ... }: { 32 | devShells.default = pkgs.mkShell { 33 | packages = [ 34 | inputs'.namaka.packages.default 35 | ]; 36 | }; 37 | }; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /templates/subflake/dev/tests/works/expr.nix: -------------------------------------------------------------------------------- 1 | { foo }: 2 | 3 | foo.answer * 2 4 | -------------------------------------------------------------------------------- /templates/subflake/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | haumea = { 4 | url = "github:nix-community/haumea/v0.2.2"; 5 | inputs.nixpkgs.follows = "nixpkgs"; 6 | }; 7 | nixpkgs.url = "github:nix-community/nixpkgs.lib"; 8 | }; 9 | 10 | outputs = { self, haumea, nixpkgs }: { 11 | lib = haumea.lib.load { 12 | src = ./src; 13 | inputs = { 14 | inherit (nixpkgs) lib; 15 | }; 16 | }; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /templates/subflake/namaka.toml: -------------------------------------------------------------------------------- 1 | [check] 2 | cmd = ["nix", "flake", "check", "./dev"] 3 | 4 | [eval] 5 | cmd = ["nix", "eval", "./dev#checks"] 6 | -------------------------------------------------------------------------------- /templates/subflake/src/answer.nix: -------------------------------------------------------------------------------- 1 | { lib }: 2 | 3 | lib.id 42 4 | -------------------------------------------------------------------------------- /tests/_snapshots/implicit: -------------------------------------------------------------------------------- 1 | #json 2 | "foo" -------------------------------------------------------------------------------- /tests/_snapshots/json: -------------------------------------------------------------------------------- 1 | #json 2 | {"answer":42,"foo":"bar"} -------------------------------------------------------------------------------- /tests/_snapshots/pretty: -------------------------------------------------------------------------------- 1 | #pretty 2 | { 3 | load = ; 4 | } -------------------------------------------------------------------------------- /tests/_snapshots/string: -------------------------------------------------------------------------------- 1 | #string 2 | foo 3 | bar 4 | baz 5 | -------------------------------------------------------------------------------- /tests/implicit/expr.nix: -------------------------------------------------------------------------------- 1 | { 2 | __toString = _: "foo"; 3 | } 4 | -------------------------------------------------------------------------------- /tests/json/expr.nix: -------------------------------------------------------------------------------- 1 | { 2 | foo = "bar"; 3 | answer = 42; 4 | } 5 | -------------------------------------------------------------------------------- /tests/pretty/expr.nix: -------------------------------------------------------------------------------- 1 | { namaka }: 2 | 3 | namaka 4 | -------------------------------------------------------------------------------- /tests/pretty/format.nix: -------------------------------------------------------------------------------- 1 | "pretty" 2 | -------------------------------------------------------------------------------- /tests/string/expr.nix: -------------------------------------------------------------------------------- 1 | '' 2 | foo 3 | bar 4 | baz 5 | '' 6 | -------------------------------------------------------------------------------- /tests/string/format.nix: -------------------------------------------------------------------------------- 1 | "string" 2 | --------------------------------------------------------------------------------