this repo has no description

feat: init

+844
+1
.envrc
··· 1 + use flake
+1
.gitignore
··· 1 + result
+440
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.24.2" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 + dependencies = [ 11 + "gimli", 12 + ] 13 + 14 + [[package]] 15 + name = "adler2" 16 + version = "2.0.0" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 + 20 + [[package]] 21 + name = "argh" 22 + version = "0.1.13" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" 25 + dependencies = [ 26 + "argh_derive", 27 + "argh_shared", 28 + "rust-fuzzy-search", 29 + ] 30 + 31 + [[package]] 32 + name = "argh_derive" 33 + version = "0.1.13" 34 + source = "registry+https://github.com/rust-lang/crates.io-index" 35 + checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" 36 + dependencies = [ 37 + "argh_shared", 38 + "proc-macro2", 39 + "quote", 40 + "syn", 41 + ] 42 + 43 + [[package]] 44 + name = "argh_shared" 45 + version = "0.1.13" 46 + source = "registry+https://github.com/rust-lang/crates.io-index" 47 + checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" 48 + dependencies = [ 49 + "serde", 50 + ] 51 + 52 + [[package]] 53 + name = "backtrace" 54 + version = "0.3.75" 55 + source = "registry+https://github.com/rust-lang/crates.io-index" 56 + checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 57 + dependencies = [ 58 + "addr2line", 59 + "cfg-if", 60 + "libc", 61 + "miniz_oxide", 62 + "object", 63 + "rustc-demangle", 64 + "windows-targets", 65 + ] 66 + 67 + [[package]] 68 + name = "cfg-if" 69 + version = "1.0.0" 70 + source = "registry+https://github.com/rust-lang/crates.io-index" 71 + checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 72 + 73 + [[package]] 74 + name = "color-eyre" 75 + version = "0.6.4" 76 + source = "registry+https://github.com/rust-lang/crates.io-index" 77 + checksum = "e6e1761c0e16f8883bbbb8ce5990867f4f06bf11a0253da6495a04ce4b6ef0ec" 78 + dependencies = [ 79 + "backtrace", 80 + "color-spantrace", 81 + "eyre", 82 + "indenter", 83 + "once_cell", 84 + "owo-colors", 85 + "tracing-error", 86 + ] 87 + 88 + [[package]] 89 + name = "color-spantrace" 90 + version = "0.2.2" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "2ddd8d5bfda1e11a501d0a7303f3bfed9aa632ebdb859be40d0fd70478ed70d5" 93 + dependencies = [ 94 + "once_cell", 95 + "owo-colors", 96 + "tracing-core", 97 + "tracing-error", 98 + ] 99 + 100 + [[package]] 101 + name = "eyre" 102 + version = "0.6.12" 103 + source = "registry+https://github.com/rust-lang/crates.io-index" 104 + checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 105 + dependencies = [ 106 + "indenter", 107 + "once_cell", 108 + ] 109 + 110 + [[package]] 111 + name = "gimli" 112 + version = "0.31.1" 113 + source = "registry+https://github.com/rust-lang/crates.io-index" 114 + checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 115 + 116 + [[package]] 117 + name = "humansize" 118 + version = "2.1.3" 119 + source = "registry+https://github.com/rust-lang/crates.io-index" 120 + checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" 121 + dependencies = [ 122 + "libm", 123 + ] 124 + 125 + [[package]] 126 + name = "indenter" 127 + version = "0.3.3" 128 + source = "registry+https://github.com/rust-lang/crates.io-index" 129 + checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 130 + 131 + [[package]] 132 + name = "itoa" 133 + version = "1.0.15" 134 + source = "registry+https://github.com/rust-lang/crates.io-index" 135 + checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 136 + 137 + [[package]] 138 + name = "lazy_static" 139 + version = "1.5.0" 140 + source = "registry+https://github.com/rust-lang/crates.io-index" 141 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 142 + 143 + [[package]] 144 + name = "libc" 145 + version = "0.2.172" 146 + source = "registry+https://github.com/rust-lang/crates.io-index" 147 + checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 148 + 149 + [[package]] 150 + name = "libm" 151 + version = "0.2.15" 152 + source = "registry+https://github.com/rust-lang/crates.io-index" 153 + checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 154 + 155 + [[package]] 156 + name = "lix-diff" 157 + version = "0.0.1" 158 + dependencies = [ 159 + "argh", 160 + "color-eyre", 161 + "humansize", 162 + "nu-ansi-term", 163 + "serde", 164 + "serde_json", 165 + ] 166 + 167 + [[package]] 168 + name = "memchr" 169 + version = "2.7.4" 170 + source = "registry+https://github.com/rust-lang/crates.io-index" 171 + checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 172 + 173 + [[package]] 174 + name = "miniz_oxide" 175 + version = "0.8.8" 176 + source = "registry+https://github.com/rust-lang/crates.io-index" 177 + checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 178 + dependencies = [ 179 + "adler2", 180 + ] 181 + 182 + [[package]] 183 + name = "nu-ansi-term" 184 + version = "0.50.1" 185 + source = "registry+https://github.com/rust-lang/crates.io-index" 186 + checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" 187 + dependencies = [ 188 + "windows-sys", 189 + ] 190 + 191 + [[package]] 192 + name = "object" 193 + version = "0.36.7" 194 + source = "registry+https://github.com/rust-lang/crates.io-index" 195 + checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 196 + dependencies = [ 197 + "memchr", 198 + ] 199 + 200 + [[package]] 201 + name = "once_cell" 202 + version = "1.21.3" 203 + source = "registry+https://github.com/rust-lang/crates.io-index" 204 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 205 + 206 + [[package]] 207 + name = "owo-colors" 208 + version = "4.2.0" 209 + source = "registry+https://github.com/rust-lang/crates.io-index" 210 + checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" 211 + 212 + [[package]] 213 + name = "pin-project-lite" 214 + version = "0.2.16" 215 + source = "registry+https://github.com/rust-lang/crates.io-index" 216 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 217 + 218 + [[package]] 219 + name = "proc-macro2" 220 + version = "1.0.95" 221 + source = "registry+https://github.com/rust-lang/crates.io-index" 222 + checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 223 + dependencies = [ 224 + "unicode-ident", 225 + ] 226 + 227 + [[package]] 228 + name = "quote" 229 + version = "1.0.40" 230 + source = "registry+https://github.com/rust-lang/crates.io-index" 231 + checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 232 + dependencies = [ 233 + "proc-macro2", 234 + ] 235 + 236 + [[package]] 237 + name = "rust-fuzzy-search" 238 + version = "0.1.1" 239 + source = "registry+https://github.com/rust-lang/crates.io-index" 240 + checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" 241 + 242 + [[package]] 243 + name = "rustc-demangle" 244 + version = "0.1.24" 245 + source = "registry+https://github.com/rust-lang/crates.io-index" 246 + checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 247 + 248 + [[package]] 249 + name = "ryu" 250 + version = "1.0.20" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 253 + 254 + [[package]] 255 + name = "serde" 256 + version = "1.0.219" 257 + source = "registry+https://github.com/rust-lang/crates.io-index" 258 + checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 259 + dependencies = [ 260 + "serde_derive", 261 + ] 262 + 263 + [[package]] 264 + name = "serde_derive" 265 + version = "1.0.219" 266 + source = "registry+https://github.com/rust-lang/crates.io-index" 267 + checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 268 + dependencies = [ 269 + "proc-macro2", 270 + "quote", 271 + "syn", 272 + ] 273 + 274 + [[package]] 275 + name = "serde_json" 276 + version = "1.0.140" 277 + source = "registry+https://github.com/rust-lang/crates.io-index" 278 + checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 279 + dependencies = [ 280 + "itoa", 281 + "memchr", 282 + "ryu", 283 + "serde", 284 + ] 285 + 286 + [[package]] 287 + name = "sharded-slab" 288 + version = "0.1.7" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 291 + dependencies = [ 292 + "lazy_static", 293 + ] 294 + 295 + [[package]] 296 + name = "syn" 297 + version = "2.0.101" 298 + source = "registry+https://github.com/rust-lang/crates.io-index" 299 + checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 300 + dependencies = [ 301 + "proc-macro2", 302 + "quote", 303 + "unicode-ident", 304 + ] 305 + 306 + [[package]] 307 + name = "thread_local" 308 + version = "1.1.8" 309 + source = "registry+https://github.com/rust-lang/crates.io-index" 310 + checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 311 + dependencies = [ 312 + "cfg-if", 313 + "once_cell", 314 + ] 315 + 316 + [[package]] 317 + name = "tracing" 318 + version = "0.1.41" 319 + source = "registry+https://github.com/rust-lang/crates.io-index" 320 + checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 321 + dependencies = [ 322 + "pin-project-lite", 323 + "tracing-core", 324 + ] 325 + 326 + [[package]] 327 + name = "tracing-core" 328 + version = "0.1.33" 329 + source = "registry+https://github.com/rust-lang/crates.io-index" 330 + checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 331 + dependencies = [ 332 + "once_cell", 333 + "valuable", 334 + ] 335 + 336 + [[package]] 337 + name = "tracing-error" 338 + version = "0.2.1" 339 + source = "registry+https://github.com/rust-lang/crates.io-index" 340 + checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 341 + dependencies = [ 342 + "tracing", 343 + "tracing-subscriber", 344 + ] 345 + 346 + [[package]] 347 + name = "tracing-subscriber" 348 + version = "0.3.19" 349 + source = "registry+https://github.com/rust-lang/crates.io-index" 350 + checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 351 + dependencies = [ 352 + "sharded-slab", 353 + "thread_local", 354 + "tracing-core", 355 + ] 356 + 357 + [[package]] 358 + name = "unicode-ident" 359 + version = "1.0.18" 360 + source = "registry+https://github.com/rust-lang/crates.io-index" 361 + checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 362 + 363 + [[package]] 364 + name = "valuable" 365 + version = "0.1.1" 366 + source = "registry+https://github.com/rust-lang/crates.io-index" 367 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 368 + 369 + [[package]] 370 + name = "windows-sys" 371 + version = "0.52.0" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 374 + dependencies = [ 375 + "windows-targets", 376 + ] 377 + 378 + [[package]] 379 + name = "windows-targets" 380 + version = "0.52.6" 381 + source = "registry+https://github.com/rust-lang/crates.io-index" 382 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 383 + dependencies = [ 384 + "windows_aarch64_gnullvm", 385 + "windows_aarch64_msvc", 386 + "windows_i686_gnu", 387 + "windows_i686_gnullvm", 388 + "windows_i686_msvc", 389 + "windows_x86_64_gnu", 390 + "windows_x86_64_gnullvm", 391 + "windows_x86_64_msvc", 392 + ] 393 + 394 + [[package]] 395 + name = "windows_aarch64_gnullvm" 396 + version = "0.52.6" 397 + source = "registry+https://github.com/rust-lang/crates.io-index" 398 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 399 + 400 + [[package]] 401 + name = "windows_aarch64_msvc" 402 + version = "0.52.6" 403 + source = "registry+https://github.com/rust-lang/crates.io-index" 404 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 405 + 406 + [[package]] 407 + name = "windows_i686_gnu" 408 + version = "0.52.6" 409 + source = "registry+https://github.com/rust-lang/crates.io-index" 410 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 411 + 412 + [[package]] 413 + name = "windows_i686_gnullvm" 414 + version = "0.52.6" 415 + source = "registry+https://github.com/rust-lang/crates.io-index" 416 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 417 + 418 + [[package]] 419 + name = "windows_i686_msvc" 420 + version = "0.52.6" 421 + source = "registry+https://github.com/rust-lang/crates.io-index" 422 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 423 + 424 + [[package]] 425 + name = "windows_x86_64_gnu" 426 + version = "0.52.6" 427 + source = "registry+https://github.com/rust-lang/crates.io-index" 428 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 429 + 430 + [[package]] 431 + name = "windows_x86_64_gnullvm" 432 + version = "0.52.6" 433 + source = "registry+https://github.com/rust-lang/crates.io-index" 434 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 435 + 436 + [[package]] 437 + name = "windows_x86_64_msvc" 438 + version = "0.52.6" 439 + source = "registry+https://github.com/rust-lang/crates.io-index" 440 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+21
Cargo.toml
··· 1 + [package] 2 + name = "lix-diff" 3 + version = "0.0.1" 4 + license = "MIT" 5 + description = "A lix plugin for diffing two generations" 6 + homepage = "https://github.com/isabelroses/lix-diff" 7 + authors = [] 8 + edition = "2024" 9 + 10 + [dependencies] 11 + argh = "0.1.13" 12 + color-eyre = "0.6.4" 13 + humansize = "2.1.3" 14 + nu-ansi-term = "0.50.1" 15 + serde = "1.0.219" 16 + serde_json = "1.0.140" 17 + 18 + [lints.clippy] 19 + all = "warn" 20 + pedantic = "warn" 21 + unreadable_literal = { level = "allow", priority = 1 }
+27
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1746576598, 6 + "narHash": "sha256-FshoQvr6Aor5SnORVvh/ZdJ1Sa2U4ZrIMwKBX5k2wu0=", 7 + "owner": "nixos", 8 + "repo": "nixpkgs", 9 + "rev": "b3582c75c7f21ce0b429898980eddbbf05c68e55", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "nixos", 14 + "ref": "nixpkgs-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs" 22 + } 23 + } 24 + }, 25 + "root": "root", 26 + "version": 7 27 + }
+25
flake.nix
··· 1 + { 2 + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 3 + 4 + outputs = 5 + { self, nixpkgs }: 6 + let 7 + forAllSystems = 8 + function: 9 + nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed ( 10 + system: function nixpkgs.legacyPackages.${system} 11 + ); 12 + in 13 + { 14 + packages = forAllSystems (pkgs: { 15 + lix-diff = pkgs.callPackage ./package.nix { }; 16 + default = self.packages.${pkgs.stdenv.hostPlatform.system}.lix-diff; 17 + }); 18 + 19 + devShells = forAllSystems (pkgs: { 20 + default = pkgs.callPackage ./shell.nix { }; 21 + }); 22 + 23 + overlays.default = final: _: { lix-diff = final.callPackage ./package.nix { }; }; 24 + }; 25 + }
+28
package.nix
··· 1 + { lib, rustPlatform }: 2 + let 3 + toml = (lib.importTOML ./Cargo.toml).package; 4 + in 5 + rustPlatform.buildRustPackage { 6 + pname = "lix-diff"; 7 + inherit (toml) version; 8 + 9 + src = lib.fileset.toSource { 10 + root = ./.; 11 + fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./.)) ( 12 + lib.fileset.unions [ 13 + ./Cargo.toml 14 + ./Cargo.lock 15 + ./src 16 + ] 17 + ); 18 + }; 19 + 20 + cargoLock.lockFile = ./Cargo.lock; 21 + 22 + meta = { 23 + inherit (toml) homepage description; 24 + license = lib.licenses.mit; 25 + maintainers = with lib.maintainers; [ isabelroses ]; 26 + mainProgram = "nvd"; 27 + }; 28 + }
+26
shell.nix
··· 1 + { 2 + mkShell, 3 + callPackage, 4 + rustPlatform, 5 + 6 + # extra tooling 7 + clippy, 8 + rustfmt, 9 + rust-analyzer, 10 + }: 11 + let 12 + defaultPackage = callPackage ./package.nix { }; 13 + in 14 + mkShell { 15 + inputsFrom = [ defaultPackage ]; 16 + 17 + env = { 18 + RUST_SRC_PATH = rustPlatform.rustLibSrc; 19 + }; 20 + 21 + packages = [ 22 + clippy 23 + rustfmt 24 + rust-analyzer 25 + ]; 26 + }
+92
src/diff.rs
··· 1 + use humansize::{format_size, DECIMAL}; 2 + use nu_ansi_term::Color::{self, Green, Red, Yellow}; 3 + use std::collections::BTreeMap; 4 + 5 + use crate::package::{DiffType, Package}; 6 + use crate::parser::DiffRoot; 7 + 8 + #[derive(Debug)] 9 + pub struct PackageListDiff { 10 + pub added: BTreeMap<String, Package>, 11 + pub removed: BTreeMap<String, Package>, 12 + pub changed: BTreeMap<String, Package>, 13 + size_delta: i64, 14 + longest_name: usize, 15 + } 16 + 17 + pub fn partition_diff(diff: &DiffRoot) -> PackageListDiff { 18 + let mut added = BTreeMap::new(); 19 + let mut removed = BTreeMap::new(); 20 + let mut changed = BTreeMap::new(); 21 + let mut size_delta: i64 = 0; 22 + let mut longest_name = 0; 23 + 24 + for (name, package) in &diff.packages { 25 + match package.diff_type { 26 + DiffType::Added => { 27 + added.insert(name.clone(), package.clone()); 28 + } 29 + DiffType::Removed => { 30 + removed.insert(name.clone(), package.clone()); 31 + } 32 + DiffType::Changed => { 33 + changed.insert(name.clone(), package.clone()); 34 + } 35 + _ => {} 36 + } 37 + 38 + size_delta += package.size_delta; 39 + longest_name = longest_name.max(name.len()); 40 + } 41 + 42 + PackageListDiff { 43 + added, 44 + removed, 45 + changed, 46 + size_delta, 47 + longest_name, 48 + } 49 + } 50 + 51 + impl std::fmt::Display for PackageListDiff { 52 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 + if self.added.is_empty() && self.removed.is_empty() && self.changed.is_empty() { 54 + return write!(f, "No differences found."); 55 + } 56 + 57 + let title_style = nu_ansi_term::Style::new() 58 + .underline() 59 + .bold() 60 + .fg(Color::LightGray); 61 + 62 + let name_width = self.longest_name + 2; 63 + 64 + writeln!(f, "{}", &title_style.paint("Changed"))?; 65 + for (name, package) in &self.changed { 66 + writeln!(f, "[{}] {name:name_width$}{package}", Yellow.paint("C"))?; 67 + } 68 + 69 + writeln!(f)?; 70 + writeln!(f, "{}", &title_style.paint("Added"))?; 71 + for (name, package) in &self.added { 72 + write!(f, "[{}] {name:name_width$}{package}", Green.paint("A"))?; 73 + writeln!(f)?; 74 + } 75 + 76 + writeln!(f)?; 77 + writeln!(f, "{}", &title_style.paint("Removed"))?; 78 + for (name, package) in &self.removed { 79 + writeln!(f, "[{}] {name:name_width$}{package}", Red.paint("R"))?; 80 + } 81 + 82 + { 83 + let delta = self.size_delta; 84 + let sign = if delta > 0 { "+" } else { "-" }; 85 + let size: u64 = delta.abs().try_into().unwrap_or(0); 86 + writeln!(f)?; 87 + writeln!(f, "size diff: {sign}{}", format_size(size, DECIMAL))?; 88 + } 89 + 90 + Ok(()) 91 + } 92 + }
+46
src/main.rs
··· 1 + use argh::FromArgs; 2 + use color_eyre::Result; 3 + use nu_ansi_term::{Color, Style}; 4 + 5 + mod diff; 6 + mod package; 7 + mod parser; 8 + 9 + #[derive(FromArgs, PartialEq, Debug)] 10 + /// List the package differences between two `NixOS` generations 11 + struct Args { 12 + /// the generation we are switching from 13 + #[argh(positional)] 14 + before: String, 15 + 16 + /// the generation we are switching to 17 + #[argh(positional)] 18 + after: String, 19 + } 20 + 21 + fn main() -> Result<()> { 22 + let args: Args = argh::from_env(); 23 + let before = args.before; 24 + let after = args.after; 25 + 26 + // Check if the generations are valid 27 + if before.is_empty() || after.is_empty() { 28 + eprintln!("Both generations must be specified."); 29 + std::process::exit(1); 30 + } 31 + 32 + let diff = parser::diff(&before, &after)?; 33 + let packages = diff::partition_diff(&diff); 34 + 35 + let arrow_style = Style::new().bold().fg(Color::LightGray); 36 + 37 + let before_text = format!("<<< {before}"); 38 + let after_text = format!(">>> {after}"); 39 + 40 + println!("{}", arrow_style.paint(before_text)); 41 + println!("{}\n", arrow_style.paint(after_text)); 42 + 43 + println!("{packages}"); 44 + 45 + Ok(()) 46 + }
+82
src/package.rs
··· 1 + use nu_ansi_term::Color::{Green, Red}; 2 + use serde::de::Deserializer; 3 + use serde::Deserialize; 4 + 5 + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] 6 + pub enum DiffType { 7 + Added, 8 + Removed, 9 + Changed, 10 + 11 + #[default] 12 + Unknown, 13 + } 14 + 15 + #[derive(Deserialize, Clone, Debug)] 16 + #[serde(rename_all = "camelCase")] 17 + pub struct Package { 18 + pub size_delta: i64, 19 + 20 + #[serde(deserialize_with = "version_deserializer")] 21 + pub versions_before: Vec<String>, 22 + 23 + #[serde(deserialize_with = "version_deserializer")] 24 + pub versions_after: Vec<String>, 25 + 26 + /// This is not a part of the JSON schema, but is used to determine the type of diff 27 + #[serde(skip)] 28 + pub diff_type: DiffType, 29 + } 30 + 31 + fn version_deserializer<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error> 32 + where 33 + D: Deserializer<'de>, 34 + { 35 + let vec = Vec::<String>::deserialize(deserializer)?; 36 + Ok(vec 37 + .into_iter() 38 + .map(|s| { 39 + if s.is_empty() { 40 + "<none>".to_string() 41 + } else { 42 + s 43 + } 44 + }) 45 + .collect()) 46 + } 47 + 48 + impl DiffType { 49 + pub fn from_versions(before: &[String], after: &[String]) -> DiffType { 50 + match (before.is_empty(), after.is_empty()) { 51 + (true, false) => DiffType::Added, 52 + (false, true) => DiffType::Removed, 53 + (false, false) => DiffType::Changed, 54 + (true, true) => DiffType::Unknown, // should be unreachable but im not sure 55 + } 56 + } 57 + } 58 + 59 + impl std::fmt::Display for Package { 60 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 + // added package 62 + match self.diff_type { 63 + DiffType::Added => { 64 + write!(f, "{}", Green.paint(self.versions_after.join(", ")))?; 65 + } 66 + DiffType::Removed => { 67 + write!(f, "{}", Red.paint(self.versions_before.join(", ")))?; 68 + } 69 + DiffType::Changed => { 70 + write!( 71 + f, 72 + "{} -> {}", 73 + Red.paint(self.versions_before.join(", ")), 74 + Green.paint(self.versions_after.join(", ")) 75 + )?; 76 + } 77 + DiffType::Unknown => unreachable!(), 78 + } 79 + 80 + Ok(()) 81 + } 82 + }
+55
src/parser.rs
··· 1 + use color_eyre::Result; 2 + use serde::Deserialize; 3 + use std::{collections::BTreeMap, process::Command}; 4 + 5 + use crate::package::{DiffType, Package}; 6 + 7 + #[derive(Deserialize, Debug)] 8 + pub struct DiffRoot { 9 + pub packages: BTreeMap<String, Package>, 10 + 11 + #[expect(dead_code)] 12 + pub schema: String, 13 + } 14 + 15 + fn run_diff(before: &str, after: &str) -> String { 16 + let raw_diff = Command::new("nix") 17 + .args(["store", "diff-closures", "--json", before, after]) 18 + .output() 19 + .expect("Failed to execute command"); 20 + 21 + if !raw_diff.status.success() { 22 + eprintln!("Error: {}", String::from_utf8_lossy(&raw_diff.stderr)); 23 + std::process::exit(1); 24 + } 25 + 26 + let diff_output = String::from_utf8_lossy(&raw_diff.stdout); 27 + if diff_output.is_empty() { 28 + eprintln!("No differences found."); 29 + std::process::exit(0); 30 + } 31 + 32 + diff_output.into_owned() 33 + } 34 + 35 + fn parse_diff(input: &str) -> Result<DiffRoot> { 36 + serde_json::from_str(input) 37 + .map(|mut diff_root: DiffRoot| { 38 + for package in diff_root.packages.values_mut() { 39 + package.diff_type = 40 + DiffType::from_versions(&package.versions_before, &package.versions_after); 41 + } 42 + diff_root 43 + }) 44 + .map_err(|e| { 45 + eprintln!("Failed to parse JSON: {e}"); 46 + std::process::exit(1); 47 + }) 48 + } 49 + 50 + pub fn diff(before: &str, after: &str) -> Result<DiffRoot> { 51 + let diff_output = run_diff(before, after); 52 + let diff_root: DiffRoot = parse_diff(&diff_output)?; 53 + 54 + Ok(diff_root) 55 + }