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