eh: improve error and warning glyphs; move logger to new crate
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I563b37a9f38f8dcec6dda7693ae45e826a6a6964
This commit is contained in:
parent
be3226bc3a
commit
e6d1b90b97
8 changed files with 107 additions and 56 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -81,6 +81,7 @@ name = "eh"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"eh-log",
|
||||||
"regex",
|
"regex",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
@ -88,6 +89,13 @@ dependencies = [
|
||||||
"yansi",
|
"yansi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "eh-log"
|
||||||
|
version = "0.1.3"
|
||||||
|
dependencies = [
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ thiserror = "2.0.17"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
yansi = "1.0.1"
|
yansi = "1.0.1"
|
||||||
|
|
||||||
eh = { path = "./eh" }
|
eh = { path = "./eh" }
|
||||||
|
eh-log = { path = "./crates/eh-log" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
|
||||||
10
crates/eh-log/Cargo.toml
Normal file
10
crates/eh-log/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "eh-log"
|
||||||
|
description = "Styled logging for eh"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yansi.workspace = true
|
||||||
31
crates/eh-log/src/lib.rs
Normal file
31
crates/eh-log/src/lib.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use yansi::Paint;
|
||||||
|
|
||||||
|
pub fn info(args: fmt::Arguments) {
|
||||||
|
eprintln!(" {} {args}", "->".green().bold());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warn(args: fmt::Arguments) {
|
||||||
|
eprintln!(" {} {args}", "->".yellow().bold());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error(args: fmt::Arguments) {
|
||||||
|
eprintln!(" {} {args}", "!".red().bold());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hint(args: fmt::Arguments) {
|
||||||
|
eprintln!(" {} {args}", "~".yellow().dim());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log_info { ($($t:tt)*) => { $crate::info(format_args!($($t)*)) } }
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log_warn { ($($t:tt)*) => { $crate::warn(format_args!($($t)*)) } }
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log_error { ($($t:tt)*) => { $crate::error(format_args!($($t)*)) } }
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! log_hint { ($($t:tt)*) => { $crate::hint(format_args!($($t)*)) } }
|
||||||
|
|
@ -12,6 +12,7 @@ name = "eh"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
|
eh-log.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -4,46 +4,46 @@ use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum EhError {
|
pub enum EhError {
|
||||||
#[error("Nix command 'nix {command}' failed")]
|
#[error("nix {command} failed")]
|
||||||
NixCommandFailed { command: String },
|
NixCommandFailed { command: String },
|
||||||
|
|
||||||
#[error("IO error: {0}")]
|
#[error("io: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Regex error: {0}")]
|
#[error("regex: {0}")]
|
||||||
Regex(#[from] regex::Error),
|
Regex(#[from] regex::Error),
|
||||||
|
|
||||||
#[error("UTF-8 conversion error: {0}")]
|
#[error("utf-8 conversion: {0}")]
|
||||||
Utf8(#[from] std::string::FromUtf8Error),
|
Utf8(#[from] std::string::FromUtf8Error),
|
||||||
|
|
||||||
#[error("Hash extraction failed: could not parse hash from nix output")]
|
#[error("could not extract hash from nix output")]
|
||||||
HashExtractionFailed { stderr: String },
|
HashExtractionFailed { stderr: String },
|
||||||
|
|
||||||
#[error("No Nix files found in the current directory")]
|
#[error("no .nix files found in the current directory")]
|
||||||
NoNixFilesFound,
|
NoNixFilesFound,
|
||||||
|
|
||||||
#[error("Failed to fix hash in file: {path}")]
|
#[error("could not update hash in {path}")]
|
||||||
HashFixFailed { path: String },
|
HashFixFailed { path: String },
|
||||||
|
|
||||||
#[error("Process exited with code: {code}")]
|
#[error("process exited with code {code}")]
|
||||||
ProcessExit { code: i32 },
|
ProcessExit { code: i32 },
|
||||||
|
|
||||||
#[error("Command execution failed: {command}")]
|
#[error("command '{command}' failed")]
|
||||||
CommandFailed { command: String },
|
CommandFailed { command: String },
|
||||||
|
|
||||||
#[error("Command '{command}' timed out after {} seconds", duration.as_secs())]
|
#[error("nix {command} timed out after {} seconds", duration.as_secs())]
|
||||||
Timeout {
|
Timeout {
|
||||||
command: String,
|
command: String,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Pre-evaluation of '{expression}' failed: {stderr}")]
|
#[error("'{expression}' failed to evaluate: {stderr}")]
|
||||||
PreEvalFailed {
|
PreEvalFailed {
|
||||||
expression: String,
|
expression: String,
|
||||||
stderr: String,
|
stderr: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Invalid input: {input} - {reason}")]
|
#[error("invalid input '{input}': {reason}")]
|
||||||
InvalidInput { input: String, reason: String },
|
InvalidInput { input: String, reason: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,31 +72,25 @@ impl EhError {
|
||||||
pub fn hint(&self) -> Option<&str> {
|
pub fn hint(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::NixCommandFailed { .. } => {
|
Self::NixCommandFailed { .. } => {
|
||||||
Some("Run with --show-trace for more details from nix")
|
Some("run with --show-trace for more details")
|
||||||
},
|
},
|
||||||
Self::PreEvalFailed { .. } => {
|
Self::PreEvalFailed { .. } => {
|
||||||
Some(
|
Some("check that the expression exists and is spelled correctly")
|
||||||
"Check that the expression exists in the flake and is spelled \
|
|
||||||
correctly",
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
Self::HashExtractionFailed { .. } => {
|
Self::HashExtractionFailed { .. } => {
|
||||||
Some(
|
Some("nix reported a hash mismatch but the hash could not be parsed")
|
||||||
"The nix output contained a hash mismatch but the hash could not be \
|
|
||||||
parsed",
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
Self::NoNixFilesFound => {
|
Self::NoNixFilesFound => {
|
||||||
Some("Run this command from a directory containing .nix files")
|
Some("run this command from a directory containing .nix files")
|
||||||
},
|
},
|
||||||
Self::Timeout { .. } => {
|
Self::Timeout { .. } => {
|
||||||
Some(
|
Some(
|
||||||
"The command took too long; try a faster network or a smaller \
|
"the command took too long; try a faster network or a smaller \
|
||||||
derivation",
|
derivation",
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Self::InvalidInput { .. } => {
|
Self::InvalidInput { .. } => {
|
||||||
Some("Avoid shell metacharacters in nix arguments")
|
Some("avoid shell metacharacters in nix arguments")
|
||||||
},
|
},
|
||||||
Self::Io(_)
|
Self::Io(_)
|
||||||
| Self::Regex(_)
|
| Self::Regex(_)
|
||||||
|
|
@ -170,10 +164,7 @@ mod tests {
|
||||||
command: "build".into(),
|
command: "build".into(),
|
||||||
duration: Duration::from_secs(300),
|
duration: Duration::from_secs(300),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(err.to_string(), "nix build timed out after 300 seconds");
|
||||||
err.to_string(),
|
|
||||||
"Command 'build' timed out after 300 seconds"
|
|
||||||
);
|
|
||||||
|
|
||||||
let err = EhError::PreEvalFailed {
|
let err = EhError::PreEvalFailed {
|
||||||
expression: "nixpkgs#hello".into(),
|
expression: "nixpkgs#hello".into(),
|
||||||
|
|
@ -185,7 +176,7 @@ mod tests {
|
||||||
let err = EhError::HashExtractionFailed {
|
let err = EhError::HashExtractionFailed {
|
||||||
stderr: "some output".into(),
|
stderr: "some output".into(),
|
||||||
};
|
};
|
||||||
assert!(err.to_string().contains("could not parse hash"));
|
assert!(err.to_string().contains("could not extract hash"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::{env, path::Path};
|
||||||
|
|
||||||
use eh::{Cli, Command, CommandFactory, Parser};
|
use eh::{Cli, Command, CommandFactory, Parser};
|
||||||
use error::Result;
|
use error::Result;
|
||||||
|
use yansi::Paint;
|
||||||
|
|
||||||
mod build;
|
mod build;
|
||||||
mod command;
|
mod command;
|
||||||
|
|
@ -16,9 +17,9 @@ fn main() {
|
||||||
match result {
|
match result {
|
||||||
Ok(code) => std::process::exit(code),
|
Ok(code) => std::process::exit(code),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error: {e}");
|
eh_log::log_error!("{e}");
|
||||||
if let Some(hint) = e.hint() {
|
if let Some(hint) = e.hint() {
|
||||||
eprintln!("Hint: {hint}");
|
eh_log::log_hint!("{hint}");
|
||||||
}
|
}
|
||||||
std::process::exit(e.exit_code());
|
std::process::exit(e.exit_code());
|
||||||
},
|
},
|
||||||
|
|
@ -41,14 +42,21 @@ fn dispatch_multicall(
|
||||||
|
|
||||||
// Handle --help/-h/--version before forwarding to nix
|
// Handle --help/-h/--version before forwarding to nix
|
||||||
if rest.iter().any(|a| a == "--help" || a == "-h") {
|
if rest.iter().any(|a| a == "--help" || a == "-h") {
|
||||||
eprintln!("{app_name}: shorthand for 'eh {subcommand}'");
|
eprintln!(
|
||||||
eprintln!("Usage: {app_name} [args...]");
|
"{}: shorthand for '{}'",
|
||||||
eprintln!("All arguments are forwarded to 'nix {subcommand}'.");
|
app_name.bold(),
|
||||||
|
format!("eh {subcommand}").bold()
|
||||||
|
);
|
||||||
|
eprintln!(" {} {app_name} [args...]", "usage:".green().bold());
|
||||||
|
eprintln!(
|
||||||
|
" All arguments are forwarded to '{}'.",
|
||||||
|
format!("nix {subcommand}").dim()
|
||||||
|
);
|
||||||
return Some(Ok(0));
|
return Some(Ok(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if rest.iter().any(|a| a == "--version") {
|
if rest.iter().any(|a| a == "--version") {
|
||||||
eprintln!("{app_name} (eh {})", env!("CARGO_PKG_VERSION"));
|
eprintln!("{} (eh {})", app_name.bold(), env!("CARGO_PKG_VERSION"));
|
||||||
return Some(Ok(0));
|
return Some(Ok(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use std::{
|
||||||
sync::LazyLock,
|
sync::LazyLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use eh_log::{log_info, log_warn};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
@ -92,7 +93,7 @@ impl NixFileFixer for DefaultNixFileFixer {
|
||||||
let mut fixed = false;
|
let mut fixed = false;
|
||||||
for file_path in nix_files {
|
for file_path in nix_files {
|
||||||
if self.fix_hash_in_file(&file_path, old_hash, new_hash)? {
|
if self.fix_hash_in_file(&file_path, old_hash, new_hash)? {
|
||||||
println!("Updated hash in {}", file_path.display());
|
log_info!("updated hash in {}", file_path.display().bold());
|
||||||
fixed = true;
|
fixed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,12 +252,11 @@ fn package_name(args: &[String]) -> &str {
|
||||||
/// Print a retry message with consistent formatting.
|
/// Print a retry message with consistent formatting.
|
||||||
/// Format: ` -> <pkg>: <reason>, setting <ENV>=1`
|
/// Format: ` -> <pkg>: <reason>, setting <ENV>=1`
|
||||||
fn print_retry_msg(pkg: &str, reason: &str, env_var: &str) {
|
fn print_retry_msg(pkg: &str, reason: &str, env_var: &str) {
|
||||||
eprintln!(
|
log_warn!(
|
||||||
" {} {}: {}, setting {}",
|
"{}: {}, setting {}",
|
||||||
"->".yellow().bold(),
|
|
||||||
pkg.bold(),
|
pkg.bold(),
|
||||||
reason,
|
reason,
|
||||||
format!("{env_var}=1").bold(),
|
format!("{env_var}=1").bold()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,14 +316,17 @@ fn pre_evaluate(args: &[String]) -> Result<RetryAction> {
|
||||||
return Ok(action);
|
return Ok(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For other eval failures, warn but let the actual command handle the
|
// Non-retryable eval failure — fail fast with a clear message
|
||||||
// error with full streaming output rather than halting here.
|
// rather than running the full command and showing the same error again.
|
||||||
let err = EhError::PreEvalFailed {
|
let stderr_clean = stderr
|
||||||
|
.trim()
|
||||||
|
.strip_prefix("error:")
|
||||||
|
.unwrap_or(stderr.trim())
|
||||||
|
.trim();
|
||||||
|
Err(EhError::PreEvalFailed {
|
||||||
expression: eval_arg.clone(),
|
expression: eval_arg.clone(),
|
||||||
stderr: stderr.trim().to_string(),
|
stderr: stderr_clean.to_string(),
|
||||||
};
|
})
|
||||||
eprintln!(" {} {}", "->".yellow().bold(), err,);
|
|
||||||
Ok(RetryAction::None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_nix_args(args: &[String]) -> Result<()> {
|
pub fn validate_nix_args(args: &[String]) -> Result<()> {
|
||||||
|
|
@ -398,10 +401,9 @@ pub fn handle_nix_with_retry(
|
||||||
let old_hash = hash_extractor.extract_old_hash(&stderr);
|
let old_hash = hash_extractor.extract_old_hash(&stderr);
|
||||||
match fixer.fix_hash_in_files(old_hash.as_deref(), &new_hash) {
|
match fixer.fix_hash_in_files(old_hash.as_deref(), &new_hash) {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
eprintln!(
|
log_info!(
|
||||||
" {} {}: hash mismatch corrected in local files, rebuilding",
|
"{}: hash mismatch corrected in local files, rebuilding",
|
||||||
"->".green().bold(),
|
pkg.bold()
|
||||||
pkg.bold(),
|
|
||||||
);
|
);
|
||||||
let mut retry_cmd = NixCommand::new(subcommand)
|
let mut retry_cmd = NixCommand::new(subcommand)
|
||||||
.print_build_logs(true)
|
.print_build_logs(true)
|
||||||
|
|
@ -416,10 +418,9 @@ pub fn handle_nix_with_retry(
|
||||||
// No files were fixed, continue with normal error handling
|
// No files were fixed, continue with normal error handling
|
||||||
},
|
},
|
||||||
Err(EhError::NoNixFilesFound) => {
|
Err(EhError::NoNixFilesFound) => {
|
||||||
eprintln!(
|
log_warn!(
|
||||||
" {} {}: hash mismatch detected but no .nix files found to update",
|
"{}: hash mismatch detected but no .nix files found to update",
|
||||||
"->".yellow().bold(),
|
pkg.bold()
|
||||||
pkg.bold(),
|
|
||||||
);
|
);
|
||||||
// Continue with normal error handling
|
// Continue with normal error handling
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue