mirror of
https://github.com/clap-rs/clap
synced 2024-12-14 14:52:33 +00:00
Add 'parse(from_occurrences)' parser. Don't special case 'u64'. (#48)
This commit is contained in:
parent
76821ba7b2
commit
35423c96c4
5 changed files with 116 additions and 56 deletions
|
@ -19,8 +19,8 @@ struct Opt {
|
|||
#[structopt(short = "d", long = "debug")]
|
||||
debug: bool,
|
||||
|
||||
/// Verbose mode
|
||||
#[structopt(short = "v", long = "verbose")]
|
||||
/// Verbose mode (-v, -vv, -vvv, etc.)
|
||||
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||
verbose: u64,
|
||||
|
||||
/// Set speed
|
||||
|
|
|
@ -82,7 +82,6 @@
|
|||
//! Type | Effect | Added method call to `clap::Arg`
|
||||
//! ---------------------|---------------------------------------------------|--------------------------------------
|
||||
//! `bool` | `true` if the flag is present | `.takes_value(false).multiple(false)`
|
||||
//! `u64` | number of times the flag is used | `.takes_value(false).multiple(true)`
|
||||
//! `Option<T: FromStr>` | optional positional argument or option | `.takes_value(true).multiple(false)`
|
||||
//! `Vec<T: FromStr>` | list of options or the other positional arguments | `.takes_value(true).multiple(true)`
|
||||
//! `T: FromStr` | required option or positional argument | `.takes_value(true).multiple(false).required(!has_default)`
|
||||
|
@ -197,7 +196,7 @@
|
|||
//! #[structopt(name = "sparkle")]
|
||||
//! /// Add magical sparkles -- the secret ingredient!
|
||||
//! Sparkle {
|
||||
//! #[structopt(short = "m")]
|
||||
//! #[structopt(short = "m", parse(from_occurrences))]
|
||||
//! magicality: u64,
|
||||
//! #[structopt(short = "c")]
|
||||
//! color: String
|
||||
|
@ -278,7 +277,7 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! There are four kinds custom string parsers:
|
||||
//! There are five kinds of custom parsers:
|
||||
//!
|
||||
//! | Kind | Signature | Default |
|
||||
//! |-------------------|---------------------------------------|---------------------------------|
|
||||
|
@ -286,9 +285,16 @@
|
|||
//! | `try_from_str` | `fn(&str) -> Result<T, E>` | `::std::str::FromStr::from_str` |
|
||||
//! | `from_os_str` | `fn(&OsStr) -> T` | `::std::convert::From::from` |
|
||||
//! | `try_from_os_str` | `fn(&OsStr) -> Result<T, OsString>` | (no default function) |
|
||||
//! | `from_occurrences`| `fn(u64) -> T` | `value as T` |
|
||||
//!
|
||||
//! When supplying a custom string parser, `bool` and `u64` will not be treated
|
||||
//! specially:
|
||||
//! The `from_occurrences` parser is special. Using `parse(from_occurrences)`
|
||||
//! results in the _number of flags occurrences_ being stored in the relevant
|
||||
//! field or being passed to the supplied function. In other words, it converts
|
||||
//! something like `-vvv` to `3`. This is equivalent to
|
||||
//! `.takes_value(false).multiple(true)`. Note that the default parser can only
|
||||
//! be used with fields of integer types (`u8`, `usize`, `i64`, etc.).
|
||||
//!
|
||||
//! When supplying a custom string parser, `bool` will not be treated specially:
|
||||
//!
|
||||
//! Type | Effect | Added method call to `clap::Arg`
|
||||
//! ------------|-------------------|--------------------------------------
|
||||
|
@ -321,7 +327,6 @@ pub fn structopt(input: TokenStream) -> TokenStream {
|
|||
#[derive(Copy, Clone, PartialEq)]
|
||||
enum Ty {
|
||||
Bool,
|
||||
U64,
|
||||
Vec,
|
||||
Option,
|
||||
Other,
|
||||
|
@ -331,7 +336,6 @@ fn ty(t: &syn::Ty) -> Ty {
|
|||
if let syn::Ty::Path(None, syn::Path { segments: ref segs, .. }) = *t {
|
||||
match segs.last().unwrap().ident.as_ref() {
|
||||
"bool" => Ty::Bool,
|
||||
"u64" => Ty::U64,
|
||||
"Option" => Ty::Option,
|
||||
"Vec" => Ty::Vec,
|
||||
_ => Ty::Other,
|
||||
|
@ -359,7 +363,7 @@ fn sub_type(t: &syn::Ty) -> Option<&syn::Ty> {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
enum AttrSource { Struct, Field, }
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Parser {
|
||||
/// Parse an option to using a `fn(&str) -> T` function. The function should never fail.
|
||||
FromStr,
|
||||
|
@ -370,6 +374,9 @@ enum Parser {
|
|||
FromOsStr,
|
||||
/// Parse an option to using a `fn(&OsStr) -> Result<T, OsString>` function.
|
||||
TryFromOsStr,
|
||||
/// Counts the number of flag occurrences. Parses using a `fn(u64) -> T` function. The function
|
||||
/// should never fail.
|
||||
FromOccurrences,
|
||||
}
|
||||
|
||||
fn extract_attrs<'a>(attrs: &'a [Attribute], attr_source: AttrSource) -> Box<Iterator<Item = (Ident, Lit)> + 'a> {
|
||||
|
@ -470,30 +477,25 @@ fn get_parser(field: &Field) -> Option<(Parser, quote::Tokens)> {
|
|||
match *attr {
|
||||
NestedMetaItem::MetaItem(MetaItem::NameValue(ref i, Lit::Str(ref v, _))) => {
|
||||
let function = parse_path(v).expect("parser function path");
|
||||
let parser = if i == "from_str" {
|
||||
Parser::FromStr
|
||||
} else if i == "try_from_str" {
|
||||
Parser::TryFromStr
|
||||
} else if i == "from_os_str" {
|
||||
Parser::FromOsStr
|
||||
} else if i == "try_from_os_str" {
|
||||
Parser::TryFromOsStr
|
||||
} else {
|
||||
panic!("unsupported parser {}", i);
|
||||
let parser = match i.as_ref() {
|
||||
"from_str" => Parser::FromStr,
|
||||
"try_from_str" => Parser::TryFromStr,
|
||||
"from_os_str" => Parser::FromOsStr,
|
||||
"try_from_os_str" => Parser::TryFromOsStr,
|
||||
"from_occurrences" => Parser::FromOccurrences,
|
||||
_ => panic!("unsupported parser {}", i)
|
||||
};
|
||||
|
||||
(parser, quote!(#function))
|
||||
}
|
||||
NestedMetaItem::MetaItem(MetaItem::Word(ref i)) => {
|
||||
if i == "from_str" {
|
||||
(Parser::FromStr, quote!(::std::convert::From::from))
|
||||
} else if i == "try_from_str" {
|
||||
(Parser::TryFromStr, quote!(::std::str::FromStr::from_str))
|
||||
} else if i == "from_os_str" {
|
||||
(Parser::FromOsStr, quote!(::std::convert::From::from))
|
||||
} else if i == "try_from_os_str" {
|
||||
panic!("cannot omit parser function name with `try_from_os_str`")
|
||||
} else {
|
||||
panic!("unsupported parser {}", i);
|
||||
match i.as_ref() {
|
||||
"from_str" => (Parser::FromStr, quote!(::std::convert::From::from)),
|
||||
"try_from_str" => (Parser::TryFromStr, quote!(::std::str::FromStr::from_str)),
|
||||
"from_os_str" => (Parser::FromOsStr, quote!(::std::convert::From::from)),
|
||||
"try_from_os_str" => panic!("cannot omit parser function name with `try_from_os_str`"),
|
||||
"from_occurrences" => (Parser::FromOccurrences, quote!({|v| v as _})),
|
||||
_ => panic!("unsupported parser {}", i)
|
||||
}
|
||||
}
|
||||
_ => panic!("unknown value parser specification"),
|
||||
|
@ -504,7 +506,7 @@ fn get_parser(field: &Field) -> Option<(Parser, quote::Tokens)> {
|
|||
|
||||
fn convert_with_custom_parse(cur_type: Ty) -> Ty {
|
||||
match cur_type {
|
||||
Ty::Bool | Ty::U64 => Ty::Other,
|
||||
Ty::Bool => Ty::Other,
|
||||
rest => rest,
|
||||
}
|
||||
}
|
||||
|
@ -545,10 +547,13 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens {
|
|||
_ => &field.ty,
|
||||
};
|
||||
|
||||
let mut occurences = false;
|
||||
let parser = get_parser(field);
|
||||
if parser.is_some() {
|
||||
if let Some((ref parser, _)) = parser {
|
||||
cur_type = convert_with_custom_parse(cur_type);
|
||||
occurences = *parser == Parser::FromOccurrences;
|
||||
}
|
||||
|
||||
let validator = match parser.unwrap_or_else(get_default_parser) {
|
||||
(Parser::TryFromStr, f) => quote! {
|
||||
.validator(|s| {
|
||||
|
@ -565,9 +570,9 @@ fn gen_augmentation(fields: &[Field], app_var: &Ident) -> quote::Tokens {
|
|||
|
||||
let modifier = match cur_type {
|
||||
Ty::Bool => quote!( .takes_value(false).multiple(false) ),
|
||||
Ty::U64 => quote!( .takes_value(false).multiple(true) ),
|
||||
Ty::Option => quote!( .takes_value(true).multiple(false) #validator ),
|
||||
Ty::Vec => quote!( .takes_value(true).multiple(true) #validator ),
|
||||
Ty::Other if occurences => quote!( .takes_value(false).multiple(true) ),
|
||||
Ty::Other => {
|
||||
let required = extract_attrs(&field.attrs, AttrSource::Field)
|
||||
.find(|&(ref i, _)| i.as_ref() == "default_value"
|
||||
|
@ -621,13 +626,15 @@ fn gen_constructor(fields: &[Field]) -> quote::Tokens {
|
|||
};
|
||||
quote!( #field_name: #subcmd_type ::from_subcommand(matches.subcommand()) #unwrapper )
|
||||
} else {
|
||||
let mut cur_type = ty(&field.ty);
|
||||
let real_ty = &field.ty;
|
||||
let mut cur_type = ty(real_ty);
|
||||
let parser = get_parser(field);
|
||||
if parser.is_some() {
|
||||
cur_type = convert_with_custom_parse(cur_type);
|
||||
}
|
||||
|
||||
let (value_of, values_of, parse) = match parser.unwrap_or_else(get_default_parser) {
|
||||
let parser = parser.unwrap_or_else(get_default_parser);
|
||||
let (value_of, values_of, parse) = match parser {
|
||||
(Parser::FromStr, f) => (
|
||||
quote!(value_of),
|
||||
quote!(values_of),
|
||||
|
@ -648,28 +655,37 @@ fn gen_constructor(fields: &[Field]) -> quote::Tokens {
|
|||
quote!(values_of_os),
|
||||
quote!(|s| #f(s).unwrap()),
|
||||
),
|
||||
(Parser::FromOccurrences, f) => (
|
||||
quote!(occurrences_of),
|
||||
quote!(),
|
||||
f,
|
||||
),
|
||||
};
|
||||
|
||||
let convert = match cur_type {
|
||||
Ty::Bool => quote!(is_present(stringify!(#name))),
|
||||
Ty::U64 => quote!(occurrences_of(stringify!(#name))),
|
||||
let occurences = parser.0 == Parser::FromOccurrences;
|
||||
let field_value = match cur_type {
|
||||
Ty::Bool => quote!(matches.is_present(stringify!(#name))),
|
||||
Ty::Option => quote! {
|
||||
#value_of(stringify!(#name))
|
||||
matches.#value_of(stringify!(#name))
|
||||
.as_ref()
|
||||
.map(#parse)
|
||||
},
|
||||
Ty::Vec => quote! {
|
||||
#values_of(stringify!(#name))
|
||||
matches.#values_of(stringify!(#name))
|
||||
.map(|v| v.map(#parse).collect())
|
||||
.unwrap_or_else(Vec::new)
|
||||
},
|
||||
Ty::Other if occurences => quote! {
|
||||
#parse(matches.#value_of(stringify!(#name)))
|
||||
},
|
||||
Ty::Other => quote! {
|
||||
#value_of(stringify!(#name))
|
||||
matches.#value_of(stringify!(#name))
|
||||
.map(#parse)
|
||||
.unwrap()
|
||||
},
|
||||
};
|
||||
quote!( #field_name: matches.#convert )
|
||||
|
||||
quote!( #field_name: #field_value )
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -157,3 +157,45 @@ fn test_parser_with_default_value() {
|
|||
]))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
struct Foo(u8);
|
||||
|
||||
fn foo(value: u64) -> Foo {
|
||||
Foo(value as u8)
|
||||
}
|
||||
|
||||
#[derive(StructOpt, PartialEq, Debug)]
|
||||
struct Occurrences {
|
||||
#[structopt(short = "s", long = "signed", parse(from_occurrences))]
|
||||
signed: i32,
|
||||
|
||||
#[structopt(short = "l", parse(from_occurrences))]
|
||||
little_signed: i8,
|
||||
|
||||
#[structopt(short = "u", parse(from_occurrences))]
|
||||
unsigned: usize,
|
||||
|
||||
#[structopt(short = "r", parse(from_occurrences))]
|
||||
little_unsigned: u8,
|
||||
|
||||
#[structopt(short = "c", long = "custom", parse(from_occurrences = "foo"))]
|
||||
custom: Foo,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser_occurrences() {
|
||||
assert_eq!(
|
||||
Occurrences {
|
||||
signed: 3,
|
||||
little_signed: 1,
|
||||
unsigned: 0,
|
||||
little_unsigned: 4,
|
||||
custom: Foo(5),
|
||||
},
|
||||
Occurrences::from_clap(Occurrences::clap().get_matches_from(&[
|
||||
"test", "-s", "--signed", "--signed", "-l", "-rrrr", "-cccc", "--custom"
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -35,20 +35,22 @@ fn unique_flag() {
|
|||
fn multiple_flag() {
|
||||
#[derive(StructOpt, PartialEq, Debug)]
|
||||
struct Opt {
|
||||
#[structopt(short = "a", long = "alice")]
|
||||
#[structopt(short = "a", long = "alice", parse(from_occurrences))]
|
||||
alice: u64,
|
||||
#[structopt(short = "b", long = "bob", parse(from_occurrences))]
|
||||
bob: u8,
|
||||
}
|
||||
|
||||
assert_eq!(Opt { alice: 0 },
|
||||
assert_eq!(Opt { alice: 0, bob: 0 },
|
||||
Opt::from_clap(Opt::clap().get_matches_from(&["test"])));
|
||||
assert_eq!(Opt { alice: 1 },
|
||||
assert_eq!(Opt { alice: 1, bob: 0 },
|
||||
Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a"])));
|
||||
assert_eq!(Opt { alice: 2 },
|
||||
assert_eq!(Opt { alice: 2, bob: 0 },
|
||||
Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a", "-a"])));
|
||||
assert_eq!(Opt { alice: 2 },
|
||||
Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a", "--alice"])));
|
||||
assert_eq!(Opt { alice: 3 },
|
||||
Opt::from_clap(Opt::clap().get_matches_from(&["test", "-aaa"])));
|
||||
assert_eq!(Opt { alice: 2, bob: 2 },
|
||||
Opt::from_clap(Opt::clap().get_matches_from(&["test", "-a", "--alice", "-bb"])));
|
||||
assert_eq!(Opt { alice: 3, bob: 1 },
|
||||
Opt::from_clap(Opt::clap().get_matches_from(&["test", "-aaa", "--bob"])));
|
||||
assert!(Opt::clap().get_matches_from_safe(&["test", "-i"]).is_err());
|
||||
assert!(Opt::clap().get_matches_from_safe(&["test", "-a", "foo"]).is_err());
|
||||
}
|
||||
|
@ -59,7 +61,7 @@ fn combined_flags() {
|
|||
struct Opt {
|
||||
#[structopt(short = "a", long = "alice")]
|
||||
alice: bool,
|
||||
#[structopt(short = "b", long = "bob")]
|
||||
#[structopt(short = "b", long = "bob", parse(from_occurrences))]
|
||||
bob: u64,
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use structopt::StructOpt;
|
|||
struct Opt {
|
||||
#[structopt(short = "f", long = "force")]
|
||||
force: bool,
|
||||
#[structopt(short = "v", long = "verbose")]
|
||||
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||
verbose: u64,
|
||||
#[structopt(subcommand)]
|
||||
cmd: Sub
|
||||
|
@ -32,7 +32,7 @@ enum Sub {
|
|||
struct Opt2 {
|
||||
#[structopt(short = "f", long = "force")]
|
||||
force: bool,
|
||||
#[structopt(short = "v", long = "verbose")]
|
||||
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
|
||||
verbose: u64,
|
||||
#[structopt(subcommand)]
|
||||
cmd: Option<Sub>
|
||||
|
|
Loading…
Reference in a new issue