]>
Commit | Line | Data |
---|---|---|
0e26eae5 | 1 | use crate::aliased_command; |
d674c229 | 2 | use crate::command_prelude::*; |
0e26eae5 | 3 | use cargo::util::errors::CargoResult; |
7084570f | 4 | use cargo::{drop_println, Config}; |
1dae5acb | 5 | use cargo_util::paths::resolve_executable; |
0e26eae5 EH |
6 | use flate2::read::GzDecoder; |
7 | use std::ffi::OsString; | |
8 | use std::io::Read; | |
9 | use std::io::Write; | |
10 | use std::path::Path; | |
11 | ||
12 | const COMPRESSED_MAN: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/man.tgz")); | |
13 | ||
d674c229 EP |
14 | pub 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 |
20 | pub 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 | 33 | fn 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. |
80 | fn 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 |
87 | fn 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 | 94 | fn 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 |
115 | fn 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 | } |