eh: add missing eh dev command; add --ask for sensitive operations
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: If48f4d337c62dad669af97f9e97c1cb76a6a6964
This commit is contained in:
parent
1174e4496b
commit
f87bc4158c
5 changed files with 101 additions and 12 deletions
|
|
@ -45,6 +45,7 @@ enum Binary {
|
|||
Nd,
|
||||
Ni,
|
||||
Nu,
|
||||
Dev,
|
||||
}
|
||||
|
||||
impl Binary {
|
||||
|
|
@ -56,6 +57,7 @@ impl Binary {
|
|||
Self::Nd => "nd",
|
||||
Self::Ni => "ni",
|
||||
Self::Nu => "nu",
|
||||
Self::Dev => "dev",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,6 +105,7 @@ fn create_multicall_binaries(
|
|||
Binary::Nd,
|
||||
Binary::Ni,
|
||||
Binary::Nu,
|
||||
Binary::Dev,
|
||||
];
|
||||
let bin_path = Path::new(bin_dir);
|
||||
|
||||
|
|
@ -166,7 +169,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 = ["dev", "nb", "nd", "ni", "nr", "ns", "nu"];
|
||||
for name in &multicall_names {
|
||||
let symlink_path = output_dir.join(format!("{name}.{shell}"));
|
||||
if symlink_path.exists() {
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ pub fn handle_nix_command(
|
|||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
cfg: &crate::config::CommandConfig,
|
||||
ask: bool,
|
||||
) -> Result<i32> {
|
||||
let intercept_env = matches!(command, "run" | "shell");
|
||||
handle_nix_with_retry(
|
||||
|
|
@ -347,6 +348,7 @@ pub fn handle_nix_command(
|
|||
classifier,
|
||||
intercept_env,
|
||||
cfg,
|
||||
ask,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,17 @@ pub mod util;
|
|||
pub use clap::{CommandFactory, Parser, Subcommand};
|
||||
pub use error::{EhError, Result};
|
||||
|
||||
/// Supported shells for completion generation.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
|
||||
pub enum Shell {
|
||||
/// Bash shell
|
||||
Bash,
|
||||
/// Zsh shell
|
||||
Zsh,
|
||||
/// Fish shell
|
||||
Fish,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "eh")]
|
||||
#[command(about = "Ergonomic Nix helper", long_about = None)]
|
||||
|
|
@ -19,21 +30,29 @@ pub struct Cli {
|
|||
pub enum Command {
|
||||
/// Run a Nix derivation
|
||||
Run {
|
||||
#[arg(short, long, default_value = "false")]
|
||||
ask: bool,
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Enter a Nix shell
|
||||
Shell {
|
||||
#[arg(short, long, default_value = "false")]
|
||||
ask: bool,
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Build a Nix derivation
|
||||
Build {
|
||||
#[arg(short, long, default_value = "false")]
|
||||
ask: bool,
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Enter a Nix development shell
|
||||
Develop {
|
||||
#[arg(short, long, default_value = "false")]
|
||||
ask: bool,
|
||||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
|
|
@ -47,4 +66,10 @@ pub enum Command {
|
|||
#[arg(trailing_var_arg = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
/// Generate shell completions
|
||||
Completion {
|
||||
/// Shell to generate completions for
|
||||
#[arg(value_enum)]
|
||||
shell: Shell,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::{env, path::Path};
|
||||
|
||||
use eh::{Cli, Command, CommandFactory, Parser};
|
||||
use clap_complete::{generate, Shell};
|
||||
use eh::{Cli, Command, CommandFactory, Parser, Shell as EhShell};
|
||||
use yansi::Paint;
|
||||
|
||||
mod commands;
|
||||
|
|
@ -26,7 +27,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_command(command: &str, args: &[String]) -> error::Result<i32> {
|
||||
fn handle_command(command: &str, args: &[String], ask: bool) -> error::Result<i32> {
|
||||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
|
|
@ -45,6 +46,7 @@ fn handle_command(command: &str, args: &[String]) -> error::Result<i32> {
|
|||
&fixer,
|
||||
&classifier,
|
||||
&cmd_cfg,
|
||||
ask,
|
||||
)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
|
|
@ -61,7 +63,7 @@ fn dispatch_multicall(
|
|||
"nr" => "run",
|
||||
"ns" => "shell",
|
||||
"nb" => "build",
|
||||
"nd" => "develop",
|
||||
"nd" | "dev" => "develop",
|
||||
"ni" => "info",
|
||||
"nu" => "update",
|
||||
_ => return None,
|
||||
|
|
@ -87,7 +89,7 @@ fn dispatch_multicall(
|
|||
return Some(Ok(0));
|
||||
}
|
||||
|
||||
Some(handle_command(subcommand, &rest))
|
||||
Some(handle_command(subcommand, &rest, false))
|
||||
}
|
||||
|
||||
fn run_app() -> error::Result<i32> {
|
||||
|
|
@ -106,17 +108,28 @@ fn run_app() -> error::Result<i32> {
|
|||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Some(Command::Run { args }) => handle_command("run", &args),
|
||||
Some(Command::Run { ask, args }) => handle_command("run", &args, ask),
|
||||
|
||||
Some(Command::Shell { args }) => handle_command("shell", &args),
|
||||
Some(Command::Shell { ask, args }) => handle_command("shell", &args, ask),
|
||||
|
||||
Some(Command::Build { args }) => handle_command("build", &args),
|
||||
Some(Command::Build { ask, args }) => handle_command("build", &args, ask),
|
||||
|
||||
Some(Command::Develop { args }) => handle_command("develop", &args),
|
||||
Some(Command::Develop { ask, args }) => handle_command("develop", &args, ask),
|
||||
|
||||
Some(Command::Info { args }) => handle_command("info", &args),
|
||||
Some(Command::Info { args }) => handle_command("info", &args, false),
|
||||
|
||||
Some(Command::Update { args }) => handle_command("update", &args),
|
||||
Some(Command::Update { args }) => handle_command("update", &args, false),
|
||||
|
||||
Some(Command::Completion { shell }) => {
|
||||
let mut cmd = Cli::command();
|
||||
let shell: Shell = match shell {
|
||||
EhShell::Bash => Shell::Bash,
|
||||
EhShell::Zsh => Shell::Zsh,
|
||||
EhShell::Fish => Shell::Fish,
|
||||
};
|
||||
generate(shell, &mut cmd, "eh", &mut std::io::stdout());
|
||||
Ok(0)
|
||||
},
|
||||
|
||||
None => {
|
||||
Cli::command().print_help()?;
|
||||
|
|
|
|||
|
|
@ -486,6 +486,7 @@ pub fn handle_nix_with_retry(
|
|||
classifier: &dyn NixErrorClassifier,
|
||||
interactive: bool,
|
||||
cfg: &crate::config::CommandConfig,
|
||||
ask: bool,
|
||||
) -> Result<i32> {
|
||||
validate_nix_args(args)?;
|
||||
|
||||
|
|
@ -501,6 +502,25 @@ pub fn handle_nix_with_retry(
|
|||
reason: reason.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// With --ask, prompt before auto-retry
|
||||
if ask && std::io::stdin().is_terminal() {
|
||||
let choices = ["Yes, retry with --impure", "No, cancel"];
|
||||
let idx = dialoguer::Select::new()
|
||||
.with_prompt(format!(
|
||||
"Package {} requires `--impure` ({}). Retry?",
|
||||
pkg.bold(),
|
||||
reason.bold()
|
||||
))
|
||||
.items(&choices)
|
||||
.default(0)
|
||||
.interact()
|
||||
.map_err(|e| EhError::Io(std::io::Error::other(e)))?;
|
||||
if idx != 0 {
|
||||
return Err(EhError::ProcessExit { code: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
print_retry_msg(pkg, reason, env_var);
|
||||
let mut retry_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
|
|
@ -540,7 +560,8 @@ pub fn handle_nix_with_retry(
|
|||
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)
|
||||
// Ask for confirmation before fixing hash.
|
||||
// With --ask: prompt always (error if no TTY). Without --ask: prompt only in TTY mode.
|
||||
let should_fix = if std::io::stdin().is_terminal() {
|
||||
dialoguer::Confirm::new()
|
||||
.with_prompt(format!(
|
||||
|
|
@ -550,6 +571,12 @@ pub fn handle_nix_with_retry(
|
|||
.default(true)
|
||||
.interact()
|
||||
.map_err(|e| EhError::Io(std::io::Error::other(e)))?
|
||||
} else if ask {
|
||||
return Err(EhError::Io(
|
||||
std::io::Error::other(
|
||||
"cannot prompt for hash fix confirmation in non-interactive mode (no TTY)"
|
||||
)
|
||||
));
|
||||
} else {
|
||||
log_warn!(
|
||||
"{}: hash mismatch detected in non-interactive mode, skipping auto-fix",
|
||||
|
|
@ -611,6 +638,25 @@ pub fn handle_nix_with_retry(
|
|||
reason: reason.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// With --ask, prompt before auto-retry
|
||||
if ask && std::io::stdin().is_terminal() {
|
||||
let choices = ["Yes, retry with --impure", "No, cancel"];
|
||||
let idx = dialoguer::Select::new()
|
||||
.with_prompt(format!(
|
||||
"Package {} requires `--impure` ({}). Retry?",
|
||||
pkg.bold(),
|
||||
reason.bold()
|
||||
))
|
||||
.items(&choices)
|
||||
.default(0)
|
||||
.interact()
|
||||
.map_err(|e| EhError::Io(std::io::Error::other(e)))?;
|
||||
if idx != 0 {
|
||||
return Err(EhError::ProcessExit { code: 1 });
|
||||
}
|
||||
}
|
||||
|
||||
print_retry_msg(pkg, reason, env_var);
|
||||
let mut retry_cmd = NixCommand::new(subcommand)
|
||||
.print_build_logs(true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue