feat: Binaries as subcommands (#2661)

* Run 'atuin-<subcmd>' if present when a given subcommand is not recognized

* Send errors to stderr

* Use String instead of OsString for external subcommands

* Remove unused import

* Move external subcommand handling up a level

* Clippy
This commit is contained in:
Michelle Tilley 2025-04-02 04:09:06 -07:00 committed by GitHub
parent a82a001391
commit 653894d359
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 78 additions and 0 deletions

View file

@ -0,0 +1,72 @@
use std::fmt::Write as _;
use std::process::Command;
use std::{io, process};
use clap::CommandFactory;
use clap::builder::{StyledStr, Styles};
use eyre::Result;
use crate::Atuin;
pub fn run(args: &[String]) -> Result<()> {
let subcommand = &args[0];
let bin = format!("atuin-{subcommand}");
let mut cmd = Command::new(&bin);
cmd.args(&args[1..]);
let spawn_result = match cmd.spawn() {
Ok(child) => Ok(child),
Err(e) => match e.kind() {
io::ErrorKind::NotFound => {
let output = render_not_found(subcommand, &bin);
Err(output)
}
_ => Err(e.to_string().into()),
},
};
match spawn_result {
Ok(mut child) => {
let status = child.wait()?;
if status.success() {
Ok(())
} else {
process::exit(status.code().unwrap_or(1));
}
}
Err(e) => {
eprintln!("{}", e.ansi());
process::exit(1);
}
}
}
fn render_not_found(subcommand: &str, bin: &str) -> StyledStr {
let mut output = StyledStr::new();
let styles = Styles::styled();
let mut atuin_cmd = Atuin::command();
let usage = atuin_cmd.render_usage();
let error = styles.get_error();
let invalid = styles.get_invalid();
let literal = styles.get_literal();
let _ = write!(output, "{error}error:{error:#} ");
let _ = write!(
output,
"unrecognized subcommand '{invalid}{subcommand}{invalid:#}' "
);
let _ = write!(
output,
"and no executable named '{invalid}{bin}{invalid:#}' found in your PATH"
);
let _ = write!(output, "\n\n");
let _ = write!(output, "{usage}");
let _ = write!(output, "\n\n");
let _ = write!(
output,
"For more information, try '{literal}--help{literal:#}'."
);
output
}

View file

@ -14,6 +14,8 @@ mod contributors;
mod gen_completions;
mod external;
#[derive(Subcommand)]
#[command(infer_subcommands = true)]
pub enum AtuinCmd {
@ -33,6 +35,9 @@ pub enum AtuinCmd {
/// Generate shell completions
GenCompletions(gen_completions::Cmd),
#[command(external_subcommand)]
External(Vec<String>),
}
impl AtuinCmd {
@ -60,6 +65,7 @@ impl AtuinCmd {
Ok(())
}
Self::GenCompletions(gen_completions) => gen_completions.run(),
Self::External(args) => external::run(&args),
}
}
}