Skip to content

Commit 653894d

Browse files
authored
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
1 parent a82a001 commit 653894d

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

crates/atuin/src/command/external.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use std::fmt::Write as _;
2+
use std::process::Command;
3+
use std::{io, process};
4+
5+
use clap::CommandFactory;
6+
use clap::builder::{StyledStr, Styles};
7+
use eyre::Result;
8+
9+
use crate::Atuin;
10+
11+
pub fn run(args: &[String]) -> Result<()> {
12+
let subcommand = &args[0];
13+
let bin = format!("atuin-{subcommand}");
14+
let mut cmd = Command::new(&bin);
15+
cmd.args(&args[1..]);
16+
17+
let spawn_result = match cmd.spawn() {
18+
Ok(child) => Ok(child),
19+
Err(e) => match e.kind() {
20+
io::ErrorKind::NotFound => {
21+
let output = render_not_found(subcommand, &bin);
22+
Err(output)
23+
}
24+
_ => Err(e.to_string().into()),
25+
},
26+
};
27+
28+
match spawn_result {
29+
Ok(mut child) => {
30+
let status = child.wait()?;
31+
if status.success() {
32+
Ok(())
33+
} else {
34+
process::exit(status.code().unwrap_or(1));
35+
}
36+
}
37+
Err(e) => {
38+
eprintln!("{}", e.ansi());
39+
process::exit(1);
40+
}
41+
}
42+
}
43+
44+
fn render_not_found(subcommand: &str, bin: &str) -> StyledStr {
45+
let mut output = StyledStr::new();
46+
let styles = Styles::styled();
47+
let mut atuin_cmd = Atuin::command();
48+
let usage = atuin_cmd.render_usage();
49+
50+
let error = styles.get_error();
51+
let invalid = styles.get_invalid();
52+
let literal = styles.get_literal();
53+
54+
let _ = write!(output, "{error}error:{error:#} ");
55+
let _ = write!(
56+
output,
57+
"unrecognized subcommand '{invalid}{subcommand}{invalid:#}' "
58+
);
59+
let _ = write!(
60+
output,
61+
"and no executable named '{invalid}{bin}{invalid:#}' found in your PATH"
62+
);
63+
let _ = write!(output, "\n\n");
64+
let _ = write!(output, "{usage}");
65+
let _ = write!(output, "\n\n");
66+
let _ = write!(
67+
output,
68+
"For more information, try '{literal}--help{literal:#}'."
69+
);
70+
71+
output
72+
}

crates/atuin/src/command/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ mod contributors;
1414

1515
mod gen_completions;
1616

17+
mod external;
18+
1719
#[derive(Subcommand)]
1820
#[command(infer_subcommands = true)]
1921
pub enum AtuinCmd {
@@ -33,6 +35,9 @@ pub enum AtuinCmd {
3335

3436
/// Generate shell completions
3537
GenCompletions(gen_completions::Cmd),
38+
39+
#[command(external_subcommand)]
40+
External(Vec<String>),
3641
}
3742

3843
impl AtuinCmd {
@@ -60,6 +65,7 @@ impl AtuinCmd {
6065
Ok(())
6166
}
6267
Self::GenCompletions(gen_completions) => gen_completions.run(),
68+
Self::External(args) => external::run(&args),
6369
}
6470
}
6571
}

0 commit comments

Comments
 (0)