Skip to content

Commit

Permalink
Auto merge of #10193 - sstangl:help-alias-10138, r=alexcrichton
Browse files Browse the repository at this point in the history
Display alias target on 'cargo help <alias>`

```
Previously, `cargo help <alias>` resolved the alias and displayed the
help for the targeted subcommand. For example, if `br` were aliased to
`build --release`, `cargo help br` would display the manpage for
cargo-build.

With this patch, it will print "'br' is aliased to 'build --release'".
```

Addresses issue #10138.

This is my first patch to Cargo. I attempted to follow the style of the surrounding code. Please let me know if any changes are required: happy to make them. In particular, I wasn't sure if any tests exist for this path.
  • Loading branch information
bors committed Dec 14, 2021
2 parents c87b986 + 4c66d18 commit c689f55
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 23 deletions.
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>> {
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

0 comments on commit c689f55

Please sign in to comment.