eh: modernize error handling; bump dependencies
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I63e346cd38bfb6cd277f6675fcefe64e6a6a6964
This commit is contained in:
parent
e1c8ecf0b6
commit
caa6dc9951
12 changed files with 265 additions and 172 deletions
93
Cargo.lock
generated
93
Cargo.lock
generated
|
|
@ -25,9 +25,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.41"
|
||||
version = "4.5.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -35,9 +35,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.41"
|
||||
version = "4.5.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
|
|
@ -45,18 +45,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.55"
|
||||
version = "4.5.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
||||
checksum = "8e602857739c5a4291dfa33b5a298aeac9006185229a700e5810a3ef7272d971"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.41"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
@ -72,10 +72,11 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
|||
|
||||
[[package]]
|
||||
name = "eh"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"regex",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"yansi",
|
||||
|
|
@ -107,12 +108,11 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
|||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -121,12 +121,6 @@ version = "1.21.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
|
|
@ -153,9 +147,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -165,9 +159,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -206,6 +200,26 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
|
|
@ -260,9 +274,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
|
|
@ -285,30 +299,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
|
|
|
|||
17
Cargo.toml
17
Cargo.toml
|
|
@ -8,16 +8,17 @@ description = "Ergonomic Nix CLI helper"
|
|||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
readme = true
|
||||
rust-version = "1.85"
|
||||
version = "0.1.1"
|
||||
rust-version = "1.89"
|
||||
version = "0.1.2"
|
||||
|
||||
[workspace.dependencies]
|
||||
clap = { default-features = false, features = [ "std", "help", "derive" ], version = "4.5" }
|
||||
clap_complete = "4.5"
|
||||
regex = "1.11"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
yansi = "1.0"
|
||||
clap = { default-features = false, features = [ "std", "help", "derive" ], version = "4.5.51" }
|
||||
clap_complete = "4.5.60"
|
||||
regex = "1.12.2"
|
||||
thiserror = "2.0.17"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.20"
|
||||
yansi = "1.0.1"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ crate-type = ["lib"]
|
|||
[dependencies]
|
||||
clap.workspace = true
|
||||
regex.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
yansi.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::error::Result;
|
||||
use crate::util::{HashExtractor, NixErrorClassifier, NixFileFixer, handle_nix_with_retry};
|
||||
|
||||
pub fn handle_nix_build(
|
||||
|
|
@ -5,6 +6,6 @@ pub fn handle_nix_build(
|
|||
hash_extractor: &dyn HashExtractor,
|
||||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
) {
|
||||
handle_nix_with_retry("build", args, hash_extractor, fixer, classifier, false);
|
||||
) -> Result<i32> {
|
||||
handle_nix_with_retry("build", args, hash_extractor, fixer, classifier, false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::error::{EhError, Result};
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::process::{Command, ExitStatus, Output, Stdio};
|
||||
|
|
@ -61,17 +62,17 @@ impl NixCommand {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn impure(mut self, yes: bool) -> Self {
|
||||
#[must_use] pub const fn impure(mut self, yes: bool) -> Self {
|
||||
self.impure = yes;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn interactive(mut self, yes: bool) -> Self {
|
||||
#[must_use] pub const fn interactive(mut self, yes: bool) -> Self {
|
||||
self.interactive = yes;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn print_build_logs(mut self, yes: bool) -> Self {
|
||||
#[must_use] pub const fn print_build_logs(mut self, yes: bool) -> Self {
|
||||
self.print_build_logs = yes;
|
||||
self
|
||||
}
|
||||
|
|
@ -80,7 +81,7 @@ impl NixCommand {
|
|||
pub fn run_with_logs<I: LogInterceptor + 'static>(
|
||||
&self,
|
||||
mut interceptor: I,
|
||||
) -> io::Result<ExitStatus> {
|
||||
) -> Result<ExitStatus> {
|
||||
let mut cmd = Command::new("nix");
|
||||
cmd.arg(&self.subcommand);
|
||||
|
||||
|
|
@ -99,15 +100,21 @@ impl NixCommand {
|
|||
cmd.stdout(Stdio::inherit());
|
||||
cmd.stderr(Stdio::inherit());
|
||||
cmd.stdin(Stdio::inherit());
|
||||
return cmd.status();
|
||||
return Ok(cmd.status()?);
|
||||
}
|
||||
|
||||
cmd.stdout(Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
|
||||
let mut child = cmd.spawn()?;
|
||||
let mut stdout = child.stdout.take().unwrap();
|
||||
let mut stderr = child.stderr.take().unwrap();
|
||||
let child_stdout = child.stdout.take().ok_or_else(|| EhError::CommandFailed {
|
||||
command: format!("nix {}", self.subcommand),
|
||||
})?;
|
||||
let child_stderr = child.stderr.take().ok_or_else(|| EhError::CommandFailed {
|
||||
command: format!("nix {}", self.subcommand),
|
||||
})?;
|
||||
let mut stdout = child_stdout;
|
||||
let mut stderr = child_stderr;
|
||||
|
||||
let mut out_buf = [0u8; 4096];
|
||||
let mut err_buf = [0u8; 4096];
|
||||
|
|
@ -126,7 +133,7 @@ impl NixCommand {
|
|||
did_something = true;
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => return Err(e),
|
||||
Err(e) => return Err(EhError::Io(e)),
|
||||
}
|
||||
|
||||
match stderr.read(&mut err_buf) {
|
||||
|
|
@ -137,7 +144,7 @@ impl NixCommand {
|
|||
did_something = true;
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => return Err(e),
|
||||
Err(e) => return Err(EhError::Io(e)),
|
||||
}
|
||||
|
||||
if !did_something && child.try_wait()?.is_some() {
|
||||
|
|
@ -150,7 +157,7 @@ impl NixCommand {
|
|||
}
|
||||
|
||||
/// Run the command and capture all output.
|
||||
pub fn output(&self) -> io::Result<Output> {
|
||||
pub fn output(&self) -> Result<Output> {
|
||||
let mut cmd = Command::new("nix");
|
||||
cmd.arg(&self.subcommand);
|
||||
|
||||
|
|
@ -174,6 +181,6 @@ impl NixCommand {
|
|||
cmd.stderr(Stdio::piped());
|
||||
}
|
||||
|
||||
cmd.output()
|
||||
Ok(cmd.output()?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
eh/src/error.rs
Normal file
44
eh/src/error.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EhError {
|
||||
#[error("Nix command failed: {0}")]
|
||||
NixCommandFailed(String),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Regex error: {0}")]
|
||||
Regex(#[from] regex::Error),
|
||||
|
||||
#[error("UTF-8 conversion error: {0}")]
|
||||
Utf8(#[from] std::string::FromUtf8Error),
|
||||
|
||||
#[error("Hash extraction failed")]
|
||||
HashExtractionFailed,
|
||||
|
||||
#[error("No Nix files found")]
|
||||
NoNixFilesFound,
|
||||
|
||||
#[error("Failed to fix hash in file: {path}")]
|
||||
HashFixFailed { path: String },
|
||||
|
||||
#[error("Process exited with code: {code}")]
|
||||
ProcessExit { code: i32 },
|
||||
|
||||
#[error("Command execution failed: {command}")]
|
||||
CommandFailed { command: String },
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, EhError>;
|
||||
|
||||
impl EhError {
|
||||
#[must_use] pub const fn exit_code(&self) -> i32 {
|
||||
match self {
|
||||
Self::ProcessExit { code } => *code,
|
||||
Self::NixCommandFailed(_) => 1,
|
||||
Self::CommandFailed { .. } => 1,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
pub mod build;
|
||||
pub mod command;
|
||||
pub mod error;
|
||||
pub mod run;
|
||||
pub mod shell;
|
||||
pub mod util;
|
||||
|
||||
pub use clap::{CommandFactory, Parser, Subcommand};
|
||||
pub use error::{EhError, Result};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "eh")]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use eh::{Cli, Command, CommandFactory, Parser};
|
||||
use error::Result;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
mod build;
|
||||
mod command;
|
||||
mod error;
|
||||
mod run;
|
||||
mod shell;
|
||||
mod util;
|
||||
|
|
@ -17,6 +19,18 @@ fn main() {
|
|||
.compact(); // use the `Compact` formatting style.
|
||||
tracing_subscriber::fmt().event_format(format).init();
|
||||
|
||||
let result = run_app();
|
||||
|
||||
match result {
|
||||
Ok(code) => std::process::exit(code),
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
std::process::exit(e.exit_code());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_app() -> Result<i32> {
|
||||
let mut args = env::args();
|
||||
let bin = args.next().unwrap_or_else(|| "eh".to_string());
|
||||
let app_name = Path::new(&bin)
|
||||
|
|
@ -31,8 +45,7 @@ fn main() {
|
|||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
run::handle_nix_run(&rest, &hash_extractor, &fixer, &classifier);
|
||||
return;
|
||||
return run::handle_nix_run(&rest, &hash_extractor, &fixer, &classifier);
|
||||
}
|
||||
|
||||
"ns" => {
|
||||
|
|
@ -40,8 +53,7 @@ fn main() {
|
|||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
shell::handle_nix_shell(&rest, &hash_extractor, &fixer, &classifier);
|
||||
return;
|
||||
return shell::handle_nix_shell(&rest, &hash_extractor, &fixer, &classifier);
|
||||
}
|
||||
|
||||
"nb" => {
|
||||
|
|
@ -49,8 +61,7 @@ fn main() {
|
|||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
build::handle_nix_build(&rest, &hash_extractor, &fixer, &classifier);
|
||||
return;
|
||||
return build::handle_nix_build(&rest, &hash_extractor, &fixer, &classifier);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -63,21 +74,21 @@ fn main() {
|
|||
|
||||
match cli.command {
|
||||
Some(Command::Run { args }) => {
|
||||
run::handle_nix_run(&args, &hash_extractor, &fixer, &classifier);
|
||||
run::handle_nix_run(&args, &hash_extractor, &fixer, &classifier)
|
||||
}
|
||||
|
||||
Some(Command::Shell { args }) => {
|
||||
shell::handle_nix_shell(&args, &hash_extractor, &fixer, &classifier);
|
||||
shell::handle_nix_shell(&args, &hash_extractor, &fixer, &classifier)
|
||||
}
|
||||
|
||||
Some(Command::Build { args }) => {
|
||||
build::handle_nix_build(&args, &hash_extractor, &fixer, &classifier);
|
||||
build::handle_nix_build(&args, &hash_extractor, &fixer, &classifier)
|
||||
}
|
||||
|
||||
_ => {
|
||||
Cli::command().print_help().unwrap();
|
||||
Cli::command().print_help()?;
|
||||
println!();
|
||||
std::process::exit(0);
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::error::Result;
|
||||
use crate::util::{HashExtractor, NixErrorClassifier, NixFileFixer, handle_nix_with_retry};
|
||||
|
||||
pub fn handle_nix_run(
|
||||
|
|
@ -5,6 +6,6 @@ pub fn handle_nix_run(
|
|||
hash_extractor: &dyn HashExtractor,
|
||||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
) {
|
||||
handle_nix_with_retry("run", args, hash_extractor, fixer, classifier, true);
|
||||
) -> Result<i32> {
|
||||
handle_nix_with_retry("run", args, hash_extractor, fixer, classifier, true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::error::Result;
|
||||
use crate::util::{HashExtractor, NixErrorClassifier, NixFileFixer, handle_nix_with_retry};
|
||||
|
||||
pub fn handle_nix_shell(
|
||||
|
|
@ -5,6 +6,6 @@ pub fn handle_nix_shell(
|
|||
hash_extractor: &dyn HashExtractor,
|
||||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
) {
|
||||
handle_nix_with_retry("shell", args, hash_extractor, fixer, classifier, true);
|
||||
) -> Result<i32> {
|
||||
handle_nix_with_retry("shell", args, hash_extractor, fixer, classifier, true)
|
||||
}
|
||||
|
|
|
|||
133
eh/src/util.rs
133
eh/src/util.rs
|
|
@ -1,4 +1,5 @@
|
|||
use crate::command::{NixCommand, StdIoInterceptor};
|
||||
use crate::error::{EhError, Result};
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
|
@ -20,61 +21,61 @@ impl HashExtractor for RegexHashExtractor {
|
|||
r"have:\s+(sha256-[a-zA-Z0-9+/=]+)",
|
||||
];
|
||||
for pattern in &patterns {
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
if let Some(captures) = re.captures(stderr) {
|
||||
if let Some(hash) = captures.get(1) {
|
||||
if let Ok(re) = Regex::new(pattern)
|
||||
&& let Some(captures) = re.captures(stderr)
|
||||
&& let Some(hash) = captures.get(1) {
|
||||
return Some(hash.as_str().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NixFileFixer {
|
||||
fn fix_hash_in_files(&self, new_hash: &str) -> bool;
|
||||
fn find_nix_files(&self) -> Vec<PathBuf>;
|
||||
fn fix_hash_in_file(&self, file_path: &Path, new_hash: &str) -> bool;
|
||||
fn fix_hash_in_files(&self, new_hash: &str) -> Result<bool>;
|
||||
fn find_nix_files(&self) -> Result<Vec<PathBuf>>;
|
||||
fn fix_hash_in_file(&self, file_path: &Path, new_hash: &str) -> Result<bool>;
|
||||
}
|
||||
|
||||
pub struct DefaultNixFileFixer;
|
||||
|
||||
impl NixFileFixer for DefaultNixFileFixer {
|
||||
fn fix_hash_in_files(&self, new_hash: &str) -> bool {
|
||||
let nix_files = self.find_nix_files();
|
||||
fn fix_hash_in_files(&self, new_hash: &str) -> Result<bool> {
|
||||
let nix_files = self.find_nix_files()?;
|
||||
let mut fixed = false;
|
||||
for file_path in nix_files {
|
||||
if self.fix_hash_in_file(&file_path, new_hash) {
|
||||
if self.fix_hash_in_file(&file_path, new_hash)? {
|
||||
println!("Updated hash in {}", file_path.display());
|
||||
fixed = true;
|
||||
}
|
||||
}
|
||||
fixed
|
||||
Ok(fixed)
|
||||
}
|
||||
|
||||
fn find_nix_files(&self) -> Vec<PathBuf> {
|
||||
fn find_nix_files(&self) -> Result<Vec<PathBuf>> {
|
||||
let mut files = Vec::new();
|
||||
let mut stack = vec![PathBuf::from(".")];
|
||||
while let Some(dir) = stack.pop() {
|
||||
if let Ok(entries) = fs::read_dir(&dir) {
|
||||
let entries = fs::read_dir(&dir)?;
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
stack.push(path);
|
||||
} else if let Some(ext) = path.extension() {
|
||||
if ext.eq_ignore_ascii_case("nix") {
|
||||
} else if let Some(ext) = path.extension()
|
||||
&& ext.eq_ignore_ascii_case("nix") {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if files.is_empty() {
|
||||
Err(EhError::NoNixFilesFound)
|
||||
} else {
|
||||
Ok(files)
|
||||
}
|
||||
}
|
||||
files
|
||||
}
|
||||
|
||||
fn fix_hash_in_file(&self, file_path: &Path, new_hash: &str) -> bool {
|
||||
if let Ok(content) = fs::read_to_string(file_path) {
|
||||
fn fix_hash_in_file(&self, file_path: &Path, new_hash: &str) -> Result<bool> {
|
||||
let content = fs::read_to_string(file_path)?;
|
||||
let patterns = [
|
||||
(r#"hash\s*=\s*"[^"]*""#, format!(r#"hash = "{new_hash}""#)),
|
||||
(
|
||||
|
|
@ -86,10 +87,10 @@ impl NixFileFixer for DefaultNixFileFixer {
|
|||
format!(r#"outputHash = "{new_hash}""#),
|
||||
),
|
||||
];
|
||||
let mut new_content = content.clone();
|
||||
let mut new_content = content;
|
||||
let mut replaced = false;
|
||||
for (pattern, replacement) in &patterns {
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
let re = Regex::new(pattern)?;
|
||||
if re.is_match(&new_content) {
|
||||
new_content = re
|
||||
.replace_all(&new_content, replacement.as_str())
|
||||
|
|
@ -97,12 +98,15 @@ impl NixFileFixer for DefaultNixFileFixer {
|
|||
replaced = true;
|
||||
}
|
||||
}
|
||||
if replaced {
|
||||
fs::write(file_path, new_content)
|
||||
.map_err(|_| EhError::HashFixFailed {
|
||||
path: file_path.to_string_lossy().to_string()
|
||||
})?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
if replaced && fs::write(file_path, new_content).is_ok() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,24 +115,21 @@ pub trait NixErrorClassifier {
|
|||
}
|
||||
|
||||
/// Pre-evaluate expression to catch errors early
|
||||
fn pre_evaluate(_subcommand: &str, args: &[String]) -> bool {
|
||||
fn pre_evaluate(_subcommand: &str, args: &[String]) -> Result<bool> {
|
||||
// Find flake references or expressions to evaluate
|
||||
// Only take the first non-flag argument (the package/expression)
|
||||
let eval_arg = args.iter().find(|arg| !arg.starts_with('-'));
|
||||
|
||||
let Some(eval_arg) = eval_arg else {
|
||||
return true; // No expression to evaluate
|
||||
return Ok(true); // No expression to evaluate
|
||||
};
|
||||
|
||||
let eval_cmd = NixCommand::new("eval").arg(eval_arg).arg("--raw");
|
||||
|
||||
let output = match eval_cmd.output() {
|
||||
Ok(output) => output,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let output = eval_cmd.output()?;
|
||||
|
||||
if output.status.success() {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
|
@ -140,11 +141,11 @@ fn pre_evaluate(_subcommand: &str, args: &[String]) -> bool {
|
|||
|| stderr.contains("has been marked as insecure")
|
||||
|| stderr.contains("has been marked as broken")
|
||||
{
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// For other eval failures, fail early
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Shared retry logic for nix commands (build/run/shell).
|
||||
|
|
@ -155,11 +156,12 @@ pub fn handle_nix_with_retry(
|
|||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
interactive: bool,
|
||||
) -> ! {
|
||||
) -> Result<i32> {
|
||||
// Pre-evaluate for build commands to catch errors early
|
||||
if !pre_evaluate(subcommand, args) {
|
||||
eprintln!("Error: Expression evaluation failed");
|
||||
std::process::exit(1);
|
||||
if !pre_evaluate(subcommand, args)? {
|
||||
return Err(EhError::NixCommandFailed(
|
||||
"Expression evaluation failed".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// For run commands, try interactive first to avoid breaking terminal
|
||||
|
|
@ -170,11 +172,9 @@ pub fn handle_nix_with_retry(
|
|||
for arg in args {
|
||||
cmd = cmd.arg(arg);
|
||||
}
|
||||
let status = cmd
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.expect("failed to run nix command");
|
||||
let status = cmd.run_with_logs(StdIoInterceptor)?;
|
||||
if status.success() {
|
||||
std::process::exit(0);
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,12 +182,13 @@ pub fn handle_nix_with_retry(
|
|||
let output_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned());
|
||||
let output = output_cmd.output().expect("failed to capture output");
|
||||
let output = output_cmd.output()?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
// Check if we need to retry with special flags
|
||||
if let Some(new_hash) = hash_extractor.extract_hash(&stderr) {
|
||||
if fixer.fix_hash_in_files(&new_hash) {
|
||||
match fixer.fix_hash_in_files(&new_hash) {
|
||||
Ok(true) => {
|
||||
info!("{}", Paint::green("✔ Fixed hash mismatch, retrying..."));
|
||||
let mut retry_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
|
|
@ -195,9 +196,23 @@ pub fn handle_nix_with_retry(
|
|||
if interactive {
|
||||
retry_cmd = retry_cmd.interactive(true);
|
||||
}
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor).unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor)?;
|
||||
return Ok(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
Ok(false) => {
|
||||
// No files were fixed, continue with normal error handling
|
||||
}
|
||||
Err(EhError::NoNixFilesFound) => {
|
||||
warn!("No .nix files found to fix hash in");
|
||||
// Continue with normal error handling
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
} else if stderr.contains("hash") || stderr.contains("sha256") {
|
||||
// If there's a hash-related error but we couldn't extract it, that's a failure
|
||||
return Err(EhError::HashExtractionFailed);
|
||||
}
|
||||
|
||||
if classifier.should_retry(&stderr) {
|
||||
|
|
@ -214,8 +229,8 @@ pub fn handle_nix_with_retry(
|
|||
if interactive {
|
||||
retry_cmd = retry_cmd.interactive(true);
|
||||
}
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor).unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor)?;
|
||||
return Ok(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("has been marked as insecure") && stderr.contains("refusing") {
|
||||
warn!(
|
||||
|
|
@ -232,8 +247,8 @@ pub fn handle_nix_with_retry(
|
|||
if interactive {
|
||||
retry_cmd = retry_cmd.interactive(true);
|
||||
}
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor).unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor)?;
|
||||
return Ok(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("has been marked as broken") && stderr.contains("refusing") {
|
||||
warn!(
|
||||
|
|
@ -248,19 +263,21 @@ pub fn handle_nix_with_retry(
|
|||
if interactive {
|
||||
retry_cmd = retry_cmd.interactive(true);
|
||||
}
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor).unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
let retry_status = retry_cmd.run_with_logs(StdIoInterceptor)?;
|
||||
return Ok(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
// If the first attempt succeeded, we're done
|
||||
if output.status.success() {
|
||||
std::process::exit(0);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Otherwise, show the error and exit
|
||||
std::io::stderr().write_all(output.stderr.as_ref()).unwrap();
|
||||
std::process::exit(output.status.code().unwrap_or(1));
|
||||
// Otherwise, show the error and return error
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
Err(EhError::ProcessExit {
|
||||
code: output.status.code().unwrap_or(1),
|
||||
})
|
||||
}
|
||||
pub struct DefaultNixErrorClassifier;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ enum Binary {
|
|||
}
|
||||
|
||||
impl Binary {
|
||||
fn name(self) -> &'static str {
|
||||
const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Nr => "nr",
|
||||
Self::Ns => "ns",
|
||||
|
|
@ -135,14 +135,14 @@ fn create_multicall_binaries(
|
|||
}
|
||||
|
||||
fn generate_completions(shell: Shell, output_dir: &Path) -> Result<(), Box<dyn error::Error>> {
|
||||
println!("generating {} completions...", shell);
|
||||
println!("generating {shell} completions...");
|
||||
|
||||
fs::create_dir_all(output_dir)?;
|
||||
|
||||
let mut cmd = eh::Cli::command();
|
||||
let bin_name = "eh";
|
||||
|
||||
let completion_file = output_dir.join(format!("{}.{}", bin_name, shell));
|
||||
let completion_file = output_dir.join(format!("{bin_name}.{shell}"));
|
||||
let mut file = fs::File::create(&completion_file)?;
|
||||
|
||||
generate(shell, &mut cmd, bin_name, &mut file);
|
||||
|
|
@ -152,7 +152,7 @@ fn generate_completions(shell: Shell, output_dir: &Path) -> Result<(), Box<dyn e
|
|||
// Create symlinks for multicall binaries
|
||||
let multicall_names = ["nb", "nr", "ns"];
|
||||
for name in &multicall_names {
|
||||
let symlink_path = output_dir.join(format!("{}.{}", name, shell));
|
||||
let symlink_path = output_dir.join(format!("{name}.{shell}"));
|
||||
if symlink_path.exists() {
|
||||
fs::remove_file(&symlink_path)?;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue