Skip to content

Commit

Permalink
fix(cli): Improve bad script errors
Browse files Browse the repository at this point in the history
  • Loading branch information
epage committed Jan 25, 2024
1 parent 2a7e5c0 commit 51b1200
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 12 deletions.
111 changes: 108 additions & 3 deletions src/bin/cargo/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use crate::util::restricted_names::is_glob_pattern;
use cargo::core::Verbosity;
use cargo::core::Workspace;
use cargo::ops::{self, CompileFilter, Packages};
use cargo::util::closest;
use cargo_util::ProcessError;
use itertools::Itertools as _;

pub fn cli() -> Command {
subcommand("run")
Expand Down Expand Up @@ -97,11 +99,81 @@ pub fn is_manifest_command(arg: &str) -> bool {
}

pub fn exec_manifest_command(config: &mut Config, cmd: &str, args: &[OsString]) -> CliResult {
if !config.cli_unstable().script {
return Err(anyhow::anyhow!("running `{cmd}` requires `-Zscript`").into());
let manifest_path = Path::new(cmd);
match (manifest_path.is_file(), config.cli_unstable().script) {
(true, true) => {}
(true, false) => {
return Err(anyhow::anyhow!("running the file `{cmd}` requires `-Zscript`").into());
}
(false, true) => {
let possible_commands = crate::list_commands(config);
let is_dir = if manifest_path.is_dir() {
format!("\n\t`{cmd}` is a directory")
} else {
"".to_owned()
};
let suggested_command = if let Some(suggested_command) = possible_commands
.keys()
.filter(|c| cmd.starts_with(c.as_str()))
.max_by_key(|c| c.len())
{
let actual_args = cmd.strip_prefix(suggested_command).unwrap();
let args = if args.is_empty() {
"".to_owned()
} else {
format!(
" {}",
args.into_iter().map(|os| os.to_string_lossy()).join(" ")
)
};
format!("\n\tDid you mean the command `{suggested_command} {actual_args}{args}`")
} else {
"".to_owned()
};
let suggested_script = if let Some(suggested_script) = suggested_script(cmd) {
format!("\n\tDid you mean the file `{suggested_script}`")
} else {
"".to_owned()
};
return Err(anyhow::anyhow!(
"no such file or subcommand `{cmd}`{is_dir}{suggested_command}{suggested_script}"
)
.into());
}
(false, false) => {
// HACK: duplicating the above for minor tweaks but this will all go away on
// stabilization
let possible_commands = crate::list_commands(config);
let suggested_command = if let Some(suggested_command) = possible_commands
.keys()
.filter(|c| cmd.starts_with(c.as_str()))
.max_by_key(|c| c.len())
{
let actual_args = cmd.strip_prefix(suggested_command).unwrap();
let args = if args.is_empty() {
"".to_owned()
} else {
format!(
" {}",
args.into_iter().map(|os| os.to_string_lossy()).join(" ")
)
};
format!("\n\tDid you mean the command `{suggested_command} {actual_args}{args}`")
} else {
"".to_owned()
};
let suggested_script = if let Some(suggested_script) = suggested_script(cmd) {
format!("\n\tDid you mean the file `{suggested_script}` with `-Zscript`")
} else {
"".to_owned()
};
return Err(anyhow::anyhow!(
"no such subcommand `{cmd}`{suggested_command}{suggested_script}"
)
.into());
}
}

let manifest_path = Path::new(cmd);
let manifest_path = root_manifest(Some(manifest_path), config)?;

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

fn suggested_script(cmd: &str) -> Option<String> {
let cmd_path = Path::new(cmd);
let mut suggestion = Path::new(".").to_owned();
for cmd_part in cmd_path.components() {
let exact_match = suggestion.join(cmd_part);
suggestion = if exact_match.exists() {
exact_match
} else {
let possible: Vec<_> = std::fs::read_dir(suggestion)
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.to_str().is_some())
.collect();
if let Some(possible) = closest(
cmd_part.as_os_str().to_str().unwrap(),
possible.iter(),
|p| p.file_name().unwrap().to_str().unwrap(),
) {
possible.to_owned()
} else {
return None;
}
};
}
if suggestion.is_dir() {
None
} else {
suggestion.into_os_string().into_string().ok()
}
}

fn to_run_error(config: &cargo::util::Config, err: anyhow::Error) -> CliError {
let proc_err = match err.downcast_ref::<ProcessError>() {
Some(e) => e,
Expand Down
23 changes: 14 additions & 9 deletions tests/testsuite/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ fn requires_nightly() {
.with_stdout("")
.with_stderr(
"\
error: running `echo.rs` requires `-Zscript`
[ERROR] running the file `echo.rs` requires `-Zscript`
",
)
.run();
Expand All @@ -201,7 +201,7 @@ fn requires_z_flag() {
.with_stdout("")
.with_stderr(
"\
error: running `echo.rs` requires `-Zscript`
[ERROR] running the file `echo.rs` requires `-Zscript`
",
)
.run();
Expand Down Expand Up @@ -600,7 +600,8 @@ fn script_like_dir() {
.with_status(101)
.with_stderr(
"\
[ERROR] manifest path `foo.rs` is a directory but expected a file
[ERROR] no such file or subcommand `foo.rs`
<tab>`foo.rs` is a directory
",
)
.run();
Expand All @@ -615,7 +616,7 @@ fn non_existent_rs() {
.with_status(101)
.with_stderr(
"\
[ERROR] manifest path `foo.rs` does not exist
[ERROR] no such file or subcommand `foo.rs`
",
)
.run();
Expand All @@ -631,7 +632,7 @@ fn non_existent_rs_stable() {
.with_stdout("")
.with_stderr(
"\
[ERROR] running `foo.rs` requires `-Zscript`
[ERROR] no such subcommand `foo.rs`
",
)
.run();
Expand All @@ -649,7 +650,8 @@ fn did_you_mean_file() {
.with_stdout("")
.with_stderr(
"\
[ERROR] manifest path `foo.rs` does not exist
[ERROR] no such file or subcommand `foo.rs`
<tab>Did you mean the file `./food.rs`
",
)
.run();
Expand All @@ -667,7 +669,8 @@ fn did_you_mean_file_stable() {
.with_stdout("")
.with_stderr(
"\
[ERROR] running `foo.rs` requires `-Zscript`
[ERROR] no such subcommand `foo.rs`
<tab>Did you mean the file `./food.rs` with `-Zscript`
",
)
.run();
Expand All @@ -683,7 +686,8 @@ fn did_you_mean_command() {
.with_stdout("")
.with_stderr(
"\
[ERROR] manifest path `build--manifest-path=./Cargo.toml` does not exist
[ERROR] no such file or subcommand `build--manifest-path=./Cargo.toml`
<tab>Did you mean the command `build --manifest-path=./Cargo.toml`
",
)
.run();
Expand All @@ -699,7 +703,8 @@ fn did_you_mean_command_stable() {
.with_stdout("")
.with_stderr(
"\
[ERROR] running `build--manifest-path=./Cargo.toml` requires `-Zscript`
[ERROR] no such subcommand `build--manifest-path=./Cargo.toml`
<tab>Did you mean the command `build --manifest-path=./Cargo.toml`
",
)
.run();
Expand Down

0 comments on commit 51b1200

Please sign in to comment.