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