1 | use std::collections::BTreeMap; |
2 | |
3 | use clap::{arg, command, ArgGroup, ArgMatches, Command}; |
4 | |
5 | fn main() { |
6 | let matches = cli().get_matches(); |
7 | let values = Value::from_matches(&matches); |
8 | println!("{values:#?}" ); |
9 | } |
10 | |
11 | fn cli() -> Command { |
12 | command!() |
13 | .group(ArgGroup::new("tests" ).multiple(true)) |
14 | .next_help_heading("TESTS" ) |
15 | .args([ |
16 | arg!(--empty "File is empty and is either a regular file or a directory" ).group("tests" ), |
17 | arg!(--name <NAME> "Base of file name (the path with the leading directories removed) matches shell pattern pattern" ).group("tests" ), |
18 | ]) |
19 | .group(ArgGroup::new("operators" ).multiple(true)) |
20 | .next_help_heading("OPERATORS" ) |
21 | .args([ |
22 | arg!(-o - -or "expr2 is not evaluate if exp1 is true" ).group("operators" ), |
23 | arg!(-a - -and "Same as `expr1 expr1`" ).group("operators" ), |
24 | ]) |
25 | } |
26 | |
27 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] |
28 | pub enum Value { |
29 | Bool(bool), |
30 | String(String), |
31 | } |
32 | |
33 | impl Value { |
34 | pub fn from_matches(matches: &ArgMatches) -> Vec<(clap::Id, Self)> { |
35 | let mut values = BTreeMap::new(); |
36 | for id in matches.ids() { |
37 | if matches.try_get_many::<clap::Id>(id.as_str()).is_ok() { |
38 | // ignore groups |
39 | continue; |
40 | } |
41 | let value_source = matches |
42 | .value_source(id.as_str()) |
43 | .expect("id came from matches" ); |
44 | if value_source != clap::parser::ValueSource::CommandLine { |
45 | // Any other source just gets tacked on at the end (like default values) |
46 | continue; |
47 | } |
48 | if Self::extract::<String>(matches, id, &mut values) { |
49 | continue; |
50 | } |
51 | if Self::extract::<bool>(matches, id, &mut values) { |
52 | continue; |
53 | } |
54 | unimplemented!("unknown type for {id}: {matches:?}" ); |
55 | } |
56 | values.into_values().collect::<Vec<_>>() |
57 | } |
58 | |
59 | fn extract<T: Clone + Into<Value> + Send + Sync + 'static>( |
60 | matches: &ArgMatches, |
61 | id: &clap::Id, |
62 | output: &mut BTreeMap<usize, (clap::Id, Self)>, |
63 | ) -> bool { |
64 | match matches.try_get_many::<T>(id.as_str()) { |
65 | Ok(Some(values)) => { |
66 | for (value, index) in values.zip( |
67 | matches |
68 | .indices_of(id.as_str()) |
69 | .expect("id came from matches" ), |
70 | ) { |
71 | output.insert(index, (id.clone(), value.clone().into())); |
72 | } |
73 | true |
74 | } |
75 | Ok(None) => { |
76 | unreachable!("`ids` only reports what is present" ) |
77 | } |
78 | Err(clap::parser::MatchesError::UnknownArgument { .. }) => { |
79 | unreachable!("id came from matches" ) |
80 | } |
81 | Err(clap::parser::MatchesError::Downcast { .. }) => false, |
82 | Err(_) => { |
83 | unreachable!("id came from matches" ) |
84 | } |
85 | } |
86 | } |
87 | } |
88 | |
89 | impl From<String> for Value { |
90 | fn from(other: String) -> Self { |
91 | Self::String(other) |
92 | } |
93 | } |
94 | |
95 | impl From<bool> for Value { |
96 | fn from(other: bool) -> Self { |
97 | Self::Bool(other) |
98 | } |
99 | } |
100 | |