]> git.proxmox.com Git - cargo.git/blob - src/bin/cargo/commands/help.rs
Auto merge of #9186 - weihanglo:issue-9054, r=alexcrichton
[cargo.git] / src / bin / cargo / commands / help.rs
1 use crate::aliased_command;
2 use cargo::util::errors::CargoResult;
3 use cargo::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 a man page was displayed. In this case, Cargo should
19 /// 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!("man 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 // Check if this is a built-in command (or alias);
50 let subcommand = match check_alias(config, subcommand) {
51 Some(s) => s,
52 None => return Ok(false),
53 };
54 if resolve_executable(Path::new("man")).is_ok() {
55 let man = match extract_man(&subcommand, "1") {
56 Some(man) => man,
57 None => return Ok(false),
58 };
59 write_and_spawn(&subcommand, &man, "man")?;
60 } else {
61 let txt = match extract_man(&subcommand, "txt") {
62 Some(txt) => txt,
63 None => return Ok(false),
64 };
65 if resolve_executable(Path::new("less")).is_ok() {
66 write_and_spawn(&subcommand, &txt, "less")?;
67 } else if resolve_executable(Path::new("more")).is_ok() {
68 write_and_spawn(&subcommand, &txt, "more")?;
69 } else {
70 drop(std::io::stdout().write_all(&txt));
71 }
72 }
73 Ok(true)
74 }
75
76 /// Checks if the given subcommand is a built-in command (possibly via an alias).
77 ///
78 /// Returns None if it is not a built-in command.
79 fn check_alias(config: &Config, subcommand: &str) -> Option<String> {
80 if super::builtin_exec(subcommand).is_some() {
81 return Some(subcommand.to_string());
82 }
83 match aliased_command(config, subcommand) {
84 Ok(Some(alias)) => {
85 let alias = alias.into_iter().next()?;
86 if super::builtin_exec(&alias).is_some() {
87 Some(alias)
88 } else {
89 None
90 }
91 }
92 _ => None,
93 }
94 }
95
96 /// Extracts the given man page from the compressed archive.
97 ///
98 /// Returns None if the command wasn't found.
99 fn extract_man(subcommand: &str, extension: &str) -> Option<Vec<u8>> {
100 let extract_name = OsString::from(format!("cargo-{}.{}", subcommand, extension));
101 let gz = GzDecoder::new(COMPRESSED_MAN);
102 let mut ar = tar::Archive::new(gz);
103 // Unwraps should be safe here, since this is a static archive generated
104 // by our build script. It should never be an invalid format!
105 for entry in ar.entries().unwrap() {
106 let mut entry = entry.unwrap();
107 let path = entry.path().unwrap();
108 if path.file_name().unwrap() != extract_name {
109 continue;
110 }
111 let mut result = Vec::new();
112 entry.read_to_end(&mut result).unwrap();
113 return Some(result);
114 }
115 None
116 }
117
118 /// Write the contents of a man page to disk and spawn the given command to
119 /// display it.
120 fn write_and_spawn(name: &str, contents: &[u8], command: &str) -> CargoResult<()> {
121 let prefix = format!("cargo-{}.", name);
122 let mut tmp = tempfile::Builder::new().prefix(&prefix).tempfile()?;
123 let f = tmp.as_file_mut();
124 f.write_all(contents)?;
125 f.flush()?;
126 let path = tmp.path();
127 // Use a path relative to the temp directory so that it can work on
128 // cygwin/msys systems which don't handle windows-style paths.
129 let mut relative_name = std::ffi::OsString::from("./");
130 relative_name.push(path.file_name().unwrap());
131 let mut cmd = std::process::Command::new(command)
132 .arg(relative_name)
133 .current_dir(path.parent().unwrap())
134 .spawn()?;
135 drop(cmd.wait());
136 Ok(())
137 }