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}