Skip to main content

winnow_args/
combinator.rs

1//! Parsers written by hand: name a flag, then say what it is.
2//!
3//! [`short`] and [`long`] name a flag; [`Named::switch`], [`Named::argument`]
4//! and the like turn the name into a winnow parser that reads **one
5//! occurrence** of it. [`args`] runs such a parser over the whole command
6//! line, and the closures given to `map` decide what to keep:
7//!
8//! ```
9//! use winnow::prelude::*;
10//! use winnow::combinator::alt;
11//! use winnow_args::BStr;
12//! use winnow_args::{Argv, Error, combinator::{args, short}};
13//!
14//! let verbose = short('v').long("verbose");
15//! let path = short('p').long("path");
16//!
17//! let words = [BStr::new("-v"), BStr::new("--path=/tmp")];
18//! let mut input = Argv::new(&words);
19//!
20//! let (mut v, mut p) = (false, None);
21//! args(alt((
22//!     verbose.switch().map(|()| v = true),
23//!     path.argument().map(|x| p = Some(x)),
24//! )))
25//! .parse_next(&mut input)?;
26//!
27//! assert!(v);
28//! assert_eq!(p.map(|x| &**x), Some(&b"/tmp"[..]));
29//! # Ok::<(), Error>(())
30//! ```
31//!
32//! What a missing or repeated flag means is up to those closures: here a
33//! repeated `--path` keeps the last one, and a missing one leaves `None`. The
34//! derive makes the same decisions from each field's type.
35
36use winnow::combinator::{alt, repeat, trace};
37use winnow::error::{ModalError as _, ParserError as _};
38use winnow::prelude::*;
39
40use crate::error::Error;
41use crate::stream::Argv;
42use crate::token::{self, Arg, ValueOptions};
43use crate::value::FromArg;
44use winnow::stream::BStr;
45
46/// The names a flag answers to: one short letter, and `N` long names (the first
47/// is the flag's name, the rest are aliases).
48///
49/// `N` is part of the type so the common `Named` stays 24 bytes: the parsers
50/// built from it copy it on every use, and larger copies stall
51/// (docs/PERF.md, step 8). Only a flag with aliases pays for them.
52#[derive(Copy, Clone, Debug, PartialEq, Eq)]
53pub struct Named<const N: usize = 1> {
54    short: Option<char>,
55    longs: [Option<&'static str>; N],
56    /// Sits in padding: a plain `Named` stays 24 bytes.
57    options: ValueOptions,
58}
59
60/// A flag named `-c`.
61pub const fn short(c: char) -> Named {
62    Named {
63        short: Some(c),
64        longs: [None],
65        options: ValueOptions::DEFAULT,
66    }
67}
68
69/// A flag named `--name`.
70pub const fn long(name: &'static str) -> Named {
71    Named {
72        short: None,
73        longs: [Some(name)],
74        options: ValueOptions::DEFAULT,
75    }
76}
77
78impl<const N: usize> Named<N> {
79    /// Also answer to `-c`.
80    pub const fn short(mut self, c: char) -> Self {
81        self.short = Some(c);
82        self
83    }
84
85    /// Answer to `--name`, replacing any long names.
86    pub const fn long(self, name: &'static str) -> Named {
87        Named {
88            short: self.short,
89            longs: [Some(name)],
90            options: self.options,
91        }
92    }
93
94    /// Answer to all of these long names, replacing any: the first is the
95    /// flag's name, the rest are aliases.
96    pub const fn longs<const M: usize>(self, names: [&'static str; M]) -> Named<M> {
97        let mut longs = [None; M];
98        let mut i = 0;
99        while i < M {
100            longs[i] = Some(names[i]);
101            i += 1;
102        }
103        Named {
104            short: self.short,
105            longs,
106            options: self.options,
107        }
108    }
109
110    /// Take the next word as the value whatever it looks like: `--args -x`, `--args --`.
111    pub const fn allow_hyphen_values(mut self) -> Self {
112        self.options.hyphen_values = true;
113        self
114    }
115
116    /// `true` for this flag, `false` for `no`, as in
117    /// `long("color").negated_by(long("no-color"))`. Fold with "last wins".
118    pub fn negated_by<'i, const M: usize>(
119        self,
120        no: Named<M>,
121    ) -> impl Parser<Argv<'i>, bool, Error> {
122        trace(
123            "negated_by",
124            alt((self.switch().value(true), no.switch().value(false))),
125        )
126    }
127
128    /// Only an attached value: `--name=v`, `-cv`; the next word is never taken.
129    pub const fn require_equals(mut self) -> Self {
130        self.options.require_equals = true;
131        self
132    }
133
134    /// Take a negative number as a detached value: `--offset -1`.
135    pub const fn allow_negative_numbers(mut self) -> Self {
136        self.options.negative_numbers = true;
137        self
138    }
139
140    /// Whether `arg` is this flag.
141    #[inline]
142    pub fn matches(&self, arg: &Arg<'_>) -> bool {
143        match arg {
144            Arg::Long(f) => self
145                .longs
146                .iter()
147                .any(|l| l.is_some_and(|l| f.name == l.as_bytes())),
148            Arg::Short(f) => self.short == Some(f.letter),
149            Arg::Word(_) | Arg::Separator { .. } => false,
150        }
151    }
152
153    /// How to name the flag in a message: the long name if it has one.
154    pub fn display(&self) -> String {
155        match (self.longs.first().copied().flatten(), self.short) {
156            (Some(l), _) => format!("--{l}"),
157            (None, Some(c)) => format!("-{c}"),
158            (None, None) => String::new(),
159        }
160    }
161
162    /// The error for a required flag that never appeared.
163    pub fn missing(&self, input: &Argv<'_>) -> Error {
164        Error::missing_required(input.offset(), self.display())
165    }
166
167    /// A switch: `--name` or `-c`, no value.
168    pub fn switch<'i>(mut self) -> impl Parser<Argv<'i>, (), Error> {
169        trace("switch", move |input: &mut Argv<'i>| {
170            self.parse_next(input)?.switch().parse_next(input)
171        })
172    }
173
174    /// An option taking one value, as bytes.
175    pub fn argument<'i>(mut self) -> impl Parser<Argv<'i>, &'i BStr, Error> {
176        trace("argument", move |input: &mut Argv<'i>| {
177            let arg = self.parse_next(input)?;
178            arg.read_value_with(input, self.options)
179        })
180    }
181
182    /// An option taking one value, converted with [`FromArg`].
183    pub fn argument_as<'i, T: FromArg>(mut self) -> impl Parser<Argv<'i>, T, Error> {
184        trace("argument_as", move |input: &mut Argv<'i>| {
185            self.parse_next(input)?
186                .value_as_with(self.options)
187                .parse_next(input)
188        })
189    }
190
191    /// An option whose value may be left out: a bare `--name`, or one followed by
192    /// another flag, gives `missing`. `--name=v` and `--name v` give `v`.
193    pub fn argument_or<'i, T: FromArg>(
194        mut self,
195        missing: &'static str,
196    ) -> impl Parser<Argv<'i>, T, Error> {
197        trace("argument_or", move |input: &mut Argv<'i>| {
198            let arg = self.parse_next(input)?;
199            let value = arg.read_value_or_with(input, self.options, BStr::new(missing));
200            arg.convert(value)
201        })
202    }
203
204    /// An option whose value may be left off: a bare `--name`, or one followed
205    /// by another flag, gives `None`. What an `Option` field holds.
206    pub fn argument_opt<'i>(mut self) -> impl Parser<Argv<'i>, Option<&'i BStr>, Error> {
207        trace("argument_opt", move |input: &mut Argv<'i>| {
208            let arg = self.parse_next(input)?;
209            Ok(arg.read_value_opt_with(input, self.options))
210        })
211    }
212
213    /// [`Named::argument_opt`], converted with [`FromArg`].
214    pub fn argument_opt_as<'i, T: FromArg>(mut self) -> impl Parser<Argv<'i>, Option<T>, Error> {
215        trace("argument_opt_as", move |input: &mut Argv<'i>| {
216            let arg = self.parse_next(input)?;
217            arg.read_value_opt_with(input, self.options)
218                .map(|v| arg.convert(v))
219                .transpose()
220        })
221    }
222
223    /// An option whose value is split on `delimiter`, each piece converted:
224    /// `--tags a,b` gives two.
225    pub fn arguments_as<'i, T: FromArg>(
226        mut self,
227        delimiter: u8,
228    ) -> impl Parser<Argv<'i>, Vec<T>, Error> {
229        trace("arguments_as", move |input: &mut Argv<'i>| {
230            let arg = self.parse_next(input)?;
231            let value = arg.read_value_with(input, self.options)?;
232            token::split(value, delimiter)
233                .map(|v| arg.convert(v))
234                .collect()
235        })
236    }
237}
238
239/// One occurrence of the flag's name, value not yet read.
240impl<'i, const N: usize> Parser<Argv<'i>, Arg<'i>, Error> for Named<N> {
241    #[inline]
242    fn parse_next(&mut self, input: &mut Argv<'i>) -> Result<Arg<'i>, Error> {
243        token::arg
244            .verify(|arg: &Arg<'i>| self.matches(arg))
245            .parse_next(input)
246    }
247}
248
249/// A positional value: one word that is not a flag, converted with [`FromArg`].
250/// `name` is how errors refer to it.
251///
252/// This matches one word; which positional a word fills is up to the caller,
253/// as with flags.
254pub fn positional<'i, T: FromArg>(name: &'static str) -> impl Parser<Argv<'i>, T, Error> {
255    trace("positional", move |input: &mut Argv<'i>| {
256        token::word(input)?.value_as(name).parse_next(input)
257    })
258}
259
260/// The spellings of a subcommand: one name, or a name and its aliases.
261pub trait Names {
262    /// Whether `word` is one of them.
263    fn contains(&self, word: &[u8]) -> bool;
264}
265
266impl Names for &str {
267    #[inline]
268    fn contains(&self, word: &[u8]) -> bool {
269        self.as_bytes() == word
270    }
271}
272
273impl<const N: usize> Names for [&str; N] {
274    #[inline]
275    fn contains(&self, word: &[u8]) -> bool {
276        self.iter().any(|name| name.as_bytes() == word)
277    }
278}
279
280/// A subcommand: the word `name` (or any of `["name", "alias", …]`), then
281/// `inner` for the rest of the line.
282///
283/// Once the word matches, `inner`'s errors are committed. After `--` no word is
284/// a subcommand.
285pub fn command<'i, O, P>(name: impl Names, mut inner: P) -> impl Parser<Argv<'i>, O, Error>
286where
287    P: Parser<Argv<'i>, O, Error>,
288{
289    trace("command", move |input: &mut Argv<'i>| {
290        let start = input.checkpoint();
291        match token::word(input) {
292            Ok(word) if name.contains(word.value) && !word.after_separator => {
293                inner.parse_next(input).map_err(|e| e.cut())
294            }
295            Ok(_) => {
296                input.reset(&start);
297                Err(Error::from_input(input))
298            }
299            Err(e) => Err(e),
300        }
301    })
302}
303
304/// Apply `item` until it stops matching, then require the command line to be
305/// over. What is left is reported as an unknown flag or an unexpected argument.
306///
307/// `item` is usually an `alt` of occurrence parsers each `map`ped into a
308/// variable. The `--` separator is consumed here, so a positional parser in
309/// `item` sees the words after it as plain words.
310pub fn args<'i, P>(mut item: P) -> impl Parser<Argv<'i>, (), Error>
311where
312    P: Parser<Argv<'i>, (), Error>,
313{
314    trace("args", move |input: &mut Argv<'i>| {
315        let separator = token::arg
316            .verify(|a: &Arg<'i>| matches!(a, Arg::Separator { .. }))
317            .void();
318        repeat::<_, _, (), _, _>(0.., winnow::combinator::alt((item.by_ref(), separator)))
319            .parse_next(input)?;
320        token::finish(input).map_err(|e| e.cut())
321    })
322}