1use std::collections::BTreeMap;
2
3use clap::{arg, command, ArgGroup, ArgMatches, Command};
4
5fn main() {
6 let matches = cli().get_matches();
7 let values = Value::from_matches(&matches);
8 println!("{values:#?}");
9}
10
11fn 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)]
28pub enum Value {
29 Bool(bool),
30 String(String),
31}
32
33impl 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
89impl From<String> for Value {
90 fn from(other: String) -> Self {
91 Self::String(other)
92 }
93}
94
95impl From<bool> for Value {
96 fn from(other: bool) -> Self {
97 Self::Bool(other)
98 }
99}
100