infer long flags and options

This commit is contained in:
Terts Diepraam
2022-12-03 16:17:21 +01:00
parent 74af32a4b9
commit f735358924
3 changed files with 82 additions and 25 deletions
+49 -25
View File
@@ -134,7 +134,9 @@ pub fn arguments(input: TokenStream) -> TokenStream {
panic!("Input should be an enum!");
};
let mut match_arms = Vec::new();
let mut short_match_arms = Vec::new();
let mut long_match_arms = Vec::new();
let mut long_options = Vec::new();
for variant in data.variants {
let variant_ident = variant.ident;
@@ -192,40 +194,62 @@ pub fn arguments(input: TokenStream) -> TokenStream {
_ => panic!("unimplemented"),
};
let short_pattern = if short_flags.is_empty() {
None
} else {
Some(quote!(uutils_args::lexopt::Arg::Short(#(#short_flags)|*)))
if !short_flags.is_empty() {
short_match_arms.push(quote!(#(#short_flags)|* => { #parse_expr }));
};
let long_pattern = if long_flags.is_empty() {
None
} else {
Some(quote!(uutils_args::lexopt::Arg::Long(#(#long_flags)|*)))
if !long_flags.is_empty() {
long_match_arms.push(quote!(#(#long_flags)|* => { #parse_expr }));
long_options.append(&mut long_flags);
};
let pattern = match (short_pattern, long_pattern) {
// No flags given, so we just ignore this variant,
// could be user error though, so we might want to
// panic
(None, None) => continue,
(Some(s), None) => s,
(None, Some(l)) => l,
(Some(s), Some(l)) => quote!(#s | #l),
};
match_arms.push(quote!(#pattern => { #parse_expr }))
}
let long_opt_len = long_options.len();
let expanded = quote!(
impl #impl_generics Arguments for #name #ty_generics #where_clause {
fn next_arg(parser: &mut uutils_args::lexopt::Parser) -> Result<Option<Self>, uutils_args::Error> {
use uutils_args::FromValue;
use uutils_args::lexopt::Arg;
use uutils_args::Error;
let Some(arg) = parser.next()? else { return Ok(None); };
Ok(Some(match arg {
#(#match_arms)*
_ => return Err(arg.unexpected().into())
}))
let long_options: [&str; #long_opt_len] = [#(#long_options),*];
let parsed = match arg {
Arg::Short(short) => {
match short {
#(#short_match_arms)*
_ => return Err(arg.unexpected().into())
}
}
Arg::Long(long) => {
let mut candidates = Vec::new();
let mut exact_match = None;
for opt in long_options {
if opt == long {
exact_match = Some(opt);
break;
} else if opt.starts_with(long) {
candidates.push(opt);
}
}
let opt = match (exact_match, &candidates[..]) {
(Some(opt), _) => opt,
(None, [opt]) => opt,
(None, []) => return Err(arg.unexpected().into()),
(None, opts) => return Err(Error::AmbiguousOption {
option: long.to_string(),
candidates: candidates.iter().map(|s| s.to_string()).collect(),
})
};
match opt {
#(#long_match_arms)*
_ => unreachable!("Should be caught by (None, []) case above.")
}
}
_ => { panic!("Values are ignored at the moment!" )}
};
Ok(Some(parsed))
}
}
);
+4
View File
@@ -19,6 +19,10 @@ pub enum Error {
value: String,
error: Box<dyn StdError + Send + Sync + 'static>,
},
AmbiguousOption {
option: String,
candidates: Vec<String>,
},
NonUnicodeValue(OsString),
Custom(Box<dyn StdError + Send + Sync + 'static>),
}
+29
View File
@@ -384,3 +384,32 @@ fn count() {
assert_eq!(Settings::parse(["-vv"]).unwrap().verbosity, 2);
assert_eq!(Settings::parse(["-vvv"]).unwrap().verbosity, 3);
}
#[test]
fn infer_long_args() {
#[derive(Arguments)]
enum Arg {
#[flag("--all")]
All,
#[flag("--almost-all")]
AlmostAll,
#[flag("--author")]
Author
}
#[derive(Options, Default)]
#[arg_type(Arg)]
struct Settings {
#[set_true(Arg::All)]
all: bool,
#[set_true(Arg::AlmostAll)]
almost_all: bool,
#[set_true(Arg::Author)]
author: bool,
}
assert!(Settings::parse(["--all"]).unwrap().all);
assert!(Settings::parse(["--alm"]).unwrap().almost_all);
assert!(Settings::parse(["--au"]).unwrap().author);
assert!(Settings::parse(["--a"]).is_err());
}