|
1 | 1 | use std::{path::PathBuf, process::Command}; |
2 | | - |
3 | | -use clap::CommandFactory; |
4 | 2 | use which::which; |
5 | 3 |
|
6 | | -use crate::{utils, Root}; |
| 4 | +use crate::utils; |
7 | 5 |
|
8 | 6 | #[derive(thiserror::Error, Debug)] |
9 | 7 | pub enum Error { |
10 | | - #[error("Plugin not provided. Should be `stellar plugin` for a binary `stellar-plugin`")] |
11 | | - MissingSubcommand, |
12 | 8 | #[error(transparent)] |
13 | 9 | 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 | + |
20 | 11 | #[error(transparent)] |
21 | 12 | Which(#[from] which::Error), |
| 13 | + |
22 | 14 | #[error(transparent)] |
23 | 15 | Regex(#[from] regex::Error), |
24 | 16 | } |
25 | 17 |
|
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. |
32 | 18 | 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 | + ); |
45 | 28 | } |
46 | 29 |
|
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(()) |
80 | 31 | } |
81 | 32 |
|
82 | 33 | const MAX_HEX_LENGTH: usize = 10; |
@@ -107,3 +58,31 @@ pub fn list() -> Result<Vec<String>, Error> { |
107 | 58 | .map(|s| s.replace("soroban-", "").replace("stellar-", "")) |
108 | 59 | .collect()) |
109 | 60 | } |
| 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