]> git.proxmox.com Git - cargo.git/blame - src/bin/cargo/commands/help.rs
refactor(cli): Make help behave like other subcommands
[cargo.git] / src / bin / cargo / commands / help.rs
CommitLineData
0e26eae5 1use crate::aliased_command;
d674c229 2use crate::command_prelude::*;
0e26eae5 3use cargo::util::errors::CargoResult;
7084570f 4use cargo::{drop_println, Config};
1dae5acb 5use cargo_util::paths::resolve_executable;
0e26eae5
EH
6use flate2::read::GzDecoder;
7use std::ffi::OsString;
8use std::io::Read;
9use std::io::Write;
10use std::path::Path;
11
12const COMPRESSED_MAN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/man.tgz"));
13
d674c229
EP
14pub fn cli() -> App {
15 subcommand("help")
16 .about("Displays help for a cargo subcommand")
17 .arg(Arg::new("SUBCOMMAND"))
0e26eae5
EH
18}
19
d674c229
EP
20pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
21 let subcommand = args.get_one::<String>("SUBCOMMAND");
22 if let Some(subcommand) = subcommand {
23 if !try_help(config, subcommand)? {
24 crate::execute_external_subcommand(config, subcommand, &[subcommand, "--help"])?;
25 }
26 } else {
27 let mut cmd = crate::cli::cli();
28 let _ = cmd.print_help();
0e26eae5 29 }
d674c229
EP
30 Ok(())
31}
7084570f 32
d674c229 33fn try_help(config: &Config, subcommand: &str) -> CargoResult<bool> {
6df4b1ef
SS
34 let subcommand = match check_alias(config, subcommand) {
35 // If this alias is more than a simple subcommand pass-through, show the alias.
36 Some(argv) if argv.len() > 1 => {
37 let alias = argv.join(" ");
38 drop_println!(config, "`{}` is aliased to `{}`", subcommand, alias);
39 return Ok(true);
40 }
41 // Otherwise, resolve the alias into its subcommand.
4c66d183
SS
42 Some(argv) => {
43 // An alias with an empty argv can be created via `"empty-alias" = ""`.
44 let first = argv.get(0).map(String::as_str).unwrap_or(subcommand);
45 first.to_string()
46 }
6df4b1ef
SS
47 None => subcommand.to_string(),
48 };
7084570f 49
6df4b1ef 50 let subcommand = match check_builtin(&subcommand) {
0e26eae5
EH
51 Some(s) => s,
52 None => return Ok(false),
53 };
6df4b1ef 54
0e26eae5 55 if resolve_executable(Path::new("man")).is_ok() {
38826af2 56 let man = match extract_man(subcommand, "1") {
0e26eae5
EH
57 Some(man) => man,
58 None => return Ok(false),
59 };
38826af2 60 write_and_spawn(subcommand, &man, "man")?;
0e26eae5 61 } else {
38826af2 62 let txt = match extract_man(subcommand, "txt") {
0e26eae5
EH
63 Some(txt) => txt,
64 None => return Ok(false),
65 };
66 if resolve_executable(Path::new("less")).is_ok() {
38826af2 67 write_and_spawn(subcommand, &txt, "less")?;
0e26eae5 68 } else if resolve_executable(Path::new("more")).is_ok() {
38826af2 69 write_and_spawn(subcommand, &txt, "more")?;
0e26eae5
EH
70 } else {
71 drop(std::io::stdout().write_all(&txt));
72 }
73 }
74 Ok(true)
75}
76
7084570f 77/// Checks if the given subcommand is an alias.
0e26eae5 78///
7084570f
SS
79/// Returns None if it is not an alias.
80fn check_alias(config: &Config, subcommand: &str) -> Option<Vec<String>> {
8a1af701 81 aliased_command(config, subcommand).ok().flatten()
0e26eae5
EH
82}
83
7084570f
SS
84/// Checks if the given subcommand is a built-in command (not via an alias).
85///
86/// Returns None if it is not a built-in command.
dc0bc584
SS
87fn check_builtin(subcommand: &str) -> Option<&str> {
88 super::builtin_exec(subcommand).map(|_| subcommand)
7084570f
SS
89}
90
0e26eae5
EH
91/// Extracts the given man page from the compressed archive.
92///
93/// Returns None if the command wasn't found.
0d6881c5 94fn extract_man(subcommand: &str, extension: &str) -> Option<Vec<u8>> {
0e26eae5
EH
95 let extract_name = OsString::from(format!("cargo-{}.{}", subcommand, extension));
96 let gz = GzDecoder::new(COMPRESSED_MAN);
97 let mut ar = tar::Archive::new(gz);
0d6881c5
EH
98 // Unwraps should be safe here, since this is a static archive generated
99 // by our build script. It should never be an invalid format!
0e26eae5
EH
100 for entry in ar.entries().unwrap() {
101 let mut entry = entry.unwrap();
102 let path = entry.path().unwrap();
103 if path.file_name().unwrap() != extract_name {
104 continue;
105 }
106 let mut result = Vec::new();
107 entry.read_to_end(&mut result).unwrap();
0d6881c5 108 return Some(result);
0e26eae5 109 }
0d6881c5 110 None
0e26eae5
EH
111}
112
113/// Write the contents of a man page to disk and spawn the given command to
114/// display it.
1a3bb390
EH
115fn write_and_spawn(name: &str, contents: &[u8], command: &str) -> CargoResult<()> {
116 let prefix = format!("cargo-{}.", name);
117 let mut tmp = tempfile::Builder::new().prefix(&prefix).tempfile()?;
0e26eae5 118 let f = tmp.as_file_mut();
47428433 119 f.write_all(contents)?;
0e26eae5 120 f.flush()?;
1a3bb390
EH
121 let path = tmp.path();
122 // Use a path relative to the temp directory so that it can work on
123 // cygwin/msys systems which don't handle windows-style paths.
124 let mut relative_name = std::ffi::OsString::from("./");
125 relative_name.push(path.file_name().unwrap());
0e26eae5 126 let mut cmd = std::process::Command::new(command)
1a3bb390
EH
127 .arg(relative_name)
128 .current_dir(path.parent().unwrap())
0e26eae5
EH
129 .spawn()?;
130 drop(cmd.wait());
131 Ok(())
132}