├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── default.nix ├── flake.lock ├── flake.nix ├── modules ├── home.nix └── npkg.nix └── src ├── libnpkg ├── execute.rs └── mod.rs ├── main.rs └── npkgcmd ├── config.rs ├── mod.rs ├── operate.rs ├── parse.rs ├── run.rs └── search.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.bak 3 | /src/*.bak 4 | /result-bin 5 | /result 6 | shell.nix -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix" 3 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" 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 = "alloc-no-stdlib" 22 | version = "2.0.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" 25 | 26 | [[package]] 27 | name = "alloc-stdlib" 28 | version = "0.2.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" 31 | dependencies = [ 32 | "alloc-no-stdlib", 33 | ] 34 | 35 | [[package]] 36 | name = "atty" 37 | version = "0.2.14" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 40 | dependencies = [ 41 | "hermit-abi", 42 | "libc", 43 | "winapi", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.1.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 51 | 52 | [[package]] 53 | name = "backtrace" 54 | version = "0.3.65" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" 57 | dependencies = [ 58 | "addr2line", 59 | "cc", 60 | "cfg-if", 61 | "libc", 62 | "miniz_oxide", 63 | "object", 64 | "rustc-demangle", 65 | ] 66 | 67 | [[package]] 68 | name = "bimap" 69 | version = "0.6.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" 72 | dependencies = [ 73 | "serde", 74 | ] 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "1.3.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 81 | 82 | [[package]] 83 | name = "brotli" 84 | version = "3.3.4" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" 87 | dependencies = [ 88 | "alloc-no-stdlib", 89 | "alloc-stdlib", 90 | "brotli-decompressor", 91 | ] 92 | 93 | [[package]] 94 | name = "brotli-decompressor" 95 | version = "2.3.2" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" 98 | dependencies = [ 99 | "alloc-no-stdlib", 100 | "alloc-stdlib", 101 | ] 102 | 103 | [[package]] 104 | name = "cbitset" 105 | version = "0.2.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b" 108 | dependencies = [ 109 | "num-traits", 110 | ] 111 | 112 | [[package]] 113 | name = "cc" 114 | version = "1.0.73" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 117 | 118 | [[package]] 119 | name = "cfg-if" 120 | version = "1.0.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 123 | 124 | [[package]] 125 | name = "clap" 126 | version = "3.2.6" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a" 129 | dependencies = [ 130 | "atty", 131 | "bitflags", 132 | "clap_derive", 133 | "clap_lex", 134 | "indexmap", 135 | "once_cell", 136 | "strsim", 137 | "termcolor", 138 | "textwrap", 139 | ] 140 | 141 | [[package]] 142 | name = "clap_derive" 143 | version = "3.2.6" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a" 146 | dependencies = [ 147 | "heck", 148 | "proc-macro-error", 149 | "proc-macro2", 150 | "quote", 151 | "syn", 152 | ] 153 | 154 | [[package]] 155 | name = "clap_lex" 156 | version = "0.2.3" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "87eba3c8c7f42ef17f6c659fc7416d0f4758cd3e58861ee63c5fa4a4dde649e4" 159 | dependencies = [ 160 | "os_str_bytes", 161 | ] 162 | 163 | [[package]] 164 | name = "countme" 165 | version = "2.0.4" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" 168 | 169 | [[package]] 170 | name = "curl" 171 | version = "0.4.43" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" 174 | dependencies = [ 175 | "curl-sys", 176 | "libc", 177 | "openssl-probe", 178 | "openssl-sys", 179 | "schannel", 180 | "socket2", 181 | "winapi", 182 | ] 183 | 184 | [[package]] 185 | name = "curl-sys" 186 | version = "0.4.53+curl-7.82.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "8092905a5a9502c312f223b2775f57ec5c5b715f9a15ee9d2a8591d1364a0352" 189 | dependencies = [ 190 | "cc", 191 | "libc", 192 | "libz-sys", 193 | "openssl-sys", 194 | "pkg-config", 195 | "vcpkg", 196 | "winapi", 197 | ] 198 | 199 | [[package]] 200 | name = "failure" 201 | version = "0.1.8" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 204 | dependencies = [ 205 | "backtrace", 206 | "failure_derive", 207 | ] 208 | 209 | [[package]] 210 | name = "failure_derive" 211 | version = "0.1.8" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 214 | dependencies = [ 215 | "proc-macro2", 216 | "quote", 217 | "syn", 218 | "synstructure", 219 | ] 220 | 221 | [[package]] 222 | name = "gimli" 223 | version = "0.26.1" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" 226 | 227 | [[package]] 228 | name = "hashbrown" 229 | version = "0.9.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 232 | 233 | [[package]] 234 | name = "hashbrown" 235 | version = "0.11.2" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 238 | 239 | [[package]] 240 | name = "heck" 241 | version = "0.4.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 244 | 245 | [[package]] 246 | name = "hermit-abi" 247 | version = "0.1.19" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 250 | dependencies = [ 251 | "libc", 252 | ] 253 | 254 | [[package]] 255 | name = "indexmap" 256 | version = "1.8.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 259 | dependencies = [ 260 | "autocfg", 261 | "hashbrown 0.11.2", 262 | ] 263 | 264 | [[package]] 265 | name = "itoa" 266 | version = "1.0.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 269 | 270 | [[package]] 271 | name = "lazy_static" 272 | version = "1.4.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 275 | 276 | [[package]] 277 | name = "libc" 278 | version = "0.2.121" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" 281 | 282 | [[package]] 283 | name = "libz-sys" 284 | version = "1.1.5" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859" 287 | dependencies = [ 288 | "cc", 289 | "libc", 290 | "pkg-config", 291 | "vcpkg", 292 | ] 293 | 294 | [[package]] 295 | name = "memchr" 296 | version = "2.5.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 299 | 300 | [[package]] 301 | name = "memoffset" 302 | version = "0.6.5" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 305 | dependencies = [ 306 | "autocfg", 307 | ] 308 | 309 | [[package]] 310 | name = "miniz_oxide" 311 | version = "0.5.3" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" 314 | dependencies = [ 315 | "adler", 316 | ] 317 | 318 | [[package]] 319 | name = "nix-editor" 320 | version = "0.2.11" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "fc6108e1a8b7d185da9753346e3d25cefe61e665e0bb3310396dc45ddea4c6c7" 323 | dependencies = [ 324 | "clap", 325 | "failure", 326 | "owo-colors", 327 | "rnix", 328 | ] 329 | 330 | [[package]] 331 | name = "npkg" 332 | version = "0.1.2" 333 | dependencies = [ 334 | "bimap", 335 | "brotli", 336 | "clap", 337 | "curl", 338 | "nix-editor", 339 | "owo-colors", 340 | "serde", 341 | "serde_json", 342 | ] 343 | 344 | [[package]] 345 | name = "num-traits" 346 | version = "0.2.14" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 349 | dependencies = [ 350 | "autocfg", 351 | ] 352 | 353 | [[package]] 354 | name = "object" 355 | version = "0.28.4" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" 358 | dependencies = [ 359 | "memchr", 360 | ] 361 | 362 | [[package]] 363 | name = "once_cell" 364 | version = "1.12.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 367 | 368 | [[package]] 369 | name = "openssl-probe" 370 | version = "0.1.5" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 373 | 374 | [[package]] 375 | name = "openssl-sys" 376 | version = "0.9.72" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" 379 | dependencies = [ 380 | "autocfg", 381 | "cc", 382 | "libc", 383 | "pkg-config", 384 | "vcpkg", 385 | ] 386 | 387 | [[package]] 388 | name = "os_str_bytes" 389 | version = "6.0.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 392 | 393 | [[package]] 394 | name = "owo-colors" 395 | version = "3.4.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" 398 | 399 | [[package]] 400 | name = "pkg-config" 401 | version = "0.3.24" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 404 | 405 | [[package]] 406 | name = "proc-macro-error" 407 | version = "1.0.4" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 410 | dependencies = [ 411 | "proc-macro-error-attr", 412 | "proc-macro2", 413 | "quote", 414 | "syn", 415 | "version_check", 416 | ] 417 | 418 | [[package]] 419 | name = "proc-macro-error-attr" 420 | version = "1.0.4" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 423 | dependencies = [ 424 | "proc-macro2", 425 | "quote", 426 | "version_check", 427 | ] 428 | 429 | [[package]] 430 | name = "proc-macro2" 431 | version = "1.0.39" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" 434 | dependencies = [ 435 | "unicode-ident", 436 | ] 437 | 438 | [[package]] 439 | name = "quote" 440 | version = "1.0.17" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" 443 | dependencies = [ 444 | "proc-macro2", 445 | ] 446 | 447 | [[package]] 448 | name = "rnix" 449 | version = "0.10.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "065c5eac8e8f7e7b0e7227bdc46e2d8dd7d69fff7a9a2734e21f686bbcb9b2c9" 452 | dependencies = [ 453 | "cbitset", 454 | "rowan", 455 | "smol_str", 456 | ] 457 | 458 | [[package]] 459 | name = "rowan" 460 | version = "0.12.6" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043" 463 | dependencies = [ 464 | "countme", 465 | "hashbrown 0.9.1", 466 | "memoffset", 467 | "rustc-hash", 468 | "text-size", 469 | ] 470 | 471 | [[package]] 472 | name = "rustc-demangle" 473 | version = "0.1.21" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 476 | 477 | [[package]] 478 | name = "rustc-hash" 479 | version = "1.1.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 482 | 483 | [[package]] 484 | name = "ryu" 485 | version = "1.0.9" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 488 | 489 | [[package]] 490 | name = "schannel" 491 | version = "0.1.19" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 494 | dependencies = [ 495 | "lazy_static", 496 | "winapi", 497 | ] 498 | 499 | [[package]] 500 | name = "serde" 501 | version = "1.0.137" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 504 | dependencies = [ 505 | "serde_derive", 506 | ] 507 | 508 | [[package]] 509 | name = "serde_derive" 510 | version = "1.0.137" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 513 | dependencies = [ 514 | "proc-macro2", 515 | "quote", 516 | "syn", 517 | ] 518 | 519 | [[package]] 520 | name = "serde_json" 521 | version = "1.0.81" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 524 | dependencies = [ 525 | "itoa", 526 | "ryu", 527 | "serde", 528 | ] 529 | 530 | [[package]] 531 | name = "smol_str" 532 | version = "0.1.21" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "61d15c83e300cce35b7c8cd39ff567c1ef42dde6d4a1a38dbdbf9a59902261bd" 535 | dependencies = [ 536 | "serde", 537 | ] 538 | 539 | [[package]] 540 | name = "socket2" 541 | version = "0.4.4" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 544 | dependencies = [ 545 | "libc", 546 | "winapi", 547 | ] 548 | 549 | [[package]] 550 | name = "strsim" 551 | version = "0.10.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 554 | 555 | [[package]] 556 | name = "syn" 557 | version = "1.0.95" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" 560 | dependencies = [ 561 | "proc-macro2", 562 | "quote", 563 | "unicode-ident", 564 | ] 565 | 566 | [[package]] 567 | name = "synstructure" 568 | version = "0.12.6" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 571 | dependencies = [ 572 | "proc-macro2", 573 | "quote", 574 | "syn", 575 | "unicode-xid", 576 | ] 577 | 578 | [[package]] 579 | name = "termcolor" 580 | version = "1.1.3" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 583 | dependencies = [ 584 | "winapi-util", 585 | ] 586 | 587 | [[package]] 588 | name = "text-size" 589 | version = "1.1.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" 592 | 593 | [[package]] 594 | name = "textwrap" 595 | version = "0.15.0" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 598 | 599 | [[package]] 600 | name = "unicode-ident" 601 | version = "1.0.0" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 604 | 605 | [[package]] 606 | name = "unicode-xid" 607 | version = "0.2.3" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" 610 | 611 | [[package]] 612 | name = "vcpkg" 613 | version = "0.2.15" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 616 | 617 | [[package]] 618 | name = "version_check" 619 | version = "0.9.4" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 622 | 623 | [[package]] 624 | name = "winapi" 625 | version = "0.3.9" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 628 | dependencies = [ 629 | "winapi-i686-pc-windows-gnu", 630 | "winapi-x86_64-pc-windows-gnu", 631 | ] 632 | 633 | [[package]] 634 | name = "winapi-i686-pc-windows-gnu" 635 | version = "0.4.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 638 | 639 | [[package]] 640 | name = "winapi-util" 641 | version = "0.1.5" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 644 | dependencies = [ 645 | "winapi", 646 | ] 647 | 648 | [[package]] 649 | name = "winapi-x86_64-pc-windows-gnu" 650 | version = "0.4.0" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 653 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "npkg" 3 | version = "0.1.2" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Some functions to manage and install packages on NixOS" 7 | repository = "https://github.com/vlinkz/npkg/" 8 | readme = "README.md" 9 | include = [ 10 | "src/*", 11 | "Cargo.toml", 12 | "LICENSE*", 13 | "README.md", 14 | ] 15 | 16 | [dependencies] 17 | nix-editor = "0.2.11" 18 | clap = { version = "3.2.6", features = ["derive"] } 19 | owo-colors = "3.4.0" 20 | serde_json = "1.0" 21 | serde = { version = "1.0", features = ["derive"] } 22 | bimap = { version = "0.6.2", features = ["serde"] } 23 | brotli = "3.3.4" 24 | curl = "0.4.43" 25 | 26 | [lib] 27 | name = "npkg" 28 | path = "src/libnpkg/mod.rs" 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Victor Fuentes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Npkg 2 | === 3 | [![Built with Nix][builtwithnix badge]][builtwithnix] 4 | [![License: MIT][MIT badge]][MIT] 5 | 6 | Npkg is a small tool that allows you to configure all your NixOS packages in one place. 7 | 8 | Npkg is written with rust and uses [nix-editor](https://github.com/vlinkz/nix-editor) and [rnix-parser](https://github.com/nix-community/rnix-parser) to parse and edit configuration files. 9 | 10 | # WARNING 11 | 12 | This tool is still new/experimental, and being that it directly modifies critical files, such as `/etc/nixos/configuration.nix`, make sure you have backups in case it messes it up or deletes such files. I've already sacrificed some of my files to the void, don't let that happen to you! 13 | 14 | # NixOS Installation 15 | 16 | ``` 17 | git clone https://github.com/vlinkz/npkg 18 | nix-env -f npkg -i npkg 19 | ``` 20 | 21 | Then modify `~/npkg/config.json` to match your configuration. 22 | 23 | # Usage with Nix Flakes 24 | 25 | ``` 26 | nix run github:vlinkz/npkg -- --help 27 | ``` 28 | 29 | # Declarative System Installation 30 | 31 | ### Head of `configuration.nix` 32 | 33 | ``` 34 | { config, pkgs, lib, ... }: 35 | 36 | let 37 | npkg = (import (pkgs.fetchFromGitHub { 38 | owner = "vlinkz"; 39 | repo = "npkg"; 40 | rev = "0.1.2"; 41 | sha256 = "0000000000000000000000000000000000000000000000000000"; 42 | })).default; 43 | in 44 | { 45 | imports = 46 | # rest of configuration 47 | ``` 48 | 49 | Packages: 50 | ``` 51 | environment.systemPackages = 52 | with pkgs; [ 53 | npkg 54 | # rest of your packages 55 | ]; 56 | ``` 57 | 58 | # Arguments 59 | 60 | ``` 61 | USAGE: 62 | npkg [OPTIONS] [PACKAGES]... 63 | 64 | ARGS: 65 | ... Packages 66 | 67 | OPTIONS: 68 | -d, --dry-run Do not build any packages, only edit configuration file 69 | -E, --env Use nix environment 'nix-env' 70 | -h, --help Print help information 71 | -H, --home Use home-manager 'home.nix' 72 | -i, --install Install a package 73 | -l, --list List installed packages 74 | -o, --output Output modified configuration file to a specified location 75 | -r, --remove Remove a package 76 | -s, --search Search for a package 77 | -S, --system Use system 'configuration.nix' 78 | -u, --update Update packages 79 | -V, --version Print version information 80 | ``` 81 | 82 | # Use cases 83 | 84 | ## Installing packages 85 | 86 | To install a package, you can run: 87 | ``` 88 | npkg -i 89 | ``` 90 | By default, this will use `nix-env` and install the package in you current environment. You can choose to use a specific available installer by using the `-S`, `-H`, or `-E` flags. 91 | 92 | - ``` 93 | npkg -iS hello 94 | ``` 95 | will install the `hello` package as a system package by modifying your `/etc/nixos/configuration.nix` file and then calling `nixos-rebuild switch`. 96 | 97 | - ``` 98 | npkg -iH hello 99 | ``` 100 | will install the `hello` package using [home-manager](https://github.com/nix-community/home-manager) if it is installed. It will modify `~/.config/nixpkgs/home.nix` and then call `home-manager switch`. 101 | 102 | - ``` 103 | npkg -iE hello 104 | ``` 105 | will install the `hello` package to the current nix environment by calling `nix-env -iA nixos.hello`. 106 | 107 | ## Removing packages 108 | 109 | Very similar to installing packages: 110 | ``` 111 | npkg -r 112 | ``` 113 | The same `-S`, `-H`, and `-E` flags apply. 114 | 115 | ## Updating packages 116 | 117 | To update all packages: 118 | ``` 119 | npkg -u 120 | ``` 121 | To specify only one type, the same `-S`, `-H`, and `-E` flags apply. 122 | 123 | ## List installed packages 124 | 125 | ``` 126 | npkg -l 127 | ``` 128 | This will list all packages installed in `/etc/nixos/configuration.nix`, `~/.config/nixpkgs/home.nix`, and with `nix-env`. 129 | 130 | You can specify only one of these by using the `-S`, `-H`, and `-E` flags. 131 | 132 | ## Search for a package 133 | ``` 134 | npkg -s 135 | ``` 136 | This will print a list of packages that match the query specified. For example: 137 | ``` 138 | $ npkg -s hello greeting 139 | 140 | * hello (2.12) (nix env) 141 | A program that produces a familiar, friendly greeting 142 | ``` 143 | 144 | This means that package `hello` version `2.12` is currently installed with `nix-env`. 145 | 146 | # Configuration 147 | 148 | A configuration file is stored in `~/.config/npkg/config.json`, by default, it contains: 149 | 150 | ```json 151 | { 152 | "systemconfig": "/etc/nixos/configuration.nix", 153 | "homeconfig": "/home/$HOME/.config/nixpkgs/home.nix", 154 | "flake": null 155 | } 156 | ``` 157 | 158 | These values can be edited to point to other locations. This is useful in [nix flake based systems](https://nixos.wiki/wiki/Flakes#Using_nix_flakes_with_NixOS) or any system where config files are not in expected locations. 159 | 160 | # But why? 161 | 162 | I wanted to code something as a proof of concept for using [nix-editor](https://github.com/vlinkz/nix-editor) as a backed for other tools. But so far I've been using it almost daily! 163 | 164 | # Future plans 165 | 166 | - Check for installed packages in other locations. 167 | 168 | For example, if installing `hello` with `home-manager`, and it's already installed with `nix-env`, give an option to switch it over. 169 | 170 | - When removing packages, automatically detect where installed instead of defaulting to `nix-env` 171 | 172 | - Whatever else pops into my head 173 | 174 | [builtwithnix badge]: https://img.shields.io/badge/Built%20With-Nix-41439A?style=flat-square&logo=nixos&logoColor=white 175 | [builtwithnix]: https://builtwithnix.org/ 176 | [MIT badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square 177 | [MIT]: https://opensource.org/licenses/MIT -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in 4 | fetchTarball { 5 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | } 8 | ) 9 | { src = ./.; } 10 | ).defaultNix 11 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1650374568, 7 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "naersk": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | }, 23 | "locked": { 24 | "lastModified": 1655042882, 25 | "narHash": "sha256-9BX8Fuez5YJlN7cdPO63InoyBy7dm3VlJkkmTt6fS1A=", 26 | "owner": "nix-community", 27 | "repo": "naersk", 28 | "rev": "cddffb5aa211f50c4b8750adbec0bbbdfb26bb9f", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "nix-community", 33 | "repo": "naersk", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs": { 38 | "locked": { 39 | "lastModified": 1655876541, 40 | "narHash": "sha256-eOz1YNclUTobC2f9meWpw+idhzWImdti43OKjLBoDq8=", 41 | "owner": "NixOS", 42 | "repo": "nixpkgs", 43 | "rev": "a0edeb02ae5b92eda6efbee4e26d8c33c15063fd", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "id": "nixpkgs", 48 | "type": "indirect" 49 | } 50 | }, 51 | "nixpkgs_2": { 52 | "locked": { 53 | "lastModified": 1655983783, 54 | "narHash": "sha256-0h1FzkYWei24IdKNpCX93onkF/FMiXQG8SdEbTc0r8A=", 55 | "owner": "nixos", 56 | "repo": "nixpkgs", 57 | "rev": "6141b8932a5cf376fe18fcd368cecd9ad946cb68", 58 | "type": "github" 59 | }, 60 | "original": { 61 | "owner": "nixos", 62 | "ref": "nixos-unstable", 63 | "repo": "nixpkgs", 64 | "type": "github" 65 | } 66 | }, 67 | "root": { 68 | "inputs": { 69 | "flake-compat": "flake-compat", 70 | "naersk": "naersk", 71 | "nixpkgs": "nixpkgs_2", 72 | "utils": "utils" 73 | } 74 | }, 75 | "utils": { 76 | "locked": { 77 | "lastModified": 1656065134, 78 | "narHash": "sha256-oc6E6ByIw3oJaIyc67maaFcnjYOz1mMcOtHxbEf9NwQ=", 79 | "owner": "numtide", 80 | "repo": "flake-utils", 81 | "rev": "bee6a7250dd1b01844a2de7e02e4df7d8a0a206c", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "numtide", 86 | "repo": "flake-utils", 87 | "type": "github" 88 | } 89 | } 90 | }, 91 | "root": "root", 92 | "version": 7 93 | } 94 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | utils.url = "github:numtide/flake-utils"; 5 | naersk.url = "github:nix-community/naersk"; 6 | flake-compat = { 7 | url = "github:edolstra/flake-compat"; 8 | flake = false; 9 | }; 10 | }; 11 | 12 | outputs = { self, nixpkgs, utils, naersk, ... }: 13 | utils.lib.eachDefaultSystem 14 | (system: 15 | let 16 | name = "npkg"; 17 | pkgs = import nixpkgs { inherit system; }; 18 | naersk-lib = naersk.lib."${system}"; 19 | in rec { 20 | packages.${name} = naersk-lib.buildPackage { 21 | pname = "${name}"; 22 | root = ./.; 23 | copyLibs = true; 24 | buildInputs = with pkgs; [ 25 | openssl 26 | pkgconfig 27 | ]; 28 | }; 29 | 30 | # `nix build` 31 | defaultPackage = packages.${name}; 32 | 33 | # `nix run` 34 | apps.${name} = utils.lib.mkApp { 35 | inherit name; 36 | drv = packages.${name}; 37 | }; 38 | defaultApp = packages.${name}; 39 | 40 | # `nix develop` 41 | devShells = { 42 | default = pkgs.mkShell { 43 | nativeBuildInputs = 44 | with pkgs; [ rustc cargo openssl pkgconfig ] ; 45 | }; 46 | }; 47 | } 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /modules/home.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, ... }: 2 | 3 | with lib; 4 | 5 | let 6 | 7 | cfg = config.programs.npkg; 8 | 9 | jsonFormat = pkgs.formats.json { }; 10 | 11 | in 12 | 13 | { 14 | options = { 15 | programs.npkg = { 16 | systemconfig = mkOption { 17 | type = types.path; 18 | default = "/etc/nixos/configuration.nix"; 19 | example = literalExpression ''/home/user/nix/configuration.nix''; 20 | description = ''Where npkg looks for configuration.nix''; 21 | }; 22 | homeconfig = mkOption { 23 | type = types.path; 24 | default = "${config.home.homeDirectory}/.config/nixpkgs/home.nix"; 25 | example = literalExpression ''/home/user/nix/home.nix''; 26 | description = ''Where npkg looks for home.nix''; 27 | }; 28 | flake = mkOption { 29 | type = with types; nullOr path; 30 | default = null; 31 | example = literalExpression ''/home/user/nix/flake.nix''; 32 | description = ''Where npkg looks for flake.nix''; 33 | }; 34 | }; 35 | }; 36 | 37 | config = mkIf (cfg.systemconfig != "/etc/nixos/configuration.nix" || cfg.homeconfig != "${config.home.homeDirectory}/.config/nixpkgs/home.nix" || cfg.flake != null) { 38 | xdg.configFile."npkg/config.json".source = jsonFormat.generate "config.json" cfg; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /modules/npkg.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, ... }: 2 | 3 | with lib; 4 | 5 | let 6 | 7 | cfg = config.programs.npkg; 8 | 9 | jsonFormat = pkgs.formats.json { }; 10 | 11 | in 12 | 13 | { 14 | options = { 15 | programs.npkg = { 16 | systemconfig = mkOption { 17 | type = types.path; 18 | default = "/etc/nixos/configuration.nix"; 19 | example = literalExpression ''/home/user/nix/configuration.nix''; 20 | description = ''Where npkg looks for configuration.nix''; 21 | }; 22 | homeconfig = mkOption { 23 | type = with types; nullOr path; 24 | default = null; 25 | example = literalExpression ''/home/user/.config/nixpkgs/home.nix''; 26 | description = ''Where npkg looks for home.nix''; 27 | }; 28 | flake = mkOption { 29 | type = with types; nullOr path; 30 | default = null; 31 | example = literalExpression ''/etc/nixos/flake.nix''; 32 | description = ''Where npkg looks for flake.nix''; 33 | }; 34 | }; 35 | }; 36 | 37 | config = mkIf (cfg.systemconfig != "/etc/nixos/configuration.nix" || cfg.homeconfig != null || cfg.flake != null) { 38 | environment.etc."npkg/config.json".source = jsonFormat.generate "config.json" cfg; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/libnpkg/execute.rs: -------------------------------------------------------------------------------- 1 | use std::process::{exit, Command}; 2 | 3 | pub enum ExecuteError { 4 | /// Thrown when running a command fails. 5 | CmdError, 6 | /// Thrown when writing to a file fails. 7 | /// The path to the file is included. 8 | WriteError(String), 9 | } 10 | 11 | /// Installs packages using `nix-env -iA nixos.` 12 | /// 13 | /// Packages must be in the `nixos` channel 14 | pub fn envinstall(pkgs: Vec) -> Result<(), ExecuteError> { 15 | let mut prefixpkgs = vec![]; 16 | for p in &pkgs { 17 | prefixpkgs.push(format!("nixos.{}", p)); 18 | } 19 | match Command::new("nix-env").arg("-iA").args(prefixpkgs).status() { 20 | Ok(_) => Ok(()), 21 | Err(_) => Err(ExecuteError::CmdError), 22 | } 23 | } 24 | 25 | /// Uninstalls packages using `nix-env -e ` 26 | /// 27 | /// Uninstalling is based on the package name rather than the package attribute used during install 28 | pub fn envremove(pkgs: Vec) -> Result<(), ExecuteError> { 29 | match Command::new("nix-env").arg("-e").args(pkgs).status() { 30 | Ok(_) => Ok(()), 31 | Err(_) => Err(ExecuteError::CmdError), 32 | } 33 | } 34 | 35 | /// Updates packages using `nix-env -u *` 36 | pub fn envupdate() -> Result<(), ExecuteError> { 37 | match Command::new("nix-env").arg("-u").arg("*").status() { 38 | Ok(_) => Ok(()), 39 | Err(_) => Err(ExecuteError::CmdError), 40 | } 41 | } 42 | 43 | /// Adds packages to a nix configuration file 44 | /// 45 | /// Package specified are added to the configuration file text in `f`. 46 | /// The configuration text with the added package is returned. 47 | /// By default the field modified is `environment.systemPackages`, but can be changed if `query` is specified. 48 | pub fn pkwrite( 49 | mut pkgs: Vec, 50 | f: &str, 51 | query: Option<&str>, 52 | ) -> Result { 53 | let q; 54 | (pkgs, q) = pkwith(pkgs, f, query); 55 | let out = match nix_editor::write::addtoarr(f, &q, pkgs) { 56 | Ok(x) => x, 57 | Err(_) => exit(1), 58 | }; 59 | 60 | Ok(out) 61 | } 62 | 63 | /// Removes packages from a nix configuration file 64 | /// 65 | /// Package specified are removed from the configuration text in `f`. 66 | /// The configuration text with the removed package is returned. 67 | /// By default the field modified is `environment.systemPackages`, but can be changed if `query` is specified. 68 | pub fn pkrm(mut pkgs: Vec, f: &str, query: Option<&str>) -> Result { 69 | let q; 70 | (pkgs, q) = pkwith(pkgs, f, query); 71 | let out = match nix_editor::write::rmarr(f, &q, pkgs) { 72 | Ok(x) => x, 73 | Err(_) => exit(1), 74 | }; 75 | 76 | Ok(out) 77 | } 78 | 79 | fn pkwith(mut pkgs: Vec, f: &str, query: Option<&str>) -> (Vec, String) { 80 | let q = query.unwrap_or("environment.systemPackages"); 81 | 82 | if let Ok(s) = nix_editor::read::getwithvalue(f, q) { 83 | if !s.contains(&"pkgs".to_string()) { 84 | pkgs = pkgs 85 | .into_iter() 86 | .map(|x| format!("pkgs.{}", x)) 87 | .collect::>(); 88 | } 89 | } 90 | 91 | (pkgs, q.to_string()) 92 | } 93 | 94 | /// Calls `nixos-rebuild switch` 95 | pub fn systemswitch() -> Result<(), ExecuteError> { 96 | match Command::new("nixos-rebuild") 97 | .arg("switch") 98 | .arg("--use-remote-sudo") 99 | .status() 100 | { 101 | Ok(_) => Ok(()), 102 | Err(_) => Err(ExecuteError::CmdError), 103 | } 104 | } 105 | 106 | /// Calls `nixos-rebuild switch` with the `--flake` flag 107 | /// 108 | /// The input `flakepath` is the path to the flake file with any arguments. 109 | /// Eg `/etc/nixos#user`. 110 | pub fn systemflakeswitch(flakepath: &str) -> Result<(), ExecuteError> { 111 | let status = Command::new("nixos-rebuild") 112 | .arg("switch") 113 | .arg("--flake") 114 | .arg(flakepath) 115 | .arg("--use-remote-sudo") 116 | .status(); 117 | match status { 118 | Ok(_) => Ok(()), 119 | Err(_) => Err(ExecuteError::CmdError), 120 | } 121 | } 122 | 123 | /// Calls `home-manager switch` 124 | pub fn homeswitch() -> Result<(), ExecuteError> { 125 | let status = Command::new("home-manager").arg("switch").status(); 126 | match status { 127 | Ok(_) => Ok(()), 128 | Err(_) => Err(ExecuteError::CmdError), 129 | } 130 | } 131 | 132 | /// Calls `home-manager switch` with the `--flake` flag 133 | /// 134 | /// The input `flakepath` is the path to the flake file with any arguments. 135 | /// Eg `/home/user/nix#user`. 136 | pub fn homeflakeswitch(flakepath: &str) -> Result<(), ExecuteError> { 137 | let status = Command::new("home-manager") 138 | .arg("switch") 139 | .arg("--flake") 140 | .arg(flakepath) 141 | .status(); 142 | match status { 143 | Ok(_) => Ok(()), 144 | Err(_) => Err(ExecuteError::CmdError), 145 | } 146 | } 147 | 148 | /// Calls `nix-channel --update` 149 | pub fn updatechannel() -> Result<(), ExecuteError> { 150 | match Command::new("nix-channel").arg("--update").status() { 151 | Ok(_) => Ok(()), 152 | Err(_) => Err(ExecuteError::CmdError), 153 | } 154 | } 155 | 156 | /// Calls `nix flake update` on the specified flake 157 | /// 158 | /// The input `flake` is the path to the flake file. 159 | pub fn updateflake(flake: &str) -> Result<(), ExecuteError> { 160 | match Command::new("nix") 161 | .arg("flake") 162 | .arg("update") 163 | .arg(flake.split('#').collect::>()[0]) 164 | .status() 165 | { 166 | Ok(_) => Ok(()), 167 | Err(_) => Err(ExecuteError::CmdError), 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/libnpkg/mod.rs: -------------------------------------------------------------------------------- 1 | mod execute; 2 | pub use execute::envinstall; 3 | pub use execute::envremove; 4 | pub use execute::envupdate; 5 | pub use execute::pkwrite; 6 | pub use execute::pkrm; 7 | pub use execute::systemswitch; 8 | pub use execute::systemflakeswitch; 9 | pub use execute::homeswitch; 10 | pub use execute::homeflakeswitch; 11 | pub use execute::updatechannel; 12 | pub use execute::updateflake; 13 | pub use execute::ExecuteError; -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod npkgcmd; 2 | fn main() { 3 | npkgcmd::run::main(); 4 | } -------------------------------------------------------------------------------- /src/npkgcmd/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json; 3 | use std::{ 4 | env, 5 | fs::{self, File}, 6 | io::Write, 7 | path::Path, 8 | }; 9 | use owo_colors::*; 10 | 11 | #[derive(Serialize, Deserialize, Debug)] 12 | struct Config { 13 | systemconfig: String, 14 | homeconfig: String, 15 | flake: Option, 16 | } 17 | 18 | pub fn checkconfig() -> String { 19 | let cfgdir = format!("{}/.config/npkg", env::var("HOME").unwrap()); 20 | if !Path::is_file(Path::new(&format!("{}/config.json", &cfgdir))) { 21 | if !Path::is_file(Path::new(&format!("/etc/npkg/config.json"))) { 22 | createconfig(); 23 | return cfgdir; 24 | } else { 25 | return "/etc/npkg/".to_string(); 26 | } 27 | } else { 28 | return cfgdir; 29 | } 30 | } 31 | 32 | fn createconfig() { 33 | let cfgdir = format!("{}/.config/npkg", env::var("HOME").unwrap()); 34 | fs::create_dir_all(&cfgdir).expect("Failed to create config directory"); 35 | let config = Config { 36 | systemconfig: "/etc/nixos/configuration.nix".to_string(), 37 | homeconfig: format!("{}/.config/nixpkgs/home.nix", env::var("HOME").unwrap()), 38 | flake: None, 39 | }; 40 | let json = serde_json::to_string_pretty(&config).unwrap(); 41 | let mut file = File::create(format!("{}/config.json", cfgdir)).unwrap(); 42 | file.write_all(json.as_bytes()).unwrap(); 43 | } 44 | 45 | pub fn readconfig(cfgdir: String) -> (String, String, Option) { 46 | let file = fs::read_to_string(format!("{}/config.json", cfgdir)).unwrap(); 47 | let config: Config = match serde_json::from_str(&file) { 48 | Ok(x) => x, 49 | Err(e) => { 50 | println!("{} {}","Failed to parse config:".red(), e); 51 | println!("Using default values"); 52 | return ( 53 | "/etc/nixos/configuration.nix".to_string(), 54 | format!("{}/.config/nixpkgs/home.nix", env::var("HOME").unwrap()), 55 | None, 56 | ); 57 | } 58 | }; 59 | if Path::is_file(Path::new(&config.systemconfig)) { 60 | return (config.systemconfig, config.homeconfig, config.flake); 61 | } else { 62 | println!("{}", "Config file is invalid".bright_red()); 63 | println!("{}", "Using default values".yellow()); 64 | return ( 65 | "/etc/nixos/configuration.nix".to_string(), 66 | format!("{}/.config/nixpkgs/home.nix", env::var("HOME").unwrap()), 67 | None, 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/npkgcmd/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parse; 2 | pub mod operate; 3 | pub mod search; 4 | pub mod config; 5 | pub mod run; 6 | use npkg; 7 | 8 | pub struct PkgData { 9 | pub pname: String, 10 | pub description: Option, 11 | pub version: String, 12 | } 13 | 14 | #[derive(Debug)] 15 | 16 | pub enum PackageTypes { 17 | System, 18 | Home, 19 | Env, 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct NpkgData { 24 | pub pkgmgr: PackageTypes, 25 | pub pkgs: Vec, 26 | pub output: Option, 27 | pub syscfg: String, 28 | pub hmcfg: String, 29 | pub dryrun: bool, 30 | pub flake: Option, 31 | pub currpkgs: Vec, 32 | } -------------------------------------------------------------------------------- /src/npkgcmd/operate.rs: -------------------------------------------------------------------------------- 1 | use crate::npkgcmd::{search::pname_to_name, NpkgData}; 2 | use npkg::*; 3 | use owo_colors::*; 4 | use std::{ 5 | env, fs, 6 | path::Path, 7 | process::{exit, Command}, 8 | }; 9 | 10 | pub enum OperateError { 11 | CmdError, 12 | WriteError(String), 13 | } 14 | 15 | enum Actions { 16 | Install, 17 | Remove, 18 | } 19 | 20 | pub fn pkinstall(opts: NpkgData) -> Result<(), OperateError> { 21 | match cfgoperate(opts, Actions::Install) { 22 | Ok(()) => Ok(()), 23 | Err(e) => Err(e), 24 | } 25 | } 26 | 27 | pub fn pkremove(opts: NpkgData) -> Result<(), OperateError> { 28 | match cfgoperate(opts, Actions::Remove) { 29 | Ok(()) => Ok(()), 30 | Err(e) => Err(e), 31 | } 32 | } 33 | 34 | pub fn chnupdate(opts: &NpkgData) { 35 | println!("{}", "Updating channels...".green()); 36 | match updatechannel() { 37 | Ok(()) => {} 38 | Err(_) => { 39 | println!("{}", "Failed to execute process nix-channel".red()); 40 | exit(1); 41 | } 42 | } 43 | println!( 44 | "{}", 45 | "Need root access to update system channels".bright_magenta() 46 | ); 47 | let _syschannel = Command::new("sudo") 48 | .arg("nix-channel") 49 | .arg("--update") 50 | .status() 51 | .expect("Failed to execute process nix-channel"); 52 | if opts.flake.is_some() { 53 | println!("{}", "Updating flake...".green()); 54 | match updateflake(opts.flake.as_ref().unwrap()) { 55 | Ok(()) => {} 56 | Err(_) => { 57 | println!("{}", "Failed to execute process nix flake".red()); 58 | exit(1); 59 | } 60 | } 61 | } 62 | } 63 | 64 | pub fn envinstall_check(opts: NpkgData) -> Result<(), OperateError> { 65 | let mut pkgs = vec![]; 66 | 67 | for p in opts.pkgs { 68 | if !opts.currpkgs.contains(&p) { 69 | pkgs.push(p); 70 | } 71 | } 72 | 73 | if pkgs.is_empty() { 74 | println!("No new packages to install"); 75 | exit(0); 76 | } 77 | 78 | match envinstall(pkgs) { 79 | Ok(()) => Ok(()), 80 | Err(ExecuteError::WriteError(e)) => Err(OperateError::WriteError(e)), 81 | Err(ExecuteError::CmdError) => Err(OperateError::CmdError), 82 | } 83 | } 84 | 85 | pub fn envremove_check(opts: NpkgData) -> Result<(), OperateError> { 86 | let mut pkgs = vec![]; 87 | 88 | for p in opts.pkgs { 89 | if opts.currpkgs.contains(&p) { 90 | pkgs.push(p); 91 | } 92 | } 93 | 94 | if pkgs.is_empty() { 95 | println!("No packages to remove"); 96 | exit(0); 97 | } 98 | pkgs = pname_to_name(&pkgs); 99 | 100 | match envremove(pkgs) { 101 | Ok(()) => Ok(()), 102 | Err(ExecuteError::WriteError(e)) => Err(OperateError::WriteError(e)), 103 | Err(ExecuteError::CmdError) => Err(OperateError::CmdError), 104 | } 105 | } 106 | 107 | fn cfgoperate(mut opts: NpkgData, action: Actions) -> Result<(), OperateError> { 108 | match &opts.output { 109 | Some(_) => { 110 | opts.dryrun = true; 111 | } 112 | None => {} 113 | }; 114 | 115 | let f = match opts.pkgmgr { 116 | crate::npkgcmd::PackageTypes::Home => { 117 | fs::read_to_string(&opts.hmcfg).expect("Failed to read file") 118 | } 119 | crate::npkgcmd::PackageTypes::System => { 120 | fs::read_to_string(&opts.syscfg).expect("Failed to read file") 121 | } 122 | _ => { 123 | println!("{}", "Unsupported package type".red()); 124 | exit(1); 125 | } 126 | }; 127 | 128 | //Add check for current packages 129 | let mut pkgs = vec![]; 130 | for p in &opts.pkgs { 131 | match action { 132 | Actions::Install => { 133 | if !opts.currpkgs.contains(&p) { 134 | pkgs.push(p.to_string()); 135 | } 136 | } 137 | Actions::Remove => { 138 | if opts.currpkgs.contains(&p) { 139 | pkgs.push(p.to_string()); 140 | } 141 | } 142 | } 143 | } 144 | 145 | if pkgs.is_empty() { 146 | println!("No new packages to install"); 147 | exit(0); 148 | } 149 | 150 | let query = match opts.pkgmgr { 151 | crate::npkgcmd::PackageTypes::Home => "home.packages", 152 | crate::npkgcmd::PackageTypes::System => "environment.systemPackages", 153 | _ => { 154 | println!("{}", "Unsupported package type".red()); 155 | exit(1); 156 | } 157 | }; 158 | 159 | let out = match action { 160 | Actions::Install => match pkwrite(pkgs, &f, Some(query)) { 161 | Ok(x) => x, 162 | Err(_) => exit(1), 163 | }, 164 | Actions::Remove => match pkrm(pkgs, &f, Some(query)) { 165 | Ok(x) => x, 166 | Err(_) => exit(1), 167 | }, 168 | }; 169 | 170 | let outfile = match opts.output { 171 | Some(ref s) => s.to_string(), 172 | None => match opts.pkgmgr { 173 | crate::npkgcmd::PackageTypes::Home => opts.hmcfg.to_string(), 174 | crate::npkgcmd::PackageTypes::System => opts.syscfg.to_string(), 175 | _ => { 176 | println!("{}", "Unsupported package type".red()); 177 | exit(1); 178 | } 179 | }, 180 | }; 181 | 182 | match fs::write(&outfile, &out) { 183 | Ok(_) => {} 184 | Err(_) => { 185 | if Path::new(&outfile).is_file() { 186 | println!( 187 | "{} {}", 188 | "Root permissions needed to modify".bright_yellow(), 189 | &outfile.green() 190 | ); 191 | 192 | let file = outfile.split("/").collect::>().pop().unwrap(); 193 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 194 | fs::create_dir_all(&cachedir).expect("Failed to create cache directory"); 195 | 196 | match fs::write(format!("{}/{}", cachedir, file), &out) { 197 | Ok(_) => { 198 | Command::new("sudo") 199 | .arg("cp") 200 | .arg(format!("{}/{}", cachedir, file)) 201 | .arg(&outfile) 202 | .status() 203 | .expect(&format!("{}", "Failed to execute process sudo cp".red())); 204 | fs::remove_file(format!("{}/{}", cachedir, file)) 205 | .expect("Failed to remove file"); 206 | } 207 | Err(_) => { 208 | let mut dir = outfile.split("/").collect::>(); 209 | dir.pop(); 210 | return Err(OperateError::WriteError(dir.join("/"))); 211 | } 212 | }; 213 | } else { 214 | let mut dir = outfile.split("/").collect::>(); 215 | dir.pop(); 216 | return Err(OperateError::WriteError(dir.join("/"))); 217 | } 218 | } 219 | }; 220 | 221 | if !opts.dryrun { 222 | match cfgswitch(&opts) { 223 | Ok(()) => {} 224 | Err(e) => { 225 | println!("{}", "Failed to switch config".red()); 226 | match fs::write(&outfile, &f) { 227 | Ok(_) => {} 228 | Err(_) => { 229 | if Path::new(&outfile).is_file() { 230 | println!( 231 | "{} {}", 232 | "Root permissions needed to restore".bright_yellow(), 233 | &outfile.green() 234 | ); 235 | 236 | let file = outfile.split("/").collect::>().pop().unwrap(); 237 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 238 | fs::create_dir_all(&cachedir) 239 | .expect("Failed to create cache directory"); 240 | 241 | match fs::write(format!("{}/{}", cachedir, file), &f) { 242 | Ok(_) => { 243 | Command::new("sudo") 244 | .arg("cp") 245 | .arg(format!("{}/{}", cachedir, file)) 246 | .arg(&outfile) 247 | .status() 248 | .expect(&format!( 249 | "{}", 250 | "Failed to execute process sudo cp".red() 251 | )); 252 | fs::remove_file(format!("{}/{}", cachedir, file)) 253 | .expect("Failed to remove file"); 254 | } 255 | Err(_) => { 256 | let mut dir = outfile.split("/").collect::>(); 257 | dir.pop(); 258 | return Err(OperateError::WriteError(dir.join("/"))); 259 | } 260 | }; 261 | } else { 262 | let mut dir = outfile.split("/").collect::>(); 263 | dir.pop(); 264 | return Err(OperateError::WriteError(dir.join("/"))); 265 | } 266 | } 267 | }; 268 | return Err(e); 269 | } 270 | } 271 | } 272 | return Ok(()); 273 | } 274 | 275 | pub fn cfgswitch(opts: &NpkgData) -> Result<(), OperateError> { 276 | match opts.pkgmgr { 277 | crate::npkgcmd::PackageTypes::Home => "home-manager".to_string(), 278 | crate::npkgcmd::PackageTypes::System => "nixos-rebuild".to_string(), 279 | _ => { 280 | println!("{}", "Unsupported package type".red()); 281 | exit(1); 282 | } 283 | }; 284 | 285 | match &opts.flake { 286 | None => match &opts.pkgmgr { 287 | crate::npkgcmd::PackageTypes::System => { 288 | println!("{}", "Need root access to rebuild system".bright_magenta()); 289 | match systemswitch() { 290 | Ok(()) => Ok(()), 291 | Err(_) => Err(OperateError::CmdError), 292 | } 293 | } 294 | _ => match homeswitch() { 295 | Ok(()) => Ok(()), 296 | Err(_) => Err(OperateError::CmdError), 297 | }, 298 | }, 299 | Some(s) => { 300 | println!("Rebuilding with nix flakes"); 301 | match &opts.pkgmgr { 302 | crate::npkgcmd::PackageTypes::System => { 303 | println!("{}", "Need root access to rebuild system".bright_magenta()); 304 | match systemflakeswitch(s) { 305 | Ok(()) => Ok(()), 306 | Err(_) => Err(OperateError::CmdError), 307 | } 308 | } 309 | _ => match homeflakeswitch(s) { 310 | Ok(()) => Ok(()), 311 | Err(_) => Err(OperateError::CmdError), 312 | }, 313 | } 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/npkgcmd/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::npkgcmd::search::name_to_pname; 2 | use serde_json::{self, Value}; 3 | use std::{fs, process::Command}; 4 | pub enum ParseError { 5 | EmptyPkgs, 6 | } 7 | 8 | pub fn hmpkgs(file: String) -> Result, ParseError> { 9 | let f = fs::read_to_string(&file).expect("Failed to read file"); 10 | 11 | //Add check for current packages 12 | let currpkgs = match nix_editor::read::getarrvals(&f, "home.packages") { 13 | Ok(x) => x, 14 | Err(_) => { 15 | return Err(ParseError::EmptyPkgs); 16 | } 17 | }; 18 | return Ok(currpkgs); 19 | } 20 | 21 | pub fn syspkgs(file: String) -> Result, ParseError> { 22 | let f = fs::read_to_string(file).expect("Failed to read file"); 23 | 24 | //Add check for current packages 25 | let currpkgs = match nix_editor::read::getarrvals(&f, "environment.systemPackages") { 26 | Ok(x) => x, 27 | Err(_) => { 28 | return Err(ParseError::EmptyPkgs); 29 | } 30 | }; 31 | return Ok(currpkgs); 32 | } 33 | 34 | pub fn envpkgs() -> Result, ParseError> { 35 | let out = Command::new("nix-env") 36 | .arg("-q") 37 | .arg("--json") 38 | .output() 39 | .expect("Failed to execute process"); 40 | 41 | let data: Value = 42 | serde_json::from_str(&String::from_utf8_lossy(&out.stdout)).expect("Failed to parse json"); 43 | 44 | let mut pcurrpkgs = vec![]; 45 | for (_, pkg) in data.as_object().unwrap() { 46 | pcurrpkgs.push( 47 | pkg.as_object().unwrap()["name"] 48 | .as_str() 49 | .unwrap() 50 | .to_string(), 51 | ); 52 | } 53 | 54 | let currpkgs = name_to_pname(&pcurrpkgs); 55 | 56 | return Ok(currpkgs); 57 | } 58 | -------------------------------------------------------------------------------- /src/npkgcmd/run.rs: -------------------------------------------------------------------------------- 1 | use crate::npkgcmd::NpkgData; 2 | use clap::{self, ArgGroup, Parser}; 3 | //use npkg::NpkgData; 4 | use crate::npkgcmd::npkg; 5 | use crate::npkgcmd::PackageTypes::*; 6 | use owo_colors::*; 7 | use std::process::exit; 8 | 9 | #[derive(Parser)] 10 | #[clap(author, version, about, long_about = None)] 11 | #[clap(group( 12 | ArgGroup::new("location") 13 | .args(&["system", "home", "env", "search"]), 14 | ))] 15 | #[clap(group( 16 | ArgGroup::new("action") 17 | .args(&["install", "remove", "list", "search", "update"]), 18 | ))] 19 | #[clap(group( 20 | ArgGroup::new("operations") 21 | .args(&["list", "packages"]), 22 | ))] 23 | struct Args { 24 | /// Install a package 25 | #[clap(short, long)] 26 | install: bool, 27 | 28 | /// Remove a package 29 | #[clap(short, long)] 30 | remove: bool, 31 | 32 | /// List installed packages 33 | #[clap(short, long)] 34 | list: bool, 35 | 36 | /// Search for a package 37 | #[clap(short, long)] 38 | search: bool, 39 | 40 | /// Update packages 41 | #[clap(short, long)] 42 | update: bool, 43 | 44 | /// Use system 'configuration.nix' 45 | #[clap(short = 'S', long)] 46 | system: bool, 47 | 48 | /// Use home-manager 'home.nix' 49 | #[clap(short = 'H', long)] 50 | home: bool, 51 | 52 | /// Use nix environment 'nix-env' 53 | #[clap(short = 'E', long)] 54 | env: bool, 55 | 56 | /// Output modified configuration file to a specified location 57 | #[clap(short, long, conflicts_with_all = &["list", "search", "env", "update"])] 58 | output: Option, 59 | 60 | /// Do not build any packages, only edit configuration file 61 | #[clap(short, long = "dry-run", conflicts_with_all = &["list", "search", "env", "update"])] 62 | dryrun: bool, 63 | 64 | /// Packages 65 | packages: Vec, 66 | } 67 | 68 | fn printerror(msg: &str) { 69 | println!("{} {}", "error:".red(), msg); 70 | } 71 | 72 | fn pppackages(prepend: &str, packages: &Vec) { 73 | println!("{} {}", prepend.green(), "Packages:".green()); 74 | for package in packages { 75 | println!(" {}", package); 76 | } 77 | } 78 | 79 | fn pklst(opts: &NpkgData) -> Vec { 80 | match opts.pkgmgr { 81 | System => match crate::npkgcmd::parse::syspkgs(opts.syscfg.to_string()) { 82 | Ok(mut x) => { 83 | x.sort(); 84 | x 85 | } 86 | Err(crate::npkgcmd::parse::ParseError::EmptyPkgs) => { 87 | printerror("Failed to get system packages"); 88 | exit(1); 89 | } 90 | }, 91 | Home => match crate::npkgcmd::parse::hmpkgs(opts.hmcfg.to_string()) { 92 | Ok(mut x) => { 93 | x.sort(); 94 | x 95 | } 96 | Err(crate::npkgcmd::parse::ParseError::EmptyPkgs) => { 97 | printerror("Failed to get home-manager packages"); 98 | exit(1); 99 | } 100 | }, 101 | Env => match crate::npkgcmd::parse::envpkgs() { 102 | Ok(mut x) => { 103 | x.sort(); 104 | x 105 | } 106 | Err(crate::npkgcmd::parse::ParseError::EmptyPkgs) => { 107 | printerror("Failed to get nix environment packages"); 108 | exit(1); 109 | } 110 | }, 111 | } 112 | } 113 | 114 | fn pkinstall(mut opts: NpkgData) { 115 | match opts.pkgmgr { 116 | System => { 117 | opts.currpkgs = pklst(&opts); 118 | match crate::npkgcmd::operate::pkinstall(opts) { 119 | Ok(()) => {} 120 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 121 | printerror("Could not rebuild configuration"); 122 | exit(1); 123 | } 124 | Err(crate::npkgcmd::operate::OperateError::WriteError(f)) => { 125 | printerror(format!("Could not write to configuration file, do you have permissions in the directory \"{}\"?", f).as_str()); 126 | exit(1); 127 | } 128 | } 129 | } 130 | Home => { 131 | opts.currpkgs = pklst(&opts); 132 | match crate::npkgcmd::operate::pkinstall(opts) { 133 | Ok(()) => {} 134 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 135 | printerror("Could not rebuild configuration"); 136 | exit(1); 137 | } 138 | Err(crate::npkgcmd::operate::OperateError::WriteError(f)) => { 139 | printerror(format!("Could not write to configuration file, does the directory \"{}\" exist?", f).as_str()); 140 | exit(1); 141 | } 142 | } 143 | } 144 | Env => { 145 | opts.currpkgs = pklst(&opts); 146 | match crate::npkgcmd::operate::envinstall_check(opts) { 147 | Ok(()) => {} 148 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 149 | printerror("Could not install packages"); 150 | exit(1); 151 | } 152 | Err(crate::npkgcmd::operate::OperateError::WriteError(_)) => { 153 | printerror("Could not write file"); 154 | exit(1); 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | fn pkremove(mut opts: NpkgData) { 162 | match opts.pkgmgr { 163 | System => { 164 | opts.currpkgs = pklst(&opts); 165 | match crate::npkgcmd::operate::pkremove(opts) { 166 | Ok(()) => {} 167 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 168 | printerror("Could not rebuild configuration"); 169 | exit(1); 170 | } 171 | Err(crate::npkgcmd::operate::OperateError::WriteError(f)) => { 172 | printerror(format!("Could not write to configuration file, does the directory \"{}\" exist?", f).as_str()); 173 | exit(1); 174 | } 175 | } 176 | } 177 | Home => { 178 | opts.currpkgs = pklst(&opts); 179 | match crate::npkgcmd::operate::pkremove(opts) { 180 | Ok(()) => {} 181 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 182 | printerror("Could not rebuild configuration"); 183 | exit(1); 184 | } 185 | Err(crate::npkgcmd::operate::OperateError::WriteError(f)) => { 186 | printerror(format!("Could not write to configuration file, does the directory \"{}\" exist?", f).as_str()); 187 | exit(1); 188 | } 189 | } 190 | } 191 | Env => { 192 | opts.currpkgs = pklst(&opts); 193 | match crate::npkgcmd::operate::envremove_check(opts) { 194 | Ok(()) => {} 195 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 196 | printerror("Could not remove packages"); 197 | exit(1); 198 | } 199 | Err(crate::npkgcmd::operate::OperateError::WriteError(_)) => { 200 | printerror("Could not write file"); 201 | exit(1); 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | fn pkupdate(opts: &NpkgData) { 209 | match opts.pkgmgr { 210 | Home => { 211 | match crate::npkgcmd::operate::cfgswitch(&opts) { 212 | Ok(()) => {} 213 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 214 | printerror("Could not rebuild configuration"); 215 | exit(1); 216 | } 217 | Err(crate::npkgcmd::operate::OperateError::WriteError(f)) => { 218 | printerror(format!("Could not write to configuration file, does the directory \"{}\" exist?", f).as_str()); 219 | exit(1); 220 | } 221 | } 222 | } 223 | System => { 224 | match crate::npkgcmd::operate::cfgswitch(&opts) { 225 | Ok(()) => {} 226 | Err(crate::npkgcmd::operate::OperateError::CmdError) => { 227 | printerror("Could not rebuild configuration"); 228 | exit(1); 229 | } 230 | Err(crate::npkgcmd::operate::OperateError::WriteError(f)) => { 231 | printerror(format!("Could not write to configuration file, does the directory \"{}\" exist?", f).as_str()); 232 | exit(1); 233 | } 234 | }; 235 | } 236 | Env => { 237 | match npkg::envupdate() { 238 | Ok(()) => {} 239 | Err(npkg::ExecuteError::CmdError) => { 240 | printerror("Could not update packages"); 241 | exit(1); 242 | } 243 | Err(npkg::ExecuteError::WriteError(_)) => { 244 | printerror("Could not write file"); 245 | exit(1); 246 | } 247 | }; 248 | } 249 | } 250 | } 251 | 252 | pub fn main() { 253 | let args = Args::parse(); 254 | 255 | let hm = match std::process::Command::new("home-manager") 256 | .arg("--help") 257 | .output() 258 | { 259 | Ok(x) => { 260 | if x.status.success() { 261 | true 262 | } else { 263 | false 264 | } 265 | } 266 | Err(_) => false, 267 | }; 268 | 269 | let cfgdir = crate::npkgcmd::config::checkconfig(); 270 | let (syscfg, hmcfg, flake) = crate::npkgcmd::config::readconfig(cfgdir); 271 | 272 | let mut opts = NpkgData { 273 | pkgmgr: Env, 274 | pkgs: args.packages, 275 | dryrun: args.dryrun || args.output.is_some(), 276 | output: args.output, 277 | syscfg: syscfg, 278 | hmcfg: hmcfg, 279 | flake: flake, 280 | currpkgs: vec![], 281 | }; 282 | 283 | if args.install { 284 | if args.home { 285 | if !hm { 286 | printerror("home-manager is not installed"); 287 | exit(1); 288 | } 289 | opts.pkgmgr = Home; 290 | println!( 291 | "{} {}", 292 | "Installing package to".cyan(), 293 | "home".green().bold() 294 | ); 295 | pkinstall(opts); 296 | } else if args.system { 297 | opts.pkgmgr = System; 298 | println!( 299 | "{} {}", 300 | "Installing package to".cyan(), 301 | "system".green().bold() 302 | ); 303 | pkinstall(opts); 304 | } else { 305 | //Default env 306 | println!( 307 | "{} {}", 308 | "Installing package to".cyan(), 309 | "nix environment".green().bold() 310 | ); 311 | pkinstall(opts); 312 | } 313 | } else if args.remove { 314 | if args.home { 315 | opts.pkgmgr = Home; 316 | if !hm { 317 | printerror("home-manager is not installed"); 318 | exit(1); 319 | } 320 | println!( 321 | "{} {}", 322 | "Removing package from".cyan(), 323 | "home".green().bold() 324 | ); 325 | pkremove(opts); 326 | } else if args.system { 327 | opts.pkgmgr = System; 328 | println!( 329 | "{} {}", 330 | "Removing package from".cyan(), 331 | "system".green().bold() 332 | ); 333 | pkremove(opts); 334 | } else { 335 | //Default env 336 | opts.pkgmgr = Env; 337 | println!( 338 | "{} {}", 339 | "Removing package from".cyan(), 340 | "nix environment".green().bold() 341 | ); 342 | pkremove(opts); 343 | } 344 | } else if args.list { 345 | if args.home { 346 | //Add check for current packages 347 | if !hm { 348 | printerror("home-manager is not installed"); 349 | exit(1); 350 | } 351 | opts.pkgmgr = Home; 352 | let currpkgs = pklst(&opts); 353 | pppackages("Home Manager", &currpkgs); 354 | } else if args.system { 355 | opts.pkgmgr = System; 356 | let currpkgs = pklst(&opts); 357 | pppackages("System", &currpkgs); 358 | } else if args.env { 359 | opts.pkgmgr = Env; 360 | let currpkgs = pklst(&opts); 361 | pppackages("Nix Environment", &currpkgs); 362 | } else { 363 | //Default to all packages 364 | opts.pkgmgr = System; 365 | let syslst = pklst(&opts); 366 | opts.pkgmgr = Home; 367 | let homelst = if hm { pklst(&opts) } else { Vec::new() }; 368 | opts.pkgmgr = Env; 369 | let envlst = pklst(&opts); 370 | pppackages("System", &syslst); 371 | if hm { 372 | pppackages("Home Manager", &homelst) 373 | } 374 | pppackages("Nix Environment", &envlst); 375 | } 376 | } else if args.search { 377 | // Get packages 378 | opts.pkgmgr = System; 379 | let syslst = pklst(&opts); 380 | opts.pkgmgr = Home; 381 | let homelst = if hm { pklst(&opts) } else { Vec::new() }; 382 | opts.pkgmgr = Env; 383 | let envlst = pklst(&opts); 384 | 385 | // Search for packages 386 | let pkgdata = match crate::npkgcmd::search::search(&opts.pkgs) { 387 | Ok(x) => x, 388 | Err(_) => { 389 | printerror("Could not search for packages"); 390 | exit(1); 391 | } 392 | }; 393 | 394 | for pkg in pkgdata { 395 | let mut name = pkg.pname.to_string(); 396 | 397 | let mut idx = vec![]; 398 | let nl = name.to_lowercase(); 399 | for st in &opts.pkgs { 400 | let sl = st.to_lowercase(); 401 | let mut y = nl.match_indices(&sl).collect::>(); 402 | idx.append(&mut y); 403 | } 404 | idx.sort(); 405 | let mut idx2: Vec<(usize, &str)> = vec![]; 406 | for i in idx { 407 | if { 408 | let mut b = true; 409 | idx2.retain(|j| { 410 | if j.0 == i.0 { 411 | if i.1.len() > j.1.len() { 412 | return false; 413 | } else { 414 | b = false; 415 | return true; 416 | } 417 | } 418 | return true; 419 | }); 420 | b 421 | } { 422 | idx2.push(i); 423 | } 424 | } 425 | let mut offset = 0; 426 | 427 | for (i, st) in idx2 { 428 | let j = i + offset; 429 | let n = name[j..j + st.len()].to_string(); 430 | name.replace_range(j..j + st.len(), &n.green().to_string()); 431 | offset += 10; 432 | } 433 | let mut outstr = format!("* {} ({})", name.bold(), pkg.version); 434 | if syslst.contains(&pkg.pname) { 435 | outstr += &format!(" ({})", "system".bright_red()); 436 | } 437 | if homelst.contains(&pkg.pname) { 438 | outstr += &format!(" ({})", "home".bright_cyan()); 439 | } 440 | if envlst.contains(&pkg.pname) { 441 | outstr += &format!(" ({})", "nix env".bright_yellow()); 442 | } 443 | println!("{}", outstr); 444 | 445 | match pkg.description { 446 | Some(x) => { 447 | let mut desc = x.trim().replace("\n", " "); 448 | let mut idx = vec![]; 449 | let dl = desc.to_lowercase(); 450 | for st in &opts.pkgs { 451 | let sl = st.to_lowercase(); 452 | let mut y = dl.match_indices(&sl).collect::>(); 453 | idx.append(&mut y); 454 | } 455 | idx.sort(); 456 | let mut idx2: Vec<(usize, &str)> = vec![]; 457 | for i in idx { 458 | if { 459 | let mut b = true; 460 | idx2.retain(|j| { 461 | if j.0 == i.0 { 462 | if i.1.len() > j.1.len() { 463 | return false; 464 | } else { 465 | b = false; 466 | return true; 467 | } 468 | } 469 | return true; 470 | }); 471 | b 472 | } { 473 | idx2.push(i); 474 | } 475 | } 476 | let mut offset = 0; 477 | for (i, st) in idx2 { 478 | let j = i + offset; 479 | let d = desc[j..j + st.len()].to_string(); 480 | desc.replace_range(j..j + st.len(), &d.green().to_string()); 481 | offset += 10; 482 | } 483 | println!(" {}", desc) 484 | } 485 | None => {} 486 | } 487 | println!(); 488 | } 489 | } else if args.update { 490 | if args.home { 491 | if !hm { 492 | printerror("home-manager is not installed"); 493 | exit(1); 494 | } 495 | opts.pkgmgr = Home; 496 | println!( 497 | "{} {}", 498 | "Updating packages in".cyan(), 499 | "home".green().bold() 500 | ); 501 | crate::npkgcmd::operate::chnupdate(&opts); 502 | pkupdate(&opts); 503 | } else if args.system { 504 | opts.pkgmgr = System; 505 | println!( 506 | "{} {}", 507 | "Updating packages in".cyan(), 508 | "system".green().bold() 509 | ); 510 | crate::npkgcmd::operate::chnupdate(&opts); 511 | pkupdate(&opts); 512 | } else if args.env { 513 | //Default env 514 | opts.pkgmgr = Env; 515 | println!( 516 | "{} {}", 517 | "Updating packages in".cyan(), 518 | "nix environment".green().bold() 519 | ); 520 | crate::npkgcmd::operate::chnupdate(&opts); 521 | pkupdate(&opts); 522 | } else { 523 | crate::npkgcmd::operate::chnupdate(&opts); 524 | opts.pkgmgr = System; 525 | println!( 526 | "{} {}", 527 | "Updating packages in".cyan(), 528 | "system".green().bold() 529 | ); 530 | pkupdate(&opts); 531 | if hm { 532 | opts.pkgmgr = Home; 533 | println!( 534 | "{} {}", 535 | "Updating packages in".cyan(), 536 | "home".green().bold() 537 | ); 538 | pkupdate(&opts); 539 | } 540 | opts.pkgmgr = Env; 541 | println!( 542 | "{} {}", 543 | "Updating packages in".cyan(), 544 | "nix environment".green().bold() 545 | ); 546 | pkupdate(&opts); 547 | } 548 | } else { 549 | printerror("no operation specified"); 550 | println!("Try 'npkg --help' for more information."); 551 | exit(-1); 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /src/npkgcmd/search.rs: -------------------------------------------------------------------------------- 1 | use crate::npkgcmd::PkgData; 2 | use bimap; 3 | use brotli; 4 | use curl::easy::Easy; 5 | use serde::{Deserialize, Serialize}; 6 | use serde_json::{self, Value}; 7 | use std::{ 8 | collections::HashMap, 9 | env, 10 | fs::{self, File}, 11 | io::{Read, Write}, 12 | path::Path, 13 | process::Command, 14 | }; 15 | 16 | #[derive(Serialize, Deserialize, Debug)] 17 | struct PackageBase { 18 | packages: HashMap, 19 | } 20 | 21 | #[derive(Serialize, Deserialize, Debug)] 22 | struct Package { 23 | name: String, 24 | pname: String, 25 | version: String, 26 | meta: Meta, 27 | } 28 | #[derive(Serialize, Deserialize, Debug)] 29 | struct Meta { 30 | broken: Option, 31 | description: Option, 32 | } 33 | 34 | pub fn search(query: &Vec) -> Result, String> { 35 | checkcache(); 36 | 37 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 38 | let file = fs::read_to_string(format!("{}/packages.json", cachedir)).unwrap(); 39 | 40 | let data: PackageBase = serde_json::from_str(&file).expect("Failed to parse json"); 41 | let pkgs = data.packages.keys().filter(|x| { 42 | let mut b = true; 43 | let desc = &data.packages.get(&x.to_string()).unwrap().meta.description; 44 | for q in query { 45 | let desc = match desc { 46 | Some(y) => y.to_lowercase().contains(q.to_lowercase().as_str()), 47 | None => false, 48 | }; 49 | b &= x.to_lowercase().contains(q.to_lowercase().as_str()) || desc; 50 | } 51 | b 52 | }); 53 | 54 | let mut out = vec![]; 55 | for i in pkgs { 56 | let pkg = data.packages.get(i).unwrap(); 57 | out.push(PkgData { 58 | pname: i.to_string(), 59 | description: pkg.meta.description.clone(), 60 | version: pkg.version.clone(), 61 | }); 62 | } 63 | 64 | out.sort_by_key(|x| x.pname.clone()); 65 | return Ok(out); 66 | } 67 | 68 | pub fn pname_to_name(query: &Vec) -> Vec { 69 | checkcache(); 70 | 71 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 72 | let file = fs::read_to_string(format!("{}/pnameref.json", cachedir)).unwrap(); 73 | 74 | let data: bimap::BiHashMap = 75 | serde_json::from_str(&file).expect("Failed to parse json"); 76 | 77 | let mut pkgs = vec![]; 78 | for q in query { 79 | pkgs.push(match data.get_by_left(q) { 80 | Some(x) => x.to_string(), 81 | None => q.to_string(), 82 | }); 83 | } 84 | return pkgs; 85 | } 86 | 87 | pub fn name_to_pname(query: &Vec) -> Vec { 88 | checkcache(); 89 | 90 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 91 | let file = fs::read_to_string(format!("{}/pnameref.json", cachedir)).unwrap(); 92 | let data: bimap::BiHashMap = 93 | serde_json::from_str(&file).expect("Failed to parse json"); 94 | let mut pkgs = vec![]; 95 | for q in query { 96 | let n = match data.get_by_right(q) { 97 | Some(x) => x.to_string(), 98 | None => q.to_string(), 99 | }; 100 | pkgs.push(n); 101 | } 102 | return pkgs; 103 | } 104 | 105 | fn checkcache() { 106 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 107 | 108 | let vout = Command::new("nixos-version") 109 | .arg("--json") 110 | .output() 111 | .unwrap(); 112 | let data: Value = 113 | serde_json::from_str(&String::from_utf8_lossy(&vout.stdout)).expect("Failed to parse json"); 114 | 115 | let version = data.as_object().unwrap()["nixosVersion"].as_str().unwrap(); 116 | 117 | if !Path::is_dir(Path::new(&cachedir)) 118 | || !Path::is_file(Path::new(&format!("{}/version.json", &cachedir))) 119 | { 120 | println!("Updating cache"); 121 | setupcache(); 122 | let mut newver = fs::File::create(format!("{}/version.json", &cachedir)).unwrap(); 123 | newver.write_all(&vout.stdout).unwrap(); 124 | } 125 | 126 | let file = fs::read_to_string(format!("{}/version.json", cachedir)).unwrap(); 127 | let olddata: Value = serde_json::from_str(&file).expect("Failed to parse json"); 128 | 129 | let oldversion = olddata.as_object().unwrap()["nixosVersion"] 130 | .as_str() 131 | .unwrap(); 132 | 133 | if version != oldversion { 134 | println!("Out of date, updating cache"); 135 | setupcache(); 136 | let mut newver = fs::File::create(format!("{}/version.json", &cachedir)).unwrap(); 137 | newver.write_all(&vout.stdout).unwrap(); 138 | } else if !Path::is_file(Path::new(&format!("{}/packages.json", &cachedir))) { 139 | println!("No packages.json, updating cache"); 140 | setupcache(); 141 | let mut newver = fs::File::create(format!("{}/version.json", &cachedir)).unwrap(); 142 | newver.write_all(&vout.stdout).unwrap(); 143 | } else if !Path::is_file(Path::new(&format!("{}/pnameref.json", &cachedir))) { 144 | println!("Updating references"); 145 | updatepnameref(); 146 | } 147 | } 148 | 149 | fn setupcache() { 150 | let vout = Command::new("nix-instantiate") 151 | .arg("") 152 | .arg("-A") 153 | .arg("version") 154 | .arg("--eval") 155 | .arg("--json") 156 | .output() 157 | .unwrap(); 158 | 159 | let dlver = String::from_utf8_lossy(&vout.stdout) 160 | .to_string() 161 | .replace('"', ""); 162 | 163 | let mut relver = dlver.split('.').collect::>().join(".")[0..5].to_string(); 164 | 165 | if dlver.len() >= 8 && &dlver[5..8] == "pre" { 166 | relver = "unstable".to_string(); 167 | } 168 | 169 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 170 | fs::create_dir_all(&cachedir).expect("Failed to create cache directory"); 171 | let url = format!( 172 | "https://releases.nixos.org/nixos/{}/nixos-{}/packages.json.br", 173 | relver, dlver 174 | ); 175 | 176 | if Path::is_file(Path::new(&format!("{}/packages.json", &cachedir))) { 177 | fs::remove_file(Path::new(&format!("{}/packages.json", &cachedir))) 178 | .expect("Failed to remove file"); 179 | } 180 | 181 | let mut dst = Vec::new(); 182 | let mut easy = Easy::new(); 183 | easy.url(&url).unwrap(); 184 | 185 | { 186 | let mut transfer = easy.transfer(); 187 | transfer 188 | .write_function(|data| { 189 | dst.extend_from_slice(data); 190 | Ok(data.len()) 191 | }) 192 | .unwrap(); 193 | transfer.perform().unwrap(); 194 | } 195 | 196 | { 197 | let mut file = File::create(format!("{}/packages.json", &cachedir).as_str()) 198 | .expect("Failed to create file"); 199 | let mut reader = brotli::Decompressor::new( 200 | dst.as_slice(), 201 | 4096, // buffer size 202 | ); 203 | let mut buf = [0u8; 4096]; 204 | loop { 205 | match reader.read(&mut buf[..]) { 206 | Err(e) => { 207 | if let std::io::ErrorKind::Interrupted = e.kind() { 208 | continue; 209 | } 210 | panic!("{}", e); 211 | } 212 | Ok(size) => { 213 | if size == 0 { 214 | break; 215 | } 216 | match file.write_all(&buf[..size]) { 217 | Err(e) => panic!("{}", e), 218 | Ok(_) => {} 219 | } 220 | } 221 | } 222 | } 223 | } 224 | updatepnameref(); 225 | } 226 | 227 | fn updatepnameref() { 228 | let cachedir = format!("{}/.cache/npkg", env::var("HOME").unwrap()); 229 | let file = fs::read_to_string(format!("{}/packages.json", cachedir)).unwrap(); 230 | let data: PackageBase = serde_json::from_str(&file).expect("Failed to parse json"); 231 | let mut hmap = bimap::BiHashMap::new(); 232 | for (s, pkg) in data.packages { 233 | hmap.insert(s, pkg.name.to_string()); 234 | } 235 | let out = match serde_json::to_string(&hmap) { 236 | Ok(x) => x, 237 | Err(e) => panic!("{}", e), 238 | }; 239 | let mut outfile = File::create(format!("{}/pnameref.json", &cachedir).as_str()) 240 | .expect("Failed to create file"); 241 | outfile 242 | .write_all(out.as_bytes()) 243 | .expect("Failed to write file"); 244 | } 245 | --------------------------------------------------------------------------------