Skip to content

Commit 56d19c5

Browse files
authored
Add subcommand lookup for missing commands. (#2014)
1 parent 14de539 commit 56d19c5

3 files changed

Lines changed: 47 additions & 64 deletions

File tree

cmd/crates/soroban-test/tests/it/plugin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fn has_no_path_failure() {
6464
.unwrap_or_else(|_| assert_cmd::Command::new("stellar"))
6565
.arg("hello")
6666
.assert()
67-
.stderr(predicates::str::contains("error: no such command: `hello`"));
67+
.stderr(predicates::str::contains("unrecognized subcommand 'hello'"));
6868
}
6969

7070
fn target_bin() -> PathBuf {

cmd/soroban-cli/src/commands/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,16 @@ impl Root {
8383
Self::try_parse().map_err(|e| {
8484
if std::env::args().any(|s| s == "--list") {
8585
let plugins = plugin::list().unwrap_or_default();
86+
8687
if plugins.is_empty() {
87-
println!("No Plugins installed. E.g. soroban-hello");
88+
println!("No Plugins installed. E.g. stellar-hello");
8889
} else {
8990
println!("Installed Plugins:\n {}", plugins.join("\n "));
9091
}
92+
9193
std::process::exit(0);
9294
}
95+
9396
match e.kind() {
9497
ErrorKind::InvalidSubcommand => match plugin::run() {
9598
Ok(()) => Error::Clap(e),
@@ -107,6 +110,7 @@ impl Root {
107110
{
108111
Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr))
109112
}
113+
110114
pub async fn run(&mut self) -> Result<(), Error> {
111115
match &mut self.cmd {
112116
Cmd::Completion(completion) => completion.run(),
Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,33 @@
11
use std::{path::PathBuf, process::Command};
2-
3-
use clap::CommandFactory;
42
use which::which;
53

6-
use crate::{utils, Root};
4+
use crate::utils;
75

86
#[derive(thiserror::Error, Debug)]
97
pub enum Error {
10-
#[error("Plugin not provided. Should be `stellar plugin` for a binary `stellar-plugin`")]
11-
MissingSubcommand,
128
#[error(transparent)]
139
IO(#[from] std::io::Error),
14-
#[error(
15-
r"error: no such command: `{0}`
16-
17-
{1}View all installed plugins with `stellar --list`"
18-
)]
19-
ExecutableNotFound(String, String),
10+
2011
#[error(transparent)]
2112
Which(#[from] which::Error),
13+
2214
#[error(transparent)]
2315
Regex(#[from] regex::Error),
2416
}
2517

26-
const SUBCOMMAND_TOLERANCE: f64 = 0.75;
27-
const PLUGIN_TOLERANCE: f64 = 0.75;
28-
const MIN_LENGTH: usize = 4;
29-
30-
/// Tries to run a plugin, if the plugin's name is similar enough to any of the current subcommands return Ok.
31-
/// Otherwise only errors can be returned because this process will exit with the plugin.
3218
pub fn run() -> Result<(), Error> {
33-
let (name, args) = {
34-
let mut args = std::env::args().skip(1);
35-
let name = args.next().ok_or(Error::MissingSubcommand)?;
36-
(name, args)
37-
};
38-
39-
if Root::command().get_subcommands().any(|c| {
40-
let sc_name = c.get_name();
41-
sc_name.starts_with(&name)
42-
|| (name.len() >= MIN_LENGTH && strsim::jaro(sc_name, &name) >= SUBCOMMAND_TOLERANCE)
43-
}) {
44-
return Ok(());
19+
if let Some((plugin_bin, args)) = find_plugin() {
20+
std::process::exit(
21+
Command::new(plugin_bin)
22+
.args(args)
23+
.spawn()?
24+
.wait()?
25+
.code()
26+
.unwrap(),
27+
);
4528
}
4629

47-
let bin = find_bin(&name).map_err(|_| {
48-
let suggestion = if let Ok(bins) = list() {
49-
let suggested_name = bins
50-
.iter()
51-
.map(|b| (b, strsim::jaro_winkler(&name, b)))
52-
.filter(|(_, i)| *i > PLUGIN_TOLERANCE)
53-
.min_by(|a, b| a.1.total_cmp(&b.1))
54-
.map(|(a, _)| a.to_string())
55-
.unwrap_or_default();
56-
57-
if suggested_name.is_empty() {
58-
suggested_name
59-
} else {
60-
format!(
61-
r"Did you mean `{suggested_name}`?
62-
"
63-
)
64-
}
65-
} else {
66-
String::new()
67-
};
68-
69-
Error::ExecutableNotFound(name, suggestion)
70-
})?;
71-
72-
std::process::exit(
73-
Command::new(bin)
74-
.args(args)
75-
.spawn()?
76-
.wait()?
77-
.code()
78-
.unwrap(),
79-
);
30+
Ok(())
8031
}
8132

8233
const MAX_HEX_LENGTH: usize = 10;
@@ -107,3 +58,31 @@ pub fn list() -> Result<Vec<String>, Error> {
10758
.map(|s| s.replace("soroban-", "").replace("stellar-", ""))
10859
.collect())
10960
}
61+
62+
fn find_plugin() -> Option<(PathBuf, Vec<String>)> {
63+
let args_vec: Vec<String> = std::env::args().skip(1).collect();
64+
let mut chain: Vec<String> = args_vec
65+
.iter()
66+
.take_while(|arg| !arg.starts_with("--"))
67+
.map(ToString::to_string)
68+
.collect();
69+
70+
while !chain.is_empty() {
71+
let name = chain.join("-");
72+
let bin = find_bin(&name).ok();
73+
74+
if let Some(bin) = &bin {
75+
let index = chain.len();
76+
let args = args_vec[index..]
77+
.iter()
78+
.map(ToString::to_string)
79+
.collect::<Vec<String>>();
80+
81+
return Some((bin.into(), args));
82+
}
83+
84+
chain.pop();
85+
}
86+
87+
None
88+
}

0 commit comments

Comments
 (0)