• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::collections::BTreeMap;
2 
3 use clap::{command, value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Command};
4 
main()5 fn main() {
6     let matches = cli().get_matches();
7     let values = Value::from_matches(&matches);
8     println!("{values:#?}");
9 }
10 
cli() -> Command11 fn cli() -> Command {
12     command!()
13         .group(ArgGroup::new("tests").multiple(true))
14         .next_help_heading("TESTS")
15         .args([
16             position_sensitive_flag(Arg::new("empty"))
17                 .long("empty")
18                 .action(ArgAction::Append)
19                 .help("File is empty and is either a regular file or a directory")
20                 .group("tests"),
21             Arg::new("name")
22                 .long("name")
23                 .action(ArgAction::Append)
24                 .help("Base of file name (the path with the leading directories removed) matches shell pattern pattern")
25                 .group("tests")
26         ])
27         .group(ArgGroup::new("operators").multiple(true))
28         .next_help_heading("OPERATORS")
29         .args([
30             position_sensitive_flag(Arg::new("or"))
31                 .short('o')
32                 .long("or")
33                 .action(ArgAction::Append)
34                 .help("expr2 is not evaluate if exp1 is true")
35                 .group("operators"),
36             position_sensitive_flag(Arg::new("and"))
37                 .short('a')
38                 .long("and")
39                 .action(ArgAction::Append)
40                 .help("Same as `expr1 expr1`")
41                 .group("operators"),
42         ])
43 }
44 
position_sensitive_flag(arg: Arg) -> Arg45 fn position_sensitive_flag(arg: Arg) -> Arg {
46     // Flags don't track the position of each occurrence, so we need to emulate flags with
47     // value-less options to get the same result
48     arg.num_args(0)
49         .value_parser(value_parser!(bool))
50         .default_missing_value("true")
51         .default_value("false")
52 }
53 
54 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
55 pub enum Value {
56     Bool(bool),
57     String(String),
58 }
59 
60 impl Value {
from_matches(matches: &ArgMatches) -> Vec<(clap::Id, Self)>61     pub fn from_matches(matches: &ArgMatches) -> Vec<(clap::Id, Self)> {
62         let mut values = BTreeMap::new();
63         for id in matches.ids() {
64             if matches.try_get_many::<clap::Id>(id.as_str()).is_ok() {
65                 // ignore groups
66                 continue;
67             }
68             let value_source = matches
69                 .value_source(id.as_str())
70                 .expect("id came from matches");
71             if value_source != clap::parser::ValueSource::CommandLine {
72                 // Any other source just gets tacked on at the end (like default values)
73                 continue;
74             }
75             if Self::extract::<String>(matches, id, &mut values) {
76                 continue;
77             }
78             if Self::extract::<bool>(matches, id, &mut values) {
79                 continue;
80             }
81             unimplemented!("unknown type for {id}: {matches:?}");
82         }
83         values.into_values().collect::<Vec<_>>()
84     }
85 
extract<T: Clone + Into<Value> + Send + Sync + 'static>( matches: &ArgMatches, id: &clap::Id, output: &mut BTreeMap<usize, (clap::Id, Self)>, ) -> bool86     fn extract<T: Clone + Into<Value> + Send + Sync + 'static>(
87         matches: &ArgMatches,
88         id: &clap::Id,
89         output: &mut BTreeMap<usize, (clap::Id, Self)>,
90     ) -> bool {
91         match matches.try_get_many::<T>(id.as_str()) {
92             Ok(Some(values)) => {
93                 for (value, index) in values.zip(
94                     matches
95                         .indices_of(id.as_str())
96                         .expect("id came from matches"),
97                 ) {
98                     output.insert(index, (id.clone(), value.clone().into()));
99                 }
100                 true
101             }
102             Ok(None) => {
103                 unreachable!("`ids` only reports what is present")
104             }
105             Err(clap::parser::MatchesError::UnknownArgument { .. }) => {
106                 unreachable!("id came from matches")
107             }
108             Err(clap::parser::MatchesError::Downcast { .. }) => false,
109             Err(_) => {
110                 unreachable!("id came from matches")
111             }
112         }
113     }
114 }
115 
116 impl From<String> for Value {
from(other: String) -> Self117     fn from(other: String) -> Self {
118         Self::String(other)
119     }
120 }
121 
122 impl From<bool> for Value {
from(other: bool) -> Self123     fn from(other: bool) -> Self {
124         Self::Bool(other)
125     }
126 }
127