Compare commits
No commits in common. "7b3452ef18bad72203a600a869fd9543b9e5c8f4" and "ab35745f53d0b83e3d1fbbff127e32a9135866a8" have entirely different histories.
7b3452ef18
...
ab35745f53
14 changed files with 57 additions and 1143 deletions
371
Cargo.lock
generated
371
Cargo.lock
generated
|
|
@ -13,21 +13,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
|
@ -37,9 +31,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -57,18 +51,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.6.2"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb"
|
||||
checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
@ -84,12 +78,13 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
|||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.3"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||
checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width",
|
||||
"windows-sys",
|
||||
]
|
||||
|
|
@ -106,25 +101,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "eh"
|
||||
version = "0.2.0"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dialoguer",
|
||||
"eh-log",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"textwrap",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"walkdir",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eh-log"
|
||||
version = "0.2.0"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
|
@ -135,12 +127,6 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
|
|
@ -153,85 +139,39 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.185"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
|
|
@ -239,33 +179,17 @@ version = "0.12.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
|
|
@ -287,9 +211,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
|
|
@ -305,9 +229,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -316,9 +240,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
|
|
@ -342,12 +266,6 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
|
|
@ -355,7 +273,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -391,27 +308,12 @@ dependencies = [
|
|||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
|
|
@ -436,17 +338,6 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
|
|
@ -467,48 +358,11 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
|
|
@ -516,12 +370,6 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
|
@ -534,54 +382,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.3+wasi-0.2.9"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.57.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -608,109 +413,15 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.2.0"
|
||||
version = "0.1.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
|
|
@ -725,6 +436,6 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
|||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
|
||||
|
|
|
|||
|
|
@ -9,20 +9,17 @@ description = "Ergonomic Nix CLI helper"
|
|||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
readme = true
|
||||
rust-version = "1.94.0"
|
||||
version = "0.2.0"
|
||||
rust-version = "1.91.0"
|
||||
version = "0.1.7"
|
||||
|
||||
[workspace.dependencies]
|
||||
clap = { default-features = false, features = [ "std", "help", "derive" ], version = "4.6.0" }
|
||||
clap_complete = "4.6.0"
|
||||
dialoguer = { default-features = false, version = "0.12.0" }
|
||||
regex = "1.12.3"
|
||||
serde = { features = [ "derive" ], version = "1.0.149" }
|
||||
serde_json = "1.0.149"
|
||||
tempfile = "3.27.0"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = "2.0.18"
|
||||
toml = { default-features = false, features = [ "parse", "serde" ], version = "1.1.2" }
|
||||
walkdir = "2.5.0"
|
||||
yansi = "1.0.1"
|
||||
|
||||
|
|
|
|||
90
README.md
90
README.md
|
|
@ -27,10 +27,6 @@ of building the package. The following variables are supported:
|
|||
- **Insecure packages**: Sets `NIXPKGS_ALLOW_INSECURE=1`
|
||||
- **Broken packages**: Sets `NIXPKGS_ALLOW_BROKEN=1`
|
||||
|
||||
Auto-retry requires that `--impure` is not explicitly disabled for the relevant
|
||||
command in the config file. By default retries are automatic. See
|
||||
[Configuration](#configuration).
|
||||
|
||||
### Hash Auto-Fix
|
||||
|
||||
When a hash mismatch is detected in the underlying `nix build`, `eh` can
|
||||
|
|
@ -59,92 +55,6 @@ nb .#myPackage # nix build .#myPackage
|
|||
nu # nix flake update
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`eh` reads configuration from the first `.eh.toml` found by walking up from the
|
||||
current directory, falling back to `~/.config/eh/config.toml`. If no file
|
||||
exists, all defaults apply and no extra flags are passed to Nix.
|
||||
|
||||
### Global settings
|
||||
|
||||
Top-level keys apply to every command unless overridden per-command:
|
||||
|
||||
```toml
|
||||
# Explicitly enable --impure for all commands (also passes it on initial run).
|
||||
impure = true
|
||||
|
||||
# Explicitly disable impure retries for all commands.
|
||||
impure = false
|
||||
```
|
||||
|
||||
When `impure` is absent (the default), auto-retry with `--impure` is
|
||||
**automatic** — `eh` will add `--impure` and the appropriate `NIXPKGS_ALLOW_*`
|
||||
variable whenever it detects an unfree, insecure, or broken package.
|
||||
|
||||
<!--markdownlint-disable MD013-->
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| -------- | ---- | ------- | -------------------------------------------------------------- |
|
||||
| `impure` | bool | - | `true` passes `--impure` always; `false` blocks impure retries |
|
||||
|
||||
<!--markdownlint-enable MD013-->
|
||||
|
||||
### Per-command settings
|
||||
|
||||
Each command can be configured independently under `[commands.<name>]`. A
|
||||
per-command setting takes precedence over the global one; the global setting
|
||||
applies to commands that do not have their own entry.
|
||||
|
||||
```toml
|
||||
[commands.build]
|
||||
impure = true
|
||||
env = { NIXPKGS_ALLOW_UNFREE = "1" }
|
||||
|
||||
[commands.develop]
|
||||
impure = false
|
||||
|
||||
[commands.develop.env]
|
||||
MY_DEV_VAR = "1"
|
||||
```
|
||||
|
||||
<!--markdownlint-disable MD013-->
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| -------- | ----- | ------- | ------------------------------------------------------------------------------- |
|
||||
| `impure` | bool | - | `true` passes `--impure` always; `false` blocks impure retries for this command |
|
||||
| `env` | table | `{}` | Extra environment variables to set for the command |
|
||||
|
||||
<!--markdownlint-enable MD013-->
|
||||
|
||||
### Impure mode and unfree/insecure/broken packages
|
||||
|
||||
When `eh` detects that a package requires `--impure` (unfree, insecure, or
|
||||
broken), it retries automatically with the appropriate `NIXPKGS_ALLOW_*`
|
||||
variable and `--impure` by default.
|
||||
|
||||
If `impure = false` is set for the active command (or globally), the retry is
|
||||
blocked and an error is shown instead:
|
||||
|
||||
```plaintext
|
||||
! package has an unfree license but `--impure` is disabled for `build` in config
|
||||
~ set `impure = true` for this command (or globally) in .eh.toml or
|
||||
~/.config/eh/config.toml, or pass `--impure` manually
|
||||
```
|
||||
|
||||
To explicitly enable `--impure` for a specific command (also adds it to the
|
||||
initial run, not just retries):
|
||||
|
||||
```toml
|
||||
[commands.build]
|
||||
impure = true
|
||||
```
|
||||
|
||||
To disable impure retries globally:
|
||||
|
||||
```toml
|
||||
impure = false
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
<!--markdownlint-disable MD059-->
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ enum Binary {
|
|||
Nr,
|
||||
Ns,
|
||||
Nb,
|
||||
Nd,
|
||||
Ni,
|
||||
Nu,
|
||||
}
|
||||
|
||||
|
|
@ -53,8 +51,6 @@ impl Binary {
|
|||
Self::Nr => "nr",
|
||||
Self::Ns => "ns",
|
||||
Self::Nb => "nb",
|
||||
Self::Nd => "nd",
|
||||
Self::Ni => "ni",
|
||||
Self::Nu => "nu",
|
||||
}
|
||||
}
|
||||
|
|
@ -96,14 +92,7 @@ fn create_multicall_binaries(
|
|||
);
|
||||
}
|
||||
|
||||
let multicall_binaries = [
|
||||
Binary::Nr,
|
||||
Binary::Ns,
|
||||
Binary::Nb,
|
||||
Binary::Nd,
|
||||
Binary::Ni,
|
||||
Binary::Nu,
|
||||
];
|
||||
let multicall_binaries = [Binary::Nr, Binary::Ns, Binary::Nb, Binary::Nu];
|
||||
let bin_path = Path::new(bin_dir);
|
||||
|
||||
for binary in multicall_binaries {
|
||||
|
|
@ -166,7 +155,7 @@ fn generate_completions(
|
|||
println!("completion file generated: {}", completion_file.display());
|
||||
|
||||
// Create symlinks for multicall binaries
|
||||
let multicall_names = ["nb", "nd", "ni", "nr", "ns", "nu"];
|
||||
let multicall_names = ["nb", "nr", "ns", "nu"];
|
||||
for name in &multicall_names {
|
||||
let symlink_path = output_dir.join(format!("{name}.{shell}"));
|
||||
if symlink_path.exists() {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,8 @@ clap.workspace = true
|
|||
dialoguer.workspace = true
|
||||
eh-log.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tempfile.workspace = true
|
||||
textwrap.workspace = true
|
||||
thiserror.workspace = true
|
||||
toml.workspace = true
|
||||
walkdir.workspace = true
|
||||
yansi.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,295 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use eh_log::{log_error, log_info};
|
||||
use serde::Deserialize;
|
||||
use yansi::Paint;
|
||||
|
||||
use crate::{
|
||||
commands::NixCommand,
|
||||
error::{EhError, Result},
|
||||
util::{make_eval_expr, print_error_suggestions},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PackageMeta {
|
||||
name: String,
|
||||
version: Option<String>,
|
||||
description: Option<String>,
|
||||
long_description: Option<String>,
|
||||
license: Option<serde_json::Value>,
|
||||
homepage: Option<String>,
|
||||
platforms: Option<Vec<String>>,
|
||||
broken: Option<bool>,
|
||||
insecure: Option<bool>,
|
||||
#[serde(rename = "unfree")]
|
||||
unfree: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PackageOutputs {
|
||||
#[serde(flatten)]
|
||||
outputs: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
pub fn handle_info(
|
||||
args: &[String],
|
||||
cfg: &crate::config::CommandConfig,
|
||||
) -> Result<i32> {
|
||||
// Get the package argument (skip flags)
|
||||
let pkg = args
|
||||
.iter()
|
||||
.find(|arg| !arg.starts_with('-'))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ".".to_string());
|
||||
|
||||
let eval_arg = make_eval_expr(&pkg);
|
||||
let pkg_name: String = if eval_arg.contains("#") {
|
||||
eval_arg
|
||||
.split("#")
|
||||
.last()
|
||||
.unwrap_or(&eval_arg)
|
||||
.trim_end_matches(".meta")
|
||||
.to_string()
|
||||
} else {
|
||||
eval_arg.trim_end_matches(".meta").to_string()
|
||||
};
|
||||
// Handle .# case - show "default" as the package name
|
||||
let pkg_name = if pkg_name.is_empty() {
|
||||
"default".to_string()
|
||||
} else {
|
||||
pkg_name
|
||||
};
|
||||
|
||||
log_info!("Fetching info for {}", pkg_name.bold());
|
||||
|
||||
// Fetch metadata
|
||||
let meta_cmd = NixCommand::new("eval")
|
||||
.arg("--json")
|
||||
.arg(&eval_arg)
|
||||
.print_build_logs(false)
|
||||
.with_config(cfg);
|
||||
|
||||
let meta_output = meta_cmd.output()?;
|
||||
|
||||
if !meta_output.status.success() {
|
||||
log_error!("Failed to fetch package info");
|
||||
print_error_suggestions(&meta_output.stderr);
|
||||
return Err(EhError::NixCommandFailed {
|
||||
command: "eval".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let meta: PackageMeta =
|
||||
serde_json::from_slice(&meta_output.stdout).map_err(|e| {
|
||||
EhError::Io(std::io::Error::other(format!(
|
||||
"Failed to parse package metadata: {}",
|
||||
e
|
||||
)))
|
||||
})?;
|
||||
|
||||
// Fetch outputs
|
||||
let outputs_expr = eval_arg
|
||||
.strip_suffix(".meta")
|
||||
.unwrap_or(&eval_arg)
|
||||
.to_string();
|
||||
let outputs_cmd = NixCommand::new("eval")
|
||||
.arg("--json")
|
||||
.arg(format!("{}.outputs", outputs_expr))
|
||||
.print_build_logs(false)
|
||||
.with_config(cfg);
|
||||
|
||||
let outputs_output = outputs_cmd.output()?;
|
||||
let outputs: Option<PackageOutputs> = if outputs_output.status.success() {
|
||||
serde_json::from_slice(&outputs_output.stdout).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Print formatted info
|
||||
print_package_info(&meta, outputs.as_ref(), &pkg);
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn print_package_info(
|
||||
meta: &PackageMeta,
|
||||
outputs: Option<&PackageOutputs>,
|
||||
pkg_ref: &str,
|
||||
) {
|
||||
println!();
|
||||
|
||||
// Header
|
||||
println!(" {} {}", "Package:".bold(), meta.name);
|
||||
|
||||
if let Some(ref version) = meta.version {
|
||||
println!(" {} {}", "Version:".bold(), version);
|
||||
}
|
||||
|
||||
if let Some(ref desc) = meta.description {
|
||||
println!(" {} {}", "Description:".bold(), desc);
|
||||
}
|
||||
|
||||
// Show long description if available and different from short description
|
||||
if let Some(ref long_desc) = meta.long_description {
|
||||
let should_show = meta
|
||||
.description
|
||||
.as_ref()
|
||||
.map(|d| d != long_desc)
|
||||
.unwrap_or(true);
|
||||
if should_show {
|
||||
println!();
|
||||
// Wrap long description to 70 chars for readability
|
||||
let wrapped = textwrap::fill(long_desc, 70);
|
||||
for line in wrapped.lines() {
|
||||
println!(" {}", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// License
|
||||
if let Some(ref license) = meta.license {
|
||||
let license_str = match license {
|
||||
serde_json::Value::String(s) => s.clone(),
|
||||
serde_json::Value::Object(obj) => {
|
||||
obj
|
||||
.get("spdxId")
|
||||
.and_then(|v| v.as_str())
|
||||
.or_else(|| obj.get("shortName").and_then(|v| v.as_str()))
|
||||
.unwrap_or("Unknown")
|
||||
.to_string()
|
||||
},
|
||||
serde_json::Value::Array(licenses) => {
|
||||
// Handle multiple licenses (e.g., neovim has Apache-2.0 AND Vim)
|
||||
let license_names: Vec<String> = licenses
|
||||
.iter()
|
||||
.filter_map(|lic| {
|
||||
match lic {
|
||||
serde_json::Value::Object(obj) => {
|
||||
obj
|
||||
.get("spdxId")
|
||||
.and_then(|v| v.as_str())
|
||||
.or_else(|| obj.get("shortName").and_then(|v| v.as_str()))
|
||||
.map(|s| s.to_string())
|
||||
},
|
||||
serde_json::Value::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if license_names.is_empty() {
|
||||
"Unknown".to_string()
|
||||
} else {
|
||||
license_names.join(", ")
|
||||
}
|
||||
},
|
||||
_ => "Unknown".to_string(),
|
||||
};
|
||||
println!(" {} {}", "License:".bold(), license_str);
|
||||
}
|
||||
|
||||
// Homepage
|
||||
if let Some(ref homepage) = meta.homepage {
|
||||
println!(" {} {}", "Homepage:".bold(), homepage);
|
||||
}
|
||||
|
||||
// Meta section
|
||||
println!();
|
||||
println!(" {}", "Meta:".bold());
|
||||
|
||||
// Status indicators
|
||||
let mut status_parts = Vec::new();
|
||||
if meta.broken == Some(true) {
|
||||
status_parts.push("Broken".red().to_string());
|
||||
}
|
||||
if meta.insecure == Some(true) {
|
||||
status_parts.push("Insecure".red().to_string());
|
||||
}
|
||||
if meta.unfree == Some(true) {
|
||||
status_parts.push("Unfree".yellow().to_string());
|
||||
}
|
||||
|
||||
if status_parts.is_empty() {
|
||||
println!(" {} {}", "Status:".bold(), "✓ Available".green());
|
||||
} else {
|
||||
println!(" {} {}", "Status:".bold(), status_parts.join(", "));
|
||||
}
|
||||
|
||||
// Platforms
|
||||
if let Some(ref platforms) = meta.platforms {
|
||||
let platform_list: Vec<_> = platforms.iter().take(4).cloned().collect();
|
||||
let platform_str = if platforms.len() > 4 {
|
||||
format!(
|
||||
"{} + {} more",
|
||||
platform_list.join(", "),
|
||||
platforms.len() - 4
|
||||
)
|
||||
} else {
|
||||
platform_list.join(", ")
|
||||
};
|
||||
println!(" {} {}", "Platforms:".bold(), platform_str);
|
||||
}
|
||||
|
||||
// Outputs section
|
||||
if let Some(outputs) = outputs {
|
||||
println!();
|
||||
println!(" {}", "Outputs:".bold());
|
||||
let output_names: Vec<_> = outputs.outputs.keys().cloned().collect();
|
||||
for name in output_names {
|
||||
let marker = if name == "out" { " (default)" } else { "" };
|
||||
println!(" • {}{}", name, marker.dim());
|
||||
}
|
||||
}
|
||||
|
||||
// Usage section
|
||||
println!();
|
||||
println!(" {}", "Usage:".bold());
|
||||
println!(
|
||||
" {} {} {}",
|
||||
"eh run".dim(),
|
||||
pkg_ref,
|
||||
"# Run the package".dim()
|
||||
);
|
||||
println!(
|
||||
" {} {} {}",
|
||||
"eh shell".dim(),
|
||||
pkg_ref,
|
||||
"# Enter shell with package".dim()
|
||||
);
|
||||
println!();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_package_meta_deserialization() {
|
||||
let json = r#"{
|
||||
"name": "hello",
|
||||
"version": "2.12.1",
|
||||
"description": "A greeting program",
|
||||
"license": "GPL-3.0",
|
||||
"homepage": "https://example.com",
|
||||
"platforms": ["x86_64-linux"],
|
||||
"broken": false,
|
||||
"insecure": false,
|
||||
"unfree": false
|
||||
}"#;
|
||||
|
||||
let meta: PackageMeta = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(meta.name, "hello");
|
||||
assert_eq!(meta.version, Some("2.12.1".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_license_object_parsing() {
|
||||
let json = r#"{
|
||||
"name": "test",
|
||||
"license": {"spdxId": "MIT", "fullName": "MIT License"}
|
||||
}"#;
|
||||
|
||||
let meta: PackageMeta = serde_json::from_str(json).unwrap();
|
||||
assert!(meta.license.is_some());
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
pub mod info;
|
||||
pub mod update;
|
||||
|
||||
const DEFAULT_BUFFER_SIZE: usize = 4096;
|
||||
|
|
@ -131,21 +130,6 @@ impl NixCommand {
|
|||
self
|
||||
}
|
||||
|
||||
/// Apply per-command configuration: sets `--impure` (when explicitly enabled)
|
||||
/// and any extra environment variables declared in the config file. Call
|
||||
/// this before any retry-specific overrides so that retry logic can still
|
||||
/// force `impure(true)` afterwards.
|
||||
#[must_use]
|
||||
pub fn with_config(mut self, cfg: &crate::config::CommandConfig) -> Self {
|
||||
if cfg.impure == Some(true) {
|
||||
self = self.impure(true);
|
||||
}
|
||||
for (k, v) in &cfg.env {
|
||||
self = self.env(k, v);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn build_command(&self) -> Command {
|
||||
let mut cmd = Command::new("nix");
|
||||
cmd.arg(&self.subcommand);
|
||||
|
|
@ -336,7 +320,6 @@ pub fn handle_nix_command(
|
|||
hash_extractor: &dyn HashExtractor,
|
||||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
cfg: &crate::config::CommandConfig,
|
||||
) -> Result<i32> {
|
||||
let intercept_env = matches!(command, "run" | "shell");
|
||||
handle_nix_with_retry(
|
||||
|
|
@ -346,7 +329,6 @@ pub fn handle_nix_command(
|
|||
fixer,
|
||||
classifier,
|
||||
intercept_env,
|
||||
cfg,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,10 +55,7 @@ fn prompt_input_selection(inputs: &[String]) -> Result<Vec<String>> {
|
|||
///
|
||||
/// If `args` is non-empty, use them as explicit input names.
|
||||
/// Otherwise, fetch inputs interactively and prompt for selection.
|
||||
pub fn handle_update(
|
||||
args: &[String],
|
||||
cfg: &crate::config::CommandConfig,
|
||||
) -> Result<i32> {
|
||||
pub fn handle_update(args: &[String]) -> Result<i32> {
|
||||
let selected = if args.is_empty() {
|
||||
let inputs = fetch_flake_inputs()?;
|
||||
if inputs.is_empty() {
|
||||
|
|
@ -69,7 +66,7 @@ pub fn handle_update(
|
|||
args.to_vec()
|
||||
};
|
||||
|
||||
let mut cmd = NixCommand::new("flake").arg("lock").with_config(cfg);
|
||||
let mut cmd = NixCommand::new("flake").arg("lock");
|
||||
for name in &selected {
|
||||
cmd = cmd.arg("--update-input").arg(name);
|
||||
}
|
||||
|
|
|
|||
236
eh/src/config.rs
236
eh/src/config.rs
|
|
@ -1,236 +0,0 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
/// When `Some(true)`, pass `--impure` to every Nix command.
|
||||
/// When `Some(false)`, block automatic impure retries for every command.
|
||||
/// When absent (`None`), retry behaviour is automatic (default).
|
||||
#[serde(default)]
|
||||
pub impure: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub commands: HashMap<String, CommandConfig>,
|
||||
}
|
||||
|
||||
/// Per-command configuration.
|
||||
#[derive(Debug, Deserialize, Default, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CommandConfig {
|
||||
/// When `Some(true)`, pass `--impure` to the underlying Nix command.
|
||||
/// When `Some(false)`, block automatic impure retries for this command.
|
||||
/// When absent (`None`), the global setting is used; if that is also absent,
|
||||
/// retry behaviour is automatic (default).
|
||||
#[serde(default)]
|
||||
pub impure: Option<bool>,
|
||||
/// Additional environment variables to set for the Nix command.
|
||||
#[serde(default)]
|
||||
pub env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Return the [`CommandConfig`] for `command`.
|
||||
///
|
||||
/// Resolution order: per-command `impure` takes precedence over the global
|
||||
/// `impure`. Neither being set means automatic retry behaviour.
|
||||
pub fn for_command(&self, command: &str) -> CommandConfig {
|
||||
let mut cmd = self.commands.get(command).cloned().unwrap_or_default();
|
||||
// Per-command setting wins; fall back to global.
|
||||
if cmd.impure.is_none() {
|
||||
cmd.impure = self.impure;
|
||||
}
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
/// Load configuration from the first `.eh.toml` found by walking up from the
|
||||
/// current directory, or from `~/.config/eh/config.toml` as a global
|
||||
/// fallback. Returns a default (empty) config if no file is found or if
|
||||
/// parsing fails.
|
||||
pub fn load() -> Config {
|
||||
if let Some(path) = find_project_config()
|
||||
&& let Some(cfg) = load_from_file(&path)
|
||||
{
|
||||
return cfg;
|
||||
}
|
||||
|
||||
if let Some(path) = global_config_path()
|
||||
&& let Some(cfg) = load_from_file(&path)
|
||||
{
|
||||
return cfg;
|
||||
}
|
||||
|
||||
Config::default()
|
||||
}
|
||||
|
||||
fn find_project_config() -> Option<PathBuf> {
|
||||
let mut dir = env::current_dir().ok()?;
|
||||
loop {
|
||||
let candidate = dir.join(".eh.toml");
|
||||
if candidate.exists() {
|
||||
return Some(candidate);
|
||||
}
|
||||
if !dir.pop() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn global_config_path() -> Option<PathBuf> {
|
||||
let home = env::var("HOME").ok()?;
|
||||
Some(
|
||||
PathBuf::from(home)
|
||||
.join(".config")
|
||||
.join("eh")
|
||||
.join("config.toml"),
|
||||
)
|
||||
}
|
||||
|
||||
fn load_from_file(path: &Path) -> Option<Config> {
|
||||
let content = fs::read_to_string(path).ok()?;
|
||||
match toml::de::from_str::<Config>(&content) {
|
||||
Ok(cfg) => Some(cfg),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"eh: warning: failed to parse config file {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_empty_config_defaults() {
|
||||
let cfg: Config = toml::from_str("").unwrap();
|
||||
assert!(cfg.impure.is_none());
|
||||
assert!(cfg.commands.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_config_impure_true() {
|
||||
let cfg: Config = toml::from_str(
|
||||
r#"
|
||||
[commands.build]
|
||||
impure = true
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cfg.for_command("build").impure, Some(true));
|
||||
assert_eq!(cfg.for_command("run").impure, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_config_impure_false() {
|
||||
let cfg: Config = toml::from_str(
|
||||
r#"
|
||||
[commands.build]
|
||||
impure = false
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cfg.for_command("build").impure, Some(false));
|
||||
assert_eq!(cfg.for_command("run").impure, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_global_impure_propagates_to_unconfigured_commands() {
|
||||
let cfg: Config = toml::from_str("impure = true").unwrap();
|
||||
// Commands with no per-command entry inherit global.
|
||||
assert_eq!(cfg.for_command("build").impure, Some(true));
|
||||
assert_eq!(cfg.for_command("nonexistent").impure, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_global_impure_false_propagates_to_unconfigured_commands() {
|
||||
let cfg: Config = toml::from_str("impure = false").unwrap();
|
||||
assert_eq!(cfg.for_command("build").impure, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_per_command_impure_overrides_global() {
|
||||
// Per-command setting wins over global.
|
||||
let cfg: Config = toml::from_str(
|
||||
r#"
|
||||
impure = false
|
||||
|
||||
[commands.build]
|
||||
impure = true
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(cfg.for_command("build").impure, Some(true));
|
||||
// Command without per-command entry falls back to global false.
|
||||
assert_eq!(cfg.for_command("run").impure, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_config_env() {
|
||||
let cfg: Config = toml::from_str(
|
||||
r#"
|
||||
[commands.develop]
|
||||
env = { FOO = "bar", BAZ = "1" }
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let dev = cfg.for_command("develop");
|
||||
assert_eq!(dev.env.get("FOO").map(String::as_str), Some("bar"));
|
||||
assert_eq!(dev.env.get("BAZ").map(String::as_str), Some("1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_config_env_table_syntax() {
|
||||
let cfg: Config = toml::from_str(
|
||||
r#"
|
||||
[commands.shell]
|
||||
impure = true
|
||||
|
||||
[commands.shell.env]
|
||||
MY_VAR = "hello"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let shell = cfg.for_command("shell");
|
||||
assert_eq!(shell.impure, Some(true));
|
||||
assert_eq!(shell.env.get("MY_VAR").map(String::as_str), Some("hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_for_command_missing_returns_default() {
|
||||
let cfg = Config::default();
|
||||
let cc = cfg.for_command("nonexistent");
|
||||
assert_eq!(cc.impure, None);
|
||||
assert!(cc.env.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_top_level_key_is_rejected() {
|
||||
let result = toml::de::from_str::<Config>("unknown_key = true");
|
||||
assert!(result.is_err(), "unknown top-level keys should be rejected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_command_key_is_rejected() {
|
||||
let result = toml::de::from_str::<Config>(
|
||||
r#"
|
||||
[commands.build]
|
||||
typo_key = true
|
||||
"#,
|
||||
);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"unknown per-command keys should be rejected"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -54,11 +54,6 @@ pub enum EhError {
|
|||
|
||||
#[error("no inputs selected")]
|
||||
UpdateCancelled,
|
||||
|
||||
#[error(
|
||||
"package {reason} but `--impure` is disabled for `{command}` in config"
|
||||
)]
|
||||
ImpureRequired { command: String, reason: String },
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, EhError>;
|
||||
|
|
@ -82,7 +77,6 @@ impl EhError {
|
|||
Self::JsonParse { .. } => 13,
|
||||
Self::NoFlakeInputs => 14,
|
||||
Self::UpdateCancelled => 0,
|
||||
Self::ImpureRequired { .. } => 15,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,12 +110,6 @@ impl EhError {
|
|||
Self::NoFlakeInputs => {
|
||||
Some("run this from a directory with a flake.lock that has inputs")
|
||||
},
|
||||
Self::ImpureRequired { .. } => {
|
||||
Some(
|
||||
"set `impure = true` for this command (or globally) in .eh.toml or \
|
||||
~/.config/eh/config.toml, or pass `--impure` manually",
|
||||
)
|
||||
},
|
||||
Self::Io(_)
|
||||
| Self::Regex(_)
|
||||
| Self::Utf8(_)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
pub mod commands;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod util;
|
||||
|
||||
|
|
@ -32,16 +31,6 @@ pub enum Command {
|
|||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Enter a Nix development shell
|
||||
Develop {
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Show package information
|
||||
Info {
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Update flake inputs interactively
|
||||
Update {
|
||||
#[arg(trailing_var_arg = true)]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use eh::{Cli, Command, CommandFactory, Parser};
|
|||
use yansi::Paint;
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
mod error;
|
||||
mod util;
|
||||
|
||||
|
|
@ -30,21 +29,16 @@ fn handle_command(command: &str, args: &[String]) -> error::Result<i32> {
|
|||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
let cfg = config::load();
|
||||
let cmd_cfg = cfg.for_command(command);
|
||||
|
||||
match command {
|
||||
"info" => commands::info::handle_info(args, &cmd_cfg),
|
||||
|
||||
"update" => commands::update::handle_update(args, &cmd_cfg),
|
||||
"run" | "shell" | "build" | "develop" => {
|
||||
"update" => commands::update::handle_update(args),
|
||||
"run" | "shell" | "build" => {
|
||||
commands::handle_nix_command(
|
||||
command,
|
||||
args,
|
||||
&hash_extractor,
|
||||
&fixer,
|
||||
&classifier,
|
||||
&cmd_cfg,
|
||||
)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
|
|
@ -61,8 +55,6 @@ fn dispatch_multicall(
|
|||
"nr" => "run",
|
||||
"ns" => "shell",
|
||||
"nb" => "build",
|
||||
"nd" => "develop",
|
||||
"ni" => "info",
|
||||
"nu" => "update",
|
||||
_ => return None,
|
||||
};
|
||||
|
|
@ -112,10 +104,6 @@ fn run_app() -> error::Result<i32> {
|
|||
|
||||
Some(Command::Build { args }) => handle_command("build", &args),
|
||||
|
||||
Some(Command::Develop { args }) => handle_command("develop", &args),
|
||||
|
||||
Some(Command::Info { args }) => handle_command("info", &args),
|
||||
|
||||
Some(Command::Update { args }) => handle_command("update", &args),
|
||||
|
||||
None => {
|
||||
|
|
|
|||
113
eh/src/util.rs
113
eh/src/util.rs
|
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
io::{BufWriter, IsTerminal, Write},
|
||||
io::{BufWriter, Write},
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
|
@ -42,13 +42,6 @@ static HASH_FIX_PATTERNS: LazyLock<[Regex; 3]> = LazyLock::new(|| {
|
|||
]
|
||||
});
|
||||
|
||||
/// Regex to extract suggestions from Nix's "Did you mean" error line.
|
||||
/// Matches patterns like:
|
||||
/// - "Did you mean one of hello, world, or foo?"
|
||||
/// - "Did you mean lib.hello?"
|
||||
static DID_YOU_MEAN_PATTERN: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"Did you mean (?:one of )?(.+?)\?"#).unwrap());
|
||||
|
||||
/// Trait for extracting store paths and hashes from nix output.
|
||||
pub trait HashExtractor {
|
||||
/// Extract the new store path/hash from nix output.
|
||||
|
|
@ -318,20 +311,9 @@ fn is_hash_mismatch_error(stderr: &str) -> bool {
|
|||
|
||||
/// Construct the eval expression for a given argument.
|
||||
/// Handles both plain package names and flake references.
|
||||
pub fn make_eval_expr(eval_arg: &str) -> String {
|
||||
// Handle . (current directory) as .# (default package of current flake)
|
||||
// Nix treats `nix build .` and `nix build .#` as equivalent
|
||||
let eval_arg = if eval_arg == "." { ".#" } else { eval_arg };
|
||||
|
||||
fn make_eval_expr(eval_arg: &str) -> String {
|
||||
if eval_arg.contains('#') {
|
||||
// Handle .# (current flake default package) case
|
||||
// .# needs to become .#default for meta evaluation to work
|
||||
// because .#.meta evaluates 'meta' on the flake itself, not the package
|
||||
if eval_arg.ends_with('#') {
|
||||
format!("{eval_arg}default.meta")
|
||||
} else {
|
||||
format!("{eval_arg}.meta")
|
||||
}
|
||||
format!("{eval_arg}.meta")
|
||||
} else {
|
||||
format!("nixpkgs#{eval_arg}.meta")
|
||||
}
|
||||
|
|
@ -485,7 +467,6 @@ pub fn handle_nix_with_retry(
|
|||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
interactive: bool,
|
||||
cfg: &crate::config::CommandConfig,
|
||||
) -> Result<i32> {
|
||||
validate_nix_args(args)?;
|
||||
|
||||
|
|
@ -495,17 +476,10 @@ pub fn handle_nix_with_retry(
|
|||
let pkg = package_name(args);
|
||||
let pre_eval_action = pre_evaluate(args)?;
|
||||
if let Some((env_var, reason)) = pre_eval_action.env_override() {
|
||||
if cfg.impure == Some(false) {
|
||||
return Err(EhError::ImpureRequired {
|
||||
command: subcommand.to_string(),
|
||||
reason: reason.to_string(),
|
||||
});
|
||||
}
|
||||
print_retry_msg(pkg, reason, env_var);
|
||||
let mut retry_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
.args_ref(args)
|
||||
.with_config(cfg)
|
||||
.env(env_var, "1")
|
||||
.impure(true);
|
||||
if interactive {
|
||||
|
|
@ -521,7 +495,6 @@ pub fn handle_nix_with_retry(
|
|||
.print_build_logs(true)
|
||||
.interactive(true)
|
||||
.args_ref(args)
|
||||
.with_config(cfg)
|
||||
.run_with_logs(StdIoInterceptor)?;
|
||||
if status.success() {
|
||||
return Ok(0);
|
||||
|
|
@ -531,38 +504,13 @@ pub fn handle_nix_with_retry(
|
|||
// Capture output to check for errors that need retry (hash mismatches etc.)
|
||||
let output_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
.args_ref(args)
|
||||
.with_config(cfg);
|
||||
.args_ref(args);
|
||||
let output = output_cmd.output()?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
// Check for hash mismatch errors
|
||||
if let Some(new_hash) = hash_extractor.extract_hash(&stderr) {
|
||||
let old_hash = hash_extractor.extract_old_hash(&stderr);
|
||||
|
||||
// Ask for confirmation before fixing hash (skip in non-interactive mode)
|
||||
let should_fix = if std::io::stdin().is_terminal() {
|
||||
dialoguer::Confirm::new()
|
||||
.with_prompt(format!(
|
||||
"Hash mismatch detected for {}. Update hash in local .nix files?",
|
||||
pkg.bold()
|
||||
))
|
||||
.default(true)
|
||||
.interact()
|
||||
.map_err(|e| EhError::Io(std::io::Error::other(e)))?
|
||||
} else {
|
||||
log_warn!(
|
||||
"{}: hash mismatch detected in non-interactive mode, skipping auto-fix",
|
||||
pkg.bold()
|
||||
);
|
||||
false
|
||||
};
|
||||
|
||||
if !should_fix {
|
||||
log_warn!("{}: hash fix cancelled", pkg.bold());
|
||||
return Err(EhError::ProcessExit { code: 1 });
|
||||
}
|
||||
|
||||
match fixer.fix_hash_in_files(old_hash.as_deref(), &new_hash) {
|
||||
Ok(true) => {
|
||||
log_info!(
|
||||
|
|
@ -571,8 +519,7 @@ pub fn handle_nix_with_retry(
|
|||
);
|
||||
let mut retry_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
.args_ref(args)
|
||||
.with_config(cfg);
|
||||
.args_ref(args);
|
||||
if interactive {
|
||||
retry_cmd = retry_cmd.interactive(true);
|
||||
}
|
||||
|
|
@ -605,17 +552,10 @@ pub fn handle_nix_with_retry(
|
|||
if classifier.should_retry(&stderr) {
|
||||
let action = classify_retry_action(&stderr);
|
||||
if let Some((env_var, reason)) = action.env_override() {
|
||||
if cfg.impure == Some(false) {
|
||||
return Err(EhError::ImpureRequired {
|
||||
command: subcommand.to_string(),
|
||||
reason: reason.to_string(),
|
||||
});
|
||||
}
|
||||
print_retry_msg(pkg, reason, env_var);
|
||||
let mut retry_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
.args_ref(args)
|
||||
.with_config(cfg)
|
||||
.env(env_var, "1")
|
||||
.impure(true);
|
||||
if interactive {
|
||||
|
|
@ -636,9 +576,6 @@ pub fn handle_nix_with_retry(
|
|||
.write_all(&output.stderr)
|
||||
.map_err(EhError::Io)?;
|
||||
|
||||
// Print contextual suggestions for common errors
|
||||
print_error_suggestions(&output.stderr);
|
||||
|
||||
match output.status.code() {
|
||||
Some(code) => Err(EhError::ProcessExit { code }),
|
||||
// No exit code means the process was killed by a signal
|
||||
|
|
@ -658,46 +595,6 @@ impl NixErrorClassifier for DefaultNixErrorClassifier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse suggestions from Nix's "Did you mean" error line.
|
||||
/// Input: "Did you mean one of neovim, hevi, navi, neo or neo4j?"
|
||||
/// Output: vec!["neovim", "hevi", "navi", "neo", "neo4j"]
|
||||
fn parse_nix_suggestions(did_you_mean_line: &str) -> Vec<String> {
|
||||
DID_YOU_MEAN_PATTERN
|
||||
.captures(did_you_mean_line)
|
||||
.and_then(|caps| caps.get(1))
|
||||
.map(|m| m.as_str())
|
||||
.map(|suggestions| {
|
||||
suggestions
|
||||
.split(", ")
|
||||
.flat_map(|part| part.split(" or "))
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Print contextual error suggestions when a command fails.
|
||||
/// Parses Nix's own "Did you mean" suggestions from stderr and presents them
|
||||
/// nicely to the user.
|
||||
pub fn print_error_suggestions(stderr: &[u8]) {
|
||||
let stderr_str = String::from_utf8_lossy(stderr);
|
||||
|
||||
// Look for Nix's "Did you mean" line in the error output
|
||||
if let Some(line) = stderr_str.lines().find(|l| l.contains("Did you mean")) {
|
||||
let suggestions = parse_nix_suggestions(line);
|
||||
|
||||
if !suggestions.is_empty() {
|
||||
let formatted = suggestions
|
||||
.iter()
|
||||
.map(|s| s.bold().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
log_info!("Did you mean: {}?", formatted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Write;
|
||||
|
|
|
|||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1776548001,
|
||||
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||
"lastModified": 1769461804,
|
||||
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue