summaryrefslogtreecommitdiffstats
path: root/src/bin/cargo/commands/help.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/bin/cargo/commands/help.rs147
1 files changed, 147 insertions, 0 deletions
diff --git a/src/bin/cargo/commands/help.rs b/src/bin/cargo/commands/help.rs
new file mode 100644
index 0000000..2839b93
--- /dev/null
+++ b/src/bin/cargo/commands/help.rs
@@ -0,0 +1,147 @@
+use crate::aliased_command;
+use crate::command_prelude::*;
+use cargo::util::errors::CargoResult;
+use cargo::{drop_println, Config};
+use cargo_util::paths::resolve_executable;
+use flate2::read::GzDecoder;
+use std::ffi::OsStr;
+use std::ffi::OsString;
+use std::io::Read;
+use std::io::Write;
+use std::path::Path;
+
+const COMPRESSED_MAN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/man.tgz"));
+
+pub fn cli() -> Command {
+ subcommand("help")
+ .about("Displays help for a cargo subcommand")
+ .arg(Arg::new("COMMAND").action(ArgAction::Set))
+}
+
+pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
+ let subcommand = args.get_one::<String>("COMMAND");
+ if let Some(subcommand) = subcommand {
+ if !try_help(config, subcommand)? {
+ match check_builtin(&subcommand) {
+ Some(s) => {
+ crate::execute_internal_subcommand(
+ config,
+ &[OsStr::new(s), OsStr::new("--help")],
+ )?;
+ }
+ None => {
+ crate::execute_external_subcommand(
+ config,
+ subcommand,
+ &[OsStr::new(subcommand), OsStr::new("--help")],
+ )?;
+ }
+ }
+ }
+ } else {
+ let mut cmd = crate::cli::cli();
+ let _ = cmd.print_help();
+ }
+ Ok(())
+}
+
+fn try_help(config: &Config, subcommand: &str) -> CargoResult<bool> {
+ 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,
+ None => return Ok(false),
+ };
+ write_and_spawn(subcommand, &man, "man")?;
+ } else {
+ let txt = match extract_man(subcommand, "txt") {
+ Some(txt) => txt,
+ None => return Ok(false),
+ };
+ if resolve_executable(Path::new("less")).is_ok() {
+ write_and_spawn(subcommand, &txt, "less")?;
+ } else if resolve_executable(Path::new("more")).is_ok() {
+ write_and_spawn(subcommand, &txt, "more")?;
+ } else {
+ drop(std::io::stdout().write_all(&txt));
+ }
+ }
+ Ok(true)
+}
+
+/// 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_builtin(subcommand: &str) -> Option<&str> {
+ super::builtin_exec(subcommand).map(|_| subcommand)
+}
+
+/// Extracts the given man page from the compressed archive.
+///
+/// Returns None if the command wasn't found.
+fn extract_man(subcommand: &str, extension: &str) -> Option<Vec<u8>> {
+ let extract_name = OsString::from(format!("cargo-{}.{}", subcommand, extension));
+ let gz = GzDecoder::new(COMPRESSED_MAN);
+ let mut ar = tar::Archive::new(gz);
+ // Unwraps should be safe here, since this is a static archive generated
+ // by our build script. It should never be an invalid format!
+ for entry in ar.entries().unwrap() {
+ let mut entry = entry.unwrap();
+ let path = entry.path().unwrap();
+ if path.file_name().unwrap() != extract_name {
+ continue;
+ }
+ let mut result = Vec::new();
+ entry.read_to_end(&mut result).unwrap();
+ return Some(result);
+ }
+ None
+}
+
+/// Write the contents of a man page to disk and spawn the given command to
+/// display it.
+fn write_and_spawn(name: &str, contents: &[u8], command: &str) -> CargoResult<()> {
+ let prefix = format!("cargo-{}.", name);
+ let mut tmp = tempfile::Builder::new().prefix(&prefix).tempfile()?;
+ let f = tmp.as_file_mut();
+ f.write_all(contents)?;
+ f.flush()?;
+ let path = tmp.path();
+ // Use a path relative to the temp directory so that it can work on
+ // cygwin/msys systems which don't handle windows-style paths.
+ let mut relative_name = std::ffi::OsString::from("./");
+ relative_name.push(path.file_name().unwrap());
+ let mut cmd = std::process::Command::new(command)
+ .arg(relative_name)
+ .current_dir(path.parent().unwrap())
+ .spawn()?;
+ drop(cmd.wait());
+ Ok(())
+}