Skip to content

Commit 51b1200

Browse files
committed
fix(cli): Improve bad script errors
1 parent 2a7e5c0 commit 51b1200

File tree

2 files changed

+122
-12
lines changed

2 files changed

+122
-12
lines changed

src/bin/cargo/commands/run.rs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ use crate::util::restricted_names::is_glob_pattern;
77
use cargo::core::Verbosity;
88
use cargo::core::Workspace;
99
use cargo::ops::{self, CompileFilter, Packages};
10+
use cargo::util::closest;
1011
use cargo_util::ProcessError;
12+
use itertools::Itertools as _;
1113

1214
pub fn cli() -> Command {
1315
subcommand("run")
@@ -97,11 +99,81 @@ pub fn is_manifest_command(arg: &str) -> bool {
9799
}
98100

99101
pub fn exec_manifest_command(config: &mut Config, cmd: &str, args: &[OsString]) -> CliResult {
100-
if !config.cli_unstable().script {
101-
return Err(anyhow::anyhow!("running `{cmd}` requires `-Zscript`").into());
102+
let manifest_path = Path::new(cmd);
103+
match (manifest_path.is_file(), config.cli_unstable().script) {
104+
(true, true) => {}
105+
(true, false) => {
106+
return Err(anyhow::anyhow!("running the file `{cmd}` requires `-Zscript`").into());
107+
}
108+
(false, true) => {
109+
let possible_commands = crate::list_commands(config);
110+
let is_dir = if manifest_path.is_dir() {
111+
format!("\n\t`{cmd}` is a directory")
112+
} else {
113+
"".to_owned()
114+
};
115+
let suggested_command = if let Some(suggested_command) = possible_commands
116+
.keys()
117+
.filter(|c| cmd.starts_with(c.as_str()))
118+
.max_by_key(|c| c.len())
119+
{
120+
let actual_args = cmd.strip_prefix(suggested_command).unwrap();
121+
let args = if args.is_empty() {
122+
"".to_owned()
123+
} else {
124+
format!(
125+
" {}",
126+
args.into_iter().map(|os| os.to_string_lossy()).join(" ")
127+
)
128+
};
129+
format!("\n\tDid you mean the command `{suggested_command} {actual_args}{args}`")
130+
} else {
131+
"".to_owned()
132+
};
133+
let suggested_script = if let Some(suggested_script) = suggested_script(cmd) {
134+
format!("\n\tDid you mean the file `{suggested_script}`")
135+
} else {
136+
"".to_owned()
137+
};
138+
return Err(anyhow::anyhow!(
139+
"no such file or subcommand `{cmd}`{is_dir}{suggested_command}{suggested_script}"
140+
)
141+
.into());
142+
}
143+
(false, false) => {
144+
// HACK: duplicating the above for minor tweaks but this will all go away on
145+
// stabilization
146+
let possible_commands = crate::list_commands(config);
147+
let suggested_command = if let Some(suggested_command) = possible_commands
148+
.keys()
149+
.filter(|c| cmd.starts_with(c.as_str()))
150+
.max_by_key(|c| c.len())
151+
{
152+
let actual_args = cmd.strip_prefix(suggested_command).unwrap();
153+
let args = if args.is_empty() {
154+
"".to_owned()
155+
} else {
156+
format!(
157+
" {}",
158+
args.into_iter().map(|os| os.to_string_lossy()).join(" ")
159+
)
160+
};
161+
format!("\n\tDid you mean the command `{suggested_command} {actual_args}{args}`")
162+
} else {
163+
"".to_owned()
164+
};
165+
let suggested_script = if let Some(suggested_script) = suggested_script(cmd) {
166+
format!("\n\tDid you mean the file `{suggested_script}` with `-Zscript`")
167+
} else {
168+
"".to_owned()
169+
};
170+
return Err(anyhow::anyhow!(
171+
"no such subcommand `{cmd}`{suggested_command}{suggested_script}"
172+
)
173+
.into());
174+
}
102175
}
103176

104-
let manifest_path = Path::new(cmd);
105177
let manifest_path = root_manifest(Some(manifest_path), config)?;
106178

107179
// Treat `cargo foo.rs` like `cargo install --path foo` and re-evaluate the config based on the
@@ -123,6 +195,39 @@ pub fn exec_manifest_command(config: &mut Config, cmd: &str, args: &[OsString])
123195
cargo::ops::run(&ws, &compile_opts, args).map_err(|err| to_run_error(config, err))
124196
}
125197

198+
fn suggested_script(cmd: &str) -> Option<String> {
199+
let cmd_path = Path::new(cmd);
200+
let mut suggestion = Path::new(".").to_owned();
201+
for cmd_part in cmd_path.components() {
202+
let exact_match = suggestion.join(cmd_part);
203+
suggestion = if exact_match.exists() {
204+
exact_match
205+
} else {
206+
let possible: Vec<_> = std::fs::read_dir(suggestion)
207+
.into_iter()
208+
.flatten()
209+
.filter_map(|e| e.ok())
210+
.map(|e| e.path())
211+
.filter(|p| p.to_str().is_some())
212+
.collect();
213+
if let Some(possible) = closest(
214+
cmd_part.as_os_str().to_str().unwrap(),
215+
possible.iter(),
216+
|p| p.file_name().unwrap().to_str().unwrap(),
217+
) {
218+
possible.to_owned()
219+
} else {
220+
return None;
221+
}
222+
};
223+
}
224+
if suggestion.is_dir() {
225+
None
226+
} else {
227+
suggestion.into_os_string().into_string().ok()
228+
}
229+
}
230+
126231
fn to_run_error(config: &cargo::util::Config, err: anyhow::Error) -> CliError {
127232
let proc_err = match err.downcast_ref::<ProcessError>() {
128233
Some(e) => e,

tests/testsuite/script.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ fn requires_nightly() {
183183
.with_stdout("")
184184
.with_stderr(
185185
"\
186-
error: running `echo.rs` requires `-Zscript`
186+
[ERROR] running the file `echo.rs` requires `-Zscript`
187187
",
188188
)
189189
.run();
@@ -201,7 +201,7 @@ fn requires_z_flag() {
201201
.with_stdout("")
202202
.with_stderr(
203203
"\
204-
error: running `echo.rs` requires `-Zscript`
204+
[ERROR] running the file `echo.rs` requires `-Zscript`
205205
",
206206
)
207207
.run();
@@ -600,7 +600,8 @@ fn script_like_dir() {
600600
.with_status(101)
601601
.with_stderr(
602602
"\
603-
[ERROR] manifest path `foo.rs` is a directory but expected a file
603+
[ERROR] no such file or subcommand `foo.rs`
604+
<tab>`foo.rs` is a directory
604605
",
605606
)
606607
.run();
@@ -615,7 +616,7 @@ fn non_existent_rs() {
615616
.with_status(101)
616617
.with_stderr(
617618
"\
618-
[ERROR] manifest path `foo.rs` does not exist
619+
[ERROR] no such file or subcommand `foo.rs`
619620
",
620621
)
621622
.run();
@@ -631,7 +632,7 @@ fn non_existent_rs_stable() {
631632
.with_stdout("")
632633
.with_stderr(
633634
"\
634-
[ERROR] running `foo.rs` requires `-Zscript`
635+
[ERROR] no such subcommand `foo.rs`
635636
",
636637
)
637638
.run();
@@ -649,7 +650,8 @@ fn did_you_mean_file() {
649650
.with_stdout("")
650651
.with_stderr(
651652
"\
652-
[ERROR] manifest path `foo.rs` does not exist
653+
[ERROR] no such file or subcommand `foo.rs`
654+
<tab>Did you mean the file `./food.rs`
653655
",
654656
)
655657
.run();
@@ -667,7 +669,8 @@ fn did_you_mean_file_stable() {
667669
.with_stdout("")
668670
.with_stderr(
669671
"\
670-
[ERROR] running `foo.rs` requires `-Zscript`
672+
[ERROR] no such subcommand `foo.rs`
673+
<tab>Did you mean the file `./food.rs` with `-Zscript`
671674
",
672675
)
673676
.run();
@@ -683,7 +686,8 @@ fn did_you_mean_command() {
683686
.with_stdout("")
684687
.with_stderr(
685688
"\
686-
[ERROR] manifest path `build--manifest-path=./Cargo.toml` does not exist
689+
[ERROR] no such file or subcommand `build--manifest-path=./Cargo.toml`
690+
<tab>Did you mean the command `build --manifest-path=./Cargo.toml`
687691
",
688692
)
689693
.run();
@@ -699,7 +703,8 @@ fn did_you_mean_command_stable() {
699703
.with_stdout("")
700704
.with_stderr(
701705
"\
702-
[ERROR] running `build--manifest-path=./Cargo.toml` requires `-Zscript`
706+
[ERROR] no such subcommand `build--manifest-path=./Cargo.toml`
707+
<tab>Did you mean the command `build --manifest-path=./Cargo.toml`
703708
",
704709
)
705710
.run();

0 commit comments

Comments
 (0)