bpaf/params.rs
1//! Tools to define primitive parsers
2//!
3//! # Ways to consume data
4//!
5//! ## Flag
6//!
7//! - [`flag`](NamedArg::flag) - a string that consists of two dashes (`--flag`) and a name and a single
8//! dash and a single character (`-f`) created with [`long`](NamedArg::long) and [`short`](NamedArg::short)
9//! respectively. Depending if this name is present or absent on the command line
10//! primitive flag parser produces one of two values. User can combine several short flags in a single
11//! invocation: `-a -b -c` is the same as `-abc`.
12//!
13#![cfg_attr(not(doctest), doc = include_str!("docs2/flag.md"))]
14//!
15//! ## Required flag
16//!
17//! Similar to `flag`, but instead of falling back to the second value required flag parser would
18//! fail. Mostly useful in combination with other parsers, created with [`NamedArg::req_flag`].
19//!
20#![cfg_attr(not(doctest), doc = include_str!("docs2/req_flag.md"))]
21//!
22//! ## Switch
23//!
24//! A special case of a flag that gets decoded into a `bool`, mostly serves as a convenient
25//! shortcut to `.flag(true, false)`. Created with [`NamedArg::switch`].
26//!
27#![cfg_attr(not(doctest), doc = include_str!("docs2/switch.md"))]
28//!
29//! ## Argument
30//!
31//! A short or long `flag` followed by either a space or `=` and
32//! then by a string literal. `-f foo`, `--flag bar` or `-o=-` are all valid argument examples. Note, string
33//! literal can't start with `-` unless separated from the flag with `=`. For short flags value
34//! can follow immediately: `-fbar`.
35//!
36#![cfg_attr(not(doctest), doc = include_str!("docs2/argument.md"))]
37//!
38//! ## Positional
39//!
40//! A positional argument with no additonal name, for example in `vim main.rs` `main.rs`
41//! is a positional argument. Can't start with `-`, created with [`positional`].
42//!
43#![cfg_attr(not(doctest), doc = include_str!("docs2/positional.md"))]
44//!
45//! ## Any
46//!
47//! Also a positional argument with no additional name, but unlike [`positional`] itself, [`any`]
48//! isn't restricted to positional looking structure and would consume any items as they appear on
49//! a command line. Can be useful to collect anything unused to pass to other applications.
50//!
51#![cfg_attr(not(doctest), doc = include_str!("docs2/any_simple.md"))]
52#![cfg_attr(not(doctest), doc = include_str!("docs2/any_literal.md"))]
53//!
54//! ## Command
55//!
56//! A command defines a starting point for an independent subparser. Name must be a valid utf8
57//! string. For example `cargo build` invokes command `"build"` and after `"build"` `cargo`
58//! starts accepting values it won't accept otherwise
59//!
60#![cfg_attr(not(doctest), doc = include_str!("docs2/command.md"))]
61//!
62use std::{ffi::OsString, marker::PhantomData, str::FromStr};
63
64use crate::{
65 args::{Arg, State},
66 error::{Message, MissingItem},
67 from_os_str::parse_os_str,
68 item::ShortLong,
69 meta_help::Metavar,
70 Doc, Error, Item, Meta, OptionParser, Parser,
71};
72
73#[cfg(doc)]
74use crate::{any, command, env, long, positional, short};
75
76/// A named thing used to create [`flag`](NamedArg::flag), [`switch`](NamedArg::switch) or
77/// [`argument`](NamedArg::argument)
78///
79/// # Combinatoric usage
80///
81/// Named items (`argument`, `flag` and `switch`) can have up to 2 visible names (one short and one long)
82/// and multiple hidden short and long aliases if needed. It's also possible to consume items from
83/// environment variables using [`env`](NamedArg::env()). You usually start with [`short`] or [`long`]
84/// function, then apply [`short`](NamedArg::short) / [`long`](NamedArg::long) / [`env`](NamedArg::env()) /
85/// [`help`](NamedArg::help) repeatedly to build a desired set of names then transform it into
86/// a parser using `flag`, `switch` or `positional`.
87///
88#[cfg_attr(not(doctest), doc = include_str!("docs2/named_arg_combine.md"))]
89///
90/// # Derive usage
91///
92/// When using derive API it is possible to omit some or all the details:
93/// 1. If no naming information is present at all - `bpaf` would use field name as a long name
94/// (or a short name if field name consists of a single character)
95/// 2. If `short` or `long` annotation is present without an argument - `bpaf` would use first character
96/// or a full name as long and short name respectively. It won't try to add implicit long or
97/// short name from the previous item.
98/// 3. If `short` or `long` annotation is present with an argument - those are values `bpaf` would
99/// use instead of the original field name
100/// 4. You can specify many `short` and `long` names, any past the first one of each type will
101/// become hidden aliases
102/// 5. If `env(arg)` annotation is present - in addition to long/short names derived according to
103/// rules 1..3 `bpaf` would also parse environment variable `arg` which can be a string literal
104/// or an expression.
105#[cfg_attr(not(doctest), doc = include_str!("docs2/named_arg_derive.md"))]
106#[derive(Clone, Debug)]
107pub struct NamedArg {
108 pub(crate) short: Vec<char>,
109 pub(crate) long: Vec<&'static str>,
110 pub(crate) env: Vec<&'static str>,
111 pub(crate) help: Option<Doc>,
112}
113
114impl NamedArg {
115 pub(crate) fn flag_item(&self) -> Option<Item> {
116 Some(Item::Flag {
117 name: ShortLong::try_from(self).ok()?,
118 help: self.help.clone(),
119 env: self.env.first().copied(),
120 shorts: self.short.clone(),
121 })
122 }
123}
124
125impl NamedArg {
126 /// Add a short name to a flag/switch/argument
127 ///
128 #[cfg_attr(not(doctest), doc = include_str!("docs2/short_long_env.md"))]
129 #[must_use]
130 pub fn short(mut self, short: char) -> Self {
131 self.short.push(short);
132 self
133 }
134
135 /// Add a long name to a flag/switch/argument
136 ///
137 #[cfg_attr(not(doctest), doc = include_str!("docs2/short_long_env.md"))]
138 #[must_use]
139 pub fn long(mut self, long: &'static str) -> Self {
140 self.long.push(long);
141 self
142 }
143
144 /// Environment variable fallback
145 ///
146 /// If named value isn't present - try to fallback to this environment variable.
147 ///
148 /// You can specify it multiple times, `bpaf` would use items past the first one as hidden aliases.
149 ///
150 /// For [`flag`](NamedArg::flag) and [`switch`](NamedArg::switch) environment variable being present
151 /// gives the same result as the flag being present, allowing to implement things like `NO_COLOR`
152 /// variables:
153 ///
154 /// ```console
155 /// $ NO_COLOR=1 app --do-something
156 /// ```
157 #[cfg_attr(not(doctest), doc = include_str!("docs2/short_long_env.md"))]
158 #[must_use]
159 pub fn env(mut self, variable: &'static str) -> Self {
160 self.env.push(variable);
161 self
162 }
163
164 /// Add a help message to a `flag`/`switch`/`argument`
165 ///
166 /// `bpaf` converts doc comments and string into help by following those rules:
167 /// 1. Everything up to the first blank line is included into a "short" help message
168 /// 2. Everything is included into a "long" help message
169 /// 3. `bpaf` preserves linebreaks followed by a line that starts with a space
170 /// 4. Linebreaks are removed otherwise
171 ///
172 /// You can pass anything that can be converted into [`Doc`], if you are not using
173 /// documentation generation functionality ([`doc`](crate::doc)) this can be `&str`.
174 ///
175 #[cfg_attr(not(doctest), doc = include_str!("docs2/switch_help.md"))]
176 #[must_use]
177 pub fn help<M>(mut self, help: M) -> Self
178 where
179 M: Into<Doc>,
180 {
181 self.help = Some(help.into());
182 self
183 }
184
185 /// Simple boolean flag
186 ///
187 /// A special case of a [`flag`](NamedArg::flag) that gets decoded into a `bool`, mostly serves as a convenient
188 /// shortcut to `.flag(true, false)`.
189 ///
190 /// In Derive API bpaf would use `switch` for `bool` fields inside named structs that don't
191 /// have other consumer annotations ([`flag`](NamedArg::flag),
192 /// [`argument`](NamedArg::argument), etc).
193 ///
194 #[cfg_attr(not(doctest), doc = include_str!("docs2/switch.md"))]
195 #[must_use]
196 pub fn switch(self) -> ParseFlag<bool> {
197 build_flag_parser(true, Some(false), self)
198 }
199
200 /// Flag with custom present/absent values
201 ///
202 /// More generic version of [`switch`](NamedArg::switch) that can use arbitrary type instead of
203 /// [`bool`].
204 #[cfg_attr(not(doctest), doc = include_str!("docs2/flag.md"))]
205 #[must_use]
206 pub fn flag<T>(self, present: T, absent: T) -> ParseFlag<T>
207 where
208 T: Clone + 'static,
209 {
210 build_flag_parser(present, Some(absent), self)
211 }
212
213 /// Required flag with custom value
214 ///
215 /// Similar to [`flag`](NamedArg::flag) takes no option arguments, but would only
216 /// succeed if user specifies its name on a command line.
217 /// Works best in combination with other parsers.
218 ///
219 /// In derive style API `bpaf` would transform field-less enum variants into a parser
220 /// that accepts one of it's variant names as `req_flag`. Additionally `bpaf` handles `()`
221 /// fields as `req_flag`.
222 #[cfg_attr(not(doctest), doc = include_str!("docs2/req_flag.md"))]
223 #[must_use]
224 pub fn req_flag<T>(self, present: T) -> impl Parser<T>
225 where
226 T: Clone + 'static,
227 {
228 build_flag_parser(present, None, self)
229 }
230
231 /// Argument
232 ///
233 /// A short (`-a`) or long (`--name`) name followed by either a space or `=` and
234 /// then by a string literal. `-f foo`, `--flag bar` or `-o=-` are all valid argument examples. Note, string
235 /// literal can't start with `-` unless separated from the flag with `=`. For short flags value
236 /// can follow immediately: `-fbar`.
237 ///
238 /// When using combinatoring API you can specify the type with turbofish, for parsing types
239 /// that don't implement [`FromStr`] you can use consume a `String`/`OsString` first and parse
240 /// it by hands.
241 ///
242 /// For `metavar` value you should pick something short and descriptive about the parameter,
243 /// usually in capital letters. For example for an abstract file parameter it could be
244 /// `"FILE"`, for a username - `"USER"`, etc.
245 ///
246 #[cfg_attr(not(doctest), doc = include_str!("docs2/argument.md"))]
247 ///
248 /// You can further restrict it using [`adjacent`](ParseArgument::adjacent)
249 #[must_use]
250 pub fn argument<T>(self, metavar: &'static str) -> ParseArgument<T>
251 where
252 T: FromStr + 'static,
253 {
254 build_argument(self, metavar)
255 }
256
257 /// `adjacent` requires for the argument to be present in the same word as the flag:
258 /// `-f bar` - no, `-fbar` or `-f=bar` - yes.
259 pub(crate) fn matches_arg(&self, arg: &Arg, adjacent: bool) -> bool {
260 match arg {
261 Arg::Short(s, is_adj, _) => self.short.contains(s) && (!adjacent || *is_adj),
262 Arg::Long(l, is_adj, _) => self.long.contains(&l.as_str()) && (!adjacent || *is_adj),
263 Arg::ArgWord(_) | Arg::Word(_) | Arg::PosWord(_) => false,
264 }
265 }
266}
267
268impl<T> OptionParser<T> {
269 /// Parse a subcommand
270 ///
271 /// Subcommands allow to use a totally independent parser inside a current one. Inner parser
272 /// can have its own help message, description, version and so on. You can nest them arbitrarily
273 /// too.
274 ///
275 /// # Important restriction
276 /// When parsing command arguments from command lines you should have parsers for all your
277 /// named values before parsers for commands and positional items. In derive API fields parsed as
278 /// positional should be at the end of your `struct`/`enum`. Same rule applies
279 /// to parsers with positional fields or commands inside: such parsers should go to the end as well.
280 ///
281 /// Use [`check_invariants`](OptionParser::check_invariants) in your test to ensure correctness.
282 ///
283 /// For example for non positional `non_pos` and a command `command` parsers
284 /// ```rust
285 /// # use bpaf::*;
286 /// # let non_pos = || short('n').switch();
287 /// # let command = || pure(()).to_options().command("POS");
288 /// let valid = construct!(non_pos(), command());
289 /// let invalid = construct!(command(), non_pos());
290 /// ```
291 ///
292 /// **`bpaf` panics during help generation unless if this restriction holds**
293 ///
294 /// You can attach a single visible short alias and multiple hiddden short and long aliases
295 /// using [`short`](ParseCommand::short) and [`long`](ParseCommand::long) methods.
296 ///
297 #[cfg_attr(not(doctest), doc = include_str!("docs2/command.md"))]
298 ///
299 /// To represent multiple possible commands it is convenient to use enums
300 #[cfg_attr(not(doctest), doc = include_str!("docs2/command_enum.md"))]
301 #[must_use]
302 pub fn command(self, name: &'static str) -> ParseCommand<T>
303 where
304 T: 'static,
305 {
306 ParseCommand {
307 longs: vec![name],
308 shorts: Vec::new(),
309 help: self.short_descr(),
310 subparser: self,
311 adjacent: false,
312 }
313 }
314}
315
316/// Builder structure for the [`command`]
317///
318/// Created with [`command`], implements parser for the inner structure, gives access to [`help`](ParseCommand::help).
319pub struct ParseCommand<T> {
320 pub(crate) longs: Vec<&'static str>,
321 pub(crate) shorts: Vec<char>,
322 // short help!
323 pub(crate) help: Option<Doc>,
324 pub(crate) subparser: OptionParser<T>,
325 pub(crate) adjacent: bool,
326}
327
328impl<P> ParseCommand<P> {
329 /// Add a brief description to a command
330 ///
331 /// `bpaf` uses this description along with the command name
332 /// in help output so it shouldn't exceed one or two lines. If `help` isn't specified
333 /// `bpaf` falls back to [`descr`](OptionParser::descr) from the inner parser.
334 ///
335 /// # Combinatoric usage
336 ///
337 /// ```rust
338 /// # use bpaf::*;
339 /// fn inner() -> OptionParser<bool> {
340 /// short('i')
341 /// .help("Mysterious inner switch")
342 /// .switch()
343 /// .to_options()
344 /// .descr("performs an operation")
345 /// }
346 ///
347 /// fn mysterious_parser() -> impl Parser<bool> {
348 /// inner().command("mystery")
349 /// .help("This command performs a mystery operation")
350 /// }
351 /// ```
352 ///
353 /// # Derive usage
354 /// `bpaf_derive` uses doc comments for inner parser, no specific options are available.
355 /// See [`descr`](OptionParser::descr) for more details
356 /// ```rust
357 /// # use bpaf::*;
358 /// /// This command performs a mystery operation
359 /// #[derive(Debug, Clone, Bpaf)]
360 /// #[bpaf(command)]
361 /// struct Mystery {
362 /// #[bpaf(short)]
363 /// /// Mysterious inner switch
364 /// inner: bool,
365 /// }
366 /// ```
367 ///
368 /// # Example
369 /// ```console
370 /// $ app --help
371 /// <skip>
372 /// Available commands:
373 /// mystery This command performs a mystery operation
374 /// ```
375 #[must_use]
376 pub fn help<M>(mut self, help: M) -> Self
377 where
378 M: Into<Doc>,
379 {
380 self.help = Some(help.into());
381 self
382 }
383
384 /// Add a custom short alias for a command
385 ///
386 /// Behavior is similar to [`short`](NamedArg::short), only first short name is visible.
387 #[must_use]
388 pub fn short(mut self, short: char) -> Self {
389 self.shorts.push(short);
390 self
391 }
392
393 /// Add a custom hidden long alias for a command
394 ///
395 /// Behavior is similar to [`long`](NamedArg::long), but since you had to specify the first long
396 /// name when making the command - this one becomes a hidden alias.
397 #[must_use]
398 pub fn long(mut self, long: &'static str) -> Self {
399 self.longs.push(long);
400 self
401 }
402
403 /// Allow for the command to succeed even if there are non consumed items present
404 ///
405 /// Normally a subcommand parser should handle the rest of the unconsumed elements thus
406 /// allowing only "vertical" chaining of commands. `adjacent` modifier lets command parser to
407 /// succeed if there are leftovers for as long as all comsumed items form a single adjacent
408 /// block. This opens possibilities to chain commands sequentially.
409 ///
410 /// Let's consider two examples with consumed items marked in bold :
411 ///
412 /// - <code>**cmd** **-a** -b **-c** -d</code>
413 /// - <code>**cmd** **-a** **-c** -b -d</code>
414 ///
415 /// In the first example `-b` breaks the adjacency for all the consumed items so parsing will fail,
416 /// while here in the second one the name and all the consumed items are adjacent to each other so
417 /// parsing will succeed.
418 ///
419 #[cfg_attr(not(doctest), doc = include_str!("docs2/adjacent_command.md"))]
420 #[must_use]
421 pub fn adjacent(mut self) -> Self {
422 self.adjacent = true;
423 self
424 }
425}
426
427impl<T> Parser<T> for ParseCommand<T> {
428 fn eval(&self, args: &mut State) -> Result<T, Error> {
429 // used to avoid allocations for short names
430 let mut tmp = String::new();
431 if self.longs.iter().any(|long| args.take_cmd(long))
432 || self.shorts.iter().any(|s| {
433 tmp.clear();
434 tmp.push(*s);
435 args.take_cmd(&tmp)
436 })
437 {
438 #[cfg(feature = "autocomplete")]
439 if args.touching_last_remove() {
440 // in completion mode prefer to autocomplete the command name vs going inside the
441 // parser
442 args.clear_comps();
443 args.push_command(self.longs[0], self.shorts.first().copied(), &self.help);
444 return Err(Error(Message::Missing(Vec::new())));
445 }
446
447 if let Some(cur) = args.current {
448 args.set_scope(cur..args.scope().end);
449 }
450
451 args.path.push(self.longs[0].to_string());
452 if self.adjacent {
453 let mut orig_args = args.clone();
454
455 // narrow down the scope to adjacently available elements
456 args.set_scope(args.adjacently_available_from(args.scope().start + 1));
457
458 match self
459 .subparser
460 .run_subparser(args)
461 .map_err(Message::ParseFailure)
462 {
463 Ok(ok) => {
464 args.set_scope(orig_args.scope());
465 Ok(ok)
466 }
467 Err(err) => {
468 let orig_scope = args.scope();
469 if let Some(narrow_scope) = args.adjacent_scope(&orig_args) {
470 orig_args.set_scope(narrow_scope);
471 if let Ok(res) = self.subparser.run_subparser(&mut orig_args) {
472 orig_args.set_scope(orig_scope);
473 std::mem::swap(&mut orig_args, args);
474 return Ok(res);
475 }
476 }
477 Err(Error(err))
478 }
479 }
480 } else {
481 self.subparser
482 .run_subparser(args)
483 .map_err(|e| Error(Message::ParseFailure(e)))
484 }
485 } else {
486 #[cfg(feature = "autocomplete")]
487 args.push_command(self.longs[0], self.shorts.first().copied(), &self.help);
488
489 let missing = MissingItem {
490 item: self.item(),
491 position: args.scope().start,
492 scope: args.scope(),
493 };
494 Err(Error(Message::Missing(vec![missing])))
495 }
496 }
497
498 fn meta(&self) -> Meta {
499 Meta::from(self.item())
500 }
501}
502
503impl<T> ParseCommand<T> {
504 fn item(&self) -> Item {
505 Item::Command {
506 name: self.longs[0],
507 short: self.shorts.first().copied(),
508 help: self.help.clone(),
509 meta: Box::new(self.subparser.inner.meta()),
510 info: Box::new(self.subparser.info.clone()),
511 }
512 }
513}
514
515fn build_flag_parser<T>(present: T, absent: Option<T>, named: NamedArg) -> ParseFlag<T>
516where
517 T: Clone + 'static,
518{
519 ParseFlag {
520 present,
521 absent,
522 named,
523 }
524}
525
526#[derive(Clone)]
527/// Parser for a named switch, created with [`NamedArg::flag`] or [`NamedArg::switch`]
528pub struct ParseFlag<T> {
529 present: T,
530 absent: Option<T>,
531 named: NamedArg,
532}
533
534impl<T: Clone + 'static> Parser<T> for ParseFlag<T> {
535 fn eval(&self, args: &mut State) -> Result<T, Error> {
536 if args.take_flag(&self.named) || self.named.env.iter().find_map(std::env::var_os).is_some()
537 {
538 #[cfg(feature = "autocomplete")]
539 if args.touching_last_remove() {
540 args.push_flag(&self.named);
541 }
542 Ok(self.present.clone())
543 } else {
544 #[cfg(feature = "autocomplete")]
545 args.push_flag(&self.named);
546 match &self.absent {
547 Some(ok) => Ok(ok.clone()),
548 None => {
549 if let Some(item) = self.named.flag_item() {
550 let missing = MissingItem {
551 item,
552 position: args.scope().start,
553 scope: args.scope(),
554 };
555 Err(Error(Message::Missing(vec![missing])))
556 } else if let Some(name) = self.named.env.first() {
557 Err(Error(Message::NoEnv(name)))
558 } else {
559 todo!("no key!")
560 }
561 }
562 }
563 }
564 }
565
566 fn meta(&self) -> Meta {
567 if let Some(item) = self.named.flag_item() {
568 item.required(self.absent.is_none())
569 } else {
570 Meta::Skip
571 }
572 }
573}
574
575impl<T> ParseFlag<T> {
576 /// Add a help message to `flag`
577 ///
578 /// See [`NamedArg::help`]
579 #[must_use]
580 pub fn help<M>(mut self, help: M) -> Self
581 where
582 M: Into<Doc>,
583 {
584 self.named.help = Some(help.into());
585 self
586 }
587}
588
589impl<T> ParseArgument<T> {
590 /// Add a help message to an `argument`
591 ///
592 /// See [`NamedArg::help`]
593 #[must_use]
594 pub fn help<M>(mut self, help: M) -> Self
595 where
596 M: Into<Doc>,
597 {
598 self.named.help = Some(help.into());
599 self
600 }
601}
602
603fn build_argument<T>(named: NamedArg, metavar: &'static str) -> ParseArgument<T> {
604 ParseArgument {
605 named,
606 metavar,
607 ty: PhantomData,
608 adjacent: false,
609 }
610}
611
612/// Parser for a named argument, created with [`argument`](NamedArg::argument).
613#[derive(Clone)]
614pub struct ParseArgument<T> {
615 ty: PhantomData<T>,
616 named: NamedArg,
617 metavar: &'static str,
618 adjacent: bool,
619}
620
621impl<T> ParseArgument<T> {
622 /// Restrict parsed arguments to have both flag and a value in the same word:
623 ///
624 /// In other words adjacent restricted `ParseArgument` would accept `--flag=value` or
625 /// `-fbar` but not `--flag value`. Note, this is different from [`adjacent`](crate::ParseCon::adjacent),
626 /// just plays a similar role.
627 ///
628 /// Should allow to parse some of the more unusual things
629 ///
630 #[cfg_attr(not(doctest), doc = include_str!("docs2/adjacent_argument.md"))]
631 #[must_use]
632 pub fn adjacent(mut self) -> Self {
633 self.adjacent = true;
634 self
635 }
636
637 fn item(&self) -> Option<Item> {
638 Some(Item::Argument {
639 name: ShortLong::try_from(&self.named).ok()?,
640 metavar: Metavar(self.metavar),
641 env: self.named.env.first().copied(),
642 help: self.named.help.clone(),
643 shorts: self.named.short.clone(),
644 })
645 }
646
647 fn take_argument(&self, args: &mut State) -> Result<OsString, Error> {
648 match args.take_arg(&self.named, self.adjacent, Metavar(self.metavar)) {
649 Ok(Some(w)) => {
650 #[cfg(feature = "autocomplete")]
651 if args.touching_last_remove() {
652 args.push_metavar(self.metavar, &self.named.help, true);
653 }
654 Ok(w)
655 }
656 Err(err) => {
657 #[cfg(feature = "autocomplete")]
658 args.push_argument(&self.named, self.metavar);
659 Err(err)
660 }
661 _ => {
662 #[cfg(feature = "autocomplete")]
663 args.push_argument(&self.named, self.metavar);
664 if let Some(val) = self.named.env.iter().find_map(std::env::var_os) {
665 args.current = None;
666 return Ok(val);
667 }
668
669 if let Some(item) = self.item() {
670 let missing = MissingItem {
671 item,
672 position: args.scope().start,
673 scope: args.scope(),
674 };
675 Err(Error(Message::Missing(vec![missing])))
676 } else if let Some(name) = self.named.env.first() {
677 Err(Error(Message::NoEnv(name)))
678 } else {
679 unreachable!()
680 }
681 }
682 }
683 }
684}
685
686impl<T> Parser<T> for ParseArgument<T>
687where
688 T: FromStr + 'static,
689 <T as std::str::FromStr>::Err: std::fmt::Display,
690{
691 fn eval(&self, args: &mut State) -> Result<T, Error> {
692 let os = self.take_argument(args)?;
693 match parse_os_str::<T>(os) {
694 Ok(ok) => Ok(ok),
695 Err(err) => Err(Error(Message::ParseFailed(args.current, err))),
696 }
697 }
698
699 fn meta(&self) -> Meta {
700 if let Some(item) = self.item() {
701 Meta::from(item)
702 } else {
703 Meta::Skip
704 }
705 }
706}
707
708pub(crate) fn build_positional<T>(metavar: &'static str) -> ParsePositional<T> {
709 ParsePositional {
710 metavar,
711 help: None,
712 position: Position::Unrestricted,
713 ty: PhantomData,
714 }
715}
716
717/// Parse a positional item, created with [`positional`](crate::positional)
718///
719/// You can add extra information to positional parsers with [`help`](Self::help),
720/// [`strict`](Self::strict), or [`non_strict`](Self::non_strict) on this struct.
721#[derive(Clone)]
722pub struct ParsePositional<T> {
723 metavar: &'static str,
724 help: Option<Doc>,
725 position: Position,
726 ty: PhantomData<T>,
727}
728
729#[derive(Copy, Clone, PartialEq, Eq)]
730enum Position {
731 Unrestricted,
732 Strict,
733 NonStrict,
734}
735
736impl<T> ParsePositional<T> {
737 /// Add a help message to a [`positional`] parser
738 ///
739 /// `bpaf` converts doc comments and string into help by following those rules:
740 /// 1. Everything up to the first blank line is included into a "short" help message
741 /// 2. Everything is included into a "long" help message
742 /// 3. `bpaf` preserves linebreaks followed by a line that starts with a space
743 /// 4. Linebreaks are removed otherwise
744 ///
745 /// You can pass anything that can be converted into [`Doc`], if you are not using
746 /// documentation generation functionality ([`doc`](crate::doc)) this can be `&str`.
747 ///
748 #[cfg_attr(not(doctest), doc = include_str!("docs2/positional.md"))]
749 #[must_use]
750 pub fn help<M>(mut self, help: M) -> Self
751 where
752 M: Into<Doc>,
753 {
754 self.help = Some(help.into());
755 self
756 }
757
758 /// Changes positional parser to be a "strict" positional
759 ///
760 /// Usually positional items can appear anywhere on a command line:
761 /// ```console
762 /// $ ls -d bpaf
763 /// $ ls bpaf -d
764 /// ```
765 /// here `ls` takes a positional item `bpaf` and a flag `-d`
766 ///
767 /// But in some cases it might be useful to have a stricter separation between
768 /// positonal items and flags, such as passing arguments to a subprocess:
769 /// ```console
770 /// $ cargo run --example basic -- --help
771 /// ```
772 ///
773 /// here `cargo` takes a `--help` as a positional item and passes it to the example
774 ///
775 /// `bpaf` allows to require user to pass `--` for positional items with `strict` annotation.
776 /// `bpaf` would display such positional elements differently in usage line as well.
777 #[cfg_attr(not(doctest), doc = include_str!("docs2/positional_strict.md"))]
778 #[must_use]
779 #[inline(always)]
780 pub fn strict(mut self) -> ParsePositional<T> {
781 self.position = Position::Strict;
782 self
783 }
784
785 /// Changes positional parser to be a "not strict" positional
786 ///
787 /// Ensures the parser always rejects "strict" positions to the right of the separator, `--`.
788 /// Essentially the inverse operation to [`ParsePositional::strict`], which can be used to ensure
789 /// adjacent strict and nonstrict args never conflict with eachother.
790 #[must_use]
791 #[inline(always)]
792 pub fn non_strict(mut self) -> Self {
793 self.position = Position::NonStrict;
794 self
795 }
796
797 #[inline(always)]
798 fn meta(&self) -> Meta {
799 let meta = Meta::from(Item::Positional {
800 metavar: Metavar(self.metavar),
801 help: self.help.clone(),
802 });
803 match self.position {
804 Position::Strict => Meta::Strict(Box::new(meta)),
805 _ => meta,
806 }
807 }
808}
809#[allow(unused_variables)] // used when autocomplete is enabled
810fn parse_pos_word(
811 args: &mut State,
812 metavar: Metavar,
813 help: &Option<Doc>,
814 position: Position,
815) -> Result<OsString, Error> {
816 match args.take_positional_word(metavar) {
817 Ok((ix, is_strict, word)) => {
818 match position {
819 Position::Strict => {
820 if !is_strict {
821 #[cfg(feature = "autocomplete")]
822 args.push_pos_sep();
823 return Err(Error(Message::StrictPos(ix, metavar)));
824 }
825 }
826 Position::NonStrict => {
827 if is_strict {
828 return Err(Error(Message::NonStrictPos(ix, metavar)));
829 }
830 }
831 Position::Unrestricted => {}
832 }
833
834 #[cfg(feature = "autocomplete")]
835 if args.touching_last_remove() && !args.check_no_pos_ahead() {
836 args.push_metavar(metavar.0, help, false);
837 args.set_no_pos_ahead();
838 }
839 Ok(word)
840 }
841 Err(err) => {
842 #[cfg(feature = "autocomplete")]
843 if !args.check_no_pos_ahead() {
844 args.push_metavar(metavar.0, help, false);
845 args.set_no_pos_ahead();
846 }
847 Err(err)
848 }
849 }
850}
851
852impl<T> Parser<T> for ParsePositional<T>
853where
854 T: FromStr + 'static,
855 <T as std::str::FromStr>::Err: std::fmt::Display,
856{
857 fn eval(&self, args: &mut State) -> Result<T, Error> {
858 let os = parse_pos_word(args, Metavar(self.metavar), &self.help, self.position)?;
859 match parse_os_str::<T>(os) {
860 Ok(ok) => Ok(ok),
861 Err(err) => Err(Error(Message::ParseFailed(args.current, err))),
862 }
863 }
864
865 #[inline(always)]
866 fn meta(&self) -> Meta {
867 self.meta()
868 }
869}
870
871/// Consume an arbitrary value that satisfies a condition, created with [`any`], implements
872/// [`anywhere`](ParseAny::anywhere).
873pub struct ParseAny<T> {
874 pub(crate) metavar: Doc,
875 pub(crate) help: Option<Doc>,
876 pub(crate) check: Box<dyn Fn(OsString) -> Option<T>>,
877 pub(crate) anywhere: bool,
878}
879
880impl<T> ParseAny<T> {
881 pub(crate) fn item(&self) -> Item {
882 Item::Any {
883 metavar: self.metavar.clone(),
884 help: self.help.clone(),
885 anywhere: self.anywhere,
886 }
887 }
888
889 /// Add a help message to [`any`] parser.
890 /// See examples in [`any`]
891 #[must_use]
892 pub fn help<M: Into<Doc>>(mut self, help: M) -> Self {
893 self.help = Some(help.into());
894 self
895 }
896
897 /// Replace metavar with a custom value
898 /// See examples in [`any`]
899 #[must_use]
900 pub fn metavar<M: Into<Doc>>(mut self, metavar: M) -> Self {
901 self.metavar = metavar.into();
902 self
903 }
904
905 /// Try to apply the parser to each unconsumed element instead of just the front one
906 ///
907 /// By default `any` tries to parse just the front unconsumed item behaving similar to
908 /// [`positional`] parser, `anywhere` changes it so it applies to every unconsumed item,
909 /// similar to argument parser.
910 ///
911 /// See examples in [`any`]
912 #[must_use]
913 pub fn anywhere(mut self) -> Self {
914 self.anywhere = true;
915 self
916 }
917}
918
919impl<T> Parser<T> for ParseAny<T> {
920 fn eval(&self, args: &mut State) -> Result<T, Error> {
921 for (ix, x) in args.items_iter() {
922 let (os, next) = match x {
923 Arg::Short(_, next, os) | Arg::Long(_, next, os) => (os, *next),
924 Arg::ArgWord(os) | Arg::Word(os) | Arg::PosWord(os) => (os, false),
925 };
926 if let Some(i) = (self.check)(os.clone()) {
927 args.remove(ix);
928 if next {
929 args.remove(ix + 1);
930 }
931
932 return Ok(i);
933 }
934 if !self.anywhere {
935 break;
936 }
937 }
938 let missing_item = MissingItem {
939 item: self.item(),
940 position: args.scope().start,
941 scope: args.scope(),
942 };
943 Err(Error(Message::Missing(vec![missing_item])))
944 }
945
946 fn meta(&self) -> Meta {
947 Meta::Item(Box::new(self.item()))
948 }
949}