bpaf/batteries.rs
1//! # Batteries included - helpful parsers that use only public API
2//!
3//! `bpaf` comes with a few extra functions that use only public API in their implementation. You
4//! might find them useful either for your code or as an inspiration source
5//!
6//! **To use anything in this module you need to enable `batteries` cargo feature.**
7//!
8//! Examples contain combinatoric usage, for derive usage you should create a parser function and
9//! use `external` annotation.
10
11use crate::{construct, literal, parsers::NamedArg, short, Parser};
12
13/// `--verbose` and `--quiet` flags with results encoded as number
14///
15/// Parameters specify the offset and minimal/maximal values. Parser accepts many `-v | --verbose` and
16/// `-q | --quiet` to increase and decrease verbosity respectively
17///
18/// # Usage
19///
20/// ```rust
21/// # use bpaf::*;
22/// use bpaf::batteries::*;
23/// fn verbose() -> impl Parser<usize> {
24/// verbose_and_quiet_by_number(2, 0, 5).map(|v| v as usize)
25/// }
26/// ```
27#[must_use]
28pub fn verbose_and_quiet_by_number(offset: isize, min: isize, max: isize) -> impl Parser<isize> {
29 #![allow(clippy::cast_possible_wrap)]
30 let verbose = short('v')
31 .long("verbose")
32 .help("Increase output verbosity, can be used several times")
33 .req_flag(())
34 .many()
35 .map(|v| v.len() as isize);
36
37 let quiet = short('q')
38 .long("quiet")
39 .help("Decrease output verbosity, can be used several times")
40 .req_flag(())
41 .many()
42 .map(|v| v.len() as isize);
43
44 construct!(verbose, quiet).map(move |(v, q)| (v - q + offset).clamp(min, max))
45}
46
47/// `--verbose` and `--quiet` flags with results choosen from a slice of values
48///
49/// Parameters specify an array of possible values and a default index
50///
51/// # Usage
52/// ```rust
53/// # use bpaf::*;
54/// use bpaf::batteries::*;
55///
56/// #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
57/// enum Level {
58/// Error,
59/// Warning,
60/// Info,
61/// Debug,
62/// Trace,
63/// }
64///
65/// fn verbose() -> impl Parser<Level> {
66/// use Level::*;
67/// verbose_by_slice(2, [Error, Warning, Info, Debug, Trace])
68/// }
69/// # let parser = verbose().to_options();
70/// # let res = parser.run_inner(&[]).unwrap();
71/// # assert_eq!(Level::Info, res);
72/// # let res = parser.run_inner(&["-q"]).unwrap();
73/// # assert_eq!(Level::Warning, res);
74/// # let res = parser.run_inner(&["-qqq"]).unwrap();
75/// # assert_eq!(Level::Error, res);
76/// # let res = parser.run_inner(&["-qqqq"]).unwrap();
77/// # assert_eq!(Level::Error, res);
78/// # let res = parser.run_inner(&["-vvvvq"]).unwrap();
79/// # assert_eq!(Level::Trace, res);
80/// ```
81#[must_use]
82pub fn verbose_by_slice<T: Copy + 'static, const N: usize>(
83 offset: usize,
84 items: [T; N],
85) -> impl Parser<T> {
86 #![allow(clippy::cast_possible_wrap)]
87 #![allow(clippy::cast_sign_loss)]
88 verbose_and_quiet_by_number(offset as isize, 0, items.len() as isize - 1)
89 .map(move |i| items[i as usize])
90}
91
92/// Pick last passed value between two different flags
93///
94/// Usually `bpaf` only allows to parse a single instance for every invocation unless
95/// you specify [`many`](Parser::many) or [`some`](Parser::some). `toggle_flag` would consume
96/// multiple instances of two different flags and returns last specified value.
97///
98/// This function relies on a fact that selection between two different parsers prefers left most
99/// value. This helps to preserve relative order of parsrs.
100/// You can use similar approach to combine multiple flags accounting for their relative order.
101///
102/// Parser returns `Optional<T>` value, you can add a fallback with [`map`](Parser::map) or turn
103/// missing value info failure with a custom error message with [`parse`](Parser::parse).
104///
105/// # Example
106/// ```console
107/// $ app --banana --no-banana --banana --banana
108/// Some(Banana)
109/// $ app
110/// None
111/// ```
112///
113/// # Usage
114/// ```rust
115/// # use bpaf::*;
116/// use bpaf::batteries::toggle_flag;
117///
118/// #[derive(Copy, Clone, Debug, PartialEq, Eq)]
119/// enum Select {
120/// Banana,
121/// NoBanana,
122/// }
123///
124/// fn pick() -> impl Parser<Option<Select>> {
125/// toggle_flag(long("banana"), Select::Banana, long("no-banana"), Select::NoBanana)
126/// }
127/// ```
128pub fn toggle_flag<T: Copy + 'static>(
129 a: NamedArg,
130 val_a: T,
131 b: NamedArg,
132 val_b: T,
133) -> impl Parser<Option<T>> {
134 let a = a.req_flag(val_a);
135 let b = b.req_flag(val_b);
136 construct!([a, b]).many().map(|xs| xs.into_iter().last())
137}
138
139/// Strip a command name if present at the front when used as a `cargo` command
140///
141/// When implementing a cargo subcommand parser needs to be able to skip the first argument which
142/// is always the same as the executable name without `cargo-` prefix. For example if executable name is
143/// `cargo-cmd` so first argument would be `cmd`. `cargo_helper` helps to support both invocations:
144/// with name present when used via cargo and without it when used locally.
145///
146/// You can read the code of this function as this approximate sequence of statements:
147/// 1. Try to parse a string literal that corresponds to a command name
148/// 2. It's okay if it's missing
149/// 3. And don't show anything to the user in `--help` or completion
150/// 4. Parse this word and then everything else as a tuple, return that second item.
151///
152#[cfg_attr(not(doctest), doc = include_str!("docs2/cargo_helper.md"))]
153///
154#[must_use]
155pub fn cargo_helper<P, T>(cmd: &'static str, parser: P) -> impl Parser<T>
156where
157 P: Parser<T>,
158{
159 let skip = literal(cmd).optional().hide();
160 construct!(skip, parser).map(|x| x.1)
161}
162
163/// Get usage for a parser
164///
165/// In some cases you might want to print usage if user gave no command line options, in this case
166/// you should add an enum variant to a top level enum, make it hidden with `#[bpaf(hide)]`, make
167/// it default for the top level parser with something like `#[bpaf(fallback(Arg::Help))]`.
168///
169/// When handling cases you can do something like this for `Help` variant:
170///
171/// ```ignore
172/// ...
173/// Arg::Help => {
174/// println!("{}", get_usage(parser()));
175/// std::process::exit(0);
176/// }
177/// ...
178/// ```
179#[allow(clippy::needless_pass_by_value)]
180#[must_use]
181pub fn get_usage<T>(parser: crate::OptionParser<T>) -> String
182where
183 T: std::fmt::Debug,
184{
185 parser.run_inner(&["--help"]).unwrap_err().unwrap_stdout()
186}