From 649303a4026abcc1edc21b3cb4ade819043ef47d Mon Sep 17 00:00:00 2001 From: Michael Baikov Date: Mon, 19 Sep 2022 12:53:06 -0400 Subject: [PATCH] move (a copy of) cargo_helper into batteries and redo the exampels --- docs/src/cargo_helper/cases.txt | 25 +++++++++++ docs/src/cargo_helper/combine.rs | 19 ++++++++ docs/src/cargo_helper/derive.rs | 13 ++++++ src/batteries.rs | 32 ++++++++++++- src/docs/cargo_helper.md | 77 ++++++++++++++++++++++++++++++++ src/lib.rs | 64 ++++---------------------- 6 files changed, 174 insertions(+), 56 deletions(-) create mode 100644 docs/src/cargo_helper/cases.txt create mode 100644 docs/src/cargo_helper/combine.rs create mode 100644 docs/src/cargo_helper/derive.rs create mode 100644 src/docs/cargo_helper.md diff --git a/docs/src/cargo_helper/cases.txt b/docs/src/cargo_helper/cases.txt new file mode 100644 index 00000000..ac668f73 --- /dev/null +++ b/docs/src/cargo_helper/cases.txt @@ -0,0 +1,25 @@ +? Let's say the goal is to parse an argument and a switch: +> --argument 15 +OK +Options { argument: 15, switch: false } + +? But when used as a `cargo` subcommand, cargo will also pass the command name, this example +? uses _wrong_ subcommand name to bypass the helper and show how it would look without it +> wrong --argument 15 +Stderr +No such command: `wrong`, did you mean `-s`? + +? When used with the right command - helper simply consumes it +> pretty --argument 42 -s +OK +Options { argument: 42, switch: true } + +? And it doesn't show up in `--help` so not to confuse users +> --help +Stdout +Usage: --argument ARG [-s] + +Available options: + --argument An argument + -s A switch + -h, --help Prints help information diff --git a/docs/src/cargo_helper/combine.rs b/docs/src/cargo_helper/combine.rs new file mode 100644 index 00000000..87305fc2 --- /dev/null +++ b/docs/src/cargo_helper/combine.rs @@ -0,0 +1,19 @@ +// +use bpaf::*; +// +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct Options { + argument: usize, + switch: bool, +} + +pub fn options() -> OptionParser { + let argument = long("argument") + .help("An argument") + .argument::("ARG"); + let switch = short('s').help("A switch").switch(); + let options = construct!(Options { argument, switch }); + + cargo_helper("pretty", options).to_options() +} diff --git a/docs/src/cargo_helper/derive.rs b/docs/src/cargo_helper/derive.rs new file mode 100644 index 00000000..5d38a1ac --- /dev/null +++ b/docs/src/cargo_helper/derive.rs @@ -0,0 +1,13 @@ +// +use bpaf::*; +// +#[allow(dead_code)] +#[derive(Debug, Clone, Bpaf)] +#[bpaf(options("pretty"))] +pub struct Options { + /// An argument + argument: usize, + /// A switch + #[bpaf(short)] + switch: bool, +} diff --git a/src/batteries.rs b/src/batteries.rs index 5c5f46ab..816d17dc 100644 --- a/src/batteries.rs +++ b/src/batteries.rs @@ -8,7 +8,7 @@ //! Examples contain combinatoric usage, for derive usage you should create a parser function and //! use `external` annotation. -use crate::{construct, parsers::NamedArg, short, Parser}; +use crate::{construct, parsers::NamedArg, positional, short, Parser}; /// `--verbose` and `--quiet` flags with results encoded as number /// @@ -135,3 +135,33 @@ pub fn toggle_flag( let b = b.req_flag(val_b); construct!([a, b]).many().map(|xs| xs.into_iter().last()) } + +/// Strip a command name if present at the front when used as a `cargo` command +/// +/// When implementing a cargo subcommand parser needs to be able to skip the first argument which +/// is always the same as the executable name without `cargo-` prefix. For example if executable name is +/// `cargo-cmd` so first argument would be `cmd`. `cargo_helper` helps to support both invocations: +/// with name present when used via cargo and without it when used locally. +/// +/// You can read the code of this functions as this approximate sequence of statements: +/// 1. Want to parse a word +/// 2. Word must match a given string literal +/// 3. It's okay if it's missing +/// 4. It's also okay when it's not matching expectations, don't consume it in this case +/// 5. And don't show anything to the user in `--help` or completion +/// 6. Parse this word and then everything else as a tuple, return that second item. +/// +#[doc = include_str!("docs/cargo_helper.md")] +/// +#[must_use] +pub fn cargo_helper(cmd: &'static str, parser: P) -> impl Parser +where + P: Parser, +{ + let skip = positional::("cmd") + .guard(move |s| s == cmd, "") + .optional() + .catch() + .hide(); + construct!(skip, parser).map(|x| x.1) +} diff --git a/src/docs/cargo_helper.md b/src/docs/cargo_helper.md new file mode 100644 index 00000000..6257ab12 --- /dev/null +++ b/src/docs/cargo_helper.md @@ -0,0 +1,77 @@ +
+Combinatoric usage + +```no_run +# use bpaf::*; +# #[allow(dead_code)] +#[derive(Debug, Clone)] +pub struct Options { + argument: usize, + switch: bool, +} + +pub fn options() -> OptionParser { + let argument = long("argument") + .help("An argument") + .argument::("ARG"); + let switch = short('s').help("A switch").switch(); + let options = construct!(Options { argument, switch }); + + cargo_helper("pretty", options).to_options() +} +``` + +
+
+Derive usage + +```no_run +# use bpaf::*; +# #[allow(dead_code)] +#[derive(Debug, Clone, Bpaf)] +#[bpaf(options("pretty"))] +pub struct Options { + /// An argument + argument: usize, + /// A switch + #[bpaf(short)] + switch: bool, +} +``` + +
+
+Examples + + +Let's say the goal is to parse an argument and a switch: +```console +% app --argument 15 +Options { argument: 15, switch: false } +``` + +But when used as a `cargo` subcommand, cargo will also pass the command name, this example +uses _wrong_ subcommand name to bypass the helper and show how it would look without it +```console +% app wrong --argument 15 +No such command: `wrong`, did you mean `-s`? +``` + +When used with the right command - helper simply consumes it +```console +% app pretty --argument 42 -s +Options { argument: 42, switch: true } +``` + +And it doesn't show up in `--help` so not to confuse users +```console +% app --help +Usage: --argument ARG [-s] + +Available options: + --argument An argument + -s A switch + -h, --help Prints help information +``` + +
diff --git a/src/lib.rs b/src/lib.rs index d7e0dabb..598c8df5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -318,6 +318,8 @@ pub mod _derive_tutorial; mod _flow; mod arg; mod args; +#[cfg(feature = "batteries")] +pub mod batteries; #[cfg(feature = "autocomplete")] mod complete_gen; #[cfg(feature = "autocomplete")] @@ -333,8 +335,6 @@ mod structs; #[cfg(test)] mod tests; -pub mod batteries; - #[doc(hidden)] pub use crate::info::Error; use crate::item::Item; @@ -1753,64 +1753,18 @@ impl ParseFailure { /// Strip a command name if present at the front when used as a `cargo` command /// -/// When implementing a cargo subcommand parser needs to be able to skip the first argument which -/// is always the same as the executable name without `cargo-` prefix. For example if executable name is -/// `cargo-cmd` so first argument would be `cmd`. `cargo_helper` helps to support both invocations: -/// with name present when used via cargo and without it when used locally. -/// -/// # Combinatoric usage -/// ```rust -/// # use bpaf::*; -/// fn options() -> OptionParser<(u32, u32)> { -/// let width = short('w').argument::("PX"); -/// let height = short('h').argument::("PX"); -/// let parser = construct!(width, height); -/// cargo_helper("cmd", parser).to_options() -/// } -/// ``` -/// -/// # Derive usage -/// -/// If you pass a cargo command name as a parameter to `options` annotation `bpaf_derive` would generate `cargo_helper`. -/// ```no_run -/// # use bpaf::*; -/// #[derive(Debug, Clone, Bpaf)] -/// #[bpaf(options("cmd"))] -/// struct Options { -/// #[bpaf(short, argument("PX"))] -/// width: u32, -/// #[bpaf(short, argument("PX"))] -/// height: u32, -/// } -/// -/// fn main() { -/// println!("{:?}", options().run()); -/// } -/// -/// ``` -/// -/// # Example -/// -/// ```console -/// $ cargo cmd -w 3 -h 5 -/// (3, 5) -/// $ cargo run --bin cargo-cmd -- -w 3 -h 5 -/// (3, 5) -/// ``` +/// See batteries::cargo_helper #[must_use] +#[doc(hidden)] pub fn cargo_helper(cmd: &'static str, parser: P) -> impl Parser where T: 'static, P: Parser, { - let eat_command = positional::("").parse(move |s| { - if cmd == s { - Ok(()) - } else { - Err(String::new()) - } - }); - let ignore_non_command = pure(()); - let skip = construct!([eat_command, ignore_non_command]).hide(); + let skip = positional::("cmd") + .guard(move |s| s == cmd, "") + .optional() + .catch() + .hide(); construct!(skip, parser).map(|x| x.1) }