Skip to content

Commit

Permalink
move (a copy of) cargo_helper into batteries
Browse files Browse the repository at this point in the history
and redo the exampels
  • Loading branch information
pacak committed Sep 19, 2022
1 parent 56a5c3d commit 649303a
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 56 deletions.
25 changes: 25 additions & 0 deletions docs/src/cargo_helper/cases.txt
Original file line number Diff line number Diff line change
@@ -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 <ARG> An argument
-s A switch
-h, --help Prints help information
19 changes: 19 additions & 0 deletions docs/src/cargo_helper/combine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
use bpaf::*;
//
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Options {
argument: usize,
switch: bool,
}

pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("An argument")
.argument::<usize>("ARG");
let switch = short('s').help("A switch").switch();
let options = construct!(Options { argument, switch });

cargo_helper("pretty", options).to_options()
}
13 changes: 13 additions & 0 deletions docs/src/cargo_helper/derive.rs
Original file line number Diff line number Diff line change
@@ -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,
}
32 changes: 31 additions & 1 deletion src/batteries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down Expand Up @@ -135,3 +135,33 @@ pub fn toggle_flag<T: Copy + 'static>(
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<P, T>(cmd: &'static str, parser: P) -> impl Parser<T>
where
P: Parser<T>,
{
let skip = positional::<String>("cmd")
.guard(move |s| s == cmd, "")
.optional()
.catch()
.hide();
construct!(skip, parser).map(|x| x.1)
}
77 changes: 77 additions & 0 deletions src/docs/cargo_helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<details>
<summary>Combinatoric usage</summary>

```no_run
# use bpaf::*;
# #[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Options {
argument: usize,
switch: bool,
}
pub fn options() -> OptionParser<Options> {
let argument = long("argument")
.help("An argument")
.argument::<usize>("ARG");
let switch = short('s').help("A switch").switch();
let options = construct!(Options { argument, switch });
cargo_helper("pretty", options).to_options()
}
```

</details>
<details>
<summary>Derive usage</summary>

```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,
}
```

</details>
<details>
<summary>Examples</summary>


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 <ARG> An argument
-s A switch
-h, --help Prints help information
```

</details>
64 changes: 9 additions & 55 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -333,8 +335,6 @@ mod structs;
#[cfg(test)]
mod tests;

pub mod batteries;

#[doc(hidden)]
pub use crate::info::Error;
use crate::item::Item;
Expand Down Expand Up @@ -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::<u32>("PX");
/// let height = short('h').argument::<u32>("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<P, T>(cmd: &'static str, parser: P) -> impl Parser<T>
where
T: 'static,
P: Parser<T>,
{
let eat_command = positional::<std::ffi::OsString>("").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::<String>("cmd")
.guard(move |s| s == cmd, "")
.optional()
.catch()
.hide();
construct!(skip, parser).map(|x| x.1)
}

0 comments on commit 649303a

Please sign in to comment.