Skip to main content

fs_core/cli/
dispatch.rs

1//! Which tool a process is, from the name it was started under.
2
3use std::ffi::OsString;
4use std::path::Path;
5
6use super::family::{Family, Tool};
7
8/// What an invocation turned out to be.
9pub enum Target {
10    /// A tool, with its own argument vector: `argv[0]` is the tool's name
11    /// whichever way it was reached.
12    Tool(&'static Tool, Vec<OsString>),
13    /// The repository-named entry point itself (`--help`, `--version`,
14    /// `doctor`), with the arguments as given.
15    Repo(Vec<OsString>),
16}
17
18/// Resolve an argument vector, `argv[0]` included.
19///
20/// `argv[0]`'s file name decides first: a dotted name is that tool. Any
21/// other name — the repository's, a renamed copy, cargo's test binary —
22/// is the repository entry point, whose first argument may then name a
23/// tool by verb or by full name. Unknown names fall through to the entry
24/// point rather than failing, so a copy renamed by a packager still
25/// answers `--help` and `doctor`.
26pub fn resolve(family: &'static Family, mut argv: Vec<OsString>) -> Target {
27    let invoked = argv
28        .first()
29        .map(|arg0| invoked_name(Path::new(arg0)))
30        .unwrap_or_default();
31    if let Some(tool) = family.by_name(&invoked) {
32        argv[0] = OsString::from(tool.name);
33        return Target::Tool(tool, argv);
34    }
35    if let Some(tool) = argv
36        .get(1)
37        .and_then(|word| word.to_str())
38        .and_then(|word| family.by_word(word))
39    {
40        let mut rest = argv.split_off(2);
41        rest.insert(0, OsString::from(tool.name));
42        return Target::Tool(tool, rest);
43    }
44    if argv.is_empty() {
45        argv.push(OsString::from(family.repo));
46    }
47    Target::Repo(argv)
48}
49
50/// The name a program was started under: the file name of `argv[0]`,
51/// without a Windows `.exe`.
52pub fn invoked_name(arg0: &Path) -> String {
53    let name = arg0
54        .file_name()
55        .map(|name| name.to_string_lossy().into_owned())
56        .unwrap_or_default();
57    match name.strip_suffix(".exe") {
58        Some(stem) => stem.to_string(),
59        None => name,
60    }
61}