Repository: pacak/bpaf
Pull request #236 (2023-07-13 13:13:58)

Release 0.9.2
Main change for 0.9.2 is a way to generate pure markdown user documentation for app's command line


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 1e23406..6616e02 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "bpaf"
-version = "0.9.1"
+version = "0.9.2"
 edition = "2021"
 categories = ["command-line-interface"]
 description = "A simple Command Line Argument Parser with parser combinators"
@@ -19,7 +19,7 @@ include = [
 
 
 [dependencies]
-bpaf_derive = { path = "./bpaf_derive", version = "=0.5.1", optional = true }
+bpaf_derive = { path = "./bpaf_derive", version = "=0.5.2", optional = true }
 owo-colors = { version = "3.5.0", default-features = false, optional = true }
 supports-color = { version = "2.0.0", optional = true }
 
diff --git a/Changelog.md b/Changelog.md
index 5db0023..6120bdc 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,6 +1,6 @@
 # Change Log
 
-## bpaf [0.9.2], bpaf_derive [0.5.2] - Unreleased
+## bpaf [0.9.2], bpaf_derive [0.5.2] - 2023-07-13
 - with `docgen` feature you can render documentation as markdown
 - cosmetic changes to error messages
 
diff --git a/bpaf_cauwugo/Cargo.toml b/bpaf_cauwugo/Cargo.toml
index 5f20341..b108cb3 100644
--- a/bpaf_cauwugo/Cargo.toml
+++ b/bpaf_cauwugo/Cargo.toml
@@ -12,7 +12,7 @@ description = "A cargo frontend with dynamic completion"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bpaf = { version = "0.9.0", path = "../", features = ["derive", "autocomplete"] }
+bpaf = { version = "0.9.2", path = "../", features = ["derive", "autocomplete"] }
 cargo_metadata = "0.15.0"
 once_cell = "1.13.1"
 
diff --git a/bpaf_derive/Cargo.toml b/bpaf_derive/Cargo.toml
index 9774caa..7dd47f3 100644
--- a/bpaf_derive/Cargo.toml
+++ b/bpaf_derive/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "bpaf_derive"
-version = "0.5.1"
+version = "0.5.2"
 edition = "2018"
 categories = ["command-line-interface"]
 description = "Derive macros for bpaf Command Line Argument Parser"
diff --git a/examples/basic.rs b/examples/basic.rs
index 12cac3e..faced68 100644
--- a/examples/basic.rs
+++ b/examples/basic.rs
@@ -1,4 +1,4 @@
-//! A somewhat comprehensive example of a typical combinatoric `bpaf` usage.
+//! A somewhat comprehensive example of a typical combinatoric `bpaf` usage in combinatoric mode
 
 use bpaf::*;
 use std::path::PathBuf;
@@ -37,7 +37,8 @@ fn opts() -> OptionParser<Out> {
         .long("speed")
         .help("Set speed")
         .argument::<f64>("SPEED") // you can specify a type to parse
-        .fallback(42.0);
+        .fallback(42.0)
+        .display_fallback();
 
     let output = short('o')
         .long("output")
@@ -46,7 +47,12 @@ fn opts() -> OptionParser<Out> {
         .complete_shell(ShellComp::File { mask: None });
 
     // no magical name transmogrifications in combinatoric API,
-    let nb_cars = short('n').long("nb-cars").argument::<u32>("N");
+    let nb_cars = short('n')
+        .long("nb-cars")
+        .help("Number of items to process")
+        .argument::<u32>("N")
+        .fallback(1)
+        .display_fallback();
 
     // a parser that consumes one argument
     // you can build the inner parser in one go or as multiple steps giving each step a name
diff --git a/examples/cargo-cmd.rs b/examples/cargo-cmd.rs
index e19aae7..175dbbe 100644
--- a/examples/cargo-cmd.rs
+++ b/examples/cargo-cmd.rs
@@ -1,8 +1,8 @@
 //! When implementing a cargo subcommand parser needs to be able to skip the first argument which
 //! is always the same as the executable name. For this example executable name is `cargo-cmd` so
 //! first argument would be `cmd`. A way to support both cases - when it's present and it's absent
-//! would be to use `positional_if`. `bpaf` also provides a helper `cargo_helper` that does exactly
-//! this.
+//! would be to use a combination of `literal`, `optional` and `hide`.
+//! `bpaf` also provides a helper `cargo_helper` that does exactly this.
 
 use bpaf::*;
 
@@ -19,10 +19,7 @@ fn main() {
     let height = short('h').argument::<usize>("HEIGHT").fallback(10);
     let parser = construct!(Opts { width, height });
 
-    let cmd = positional::<String>("")
-        .guard(|s| s == "cmd", "")
-        .optional()
-        .hide();
+    let cmd = literal("cmd").optional().hide();
     let combined_parser = construct!(cmd, parser).map(|x| x.1);
 
     let opts = combined_parser.to_options().run();
diff --git a/examples/coreutils.rs b/examples/coreutils.rs
index 08fff8d..9abc7f4 100644
--- a/examples/coreutils.rs
+++ b/examples/coreutils.rs
@@ -1,4 +1,5 @@
-/// WIP
+//! This example implements parsers for a few coreutils tools - they tend to have complicated CLI
+//! rules due to historical reasons
 use bpaf::*;
 
 mod boilerplate {
diff --git a/examples/csample.rs b/examples/csample.rs
index fd1ec23..c34a862 100644
--- a/examples/csample.rs
+++ b/examples/csample.rs
@@ -1,3 +1,5 @@
+//! This example shows dynamic shell completion features
+
 fn complete_calculator(input: &String) -> Vec<(&'static str, Option<&'static str>)> {
     let items = ["alpha", "beta", "banana", "cat", "durian"];
     items
@@ -23,12 +25,12 @@ fn main() {
         .footer(
             "\
     bpaf supports dynamic autocompletion for a few shells, make sure your binary is in $PATH
-    and try using one of those this output should go into a file that depends on your shell:
+     and try using one of those this output should go into a file that depends on your shell:
     $ csample --bpaf-complete-style-bash
     $ csample --bpaf-complete-style-zsh
     $ csample --bpaf-complete-style-fish
     $ csample --bpaf-complete-style-elvish",
         );
 
-    println!("{:?}", parser.run());
+    println!("{:?}", parser.fallback_to_usage().run());
 }
diff --git a/examples/derive-smart-pointer.rs b/examples/derive-smart-pointer.rs
new file mode 100644
index 0000000..53c0be8
--- /dev/null
+++ b/examples/derive-smart-pointer.rs
@@ -0,0 +1,32 @@
+//! With minimal changes you can parse directly into smart pointers.
+//!
+//! While Bpaf derive macro knows nothing about Box or Arc it lets you
+//! to parse into them by overriding the parsing sequence.
+//!
+//! Here `name` first parsed into a regular `String` then converted
+//! into `Box<str>` using `map`.
+//!
+//! Similarly a set of coins is parsed into a regular vector and later converted into Arc still
+//! inside the parser
+
+#![allow(dead_code)]
+
+use bpaf::*;
+use std::sync::Arc;
+
+#[derive(Debug, Clone, Bpaf)]
+#[bpaf(options)]
+pub struct Options {
+    #[bpaf(argument::<String>("NAME"), map(Box::from))]
+    /// Adventurer's name
+    name: Box<str>,
+
+    #[bpaf(positional::<usize>("COIN"), many, map(Arc::from))]
+    /// A set of coins
+    coins: Arc<[usize]>,
+}
+
+fn main() {
+    let xs = options().fallback_to_usage().run();
+    println!("{xs:?}");
+}
diff --git a/examples/derive_commands.rs b/examples/derive_commands.rs
index 543ad0f..21e342d 100644
--- a/examples/derive_commands.rs
+++ b/examples/derive_commands.rs
@@ -1,8 +1,8 @@
 //! Snippet from cargo-hackerman crate, shows how to use derive to parse commands and
-//! "positional_if" pattern.
+//! conditional skip for options
 //!
 //! Command explain takes 3 parameters: required crate name and optional feature and crate version,
-//! user is allowed to omitt either field. This example uses simplified is_version, in practice youwould
+//! user is allowed to omit either field. This example uses simplified is_version, in practice you would
 //! would use semver crate.
 //!
 //! End user would be able to run commands like
@@ -31,15 +31,23 @@ pub enum Action {
 }
 
 fn feature_if() -> impl Parser<Option<String>> {
+    // here feature starts as any string on a command line that does not start with a dash
     positional::<String>("FEATURE")
+        // guard restricts it such that it can't be a valid version
         .guard(move |s| !is_version(s), "")
+        // last two steps describe what to do with strings in this position but are actually
+        // versions.
+        // optional allows parser to represent an ignored value with None
         .optional()
+        // and catch lets optional to handle parse failures coming from guard
+        .catch()
 }
 
 fn version_if() -> impl Parser<Option<String>> {
     positional::<String>("VERSION")
         .guard(move |s| is_version(s), "")
         .optional()
+        .catch()
 }
 
 fn is_version(v: &str) -> bool {
@@ -47,5 +55,5 @@ fn is_version(v: &str) -> bool {
 }
 
 fn main() {
-    println!("{:?}", action().run());
+    println!("{:?}", action().fallback_to_usage().run());
 }
diff --git a/examples/derive_rudo.rs b/examples/derive_rudo.rs
index 0a4d1e3..891303a 100644
--- a/examples/derive_rudo.rs
+++ b/examples/derive_rudo.rs
@@ -1,4 +1,5 @@
 /// parser inspired by https://github.com/hood/rudo/blob/e448942b752c56dd2be2e2bb5026ced45e215ed6/src/main.rs
+///
 use bpaf::*;
 
 #[derive(Debug, Clone, Bpaf)]
diff --git a/examples/derive_this_or_that.rs b/examples/derive_this_or_that.rs
index ed7fc6f..bf34c12 100644
--- a/examples/derive_this_or_that.rs
+++ b/examples/derive_this_or_that.rs
@@ -1,5 +1,5 @@
-//! deriving for tri-state enabled/disabled/undecided switch, non derive version would use
-//! combination of `req_flag` and `construct!([on, off, undecined])`
+//! deriving for tri-state enabled/disabled/undecided switch, combinatoric version would use
+//! combination of `req_flag` and `construct!([on, off])`
 
 #![allow(dead_code)]
 use bpaf::Bpaf;
@@ -15,7 +15,7 @@ enum Opts {
     /// disabled
     Off,
     /// undecined
-    #[bpaf(hide)]
+    #[bpaf(skip)]
     Undecided,
 }
 
diff --git a/examples/env_logger.rs b/examples/env_logger.rs
index c5a1b90..058e364 100644
--- a/examples/env_logger.rs
+++ b/examples/env_logger.rs
@@ -29,12 +29,10 @@ fn verbose() -> impl Parser<LevelFilter> {
     short('v')
         .help("Verbosity level, use multiple times for more verbosity")
         .req_flag(())
-        .many()
-        .map(|v| {
+        .count()
+        .map(|l| {
             use LevelFilter::*;
-            *[Off, Error, Warn, Info, Debug, Trace]
-                .get(v.len())
-                .unwrap_or(&Trace)
+            [Off, Error, Warn, Info, Debug, Trace][l.max(5)]
         })
 }
 
diff --git a/examples/many_comma_separated_args.rs b/examples/many_comma_separated_args.rs
index 858b328..f21b3a7 100644
--- a/examples/many_comma_separated_args.rs
+++ b/examples/many_comma_separated_args.rs
@@ -1,3 +1,4 @@
+/// To parse comma separated values it's easier to treat them as strings
 use bpaf::*;
 use std::str::FromStr;
 
@@ -7,8 +8,8 @@ fn args() -> impl Parser<Vec<u16>> {
         .help("Comma separated list of ports")
         .argument::<String>("PORTS")
         .parse(|s| {
-            s.split(",")
-                .map(|c| u16::from_str(c))
+            s.split(',')
+                .map(u16::from_str)
                 .collect::<Result<Vec<_>, _>>()
         })
         .many()
diff --git a/examples/many_comma_separated_args_derive.rs b/examples/many_comma_separated_args_derive.rs
index 5886227..e9c2d65 100644
--- a/examples/many_comma_separated_args_derive.rs
+++ b/examples/many_comma_separated_args_derive.rs
@@ -1,9 +1,10 @@
+/// To parse comma separated values it's easier to treat them as strings
 use bpaf::*;
 use std::{num::ParseIntError, str::FromStr};
 
 fn split_and_parse(s: String) -> Result<Vec<u16>, ParseIntError> {
-    s.split(",")
-        .map(|c| u16::from_str(c))
+    s.split(',')
+        .map(u16::from_str)
         .collect::<Result<Vec<_>, _>>()
 }
 
diff --git a/examples/numeric_prefix.rs b/examples/numeric_prefix.rs
index d06f399..d8b843d 100644
--- a/examples/numeric_prefix.rs
+++ b/examples/numeric_prefix.rs
@@ -1,3 +1,14 @@
+/// You can parse multiple positional elements with earlier being optional as well
+/// This example takes two - optional numeric prefix and a command name:
+///
+/// > numeric_prefix 8 work
+/// Options { prefix: Some(8), command: "work" }
+///
+/// > numeric_prefix sleep
+/// Options { prefix: None, command: "sleep" }
+///
+/// Generated usage reflects that:
+/// Usage: numeric_prefix [PREFIX] COMMAND
 use bpaf::*;
 
 #[derive(Debug, Clone)]
diff --git a/examples/simple_dynamic.rs b/examples/simple_dynamic.rs
index cba1e54..9d7bb2e 100644
--- a/examples/simple_dynamic.rs
+++ b/examples/simple_dynamic.rs
@@ -1,3 +1,5 @@
+//! Simple dynamic completion example
+
 #![allow(dead_code)]
 use bpaf::*;
 
diff --git a/examples/verbose.rs b/examples/verbose.rs
index e3af50f..062a67b 100644
--- a/examples/verbose.rs
+++ b/examples/verbose.rs
@@ -19,12 +19,8 @@ fn main() {
     // and fails otherwise;
     let verbose = short('v').long("verbose").req_flag(());
 
-    // .many() tries to appy parser as many times as possible and collects the results.
-    // We can't use non failing parse with .many() since it will loop forever.
-    let verbose = verbose.many();
-
-    // Then count how many times parser succeeded
-    let verbose = verbose.map(|v| v.len());
+    // Try to apply the inner parser as many times as it succeeds, return the number
+    let verbose = verbose.count();
 
     // And add a simple sanity checker.
     // By this time when this parser succeeds - it will contain verbosity in 0..3 range, inclusive.
diff --git a/examples/xorg.rs b/examples/xorg.rs
index 0f9a451..d1ec715 100644
--- a/examples/xorg.rs
+++ b/examples/xorg.rs
@@ -1,3 +1,4 @@
+/// A way to represent xorg like flags, not a typical usage
 use bpaf::*;
 #[derive(Debug, Clone)]
 #[allow(dead_code)]
diff --git a/src/args.rs b/src/args.rs
index ac597c3..3e36025 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -698,7 +698,9 @@ impl State {
 
     /// take a static string argument from the first present argument
     pub(crate) fn take_cmd(&mut self, word: &str) -> bool {
-        if let Some((ix, Arg::Word(w))) = self.items_iter().next() {
+        if let Some((ix, Arg::Word(w) | Arg::Short(_, _, w) | Arg::Long(_, false, w))) =
+            self.items_iter().next()
+        {
             if w == word {
                 self.remove(ix);
                 self.current = Some(ix);
diff --git a/src/doc.rs b/src/doc.rs
index 81c1543..7e1fc45 100644
--- a/src/doc.rs
+++ b/src/doc.rs
@@ -1,5 +1,27 @@
 //! Documentation generation system
 //!
+//! # Command line parser documentation generation
+//!
+//! [`OptionParser`] implements two methods: [`render_html`](OptionParser::render_html) and
+//! [`render_manpage`](OptionParser::render_manpage) that create a documentation in a mix of
+//! html/markdown and ROFF formats respectively.
+//!
+//! To use it you should do something like this
+//! ```
+//! #[test]
+//! fn update_doc() {
+//!     # use bpaf::*;
+//!     # let options = || short('a').switch().to_options();
+//!     let options = options();
+//!     let md = options.render_markdown("app_name");
+//!     let roff = options.render_manpage("app_name", Section::General, None, None, None);
+//!     # drop(md); drop(roff);
+//!     // then save those docs into a files
+//!     // If you commit those docs into your repo and optionally fail a test if there
+//!     // are changes - CI will ensure that documentation is always up to date
+//! }
+//! ```
+//!
 //! # Documentation fragments to use inside `--help` messages
 //!
 //! `bpaf` tries to use semantic approach to documentation generation, instead of describing what
@@ -26,24 +48,6 @@
 //!
 //! 4. A structure from your own crate that can be converted into `Doc`
 //!
-//! # Command line parser documentation generation
-//!
-//! [`OptionParser`] implements two methods: [`render_html`](OptionParser::render_html) and
-//! [`render_manpage`](OptionParser::render_manpage) that create a documentation in a mix of
-//! html/markdown and ROFF formats respectively.
-//!
-//! To use it you should do something like this
-//! ```ignore
-//! #[test]
-//! fn update_doc() {
-//!     let options = options();
-//!     let html = options.render_html("app_name");
-//!     let roff = options.render_manpage("app_name", Section::General, None, None, None);
-//!     // then save those docs into a files
-//!     // If you commit those docs into your repo and optionally fail a test if there
-//!     // are changes - CI will ensure that documentation is always up to date
-//! }
-//! ```
 
 #[doc(inline)]
 pub use crate::buffer::{Doc, MetaInfo, Style};
diff --git a/src/docs2/simple_dynamic.md b/src/docs2/simple_dynamic.md
index c5213c6..ab8d3c4 100644
--- a/src/docs2/simple_dynamic.md
+++ b/src/docs2/simple_dynamic.md
@@ -1,6 +1,8 @@
 <details><summary><tt>examples/simple_dynamic.rs</tt></summary>
 
 ```no_run
+//! Simple dynamic completion example
+
 #![allow(dead_code)]
 use bpaf::*;
 
diff --git a/src/lib.rs b/src/lib.rs
index 0b33544..e76a2e7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -257,9 +257,10 @@
 //!
 //! - [`complete`](Parser::complete) and [`complete_shell`](Parser::complete_shell)
 //!
-//! And finally you can generate documentation for command line in html-markdown mix and manpage
-//! formats using [`render_html`](OptionParser::render_html) and
-//! [`render_manpage`](OptionParser::render_manpage), for more detailed info see [`doc`] module
+//! And finally you can generate documentation for command line in markdown, html and manpage
+//! formats using [`render_markdown`](OptionParser::render_markdown),
+//! [`render_html`](OptionParser::render_html) and [`render_manpage`](OptionParser::render_manpage),
+//! for more detailed info see [`doc`] module
 //!
 //! ## 6. Testing your parsers and running them
 //! - You can [`OptionParser::run`] the parser on the arguments passed on the command line


Test patch:
diff --git a/tests/tests.rs b/tests/tests.rs
index 1c20e3b..ca529cd 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1762,3 +1762,28 @@ fn fallback_for_some() {
     let r = parser.run_inner(&[]).unwrap();
     assert_eq!(r, vec![1, 2, 3]);
 }
+
+#[test]
+fn flag_like_commands() {
+    let a = short('a').req_flag(1).to_options().command("--add");
+    let b = short('b').req_flag(2).to_options().command("remove");
+    let parser = construct!([a, b]).to_options();
+
+    let r = parser.run_inner(&["--add", "-a"]).unwrap();
+    assert_eq!(r, 1);
+
+    let r = parser.run_inner(&["remove", "-b"]).unwrap();
+    assert_eq!(r, 2);
+
+    let r = parser.run_inner(&["--help"]).unwrap_err().unwrap_stdout();
+    let expected = "Usage: COMMAND ...\n\nAvailable options:\n    -h, --help  Prints help information\n\nAvailable commands:\n    --add\n    remove\n";
+    assert_eq!(r, expected);
+
+    let r = parser
+        .run_inner(&["--add", "--help"])
+        .unwrap_err()
+        .unwrap_stdout();
+    let expected =
+        "Usage: --add -a\n\nAvailable options:\n    -a\n    -h, --help  Prints help information\n";
+    assert_eq!(r, expected);
+}