]> git.proxmox.com Git - cargo.git/blob - src/bin/cargo/commands/help.rs
Auto merge of #10196 - charlesroussel:master, r=alexcrichton
[cargo.git] / src / bin / cargo / commands / help.rs
1 use crate::aliased_command;
2 use cargo::util::errors::CargoResult;
3 use cargo::{drop_println, Config};
4 use cargo_util::paths::resolve_executable;
5 use flate2::read::GzDecoder;
6 use std::ffi::OsString;
7 use std::io::Read;
8 use std::io::Write;
9 use std::path::Path;
10
11 const COMPRESSED_MAN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/man.tgz"));
12
13 /// Checks if the `help` command is being issued.
14 ///
15 /// This runs before clap processing, because it needs to intercept the `help`
16 /// command if a man page is available.
17 ///
18 /// Returns `true` if help information was successfully displayed to the user.
19 /// In this case, Cargo should exit.
20 pub fn handle_embedded_help(config: &Config) -> bool {
21 match try_help(config) {
22 Ok(true) => true,
23 Ok(false) => false,
24 Err(e) => {
25 log::warn!("help failed: {:?}", e);
26 false
27 }
28 }
29 }
30
31 fn try_help(config: &Config) -> CargoResult<bool> {
32 let mut args = std::env::args_os()
33 .skip(1)
34 .skip_while(|arg| arg.to_str().map_or(false, |s| s.starts_with('-')));
35 if !args
36 .next()
37 .map_or(false, |arg| arg.to_str() == Some("help"))
38 {
39 return Ok(false);
40 }
41 let subcommand = match args.next() {
42 Some(arg) => arg,
43 None => return Ok(false),
44 };
45 let subcommand = match subcommand.to_str() {
46 Some(s) => s,
47 None => return Ok(false),
48 };
49
50 let subcommand = match check_alias(config, subcommand) {
51 // If this alias is more than a simple subcommand pass-through, show the alias.
52 Some(argv) if argv.len() > 1 => {
53 let alias = argv.join(" ");
54 drop_println!(config, "`{}` is aliased to `{}`", subcommand, alias);
55 return Ok(true);
56 }
57 // Otherwise, resolve the alias into its subcommand.
58 Some(argv) => {
59 // An alias with an empty argv can be created via `"empty-alias" = ""`.
60 let first = argv.get(0).map(String::as_str).unwrap_or(subcommand);
61 first.to_string()
62 }
63 None => subcommand.to_string(),
64 };
65
66 let subcommand = match check_builtin(&subcommand) {
67 Some(s) => s,
68 None => return Ok(false),
69 };
70
71 if resolve_executable(Path::new("man")).is_ok() {
72 let man = match extract_man(&subcommand, "1") {
73 Some(man) => man,
74 None => return Ok(false),
75 };
76 write_and_spawn(&subcommand, &man, "man")?;
77 } else {
78 let txt = match extract_man(&subcommand, "txt") {
79 Some(txt) => txt,
80 None => return Ok(false),
81 };
82 if resolve_executable(Path::new("less")).is_ok() {
83 write_and_spawn(&subcommand, &txt, "less")?;
84 } else if resolve_executable(Path::new("more")).is_ok() {
85 write_and_spawn(&subcommand, &txt, "more")?;
86 } else {
87 drop(std::io::stdout().write_all(&txt));
88 }
89 }
90 Ok(true)
91 }
92
93 /// Checks if the given subcommand is an alias.
94 ///
95 /// Returns None if it is not an alias.
96 fn check_alias(config: &Config, subcommand: &str) -> Option<Vec<String>> {
97 aliased_command(config, subcommand).ok().flatten()
98 }
99
100 /// Checks if the given subcommand is a built-in command (not via an alias).
101 ///
102 /// Returns None if it is not a built-in command.
103 fn check_builtin(subcommand: &str) -> Option<&str> {
104 super::builtin_exec(subcommand).map(|_| subcommand)
105 }
106
107 /// Extracts the given man page from the compressed archive.
108 ///
109 /// Returns None if the command wasn't found.
110 fn extract_man(subcommand: &str, extension: &str) -> Option<Vec<u8>> {
111 let extract_name = OsString::from(format!("cargo-{}.{}", subcommand, extension));
112 let gz = GzDecoder::new(COMPRESSED_MAN);
113 let mut ar = tar::Archive::new(gz);
114 // Unwraps should be safe here, since this is a static archive generated
115 // by our build script. It should never be an invalid format!
116 for entry in ar.entries().unwrap() {
117 let mut entry = entry.unwrap();
118 let path = entry.path().unwrap();
119 if path.file_name().unwrap() != extract_name {
120 continue;
121 }
122 let mut result = Vec::new();
123 entry.read_to_end(&mut result).unwrap();
124 return Some(result);
125 }
126 None
127 }
128
129 /// Write the contents of a man page to disk and spawn the given command to
130 /// display it.
131 fn write_and_spawn(name: &str, contents: &[u8], command: &str) -> CargoResult<()> {
132 let prefix = format!("cargo-{}.", name);
133 let mut tmp = tempfile::Builder::new().prefix(&prefix).tempfile()?;
134 let f = tmp.as_file_mut();
135 f.write_all(contents)?;
136 f.flush()?;
137 let path = tmp.path();
138 // Use a path relative to the temp directory so that it can work on
139 // cygwin/msys systems which don't handle windows-style paths.
140 let mut relative_name = std::ffi::OsString::from("./");
141 relative_name.push(path.file_name().unwrap());
142 let mut cmd = std::process::Command::new(command)
143 .arg(relative_name)
144 .current_dir(path.parent().unwrap())
145 .spawn()?;
146 drop(cmd.wait());
147 Ok(())
148 }