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}