Add 'parse(from_occurrences)' parser. Don't special case 'u64'. (#48)

This commit is contained in:
Sergio Benitez 2018-02-03 06:15:03 -08:00 committed by Guillaume P
parent 76821ba7b2
commit 35423c96c4
5 changed files with 116 additions and 56 deletions

View file

@ -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

View file

@ -50,9 +50,9 @@
//! extern crate structopt;
//! #[macro_use]
//! extern crate structopt_derive;
//!
//!
//! use structopt::StructOpt;
//!
//!
//! #[derive(StructOpt, Debug)]
//! #[structopt(setting_raw = "clap::AppSettings::ColoredHelp")]
//! struct Opt {
@ -61,7 +61,7 @@
//! #[structopt(short = "d")]
//! debug: bool,
//! }
//!
//!
//! fn main() {
//! let opt = Opt::from_args();
//! println!("{:?}", opt);
@ -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 )
}
});

View file

@ -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"
]))
);
}

View file

@ -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,
}

View file

@ -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>