Repository: pacak/bpaf (rust)

Issue:
Question: Default/Fallback for many/some?
Hello, first of all many thanks for this beautiful library!

I was wondering how I could achieve providing a calculated default for a `.many()` parser as demonstrated in the following test. (I also tried using `.some(..)` but that fails as well :/) Any ideas?

```rust
    #[test]
    fn default_positional() {
        use bpaf;

        let roots = bpaf::positional::<String>("ROOTS")
            .fallback_with::<_, String>(|| Ok(String::from(".")))
            .many();

        struct Roots { roots: Vec<String>, }

        let r = construct!(Roots {roots}).to_options().run_inner(Args::from(&[]));
        assert_eq!(vec![String::from(".")], r.unwrap().roots);
    }
```

Pull request description:
Provide `pure_with` parser
* Adds a `pure_with` parser to be the lazily computed, fallible equivalent to `pure`
* Resolves #79

Patch:
diff --git a/src/lib.rs b/src/lib.rs
index c281b61..724afe1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -356,8 +356,8 @@ pub mod parsers {
 
 use structs::{
     ParseAdjacent, ParseAnywhere, ParseFail, ParseFallback, ParseFallbackWith, ParseGroupHelp,
-    ParseGuard, ParseHide, ParseMany, ParseMap, ParseOptional, ParseOrElse, ParsePure, ParseSome,
-    ParseWith,
+    ParseGuard, ParseHide, ParseMany, ParseMap, ParseOptional, ParseOrElse, ParsePure,
+    ParsePureWith, ParseSome, ParseWith,
 };
 
 #[cfg(feature = "autocomplete")]
@@ -1716,6 +1716,30 @@ pub fn pure<T>(val: T) -> ParsePure<T> {
     ParsePure(val)
 }
 
+///
+/// Wrap a lazily calculated value into a `Parser`
+///
+/// This parser represents an equivalent to [`pure`] with the function of [`Parser::fallback_with`].
+/// It produces `T`  by invoking the provided callback without consuming anything from the command
+/// line, can be useful with [`construct!`]. As with any parsers `T` should be `Clone` and `Debug`.
+///
+/// # Combinatoric usage
+/// ```rust
+/// # use bpaf::*;
+/// fn pair() -> impl Parser<bool> {
+///     let a = long("flag-a").switch();
+///     let b = pure_with::<_, _, String>(|| {/* search for history file and try to fish out the last used value ...*/ Ok(false)});
+///     construct!([a, b])
+/// }
+/// ```
+pub fn pure_with<T, F, E>(val: F) -> ParsePureWith<T, F, E>
+where
+    F: Fn() -> Result<T, E>,
+    E: ToString,
+{
+    ParsePureWith(val)
+}
+
 /// Fail with a fixed error message
 ///
 /// This parser produces `T` of any type but instead of producing it when asked - it fails
diff --git a/src/structs.rs b/src/structs.rs
index 16a16f5..e403bbe 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -482,6 +482,25 @@ impl<T: Clone + 'static> Parser<T> for ParsePure<T> {
     }
 }
 
+pub struct ParsePureWith<T, F, E>(pub(crate) F)
+where
+    F: Fn() -> Result<T, E>,
+    E: ToString;
+impl<T: Clone + 'static, F: Fn() -> Result<T, E>, E: ToString> Parser<T>
+    for ParsePureWith<T, F, E>
+{
+    fn eval(&self, _args: &mut Args) -> Result<T, Error> {
+        match (self.0)() {
+            Ok(ok) => Ok(ok),
+            Err(e) => Err(Error::Stderr(e.to_string())),
+        }
+    }
+
+    fn meta(&self) -> Meta {
+        Meta::Skip
+    }
+}
+
 /// Parser that fails without consuming any input, created with [`fail`](crate::fail).
 pub struct ParseFail<T> {
     pub(crate) field1: &'static str,


Test patch:
diff --git a/tests/tests.rs b/tests/tests.rs
index 883603a..45786fb 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1628,3 +1628,62 @@ fn default_for_many() {
     let r = parser.run_inner(Args::from(&[])).unwrap();
     assert_eq!(r, vec![String::from(".")]);
 }
+
+#[test]
+fn default_value_using_pure_with_ok() {
+    // ~ this is a bit artifactial (we'd use fallback_with instead actually), but it demonstrates the concept
+    let a = short('t').argument::<u32>("N");
+    let b = pure_with::<_, _, String>(|| Ok(42u32));
+
+    let opts = construct!([a, b]).to_options();
+
+    let r = opts.run_inner(Args::from(&["-t", "87"]));
+    assert_eq!(87, r.unwrap());
+
+    let r = opts.run_inner(Args::from(&[]));
+    assert_eq!(42, r.unwrap());
+}
+
+#[test]
+fn default_value_using_pure_with_err() {
+    let a = short('t').argument::<u32>("N");
+    let b = pure_with::<_, _, String>(|| Err(String::from("some-err")));
+
+    let opts = construct!([a, b]).to_options();
+    let r = opts.run_inner(Args::from(&[]));
+    let e = r.unwrap_err().unwrap_stderr();
+    assert_eq!("some-err", e);
+}
+
+#[test]
+fn default_value_using_pure_with_ok_for_some() {
+    let user_seeds = positional::<u32>("SEED").some("at least one required");
+    let last_seeds = pure_with::<_, _, String>(|| {
+        // ~ trying to lookup the last used seeds
+        Ok(vec![3, 5, 7, 11])
+    });
+    let seeds = construct!([user_seeds, last_seeds]).to_options();
+
+    let r = seeds.run_inner(Args::from(&["23", "59"]));
+    assert_eq!(vec![23, 59], r.unwrap());
+
+    let r = seeds.run_inner(Args::from(&[]));
+    assert_eq!(vec![3, 5, 7, 11], r.unwrap());
+}
+
+#[test]
+fn default_value_using_pure_with_err_for_some() {
+    let user_seeds = positional::<u32>("SEED").some("at least one required");
+    let last_seeds = pure_with(|| {
+        // ~ trying to lookup the last used seeds but we're failing
+        Err("oh, no!")
+    });
+    let default_seeds = pure(vec![1, 2]);
+    let seeds = construct!([user_seeds, last_seeds, default_seeds]).to_options();
+
+    let r = seeds.run_inner(Args::from(&["23", "59"]));
+    assert_eq!(vec![23, 59], r.unwrap());
+
+    let r = seeds.run_inner(Args::from(&[]));
+    assert_eq!(r.unwrap(), vec![1, 2]);
+}