Repository: pacak/bpaf
Pull request #257 (2023-08-03 18:13:14)

changes for 0.9.4
- add `help` to `ParseFlag` and `ParseArgument `
- stop deprecating `Parser::run`
- documentation


Patch:
diff --git a/Changelog.md b/Changelog.md
index b5b1646..6734b1d 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,5 +1,11 @@
 # Change Log
 
+## bpaf [0.9.4] - Unreleased
+- add `help` to `ParseFlag` and `ParseArgument `
+- stop deprecating `Parser::run`
+- documentation
+- changes to rendered markdown
+
 ## bpaf [0.9.3], bpaf_derive [0.5.3] - 2023-07-26
 - `Parser::collect` allows to collect multiple items into an arbitrary `FromIterator` collection
 - Bugfix in parsing for unit structs
diff --git a/docs2/src/deb_fallback/cases.md b/docs2/src/deb_fallback/cases.md
new file mode 100644
index 0000000..03cb00c
--- /dev/null
+++ b/docs2/src/deb_fallback/cases.md
@@ -0,0 +1,17 @@
+`fallback` changes parser to fallback to a default value used when argument is not specified
+
+>
+
+If value is present - fallback value is ignored
+
+> --jobs 10
+
+Parsing errors are preserved and preserved to user
+
+> --jobs ten
+
+With [`display_fallback`](ParseFallback::display_fallback) and
+[`debug_fallback`](ParseFallback::debug_fallback) you can make it so default value
+is visible in `--help` output
+
+> --help
diff --git a/docs2/src/deb_fallback/combine.rs b/docs2/src/deb_fallback/combine.rs
new file mode 100644
index 0000000..ec34530
--- /dev/null
+++ b/docs2/src/deb_fallback/combine.rs
@@ -0,0 +1,15 @@
+//
+use bpaf::*;
+#[derive(Debug, Clone)]
+pub struct Options {
+    jobs: usize,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let jobs = long("jobs")
+        .help("Number of jobs")
+        .argument("JOBS")
+        .fallback(42)
+        .debug_fallback();
+    construct!(Options { jobs }).to_options()
+}
diff --git a/docs2/src/deb_fallback/derive.rs b/docs2/src/deb_fallback/derive.rs
new file mode 100644
index 0000000..21222ca
--- /dev/null
+++ b/docs2/src/deb_fallback/derive.rs
@@ -0,0 +1,10 @@
+//
+use bpaf::*;
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+#[allow(dead_code)]
+pub struct Options {
+    /// Number of jobs
+    #[bpaf(argument("JOBS"), fallback(42), debug_fallback)]
+    jobs: usize,
+}
diff --git a/docs2/src/deb_fallback_with/cases.md b/docs2/src/deb_fallback_with/cases.md
new file mode 100644
index 0000000..eb613b9
--- /dev/null
+++ b/docs2/src/deb_fallback_with/cases.md
@@ -0,0 +1,16 @@
+`fallback_with` changes parser to fallback to a value that comes from a potentially failing
+computation when argument is not specified
+
+>
+
+If value is present - fallback value is ignored
+
+> --version 10
+
+Parsing errors are preserved and preserved to user
+
+> --version ten
+
+`bpaf` encases parsers with fallback value of some sort in usage with `[]`
+
+> --help
diff --git a/docs2/src/deb_fallback_with/combine.rs b/docs2/src/deb_fallback_with/combine.rs
new file mode 100644
index 0000000..8e0af07
--- /dev/null
+++ b/docs2/src/deb_fallback_with/combine.rs
@@ -0,0 +1,19 @@
+//
+use bpaf::*;
+fn try_to_get_version() -> Result<usize, &'static str> {
+    Ok(42)
+}
+
+#[derive(Debug, Clone)]
+pub struct Options {
+    version: usize,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let version = long("version")
+        .help("Specify protocol version")
+        .argument("VERS")
+        .fallback_with(try_to_get_version)
+        .debug_fallback();
+    construct!(Options { version }).to_options()
+}
diff --git a/docs2/src/deb_fallback_with/derive.rs b/docs2/src/deb_fallback_with/derive.rs
new file mode 100644
index 0000000..3edfac6
--- /dev/null
+++ b/docs2/src/deb_fallback_with/derive.rs
@@ -0,0 +1,13 @@
+//
+use bpaf::*;
+fn try_to_get_version() -> Result<usize, &'static str> {
+    Ok(42)
+}
+
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+pub struct Options {
+    #[bpaf(argument("VERS"), fallback_with(try_to_get_version), debug_fallback)]
+    /// Specify protocol version
+    version: usize,
+}
diff --git a/docs2/src/dis_fallback/cases.md b/docs2/src/dis_fallback/cases.md
new file mode 100644
index 0000000..03cb00c
--- /dev/null
+++ b/docs2/src/dis_fallback/cases.md
@@ -0,0 +1,17 @@
+`fallback` changes parser to fallback to a default value used when argument is not specified
+
+>
+
+If value is present - fallback value is ignored
+
+> --jobs 10
+
+Parsing errors are preserved and preserved to user
+
+> --jobs ten
+
+With [`display_fallback`](ParseFallback::display_fallback) and
+[`debug_fallback`](ParseFallback::debug_fallback) you can make it so default value
+is visible in `--help` output
+
+> --help
diff --git a/docs2/src/dis_fallback/combine.rs b/docs2/src/dis_fallback/combine.rs
new file mode 100644
index 0000000..551a4e0
--- /dev/null
+++ b/docs2/src/dis_fallback/combine.rs
@@ -0,0 +1,15 @@
+//
+use bpaf::*;
+#[derive(Debug, Clone)]
+pub struct Options {
+    jobs: usize,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let jobs = long("jobs")
+        .help("Number of jobs")
+        .argument("JOBS")
+        .fallback(42)
+        .display_fallback();
+    construct!(Options { jobs }).to_options()
+}
diff --git a/docs2/src/dis_fallback/derive.rs b/docs2/src/dis_fallback/derive.rs
new file mode 100644
index 0000000..d355aaa
--- /dev/null
+++ b/docs2/src/dis_fallback/derive.rs
@@ -0,0 +1,10 @@
+//
+use bpaf::*;
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+#[allow(dead_code)]
+pub struct Options {
+    /// Number of jobs
+    #[bpaf(argument("JOBS"), fallback(42), display_fallback)]
+    jobs: usize,
+}
diff --git a/docs2/src/dis_fallback_with/cases.md b/docs2/src/dis_fallback_with/cases.md
new file mode 100644
index 0000000..eb613b9
--- /dev/null
+++ b/docs2/src/dis_fallback_with/cases.md
@@ -0,0 +1,16 @@
+`fallback_with` changes parser to fallback to a value that comes from a potentially failing
+computation when argument is not specified
+
+>
+
+If value is present - fallback value is ignored
+
+> --version 10
+
+Parsing errors are preserved and preserved to user
+
+> --version ten
+
+`bpaf` encases parsers with fallback value of some sort in usage with `[]`
+
+> --help
diff --git a/docs2/src/dis_fallback_with/combine.rs b/docs2/src/dis_fallback_with/combine.rs
new file mode 100644
index 0000000..00ffb22
--- /dev/null
+++ b/docs2/src/dis_fallback_with/combine.rs
@@ -0,0 +1,19 @@
+//
+use bpaf::*;
+fn try_to_get_version() -> Result<usize, &'static str> {
+    Ok(42)
+}
+
+#[derive(Debug, Clone)]
+pub struct Options {
+    version: usize,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let version = long("version")
+        .help("Specify protocol version")
+        .argument("VERS")
+        .fallback_with(try_to_get_version)
+        .display_fallback();
+    construct!(Options { version }).to_options()
+}
diff --git a/docs2/src/dis_fallback_with/derive.rs b/docs2/src/dis_fallback_with/derive.rs
new file mode 100644
index 0000000..346c687
--- /dev/null
+++ b/docs2/src/dis_fallback_with/derive.rs
@@ -0,0 +1,13 @@
+//
+use bpaf::*;
+fn try_to_get_version() -> Result<usize, &'static str> {
+    Ok(42)
+}
+
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+pub struct Options {
+    #[bpaf(argument("VERS"), fallback_with(try_to_get_version), display_fallback)]
+    /// Specify protocol version
+    version: usize,
+}
diff --git a/src/docs2/deb_fallback.md b/src/docs2/deb_fallback.md
new file mode 100644
index 0000000..f7abe00
--- /dev/null
+++ b/src/docs2/deb_fallback.md
@@ -0,0 +1,113 @@
+<details><summary>Combinatoric example</summary>
+
+```no_run
+#[derive(Debug, Clone)]
+pub struct Options {
+    jobs: usize,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let jobs = long("jobs")
+        .help("Number of jobs")
+        .argument("JOBS")
+        .fallback(42)
+        .debug_fallback();
+    construct!(Options { jobs }).to_options()
+}
+
+fn main() {
+    println!("{:?}", options().run())
+}
+```
+
+</details>
+<details><summary>Derive example</summary>
+
+```no_run
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+#[allow(dead_code)]
+pub struct Options {
+    /// Number of jobs
+    #[bpaf(argument("JOBS"), fallback(42), debug_fallback)]
+    jobs: usize,
+}
+
+fn main() {
+    println!("{:?}", options().run())
+}
+```
+
+</details>
+<details><summary>Output</summary>
+
+`fallback` changes parser to fallback to a default value used when argument is not specified
+
+
+<div class='bpaf-doc'>
+$ app <br>
+Options { jobs: 42 }
+</div>
+
+
+If value is present - fallback value is ignored
+
+
+<div class='bpaf-doc'>
+$ app --jobs 10<br>
+Options { jobs: 10 }
+</div>
+
+
+Parsing errors are preserved and preserved to user
+
+
+<div class='bpaf-doc'>
+$ app --jobs ten<br>
+<b>Error:</b> couldn't parse <b>ten</b>: invalid digit found in string
+<style>
+div.bpaf-doc {
+    padding: 14px;
+    background-color:var(--code-block-background-color);
+    font-family: "Source Code Pro", monospace;
+    margin-bottom: 0.75em;
+}
+div.bpaf-doc dt { margin-left: 1em; }
+div.bpaf-doc dd { margin-left: 3em; }
+div.bpaf-doc dl { margin-top: 0; padding-left: 1em; }
+div.bpaf-doc  { padding-left: 1em; }
+</style>
+</div>
+
+
+With [`display_fallback`](ParseFallback::display_fallback) and
+[`debug_fallback`](ParseFallback::debug_fallback) you can make it so default value
+is visible in `--help` output
+
+
+<div class='bpaf-doc'>
+$ app --help<br>
+<p><b>Usage</b>: <tt><b>app</b></tt> [<tt><b>--jobs</b></tt>=<tt><i>JOBS</i></tt>]</p><p><div>
+<b>Available options:</b></div><dl><dt><tt><b>    --jobs</b></tt>=<tt><i>JOBS</i></tt></dt>
+<dd>Number of jobs</dd>
+<dt></dt>
+<dd>[default: 42]</dd>
+<dt><tt><b>-h</b></tt>, <tt><b>--help</b></tt></dt>
+<dd>Prints help information</dd>
+</dl>
+</p>
+<style>
+div.bpaf-doc {
+    padding: 14px;
+    background-color:var(--code-block-background-color);
+    font-family: "Source Code Pro", monospace;
+    margin-bottom: 0.75em;
+}
+div.bpaf-doc dt { margin-left: 1em; }
+div.bpaf-doc dd { margin-left: 3em; }
+div.bpaf-doc dl { margin-top: 0; padding-left: 1em; }
+div.bpaf-doc  { padding-left: 1em; }
+</style>
+</div>
+
+</details>
\ No newline at end of file
diff --git a/src/docs2/deb_fallback_with.md b/src/docs2/deb_fallback_with.md
new file mode 100644
index 0000000..5788d23
--- /dev/null
+++ b/src/docs2/deb_fallback_with.md
@@ -0,0 +1,119 @@
+<details><summary>Combinatoric example</summary>
+
+```no_run
+fn try_to_get_version() -> Result<usize, &'static str> {
+    Ok(42)
+}
+
+#[derive(Debug, Clone)]
+pub struct Options {
+    version: usize,
+}
+
+pub fn options() -> OptionParser<Options> {
+    let version = long("version")
+        .help("Specify protocol version")
+        .argument("VERS")
+        .fallback_with(try_to_get_version)
+        .debug_fallback();
+    construct!(Options { version }).to_options()
+}
+
+fn main() {
+    println!("{:?}", options().run())
+}
+```
+
+</details>
+<details><summary>Derive example</summary>
+
+```no_run
+fn try_to_get_version() -> Result<usize, &'static str> {
+    Ok(42)
+}
+
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+pub struct Options {
+    #[bpaf(argument("VERS"), fallback_with(try_to_get_version), debug_fallback)]
+    /// Specify protocol version
+    version: usize,
+}
+
+fn main() {
+    println!("{:?}", options().run())
+}
+```
+
+</details>
+<details><summary>Output</summary>
+
+`fallback_with` changes parser to fallback to a value that comes from a potentially failing
+computation when argument is not specified
+
+
+<div class='bpaf-doc'>
+$ app <br>
+Options { version: 42 }
+</div>
+
+
+If value is present - fallback value is ignored
+
+
+<div class='bpaf-doc'>
+$ app --version 10<br>
+Options { version: 10 }
+</div>
+
+
+Parsing errors are preserved and preserved to user
+
+
+<div class='bpaf-doc'>
+$ app --version ten<br>
+<b>Error:</b> couldn't parse <b>ten</b>: invalid digit found in string
+<style>
+div.bpaf-doc {
+    padding: 14px;
+    background-color:var(--code-block-background-color);
+    font-family: "Source Code Pro", monospace;
+    margin-bottom: 0.75em;
+}
+div.bpaf-doc dt { margin-left: 1em; }
+div.bpaf-doc dd { margin-left: 3em; }
+div.bpaf-doc dl { margin-top: 0; padding-left: 1em; }
+div.bpaf-doc  { padding-left: 1em; }
+</style>
+</div>
+
+
+`bpaf` encases parsers with fallback value of some sort in usage with `[]`
+
+
+<div class='bpaf-doc'>
+$ app --help<br>
+<p><b>Usage</b>: <tt><b>app</b></tt> [<tt><b>--version</b></tt>=<tt><i>VERS</i></tt>]</p><p><div>
+<b>Available options:</b></div><dl><dt><tt><b>    --version</b></tt>=<tt><i>VERS</i></tt></dt>
+<dd>Specify protocol version</dd>
+<dt></dt>
+<dd>[default: 42]</dd>
+<dt><tt><b>-h</b></tt>, <tt><b>--help</b></tt></dt>
+<dd>Prints help information</dd>
+</dl>
+</p>
+<style>
+div.bpaf-doc {
+    padding: 14px;
+    background-color:var(--code-block-background-color);
+    font-family: "Source Code Pro", monospace;
+    margin-bottom: 0.75em;
+}
+div.bpaf-doc dt { margin-left: 1em; }
+div.bpaf-doc dd { margin-left: 3em; }
+div.bpaf-doc dl { margin-top: 0; padding-left: 1em; }
+div.bpaf-doc  { padding-left: 1em; }
+</style>
+</div>
+
+</details>
\ No newline at end of file
diff --git a/src/docs2/fallback.md b/src/docs2/dis_fallback.md
similarity index 100%
rename from src/docs2/fallback.md
rename to src/docs2/dis_fallback.md
diff --git a/src/docs2/fallback_with.md b/src/docs2/dis_fallback_with.md
similarity index 100%
rename from src/docs2/fallback_with.md
rename to src/docs2/dis_fallback_with.md
diff --git a/src/lib.rs b/src/lib.rs
index 77c8bd0..4e83a86 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -195,7 +195,9 @@ pub mod parsers {
     #[doc(inline)]
     pub use crate::complete_shell::ParseCompShell;
     #[doc(inline)]
-    pub use crate::params::{NamedArg, ParseAny, ParseArgument, ParseCommand, ParsePositional};
+    pub use crate::params::{
+        NamedArg, ParseAny, ParseArgument, ParseCommand, ParseFlag, ParsePositional,
+    };
     #[doc(inline)]
     pub use crate::structs::{
         ParseCollect, ParseCon, ParseCount, ParseFallback, ParseFallbackWith, ParseLast, ParseMany,
@@ -869,7 +871,7 @@ pub trait Parser<T> {
     ///
     /// Parser would still fail if value is present but failure comes from some transformation
     ///
-    #[cfg_attr(not(doctest), doc = include_str!("docs2/fallback.md"))]
+    #[cfg_attr(not(doctest), doc = include_str!("docs2/dis_fallback.md"))]
     ///
     /// # See also
     /// [`fallback_with`](Parser::fallback_with) would allow to try to fallback to a value that
@@ -895,7 +897,7 @@ pub trait Parser<T> {
     ///
     /// Would still fail if value is present but failure comes from some earlier transformation
     ///
-    #[cfg_attr(not(doctest), doc = include_str!("docs2/fallback_with.md"))]
+    #[cfg_attr(not(doctest), doc = include_str!("docs2/dis_fallback_with.md"))]
     ///
     /// # See also
     /// [`fallback`](Parser::fallback) implements similar logic expect that failures aren't expected.
@@ -1209,8 +1211,18 @@ pub trait Parser<T> {
     }
     // }}}
 
-    #[doc(hidden)]
-    #[deprecated = "You should finalize the parser first: see Parser::to_options"]
+    /// Finalize and run the parser
+    ///
+    /// Generally you'd want to use [`Parser::to_options`] to finalize the parser and [`OptionParser::run`],
+    /// but this also works for simple cases:
+    ///
+    /// ```no_run
+    /// # use bpaf::*;
+    /// fn main() {
+    ///     let name = short('n').long("name").argument::<String>("USER").run();
+    ///     // do things with name
+    /// }
+    /// ```
     fn run(self) -> T
     where
         Self: Sized + Parser<T> + 'static,
@@ -1222,7 +1234,7 @@ pub trait Parser<T> {
     ///
     ///
 
-    /// Boxed parser doesn't expose internal representation in it's type and allows to return
+    /// Boxed parser doesn't expose internal representation in its type and allows to return
     /// different parsers in different conditional branches
     ///
     /// You can create it with a single argument `construct` macro or by using `boxed` annotation
diff --git a/src/params.rs b/src/params.rs
index 0c7be3f..50005ba 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -193,7 +193,7 @@ impl NamedArg {
     ///
     #[cfg_attr(not(doctest), doc = include_str!("docs2/switch.md"))]
     #[must_use]
-    pub fn switch(self) -> impl Parser<bool> {
+    pub fn switch(self) -> ParseFlag<bool> {
         build_flag_parser(true, Some(false), self)
     }
 
@@ -203,7 +203,7 @@ impl NamedArg {
     /// [`bool`].
     #[cfg_attr(not(doctest), doc = include_str!("docs2/flag.md"))]
     #[must_use]
-    pub fn flag<T>(self, present: T, absent: T) -> impl Parser<T>
+    pub fn flag<T>(self, present: T, absent: T) -> ParseFlag<T>
     where
         T: Clone + 'static,
     {
@@ -514,7 +514,8 @@ where
 }
 
 #[derive(Clone)]
-struct ParseFlag<T> {
+/// Parser for a named switch, created with [`NamedArg::flag`] or [`NamedArg::switch`]
+pub struct ParseFlag<T> {
     present: T,
     absent: Option<T>,
     named: NamedArg,
@@ -551,6 +552,34 @@ impl<T: Clone + 'static> Parser<T> for ParseFlag<T> {
     }
 }
 
+impl<T> ParseFlag<T> {
+    /// Add a help message to `flag`
+    ///
+    /// See [`NamedArg::help`]
+    #[must_use]
+    pub fn help<M>(mut self, help: M) -> Self
+    where
+        M: Into<Doc>,
+    {
+        self.named.help = Some(help.into());
+        self
+    }
+}
+
+impl<T> ParseArgument<T> {
+    /// Add a help message to an `argument`
+    ///
+    /// See [`NamedArg::help`]
+    #[must_use]
+    pub fn help<M>(mut self, help: M) -> Self
+    where
+        M: Into<Doc>,
+    {
+        self.named.help = Some(help.into());
+        self
+    }
+}
+
 fn build_argument<T>(named: NamedArg, metavar: &'static str) -> ParseArgument<T> {
     ParseArgument {
         named,
diff --git a/src/structs.rs b/src/structs.rs
index 8ca99d1..9dbc08d 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -495,7 +495,7 @@ impl<P, T: std::fmt::Display> ParseFallback<P, T> {
     /// Show [`fallback`](Parser::fallback) value in `--help` using [`Display`](std::fmt::Display)
     /// representation
     ///
-    #[cfg_attr(not(doctest), doc = include_str!("docs2/fallback.md"))]
+    #[cfg_attr(not(doctest), doc = include_str!("docs2/dis_fallback.md"))]
     #[must_use]
     pub fn display_fallback(mut self) -> Self {
         self.value_str = format!("[default: {}]", self.value);
@@ -507,7 +507,7 @@ impl<P, T: std::fmt::Debug> ParseFallback<P, T> {
     /// Show [`fallback`](Parser::fallback) value in `--help` using [`Debug`](std::fmt::Debug)
     /// representation
     ///
-    #[cfg_attr(not(doctest), doc = include_str!("docs2/fallback_with.md"))]
+    #[cfg_attr(not(doctest), doc = include_str!("docs2/deb_fallback_with.md"))]
     #[must_use]
     pub fn debug_fallback(mut self) -> Self {
         self.value_str = format!("[default: {:?}]", self.value);
@@ -524,7 +524,7 @@ where
     ///
     /// If fallback function fails - no value will show up
     ///
-    #[cfg_attr(not(doctest), doc = include_str!("docs2/fallback_with.md"))]
+    #[cfg_attr(not(doctest), doc = include_str!("docs2/dis_fallback_with.md"))]
     #[must_use]
     pub fn display_fallback(mut self) -> Self {
         if let Ok(val) = (self.fallback)() {
@@ -543,7 +543,7 @@ where
     ///
     /// If fallback function fails - no value will show up
     ///
-    #[cfg_attr(not(doctest), doc = include_str!("docs2/fallback.md"))]
+    #[cfg_attr(not(doctest), doc = include_str!("docs2/deb_fallback.md"))]
     #[must_use]
     pub fn debug_fallback(mut self) -> Self {
         if let Ok(val) = (self.fallback)() {


Test patch:
diff --git a/tests/help_format.rs b/tests/help_format.rs
index 20ee16f..2c502c7 100644
--- a/tests/help_format.rs
+++ b/tests/help_format.rs
@@ -1,5 +1,13 @@
 use bpaf::*;
 
+#[test]
+fn help_after_switch() {
+    let parser = short('a').switch().help("this is help").to_options();
+    let r = parser.run_inner(&["--help"]).unwrap_err().unwrap_stdout();
+    let expected =   "Usage: [-a]\n\nAvailable options:\n    -a          this is help\n    -h, --help  Prints help information\n";
+    assert_eq!(r, expected);
+}
+
 #[test]
 fn fancy_meta() {
     let a = long("trailing-comma").argument::<String>("all|es5|none");