From fd2cc62b631c2bca290a94f7e62b7bd5082fc229 Mon Sep 17 00:00:00 2001 From: Andy Weiss Date: Thu, 10 Oct 2019 00:09:13 -0700 Subject: [PATCH] Add from_flag parser (#271) --- CHANGELOG.md | 4 ++++ src/lib.rs | 5 +++++ structopt-derive/src/attrs.rs | 3 +++ structopt-derive/src/lib.rs | 12 ++++++++++++ tests/flags.rs | 31 +++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b4958b..77defa01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v0.3.3 (Upcoming) +* Add `from_flag` custom parser to create flags from non-bool types. + Fixes [#185](https://github.com/TeXitoi/structopt/issues/185) + # v0.3.2 (2019-09-18) * `structopt` does not replace `:` with `, ` inside "author" strings while inside `<...>`. diff --git a/src/lib.rs b/src/lib.rs index bb3a2454..a3ba9863 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -684,6 +684,7 @@ //! | `from_os_str` | `fn(&OsStr) -> T` | `::std::convert::From::from` | //! | `try_from_os_str` | `fn(&OsStr) -> Result` | (no default function) | //! | `from_occurrences`| `fn(u64) -> T` | `value as T` | +//! | `from_flag` | `fn(bool) -> T` | `::std::convert::From::from` | //! //! The `from_occurrences` parser is special. Using `parse(from_occurrences)` //! results in the _number of flags occurrences_ being stored in the relevant @@ -692,6 +693,10 @@ //! `.takes_value(false).multiple(true)`. Note that the default parser can only //! be used with fields of integer types (`u8`, `usize`, `i64`, etc.). //! +//! The `from_flag` parser is also special. Using `parse(from_flag)` or +//! `parse(from_flag = some_func)` will result in the field being treated as a +//! flag even if it does not have type `bool`. +//! //! When supplying a custom string parser, `bool` will not be treated specially: //! //! Type | Effect | Added method call to `clap::Arg` diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index 9d64d080..8dee4927 100644 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -56,6 +56,7 @@ pub enum ParserKind { FromOsStr, TryFromOsStr, FromOccurrences, + FromFlag, } /// Defines the casing for the attributes long representation. @@ -141,6 +142,7 @@ impl Parser { "from_os_str" => FromOsStr, "try_from_os_str" => TryFromOsStr, "from_occurrences" => FromOccurrences, + "from_flag" => FromFlag, s => span_error!(spec.kind.span(), "unsupported parser `{}`", s), }; @@ -153,6 +155,7 @@ impl Parser { "cannot omit parser function name with `try_from_os_str`" ), FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }), + FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from), }, Some(func) => match func { diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 2e36824c..da99667d 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -119,6 +119,7 @@ fn gen_augmentation( }; let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; + let flag = *attrs.parser().kind == ParserKind::FromFlag; let parser = attrs.parser(); let func = &parser.func; @@ -174,6 +175,11 @@ fn gen_augmentation( .multiple(true) }, + Ty::Other if flag => quote_spanned! { ty.span()=> + .takes_value(false) + .multiple(false) + }, + Ty::Other => { let required = !attrs.has_method("default_value"); quote_spanned! { ty.span()=> @@ -267,8 +273,10 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) quote!(), func.clone(), ), + FromFlag => (quote!(), quote!(), func.clone()), }; + let flag = *attrs.parser().kind == ParserKind::FromFlag; let occurrences = *attrs.parser().kind == ParserKind::FromOccurrences; let name = attrs.cased_name(); let field_value = match **ty { @@ -305,6 +313,10 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) #parse(matches.#value_of(#name)) }, + Ty::Other if flag => quote_spanned! { ty.span()=> + #parse(matches.is_present(#name)) + }, + Ty::Other => quote_spanned! { ty.span()=> matches.#value_of(#name) .map(#parse) diff --git a/tests/flags.rs b/tests/flags.rs index 45ab09db..b3415f80 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -76,6 +76,37 @@ fn multiple_flag() { .is_err()); } +fn parse_from_flag(b: bool) -> std::sync::atomic::AtomicBool { + std::sync::atomic::AtomicBool::new(b) +} + +#[test] +fn non_bool_flags() { + #[derive(StructOpt, Debug)] + struct Opt { + #[structopt(short = "a", long = "alice", parse(from_flag = parse_from_flag))] + alice: std::sync::atomic::AtomicBool, + #[structopt(short = "b", long = "bob", parse(from_flag))] + bob: std::sync::atomic::AtomicBool, + } + + let falsey = Opt::from_clap(&Opt::clap().get_matches_from(&["test"])); + assert!(!falsey.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!falsey.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let alice = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-a"])); + assert!(alice.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(!alice.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let bob = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b"])); + assert!(!bob.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(bob.bob.load(std::sync::atomic::Ordering::Relaxed)); + + let both = Opt::from_clap(&Opt::clap().get_matches_from(&["test", "-b", "-a"])); + assert!(both.alice.load(std::sync::atomic::Ordering::Relaxed)); + assert!(both.bob.load(std::sync::atomic::Ordering::Relaxed)); +} + #[test] fn combined_flags() { #[derive(StructOpt, PartialEq, Debug)]