Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display alias target on 'cargo help <alias>` #10193

Merged
merged 6 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/cargo-test-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,15 @@ impl Execs {
}
}

#[track_caller]
pub fn run_expect_error(&mut self) {
self.ran = true;
let p = (&self.process_builder).clone().unwrap();
if self.match_process(&p).is_ok() {
panic!("test was expected to fail, but succeeded running {}", p);
}
}

/// Runs the process, checks the expected output, and returns the first
/// JSON object on stdout.
#[track_caller]
Expand Down
53 changes: 32 additions & 21 deletions src/bin/cargo/commands/help.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::aliased_command;
use cargo::util::errors::CargoResult;
use cargo::Config;
use cargo::{drop_println, Config};
use cargo_util::paths::resolve_executable;
use flate2::read::GzDecoder;
use std::ffi::OsString;
Expand All @@ -15,14 +15,14 @@ const COMPRESSED_MAN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/man.tgz"
/// This runs before clap processing, because it needs to intercept the `help`
/// command if a man page is available.
///
/// Returns `true` if a man page was displayed. In this case, Cargo should
/// exit.
/// Returns `true` if help information was successfully displayed to the user.
/// In this case, Cargo should exit.
pub fn handle_embedded_help(config: &Config) -> bool {
match try_help(config) {
Ok(true) => true,
Ok(false) => false,
Err(e) => {
log::warn!("man failed: {:?}", e);
log::warn!("help failed: {:?}", e);
false
}
}
Expand All @@ -46,11 +46,28 @@ fn try_help(config: &Config) -> CargoResult<bool> {
Some(s) => s,
None => return Ok(false),
};
// Check if this is a built-in command (or alias);

let subcommand = match check_alias(config, subcommand) {
// If this alias is more than a simple subcommand pass-through, show the alias.
Some(argv) if argv.len() > 1 => {
let alias = argv.join(" ");
drop_println!(config, "`{}` is aliased to `{}`", subcommand, alias);
return Ok(true);
}
// Otherwise, resolve the alias into its subcommand.
Some(argv) => {
// An alias with an empty argv can be created via `"empty-alias" = ""`.
let first = argv.get(0).map(String::as_str).unwrap_or(subcommand);
first.to_string()
}
None => subcommand.to_string(),
};

let subcommand = match check_builtin(&subcommand) {
Some(s) => s,
None => return Ok(false),
};

if resolve_executable(Path::new("man")).is_ok() {
let man = match extract_man(&subcommand, "1") {
Some(man) => man,
Expand All @@ -73,24 +90,18 @@ fn try_help(config: &Config) -> CargoResult<bool> {
Ok(true)
}

/// Checks if the given subcommand is a built-in command (possibly via an alias).
/// Checks if the given subcommand is an alias.
///
/// Returns None if it is not an alias.
fn check_alias(config: &Config, subcommand: &str) -> Option<Vec<String>> {
sstangl marked this conversation as resolved.
Show resolved Hide resolved
aliased_command(config, subcommand).ok().flatten()
}

/// Checks if the given subcommand is a built-in command (not via an alias).
///
/// Returns None if it is not a built-in command.
fn check_alias(config: &Config, subcommand: &str) -> Option<String> {
if super::builtin_exec(subcommand).is_some() {
return Some(subcommand.to_string());
}
match aliased_command(config, subcommand) {
Ok(Some(alias)) => {
let alias = alias.into_iter().next()?;
if super::builtin_exec(&alias).is_some() {
Some(alias)
} else {
None
}
}
_ => None,
}
fn check_builtin(subcommand: &str) -> Option<&str> {
super::builtin_exec(subcommand).map(|_| subcommand)
}

/// Extracts the given man page from the compressed archive.
Expand Down
32 changes: 30 additions & 2 deletions tests/testsuite/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ fn help_with_man_and_path(
assert_eq!(stdout, contents);
}

fn help_with_stdout_and_path(subcommand: &str, path: &Path) -> String {
let output = process(&cargo_exe())
.arg("help")
.arg(subcommand)
.env("PATH", path)
.exec_with_output()
.unwrap();
assert!(output.status.success());
let stderr = from_utf8(&output.stderr).unwrap();
assert_eq!(stderr, "");
let stdout = from_utf8(&output.stdout).unwrap();
stdout.to_string()
}

#[cargo_test]
fn help_man() {
// Checks that `help command` displays the man page using the given command.
Expand All @@ -132,11 +146,25 @@ fn help_alias() {
config,
r#"
[alias]
my-alias = ["build", "--release"]
empty-alias = ""
simple-alias = "build"
complex-alias = ["build", "--release"]
"#,
)
.unwrap();
help_with_man_and_path("", "my-alias", "build", Path::new(""));

// The `empty-alias` returns an error.
cargo_process("help empty-alias")
.env("PATH", Path::new(""))
.with_stderr_contains("[..]The subcommand 'empty-alias' wasn't recognized[..]")
.run_expect_error();

// Because `simple-alias` aliases a subcommand with no arguments, help shows the manpage.
help_with_man_and_path("", "simple-alias", "build", Path::new(""));

// Help for `complex-alias` displays the full alias command.
let out = help_with_stdout_and_path("complex-alias", Path::new(""));
assert_eq!(out, "`complex-alias` is aliased to `build --release`\n");
}

#[cargo_test]
Expand Down