Repository: pacak/bpaf
Pull request #99 (2022-10-06 12:38:16)

applicative docs + misc changes
- `bpaf_derive`: cleanup and change MSRV to 1.34.0
- minor optimizations
- applicative docs


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 13f97f6..3e0126d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ exclude = [".github/workflows", "tarp.sh"]
 
 
 [dependencies]
-bpaf_derive = { path = "./bpaf_derive", version = "=0.3.0", optional = true }
+bpaf_derive = { path = "./bpaf_derive", version = "=0.3.1", optional = true }
 
 [dev-dependencies]
 bpaf = { path = ".",  features = ["derive", "extradocs", "autocomplete"] }
diff --git a/bpaf_derive/Cargo.toml b/bpaf_derive/Cargo.toml
index 788c8f6..018188a 100644
--- a/bpaf_derive/Cargo.toml
+++ b/bpaf_derive/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "bpaf_derive"
-version = "0.3.0"
-edition = "2021"
+version = "0.3.1"
+edition = "2018"
 categories = ["command-line-interface"]
 description = "Derive macros for bpaf Command Line Argument Parser"
 keywords = ["args", "arguments", "cli", "parser", "parse"]
diff --git a/bpaf_derive/src/field.rs b/bpaf_derive/src/field.rs
index 3d5e14e..076a410 100644
--- a/bpaf_derive/src/field.rs
+++ b/bpaf_derive/src/field.rs
@@ -54,7 +54,7 @@ enum PostprAttr {
 }
 
 impl PostprAttr {
-    const fn can_derive(&self) -> bool {
+    fn can_derive(&self) -> bool {
         match self {
             PostprAttr::Many(..)
             | PostprAttr::Map(..)
@@ -102,8 +102,8 @@ impl Parse for Doc {
     fn parse(input: parse::ParseStream) -> Result<Self> {
         input.parse::<Token![=]>()?;
         let mut s = input.parse::<LitStr>()?.value();
-        if let Some(rest) = s.strip_prefix(' ') {
-            s = rest.to_string();
+        if s.starts_with(' ') {
+            s = s[1..].to_string();
         }
         Ok(Doc(s))
     }
diff --git a/bpaf_derive/src/lib.rs b/bpaf_derive/src/lib.rs
index 129f785..8082e44 100644
--- a/bpaf_derive/src/lib.rs
+++ b/bpaf_derive/src/lib.rs
@@ -1,6 +1,12 @@
-#![doc = include_str!("../README.md")]
+//! # Derive macro for bpaf command line parser
+//!
+//! For documentation refer to `bpaf` library docs: <https://docs.rs/bpaf/latest/bpaf/>
+
+#![allow(clippy::manual_range_contains)]
+#![allow(clippy::single_match_else)]
 use quote::ToTokens;
 use syn::parse_macro_input;
+extern crate proc_macro;
 
 mod field;
 #[cfg(test)]
diff --git a/bpaf_derive/src/top.rs b/bpaf_derive/src/top.rs
index 5535a32..ad6dcfb 100644
--- a/bpaf_derive/src/top.rs
+++ b/bpaf_derive/src/top.rs
@@ -308,8 +308,6 @@ impl Outer {
         if let Some(OuterKind::Command(cmd)) = &mut res.kind {
             cmd.shorts.append(&mut res.shorts);
             cmd.longs.append(&mut res.longs);
-        } else if !(res.shorts.is_empty() && res.longs.is_empty()) {
-            todo!()
         }
 
         res.decor = Decor::new(&help, res.version.take());
@@ -446,7 +444,12 @@ impl Top {
         }
 
         let inner = match branches.len() {
-            0 => todo!(),
+            0 => {
+                return Err(syn::Error::new(
+                    outer_ty.span(),
+                    "Can't construct a parser from empty enum",
+                ))
+            }
             1 => branches.remove(0),
             _ => BParser::Fold(branches, outer.fallback.clone()),
         };
@@ -634,7 +637,7 @@ impl Fields {
         }
     }
 
-    const fn struct_definition_followed_by_semi(&self) -> bool {
+    fn struct_definition_followed_by_semi(&self) -> bool {
         match self {
             Fields::Named(_) | Fields::NoFields => false,
             Fields::Unnamed(_) => true,
diff --git a/bpaf_derive/src/utils.rs b/bpaf_derive/src/utils.rs
index 9e3e604..4c87858 100644
--- a/bpaf_derive/src/utils.rs
+++ b/bpaf_derive/src/utils.rs
@@ -44,7 +44,7 @@ pub fn to_kebab_case(input: &str) -> String {
 pub fn to_custom_case(input: &str, sep: char) -> String {
     let mut res = String::with_capacity(input.len() * 2);
     for c in input.chars() {
-        if ('A'..='Z').contains(&c) {
+        if c >= 'A' && c <= 'Z' {
             if !res.is_empty() {
                 res.push(sep);
             }
diff --git a/src/_applicative.rs b/src/_applicative.rs
index 72daa4c..3cd92e3 100644
--- a/src/_applicative.rs
+++ b/src/_applicative.rs
@@ -5,15 +5,11 @@
 
 //! ## Functors
 //!
-//! Let's start by talking about what a `Functor` is. In Haskell it is defined as
-//!
-//! ```haskell
-//! class Functor f where
-//!     fmap :: (a -> b) -> f a -> f b
-//! ```
-//!
-//! looks scary, but in Rust terms it's a trait that takes a *value in a context* or a container such as
-//! `Option<A>` and a function `fn(A) -> B` and gives you `Option<B>` back.
+//! Let's start by talking about what a `Functor` is. Wikipedia defines it as a "design pattern
+//! that allows for a generic type to apply a function inside without changing the structure of
+//! the generic type". Sounds scary, but in Rust terms it's a trait that takes a value or values
+//! in a container (or more general *value in a context* ) such as `Option<A>` and a function
+//! `fn(A) -> B` and gives you `Option<B>` back.
 //!
 //! Closest analogy in a real code you can write in Rust right now would be modifying an `Option`
 //! using only `Option::map`:
@@ -29,19 +25,19 @@
 //! assert_eq!(plus_one(absent), None);
 //! ```
 //!
-//! `Vec`, `Result` and other types that implement `map` are Functors as well, but functors are not
-//! limited just to containers - you don't have to have a value inside to be able to manipulate it.
-//! Consider `Reader` that allows you to perform pure transformations on a *value in a context* `T`
-//! without having any value until it's ready to execute:
+//! `Vec`, `Result` and other types that implement `map` are `Functor` as well, but `Functor`
+//! is not limited just to containers - you don't have to have a value inside to be able to
+//! manipulate it. In fact a regular rust function is also a `Functor` if you squint hard enough:
+//! consider `Reader` that allows you to perform transformations on a *value in a context* `T`
+//! without having any value until it the execution time:
 //!
 //! ```rust
-//! # use std::marker::PhantomData;
-//! struct Reader<T>(PhantomData<T>, Box<dyn Fn(T) -> T>);
+//! struct Reader<T>(Box<dyn Fn(T) -> T>);
 //!
 //! impl<T> Reader<T> {
 //!     /// Initialize an new value in a context
 //!     fn new() -> Self {
-//!         Self(PhantomData, Box::new(|x| x))
+//!         Self(Box::new(|x| x))
 //!     }
 //!
 //!     /// Modify a value in a context
@@ -51,9 +47,8 @@
 //!         T: 'static,
 //!     {
 //!         Self(
-//!             PhantomData,
 //!             Box::new(move |x| {
-//!                 let inner = &self.1;
+//!                 let inner = &self.0;
 //!                 f(inner(x))
 //!             }),
 //!         )
@@ -61,7 +56,7 @@
 //!
 //!     /// Apply the changes by giving it the initial value
 //!     fn run(self, input: T) -> T {
-//!         let f = &self.1;
+//!         let f = &self.0;
 //!         f(input)
 //!     }
 //! }
@@ -74,9 +69,10 @@
 
 //! ## Applicative Functors
 //!
-//! `map` in `Functors` is limited to a single value, `Applicative Functors` extend it to multiple values, closest Rust
-//! analogy would be doing computations on `Option` or `Result` using only `?` and `Some`/`Ok` but
-//! without making any decisions on input values after they been extracted from `Option`.
+//! `map` in `Functor` is limited to a single *value in a context*, `Applicativ Functor` extends it
+//! to operations combining multiple values, closest Rust analogy would be doing computations on
+//! `Option` or `Result` using only `?` and `Some`/`Ok` but without making any decisions that would
+//! change the shape of the output based on input values after they been extracted with `?`.
 //! ```rust
 //! fn add_numbers(input_a: Option<u32>, input_b: Option<u32>) -> Option<u32> {
 //!     Some(input_a? + input_b?)
@@ -98,14 +94,55 @@
 //! but `Applicative Functors` in general don't have to - in fact to implement dynamic completion
 //! `bpaf` needs to check items past the first failure point to collect all the possible
 //! completions.
+
+//! ## Alternative Functors
+//!
+//! So far `Applicative Functors` allow us to create structs containing multiple fields out of
+//! individual parsers for each field. `Alternative` extends `Applicative` with two extra
+//! things: one for choosing a better of two *values in a context* and an idenity element
+//! for this operation. In Rust a closest analogy would be `Option::or` and `Option::None`:
+//!
+//! ```rust
+//! fn pick_number(a: Option<u32>, b: Option<u32>) -> Option<u32> {
+//!     a.or(b)
+//! }
 //!
+//! let present_1 = Some(10);
+//! let present_2 = Some(20);
+//! let empty = None;
+//! assert_eq!(pick_number(present_1, present_2), present_1);
+//! assert_eq!(pick_number(present_1, empty), present_1);
+//! assert_eq!(pick_number(empty, present_1), present_1);
+//! assert_eq!(pick_number(empty, empty), empty);
+//! ```
 
-//! ## `Parser` trait
+//! ## `Parser` trait and `construct!` macro
 //!
-//! Similarly to `Reader` struct described above individual parsers don't actually contain a value
-//! but only a value in context - they contain a computation describing the initial consumer plus
+//! Similarly to `Reader` struct described above parsers don't actually contain any values
+//! but only define a context for them - they contain a computation describing the initial consumer plus
 //! all the transformations and validations layered on top. [`construct!`] macro takes several
-//! parser objects and composes them similarly to `add_numbers` function above.
+//! parser objects and composes them either according to `Applicative` or `Alternative` functor
+//! laws depending on the usage.
+
+//! ## So why use `Applicative Functors` then?
+//!
+//! As a user I want to be able to express requirements using full power of Rust algebraic
+//! datatypes: `struct` for product types and `enum` for sum types. To give an example -
+//! `cargo-show-asm` asks user to specify what to output - Intel or AT&T asm, LLVM or Rust's MIR
+//! and opts to represent it as one of four flags: `--intel`, `--att`, `--llvm` and `--mir`. While
+//! each flag can be though of a boolean value - present/absent - consuming it as an enum with four
+//! possible values is much more convenient compared to tuple with all the possible combinations.
+//! `Applicative` interface gives just enough power to compose simple parsers as an arbitrary tree
+//! ready for consumption.
+//!
+//! As a library author I need to be able to extract information from the tree constructed by user
+//! to generate `--help` information and do command line completion. As long as the tree uses only
+//! `Applicative` powers - it is possible to evaluate it without giving it any input.
+//! Adding `Monadic` powers (deciding what to parse next depending on the previous input) would
+//! make this impossible.
+//!
+//! So `Applicative Functors` sits right in the middle between what users want to express and
+//! library can consume.
 
 #[cfg(doc)]
-use crate::construct;
+use crate::*;
diff --git a/src/args.rs b/src/args.rs
index b9a2141..e2a6686 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -309,20 +309,28 @@ mod inner {
         ) -> Result<(), ParseFailure> {
             let mut pos = 0;
 
+            if self.ambig == 0 {
+                return Ok(());
+            }
+
+            let mut new_items = self.items.to_vec();
+
             // can't iterate over items since it might change in process
             loop {
                 if self.ambig == 0 || pos == self.items.len() {
-                    return Ok(());
+                    break;
                 }
                 // look for ambiguities, resolve or skip them. resolving involves recreating
                 // `items` and `removed`
 
-                if let Arg::Ambiguity(items, os) = &self.items[pos] {
+                if let Arg::Ambiguity(items, os) = &mut new_items[pos] {
                     let flag_ok = items.iter().all(|i| flags.contains(i));
                     let arg_ok = args.contains(&items[0]);
+                    self.ambig -= 1;
 
                     match (flag_ok, arg_ok) {
                         (true, true) => {
+                            // give up and exit
                             let s = os.to_str().unwrap();
                             let msg = format!(
                                 "Parser supports -{} as both option and option-argument, \
@@ -338,8 +346,10 @@ mod inner {
                             return Err(ParseFailure::Stderr(msg));
                         }
                         (true, false) => {
-                            // disambiguate as multiple flags
-                            let mut new_items = self.items.to_vec();
+                            // disambiguate as multiple short flags
+                            let items = std::mem::take(items);
+                            let os = std::mem::take(os);
+
                             new_items.remove(pos);
                             self.removed.remove(pos);
                             for short in items.iter().rev() {
@@ -347,28 +357,33 @@ mod inner {
                                 new_items.insert(pos, Arg::Short(*short, false, OsString::new()));
                                 self.removed.insert(pos, false);
                             }
-                            new_items[pos] = Arg::Short(items[0], false, os.clone());
+                            // items[0] is written twice - in a loop above without `os` and right here,
+                            // with `os` present
+                            new_items[pos] = Arg::Short(items[0], false, os);
+
                             self.remaining += items.len() - 1;
-                            self.items = Rc::from(new_items);
-                            self.ambig -= 1;
                         }
                         (false, true) => {
                             // disambiguate as a single option-argument
-                            let mut new_items = self.items.to_vec();
-                            new_items[pos] = Arg::Short(items[0], true, os.clone());
-                            let word = os.to_str().unwrap()[1 + items[0].len_utf8()..].to_string();
-                            new_items.insert(pos + 1, Arg::Word(OsString::from(word)));
-                            self.items = Rc::from(new_items);
+                            let word = Arg::Word(OsString::from(
+                                &os.to_str().unwrap()[1 + items[0].len_utf8()..],
+                            ));
+                            new_items[pos] = Arg::Short(items[0], true, std::mem::take(os));
+                            new_items.insert(pos + 1, word);
                             self.removed.insert(pos, false);
 
                             self.remaining += 1; // removed Ambiguity, added short and word
-                            self.ambig -= 1;
                         }
-                        (false, false) => {}
+                        (false, false) => {
+                            // can't parse it as neither flag or argument, give up.
+                            // ambiguity will stay and will be reported around meta_youmean
+                        }
                     }
                 }
                 pos += 1;
             }
+            self.items = Rc::from(new_items);
+            Ok(())
         }
     }
 
diff --git a/src/from_os_str.rs b/src/from_os_str.rs
index 1e89fe2..3a29dfd 100644
--- a/src/from_os_str.rs
+++ b/src/from_os_str.rs
@@ -1,13 +1,15 @@
-use std::{ffi::OsString, path::PathBuf, str::FromStr};
+use std::{
+    any::{Any, TypeId},
+    ffi::OsString,
+    path::PathBuf,
+    str::FromStr,
+};
 
 pub(crate) fn parse_os_str<T>(os: OsString) -> Result<T, String>
 where
     T: FromStr + 'static,
     <T as std::str::FromStr>::Err: std::fmt::Display,
 {
-    use std::any::Any;
-    use std::any::*;
-
     if TypeId::of::<T>() == TypeId::of::<OsString>() {
         let anybox: Box<dyn Any> = Box::new(os);
         Ok(*(anybox.downcast::<T>().unwrap()))
diff --git a/src/meta_help.rs b/src/meta_help.rs
index 290c6b9..5a87ea8 100644
--- a/src/meta_help.rs
+++ b/src/meta_help.rs
@@ -185,12 +185,11 @@ impl std::fmt::Display for HelpItem<'_> {
                 let width = f.width().unwrap();
                 if let Some(env) = env {
                     let pad = width - self.full_width();
-                    let val = match std::env::var(env) {
-                        Ok(val) => format!(" = {:?}", val),
-                        Err(std::env::VarError::NotPresent) => ": N/A".to_string(),
-                        Err(std::env::VarError::NotUnicode(_)) => {
-                            ": current value is not utf8".to_string()
-                        }
+
+                    let m_os = std::env::var_os(env);
+                    let val = match &m_os {
+                        Some(s) => std::borrow::Cow::from(format!(" = {:?}", s.to_string_lossy())),
+                        None => std::borrow::Cow::Borrowed(": N/A"),
                     };
                     let next_pad = 4 + self.full_width();
                     write!(f, "{:pad$}  [env:{}{}]", "", env, val, pad = pad,)?;
@@ -200,7 +199,7 @@ impl std::fmt::Display for HelpItem<'_> {
                 }
                 Ok(())
             }
-            HelpItem::Decor { help } => return write!(f, "  {}", help),
+            HelpItem::Decor { help } => write!(f, "  {}", help),
             HelpItem::BlankDecor => Ok(()),
             HelpItem::Positional {
                 metavar,
@@ -310,8 +309,7 @@ impl HelpItem<'_> {
 
     fn help(&self) -> Option<&str> {
         match self {
-            HelpItem::Decor { help } => Some(help),
-            HelpItem::BlankDecor => None,
+            HelpItem::Decor { .. } | HelpItem::BlankDecor => None,
             HelpItem::Command { help, .. }
             | HelpItem::Flag { help, .. }
             | HelpItem::Argument { help, .. }
diff --git a/src/params.rs b/src/params.rs
index 9f17f32..8adf014 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -866,38 +866,35 @@ fn parse_word(
     metavar: &'static str,
     help: &Option<String>,
 ) -> Result<OsString, Error> {
-    match args.take_positional_word()? {
-        Some((is_strict, word)) => {
-            if strict && !is_strict {
-                #[cfg(feature = "autocomplete")]
-                args.push_value("--", &Some("-- Positional only items".to_owned()), false);
-
-                return Err(Error::Stderr(format!(
-                    "Expected <{}> to be on the right side of --",
-                    metavar,
-                )));
-            }
-            #[cfg(feature = "autocomplete")]
-            if args.touching_last_remove() && !args.no_pos_ahead {
-                args.push_metadata(metavar, help, false);
-                args.no_pos_ahead = true;
-            }
-            Ok(word)
-        }
-        None => {
+    if let Some((is_strict, word)) = args.take_positional_word()? {
+        if strict && !is_strict {
             #[cfg(feature = "autocomplete")]
-            if !args.no_pos_ahead {
-                args.push_metadata(metavar, help, false);
-                args.no_pos_ahead = true;
-            }
+            args.push_value("--", &Some("-- Positional only items".to_owned()), false);
 
-            let item = Item::Positional {
+            return Err(Error::Stderr(format!(
+                "Expected <{}> to be on the right side of --",
                 metavar,
-                help: help.clone(),
-                strict,
-            };
-            Err(Error::Missing(vec![item]))
+            )));
+        }
+        #[cfg(feature = "autocomplete")]
+        if args.touching_last_remove() && !args.no_pos_ahead {
+            args.push_metadata(metavar, help, false);
+            args.no_pos_ahead = true;
+        }
+        Ok(word)
+    } else {
+        #[cfg(feature = "autocomplete")]
+        if !args.no_pos_ahead {
+            args.push_metadata(metavar, help, false);
+            args.no_pos_ahead = true;
         }
+
+        let item = Item::Positional {
+            metavar,
+            help: help.clone(),
+            strict,
+        };
+        Err(Error::Missing(vec![item]))
     }
 }
 


Test patch:
diff --git a/bpaf_derive/src/top_tests.rs b/bpaf_derive/src/top_tests.rs
index fb0d6a8..be6ff6e 100644
--- a/bpaf_derive/src/top_tests.rs
+++ b/bpaf_derive/src/top_tests.rs
@@ -140,6 +140,41 @@ fn struct_command() {
     assert_eq!(input.to_token_stream().to_string(), expected.to_string());
 }
 
+#[test]
+fn struct_command_short() {
+    let input: Top = parse_quote! {
+        /// those are options
+        #[bpaf(command, short('x'))]
+        struct O{}
+    };
+
+    let expected = quote! {
+        fn o() -> impl ::bpaf::Parser<O> {
+            #[allow (unused_imports)]
+            use ::bpaf::Parser;
+            {
+                let inner_cmd =
+                    { ::bpaf::construct!(O{}) }
+                    .to_options()
+                    .descr("those are options")
+                ;
+                ::bpaf::command("o", inner_cmd)
+                    .help("those are options")
+                    .short('x')
+            }
+        }
+    };
+    assert_eq!(input.to_token_stream().to_string(), expected.to_string());
+}
+
+#[should_panic(expected = "Can't construct a parser from empty enum")]
+#[test]
+fn empty_enum() {
+    let _: Top = parse_quote! {
+        enum Opt { }
+    };
+}
+
 #[test]
 fn enum_command() {
     let input: Top = parse_quote! {
diff --git a/tests/completion.rs b/tests/completion.rs
index 83d9bd9..1e27377 100644
--- a/tests/completion.rs
+++ b/tests/completion.rs
@@ -837,7 +837,7 @@ fn test_zsh_comp<T: std::fmt::Debug>(
 
 #[test]
 #[should_panic(expected = "Parser supports ")]
-fn ambiguity() {
+fn ambiguity_no_resolve() {
     #[derive(Debug, Clone)]
     enum A {
         V(Vec<bool>),
@@ -851,6 +851,26 @@ fn ambiguity() {
     test_zsh_comp(&parser, &["-aaa"], &[["-aaa", "", "", ""]]);
 }
 
+#[test]
+fn ambiguity_to_flags() {
+    let parser = short('a').switch().many().to_options();
+
+    // TODO - this should probably suggest "-aaa" and exit
+    test_zsh_comp(&parser, &["-aaa"], &[]);
+}
+
+#[test]
+fn ambiguity_to_arg() {
+    let parser = short('a').argument::<String>("AAAAAA").to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["-aaa"]).set_comp(1))
+        .unwrap_err()
+        .unwrap_stdout();
+    // TODO - this should probably suggest "-aaa" and exit
+    assert_eq!(r, "aa\n");
+}
+
 #[test]
 fn zsh_style_completion_visible() {
     let a = short('a')
diff --git a/tests/tests.rs b/tests/tests.rs
index 883603a..949e308 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -244,6 +244,43 @@ Available options:
     assert!(!res);
 }
 
+#[test]
+fn fallback_with_ok() {
+    let parser = short('a')
+        .argument("ARG")
+        .fallback_with::<_, &str>(|| Ok(10u32))
+        .to_options();
+
+    let r = parser.run_inner(Args::from(&["-a", "1"])).unwrap();
+    assert_eq!(r, 1);
+
+    let r = parser.run_inner(Args::from(&[])).unwrap();
+    assert_eq!(r, 10);
+}
+
+#[test]
+fn fallback_with_err() {
+    let parser = short('a')
+        .argument::<u32>("ARG")
+        .fallback_with::<_, &str>(|| Err("nope"))
+        .to_options();
+
+    let r = parser.run_inner(Args::from(&["-a", "1"])).unwrap();
+    assert_eq!(r, 1);
+
+    let r = parser
+        .run_inner(Args::from(&["-a", "x"]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "Couldn't parse \"x\": invalid digit found in string");
+
+    let r = parser
+        .run_inner(Args::from(&[]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "nope");
+}
+
 #[test]
 fn default_arguments() {
     let a = short('a').argument::<i32>("ARG").fallback(42);
@@ -1628,3 +1665,53 @@ fn default_for_many() {
     let r = parser.run_inner(Args::from(&[])).unwrap();
     assert_eq!(r, vec![String::from(".")]);
 }
+
+#[test]
+fn parse_option_catch() {
+    #[derive(Debug, Clone, Eq, PartialEq)]
+    enum A {
+        U32(u32),
+        S(String),
+    }
+    let a1 = short('a').argument("N").map(A::U32).optional().catch();
+    let a2 = short('a').argument("S").map(A::S).optional().catch();
+    let parser = construct!([a1, a2]).to_options();
+
+    let r = parser.run_inner(Args::from(&["-a", "10"])).unwrap();
+    assert_eq!(r, Some(A::U32(10)));
+
+    let r = parser.run_inner(Args::from(&["-a", "x"])).unwrap();
+    assert_eq!(r, Some(A::S("x".to_string())));
+
+    let r = parser.run_inner(Args::from(&[])).unwrap();
+    assert_eq!(r, None);
+}
+
+#[test]
+fn parse_some_catch() {
+    #[derive(Debug, Clone, Eq, PartialEq)]
+    enum A {
+        U32(u32),
+        S(String),
+    }
+    let a1 = short('a')
+        .argument("N")
+        .map(A::U32)
+        .some("A")
+        .catch()
+        .hide();
+    let a2 = short('a').argument("S").map(A::S).some("A").catch().hide();
+    let parser = construct!([a1, a2]).to_options();
+
+    let r = parser.run_inner(Args::from(&["-a", "10"])).unwrap();
+    assert_eq!(r, vec![A::U32(10)]);
+
+    let r = parser.run_inner(Args::from(&["-a", "x"])).unwrap();
+    assert_eq!(r, vec![A::S("x".to_string())]);
+
+    let r = parser
+        .run_inner(Args::from(&[]))
+        .unwrap_err()
+        .unwrap_stderr();
+    assert_eq!(r, "A");
+}