Skip to main content

winnow_args/
lib.rs

1//! Command line parsing built from [winnow] parsers.
2//!
3//! winnow-args parses a program's arguments into typed values, typically a
4//! struct with one field per flag or positional. It distinguishes:
5//!
6//! - **flags**, long (`--verbose`) or short (`-v`); short flags can be bundled,
7//!   `-vq` for `-v -q`;
8//! - **values** of flags, given as the next word (`--path /tmp`, `-p /tmp`) or
9//!   attached (`--path=/tmp`, `-p/tmp`);
10//! - **positionals**, words identified by their position: `a` and `b` in
11//!   `cp a b`;
12//! - **subcommands**, words that select a command with arguments of its own:
13//!   `add` in `git add -p`;
14//! - `--`, after which every word is a positional.
15//!
16//! A failed parse returns an [`Error`] with its kind, the word at fault and its
17//! offset.
18//!
19//! There are two ways to say which of these a program takes:
20//!
21//! - `#[derive(Args)]` on a struct. A field is a flag or a positional, its type
22//!   says how many values it takes and what they convert to, and its doc comment
23//!   is its help. This is what most programs want, and the faster of the two.
24//! - By hand, with [`combinator`]. A flag is a [winnow] parser, and
25//!   `alt`, `repeat` and the rest of winnow combine flags into a command line.
26//!
27//! Either way the words are read where the shell left them: nothing is copied or
28//! re-split, and the bytes need not be UTF-8 until a value's type asks for text.
29//! Both also cover command lines older than `--long`: `+x` options as in a bash
30//! builtin, and flags whose order matters as in `ld`.
31//!
32// The example needs the `derive` feature.
33#![cfg_attr(feature = "derive", doc = "```")]
34#![cfg_attr(not(feature = "derive"), doc = "```ignore")]
35//! use std::path::PathBuf;
36//! use winnow_args::Args;
37//!
38//! /// Look somewhere.
39//! #[derive(Args, Debug)]
40//! struct Cli {
41//!     /// Say more.
42//!     #[arg(short, long)]
43//!     verbose: bool,
44//!     /// Where to look.
45//!     #[arg(short, long)]
46//!     path: Option<PathBuf>,
47//!     #[arg(positional)]
48//!     files: Vec<String>,
49//! }
50//!
51//! // In `main`: `let cli = Cli::parse();`
52//! let cli = Cli::parse_from(["-v", "--path=/tmp", "a", "b"])?;
53//! assert!(cli.verbose);
54//! assert_eq!(cli.path, Some(PathBuf::from("/tmp")));
55//! assert_eq!(cli.files, ["a", "b"]);
56//! # Ok::<(), winnow_args::Error>(())
57//! ```
58//!
59//! # Where to look
60//!
61#![cfg_attr(
62    feature = "derive",
63    doc = "- [`derive@Args`], [`derive@Subcommand`], [`derive@ValueEnum`] and [`derive@Occurrence`]:
64  the derives. Each page lists every attribute it takes."
65)]
66//! - [`combinator`]: the same command lines written by hand, and [`token`],
67//!   the lexer under both.
68//! - [`FromArg`]: what a value can be, and the types in [`value`] for a
69//!   `FromStr` type, a C-syntax number and `key=value`.
70//! - [`Error`] and [`report`]: what a failed parse holds, and how it is printed,
71//!   with a tip when something close to what was typed is accepted.
72//! - [`help`] and [`color`]: the help text and its theme. A description's
73//!   `` `quoted` ``, `*emphasized*` and `**strong**` spans are painted, each
74//!   by a paint of its own in [`color::Palette`].
75//! - [`complete`]: completion scripts for bash, zsh, fish, elvish and
76//!   PowerShell, for flags, subcommands and values.
77//! - [`response`]: `@file` words.
78//! - [`with_env`]: a test's own environment variables.
79//!
80//! Man and markdown pages are made by `winnow-args-man` and
81//! `winnow-args-markdown`, from what the derives write when the environment
82//! variable `WINNOW_ARGS_SPEC` names a directory; the derive crate's
83//! documentation says how.
84//!
85//! # Entry points
86//!
87//! - [`Args::parse`] in `main`. Help, version and a failure are printed, and
88//!   the process exits.
89//! - [`Args::try_parse`] returns the [`Error`] for the caller to handle.
90//! - [`Args::parse_from`] takes the words after the program name: what a test
91//!   calls.
92//! - [`Args::parse_from_argv`] takes the whole line, program name first.
93//! - [`Args::parse_words`] takes words that are already [`BStr`]s.
94//! - [`Args::completion_request`] goes before any of them in a program that
95//!   has [`complete`] scripts.
96//!
97//! # Performance
98//!
99//! The parser is generated at compile time, so a parse costs the same however
100//! many commands the program declares; clap and bpaf build theirs at each
101//! start. A parse is a few thousand instructions and a few hundred
102//! nanoseconds. The measurements, against clap, bpaf and usage, and the
103//! method are in the repository's
104//! [benchmarks](https://github.com/lu-zero/winnow-args/tree/HEAD/benchmarks).
105//!
106//! # Cargo features
107//!
108//! - `derive` (default): the derives.
109//! - `help-text` (default): the prose of derived help. Without it, help still
110//!   lists commands, flags, values and defaults, and the binary is smaller.
111//! - `terminal-size`: wrap help to the terminal's width when `COLUMNS` is unset.
112//!
113//! # From clap and usage
114//!
115//! The spellings that differ. Everything else is on the derive's own page.
116//!
117//! | | clap | usage | winnow-args |
118//! |---|---|---|---|
119//! | the command | `#[derive(Parser)]` | `#[derive(Cli)]` | `#[derive(Args)]` |
120//! | its name, its version | `#[command(name, version)]` | `#[usage(bin, version)]` | `#[arg(name, version)]` |
121//! | an attribute | `#[arg]`, `#[command]` | `#[usage]` | `#[arg]` |
122//! | subcommands | `#[derive(Subcommand)]` | `#[derive(Subcommands)]` | `#[derive(Subcommand)]` |
123//! | words after the program name | none: the name comes first | `parse_from` | `parse_from` |
124//! | the whole line | `try_parse_from` | `parse_from_argv` | `parse_from_argv` |
125//! | a count | `action = ArgAction::Count` | `count` | `count` |
126//! | a positional | no `long` or `short` | no `short` or `long` | `#[arg(positional)]` |
127//! | a field with no attribute | a positional | a positional | `--field-name` |
128//! | a default, an environment variable | `default_value`, `env` | `default`, `env` | `default`, `env` |
129//! | several words, a delimiter | `num_args`, `value_delimiter` | `num_args`, `delimiter` | `values`, `delimiter` |
130//! | a value that may be left off | `default_missing_value` | `default_missing` | `default_missing` |
131//! | choices from an enum | `#[arg(value_enum)]` | `#[usage(value_enum)]` | the type derives `ValueEnum` |
132//! | a conflict | `conflicts_with = "file"` | `conflicts("--file")` | `conflicts("--file")` |
133//! | the flag that clears a switch | `ArgAction::SetFalse` | `negate = "--no-force"` | `negate = "no-force"` |
134//! | shared flags | `#[command(flatten)]` | `#[usage(flatten)]` | `#[arg(flatten)]` |
135//! | a subcommand field | `#[command(subcommand)]` | `#[usage(subcommand)]` | `#[arg(subcommand)]` |
136//! | an unknown flag | an error | a value, unless `unknown_flags = "error"` | an error, unless `unknown_flags = "value"` |
137
138pub mod color;
139pub mod combinator;
140pub mod complete;
141pub mod env;
142pub mod error;
143pub mod help;
144pub mod response;
145pub mod stream;
146pub mod token;
147pub mod value;
148
149pub use env::with_env;
150pub use error::{Error, ErrorKind};
151pub use stream::{Argv, words};
152pub use token::Arg;
153pub use value::{ChoiceError, FromArg};
154/// A word of the command line, and a flag's value: bytes, not necessarily
155/// UTF-8. What [`FromArg::from_arg`] is given.
156pub use winnow::stream::BStr;
157
158#[cfg(feature = "derive")]
159pub use winnow_args_derive::{Args, Occurrence, Subcommand, ValueEnum};
160
161// The README's example is compiled as a doctest.
162#[cfg(all(doctest, feature = "derive"))]
163#[doc = include_str!("../README.md")]
164pub struct Readme;
165
166// The derive crate cannot call `parse_from`, so these examples are tested here.
167#[cfg(all(doctest, feature = "derive"))]
168pub mod derive_examples {
169    //! ```
170    //! use winnow_args::Args;
171    //!
172    //! /// Copy files.
173    //! #[derive(Args, Debug)]
174    //! #[arg(name = "cp", version)]
175    //! struct Cp {
176    //!     /// Explain what is done; repeat for more.
177    //!     #[arg(short, long, count)]
178    //!     verbose: u8,
179    //!     /// Overwrite without asking.
180    //!     #[arg(short, long, negate)]
181    //!     force: bool,
182    //!     /// Attributes to keep.
183    //!     #[arg(long, delimiter = ',')]
184    //!     preserve: Vec<String>,
185    //!     /// Jobs to run.
186    //!     #[arg(short, long, env = "CP_JOBS", default = "1")]
187    //!     jobs: usize,
188    //!     #[arg(positional)]
189    //!     source: String,
190    //!     #[arg(positional)]
191    //!     dest: Option<String>,
192    //! }
193    //!
194    //! let cp = Cp::parse_from(["-vvf", "--preserve=mode,links", "a", "--no-force", "b"])?;
195    //! assert_eq!((cp.verbose, cp.force, cp.jobs), (2, false, 1));
196    //! assert_eq!(cp.preserve, ["mode", "links"]);
197    //! assert_eq!((cp.source.as_str(), cp.dest.as_deref()), ("a", Some("b")));
198    //! # Ok::<(), winnow_args::Error>(())
199    //! ```
200    //!
201    //! ```
202    //! use winnow_args::{Args, Subcommand};
203    //!
204    //! #[derive(Args, Debug, PartialEq)]
205    //! struct Add {
206    //!     #[arg(positional)]
207    //!     paths: Vec<String>,
208    //! }
209    //!
210    //! #[derive(Subcommand, Debug, PartialEq)]
211    //! enum Command {
212    //!     /// Stage files.
213    //!     Add(Add),
214    //!     /// Show what changed.
215    //!     #[arg(alias = "st")]
216    //!     Status,
217    //! }
218    //!
219    //! #[derive(Args, Debug)]
220    //! struct Git {
221    //!     /// Say less.
222    //!     #[arg(short, long, global)]
223    //!     quiet: bool,
224    //!     #[arg(subcommand)]
225    //!     command: Command,
226    //! }
227    //!
228    //! let git = Git::parse_from(["add", "-q", "a", "b"])?;
229    //! assert!(git.quiet);
230    //! assert_eq!(git.command, Command::Add(Add { paths: vec!["a".into(), "b".into()] }));
231    //! assert_eq!(Git::parse_from(["st"])?.command, Command::Status);
232    //!
233    //! match git.command {
234    //!     Command::Add(Add { paths }) => println!("staging {paths:?}"),
235    //!     Command::Status => println!("on branch main"),
236    //! }
237    //! # Ok::<(), winnow_args::Error>(())
238    //! ```
239    //!
240    //! ```
241    //! use winnow_args::{Args, Occurrence};
242    //!
243    //! #[derive(Occurrence, Debug, PartialEq)]
244    //! enum Item {
245    //!     /// Link what follows only if needed.
246    //!     #[arg(long)]
247    //!     AsNeeded,
248    //!     /// Search for a library.
249    //!     #[arg(short = 'l', prefix)]
250    //!     Library(String),
251    //!     #[arg(positional, value_name = "FILE")]
252    //!     Input(String),
253    //! }
254    //!
255    //! #[derive(Args, Debug)]
256    //! struct Ld {
257    //!     #[arg(short, long)]
258    //!     output: Option<String>,
259    //!     #[arg(sequence)]
260    //!     items: Vec<Item>,
261    //! }
262    //!
263    //! let ld = Ld::parse_from(["a.o", "--as-needed", "-lm", "-o", "out", "b.o"])?;
264    //! assert_eq!(ld.output.as_deref(), Some("out"));
265    //! assert_eq!(
266    //!     ld.items,
267    //!     [
268    //!         Item::Input("a.o".into()),
269    //!         Item::AsNeeded,
270    //!         Item::Library("m".into()),
271    //!         Item::Input("b.o".into()),
272    //!     ]
273    //! );
274    //! # Ok::<(), winnow_args::Error>(())
275    //! ```
276    //!
277    //! ```
278    //! use winnow_args::{Args, ErrorKind, ValueEnum};
279    //!
280    //! #[derive(ValueEnum, Debug, PartialEq)]
281    //! enum Color {
282    //!     Always,
283    //!     #[arg(alias = "no")]
284    //!     Never,
285    //!     #[arg(name = "auto")]
286    //!     WhenTerminal,
287    //! }
288    //!
289    //! #[derive(Args, Debug)]
290    //! struct Cli {
291    //!     #[arg(long)]
292    //!     color: Option<Color>,
293    //! }
294    //!
295    //! assert_eq!(Cli::parse_from(["--color=auto"])?.color, Some(Color::WhenTerminal));
296    //! assert_eq!(Cli::parse_from(["--color", "no"])?.color, Some(Color::Never));
297    //! let error = Cli::parse_from(["--color=red"]).unwrap_err();
298    //! assert_eq!(error.kind(), ErrorKind::InvalidChoice);
299    //! # Ok::<(), winnow_args::Error>(())
300    //! ```
301}
302
303/// A type parsed from a whole command line.
304pub trait Args: Sized {
305    /// The name the derive files this type under when the documentation is
306    /// built. Empty for an implementation written by hand.
307    #[doc(hidden)]
308    const SPEC: &'static str = "";
309
310    /// Parse the command line in `input`, consuming all of it.
311    ///
312    /// This is a winnow parser: `Cli::parse_argv` can be passed anywhere a
313    /// `Parser<Argv, Cli, Error>` is expected.
314    fn parse_argv(input: &mut Argv<'_>) -> Result<Self, Error> {
315        Self::parse_argv_with(input, &mut ())
316    }
317
318    /// [`Args::parse_argv`] as a subcommand: flags it does not declare itself
319    /// are offered to `globals`, its ancestors' global flags.
320    fn parse_argv_with(input: &mut Argv<'_>, globals: &mut dyn Globals) -> Result<Self, Error>;
321
322    /// [`Args::HELP`], from where the type is. A constant is built again in
323    /// each crate that names it, and help is large: the derive writes this
324    /// beside the type, so a program in another crate has the one copy.
325    #[doc(hidden)]
326    fn help_data() -> &'static help::Command {
327        Self::HELP
328    }
329
330    /// This command's help, rendered only when asked for.
331    const HELP: &'static help::Command = &help::Command {
332        name: "",
333        about: "",
334        long_about: "",
335        after_help: "",
336        after_long_help: "",
337        items: &[],
338        subcommands: &[],
339        subcommand_required: false,
340        help_flag: false,
341        help_short: false,
342        version: None,
343    };
344
345    /// Answer a completion script's callback (`PROG __complete_word__ …`)
346    /// from this command's help data; `None` when `args` (the command line
347    /// after the program's name) is not one. See [`complete`].
348    fn completion_request(args: &[std::ffi::OsString]) -> Option<String> {
349        complete::answer(Self::help_data(), args)
350    }
351
352    /// The completion script for `shell`, calling back the program named in
353    /// the help data (`#[arg(name = "…")]`).
354    ///
355    /// # Panics
356    ///
357    /// If the command has no name, or one that is not a plain word.
358    fn completion_script(shell: complete::Shell) -> String {
359        complete::script(Self::HELP.name, shell)
360    }
361
362    /// Parse `words`, the arguments after the program name, already borrowed
363    /// as [`BStr`]s: what [`response::expand`] and [`words`] give. Nothing is
364    /// copied.
365    ///
366    /// An unknown flag or subcommand that is close to one of the program's
367    /// has it as [`Error::similar`].
368    fn parse_words(words: &[&BStr]) -> Result<Self, Error> {
369        Self::parse_argv(&mut Argv::new(words)).map_err(|error| error.suggested(Self::help_data()))
370    }
371
372    /// Parse `argv`, the whole command line, program name first. The name is
373    /// skipped.
374    fn parse_from_argv<I, S>(argv: I) -> Result<Self, Error>
375    where
376        I: IntoIterator<Item = S>,
377        S: AsRef<std::ffi::OsStr>,
378    {
379        Self::parse_from(argv.into_iter().skip(1))
380    }
381
382    /// Parse `args`, the arguments after the program name.
383    ///
384    /// ```
385    /// # #[cfg(feature = "derive")] {
386    /// use winnow_args::{Args, ErrorKind};
387    ///
388    /// #[derive(Args)]
389    /// struct Cli {
390    ///     #[arg(short, long)]
391    ///     verbose: bool,
392    /// }
393    ///
394    /// assert!(Cli::parse_from(["-v"]).unwrap().verbose);
395    /// assert!(Cli::parse_from_argv(["prog", "-v"]).unwrap().verbose);
396    /// let error = Cli::parse_from(["--loud"]).err().unwrap();
397    /// assert_eq!(error.kind(), ErrorKind::UnknownFlag);
398    /// # }
399    /// ```
400    fn parse_from<I, S>(args: I) -> Result<Self, Error>
401    where
402        I: IntoIterator<Item = S>,
403        S: AsRef<std::ffi::OsStr>,
404    {
405        let args: Vec<S> = args.into_iter().collect();
406        Self::parse_words(&words(&args))
407    }
408
409    /// Parse the process's arguments; on help, version or a failure, print
410    /// and exit as [`report`] does. It answers no completion callback: call
411    /// [`Args::completion_request`] first if the program has [`complete`] scripts.
412    fn parse() -> Self {
413        match Self::try_parse() {
414            Ok(parsed) => parsed,
415            Err(error) => std::process::exit(report(&error, &Self::program())),
416        }
417    }
418
419    /// Parse the process's arguments, leaving help, version and failures to
420    /// the caller: [`Args::parse`] without the printing and the exit.
421    ///
422    /// ```no_run
423    /// # #[cfg(feature = "derive")] {
424    /// use winnow_args::Args;
425    ///
426    /// #[derive(Args)]
427    /// struct Cli {
428    ///     #[arg(short, long)]
429    ///     verbose: bool,
430    /// }
431    ///
432    /// let cli = match Cli::try_parse() {
433    ///     Ok(cli) => cli,
434    ///     Err(error) => std::process::exit(winnow_args::report(&error, &Cli::program())),
435    /// };
436    /// # let _ = cli.verbose;
437    /// # }
438    /// ```
439    fn try_parse() -> Result<Self, Error> {
440        Self::parse_from_argv(std::env::args_os())
441    }
442
443    /// The program's name, as messages give it: `#[arg(name = "…")]`, else
444    /// the file name the process was run as.
445    fn program() -> String {
446        if !Self::HELP.name.is_empty() {
447            return Self::HELP.name.to_owned();
448        }
449        let argv0 = std::env::args_os().next().unwrap_or_default();
450        std::path::Path::new(&argv0)
451            .file_name()
452            .map(|name| name.to_string_lossy().into_owned())
453            .unwrap_or_default()
454    }
455}
456
457/// Print what a failed parse means, and give the exit status: help and version
458/// on stdout with 0, a bare `arg_required_else_help` invocation's help on stderr
459/// with 2, anything else as an error on stderr with 2. Colored with
460/// [`color::Theme::DEFAULT`] where the stream written to shows color; see
461/// [`report_with`] for another theme.
462pub fn report(error: &Error, program: &str) -> i32 {
463    report_with(error, program, &color::Theme::DEFAULT)
464}
465
466/// [`report`], painted with `theme`: its palette for the depth
467/// [`color::Depth::detect`] finds on the stream written to.
468///
469/// ```
470/// use winnow_args::color::{Paint, Palette, Theme};
471///
472/// // Bold, uncolored headers on 16-color terminals; the default elsewhere.
473/// const THEME: Theme = Theme {
474///     ansi16: Palette { header: Paint::NONE.bold(), ..Palette::DEFAULT },
475///     ..Theme::DEFAULT
476/// };
477/// # let _ = |e: &winnow_args::Error| winnow_args::report_with(e, "prog", &THEME);
478/// ```
479pub fn report_with(error: &Error, program: &str, theme: &color::Theme) -> i32 {
480    use std::io::IsTerminal as _;
481    let stdout = || help::Style::themed(theme, std::io::stdout().is_terminal());
482    let stderr = || help::Style::themed(theme, std::io::stderr().is_terminal());
483    // Help goes to stdout, except a bare invocation's, which is an error.
484    let bare = error.is_bare_help();
485    let style = if bare { stderr() } else { stdout() };
486    if let Some(help) = error.render_help_styled(program, style) {
487        if bare {
488            eprintln!("{help}");
489            return 2;
490        }
491        println!("{help}");
492        return 0;
493    }
494    if let Some(version) = error.version_text(program) {
495        println!("{version}");
496        return 0;
497    }
498    eprintln!("{}", error.render(stderr()));
499    2
500}
501
502/// Flags and words kept in command-line order: an enum deriving `Occurrence`,
503/// one variant per flag or positional, collected by an `#[arg(sequence)]`
504/// `Vec` field of an [`Args`] struct.
505///
506/// For command lines where position is meaning, as `ld`'s: `--as-needed` and
507/// `--whole-archive` apply to the inputs after them, so the linker folds the
508/// sequence rather than reading fields.
509pub trait Occurrence: Sized {
510    /// The name the derive files this type under when the documentation is
511    /// built. Empty for an implementation written by hand.
512    #[doc(hidden)]
513    const SPEC: &'static str = "";
514
515    /// Letters that always take the rest of their word (`-lfoo`), for a
516    /// `long_only` parent: no single-dash long name starting with one is tried.
517    const PREFIXES: &'static [u8] = &[];
518
519    /// `arg` as one of the variants, its value read from `input`; `None` when
520    /// no variant is spelled so.
521    fn from_arg<'i>(arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<Option<Self>, Error>;
522
523    /// A word as the positional variant; `None` if there is none.
524    fn from_word(word: &token::Word<'_>) -> Result<Option<Self>, Error>;
525
526    /// Whether `name` is a variant's long name that may be spelled with one
527    /// dash, for a `long_only` parent.
528    fn is_long(name: &[u8]) -> bool;
529
530    /// The variants as help lists them, after the parent's own flags.
531    const ITEMS: &'static [help::Item] = &[];
532
533    /// Whether there is a positional variant: each plain word is that variant.
534    const POSITIONAL: bool = false;
535
536    /// Whether there is an `#[arg(bundle)]` variant.
537    const BUNDLES: bool = false;
538
539    /// A word of several short flags (`-sS`), whole, as the `#[arg(bundle)]`
540    /// variant: an item put before those of its letters, whoever takes them,
541    /// for a program that reports grouping (GNU ld deprecates it). `None` if
542    /// there is none.
543    fn from_bundle(word: &token::Word<'_>) -> Result<Option<Self>, Error> {
544        let _ = word;
545        Ok(None)
546    }
547
548    /// A flag no variant names, as the `#[arg(unknown)]` variant; `None` if
549    /// there is none. Asked by a parent whose field is
550    /// `#[arg(sequence, unknown)]`.
551    fn from_unknown(word: &token::Word<'_>) -> Result<Option<Self>, Error> {
552        let _ = word;
553        Ok(None)
554    }
555
556    /// Whether `letter` is a variant's short flag, and whether it takes a
557    /// value: how a parent with an `unknown` field tells an unknown bundle.
558    fn short(letter: char) -> Option<bool> {
559        let _ = letter;
560        None
561    }
562}
563
564/// An enum of subcommands, selected by a word.
565///
566/// Derived with `#[derive(Subcommand)]`; a struct holds one in an
567/// `#[arg(subcommand)]` field. The derive also implements [`Args`] for the enum,
568/// so it can be the whole command line.
569pub trait Subcommand: Sized {
570    /// The name the derive files this type under when the documentation is
571    /// built. Empty for an implementation written by hand.
572    #[doc(hidden)]
573    const SPEC: &'static str = "";
574
575    /// Whether `name` names a subcommand. Asked of every eligible word, so kept
576    /// apart from [`Subcommand::parse_subcommand`] and cheap to inline.
577    fn has(name: &[u8]) -> bool;
578
579    /// Parse the rest of `input` as the subcommand `name`, which [`Subcommand::has`];
580    /// `globals` binds its ancestors' global flags.
581    fn parse_subcommand(
582        name: &[u8],
583        input: &mut Argv<'_>,
584        globals: &mut dyn Globals,
585    ) -> Result<Self, Error>;
586}
587
588/// The global flags of a subcommand's ancestors, which it accepts as its own.
589///
590/// A parent hands one down to its subcommand; the subcommand offers it every
591/// flag it does not declare, so its own declarations take precedence.
592pub trait Globals {
593    /// Bind `arg`, read from `input`, if it is a global flag; `Ok(false)` if not.
594    fn bind<'i>(&mut self, arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<bool, Error>;
595
596    /// Whether `-letter` is one of these flags, and if so whether it takes a
597    /// value. A command with `unknown_flags = "value"` asks about every letter
598    /// of a bundle before binding any.
599    fn short(&self, letter: char) -> Option<bool> {
600        let _ = letter;
601        None
602    }
603}
604
605/// No global flags: what a command line's root is given.
606impl Globals for () {
607    #[inline]
608    fn bind<'i>(&mut self, _: &Arg<'i>, _: &mut Argv<'i>) -> Result<bool, Error> {
609        Ok(false)
610    }
611}
612
613/// [`Globals`] from a closure. It does not answer [`Globals::short`]: the derive
614/// calls `inherit`, which forwards the parent's letters, so a lenient
615/// subcommand still sees them.
616pub fn globals<F>(bind: F) -> impl Globals
617where
618    F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>) -> Result<bool, Error>,
619{
620    struct FromFn<F>(F);
621    impl<F> Globals for FromFn<F>
622    where
623        F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>) -> Result<bool, Error>,
624    {
625        #[inline]
626        fn bind<'i>(&mut self, arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<bool, Error> {
627            (self.0)(arg, input)
628        }
629    }
630    FromFn(bind)
631}
632
633/// [`Globals`] that are `shorts` (letter, takes a value) and `bind`, then
634/// `parent`'s: what the derive hands a subcommand of a struct with global flags.
635/// Unlike [`globals`], `short` sees those letters.
636#[doc(hidden)]
637pub fn inherit<'a, F>(
638    shorts: &'static [(char, bool)],
639    parent: &'a mut dyn Globals,
640    bind: F,
641) -> impl Globals + 'a
642where
643    F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>, &mut dyn Globals) -> Result<bool, Error> + 'a,
644{
645    struct Inherit<'a, F> {
646        shorts: &'static [(char, bool)],
647        parent: &'a mut dyn Globals,
648        bind: F,
649    }
650    impl<F> Globals for Inherit<'_, F>
651    where
652        F: for<'i> FnMut(&Arg<'i>, &mut Argv<'i>, &mut dyn Globals) -> Result<bool, Error>,
653    {
654        #[inline]
655        fn bind<'i>(&mut self, arg: &Arg<'i>, input: &mut Argv<'i>) -> Result<bool, Error> {
656            (self.bind)(arg, input, &mut *self.parent)
657        }
658
659        fn short(&self, letter: char) -> Option<bool> {
660            match self.shorts.iter().find(|(c, _)| *c == letter) {
661                Some(&(_, takes_value)) => Some(takes_value),
662                None => self.parent.short(letter),
663            }
664        }
665    }
666    Inherit {
667        shorts,
668        parent,
669        bind,
670    }
671}
672
673/// Help prose the derive emits: kept with the `help-text` feature.
674#[cfg(feature = "help-text")]
675#[doc(hidden)]
676#[macro_export]
677macro_rules! __text {
678    ($text:literal) => {
679        $text
680    };
681}
682
683/// Help prose the derive emits: `""` without the `help-text` feature.
684#[cfg(not(feature = "help-text"))]
685#[doc(hidden)]
686#[macro_export]
687macro_rules! __text {
688    ($text:literal) => {
689        ""
690    };
691}
692
693#[doc(hidden)]
694pub mod __private {
695    pub use crate::env;
696    pub use crate::error::Error;
697    pub use crate::help::concat_items;
698    pub use crate::stream::Argv;
699    pub use crate::token::{Arg, ValueOptions, Word, arg, arg_plus, long_only, number, split};
700    pub use crate::value::{ChoiceError, FromArg, documented_choices, filed_as, stated_choices};
701    pub use crate::{Globals, Subcommand, globals, inherit};
702    pub use winnow::stream::BStr;
703    pub type BoxError = crate::error::BoxError;
704
705    use crate::token::is_flag_like;
706
707    /// The flags of a struct that another flattens (`#[arg(flatten)]`), derived
708    /// for every `Args` struct that has flags only.
709    ///
710    /// The parent keeps a [`Flatten::Slots`], offers it each flag its own
711    /// arms do not take, and builds the value at the end.
712    #[diagnostic::on_unimplemented(
713        message = "`{Self}` cannot be flattened",
714        note = "a flattened struct derives `Args` and has only flags: no positionals, \
715                subcommand, `sequence`, `unknown`, `keywords`, `global` flags, \
716                `unknown_flags = \"value\"` or generics"
717    )]
718    pub trait Flatten: Sized {
719        /// What has been parsed so far.
720        type Slots: Default;
721
722        /// Bind `arg` if it is one of these flags; `Ok(false)` if not.
723        fn bind<'i>(
724            slots: &mut Self::Slots,
725            arg: &Arg<'i>,
726            input: &mut Argv<'i>,
727        ) -> Result<bool, Error>;
728
729        /// Whether `-letter` is one of these flags, and whether it takes a value.
730        fn short(letter: char) -> Option<bool>;
731
732        /// Whether `name` is a long name that may take one dash (`long_only`).
733        fn is_long(name: &[u8]) -> bool;
734
735        /// Whether `-letter` always takes the rest of its word (`prefix`).
736        fn is_prefix(letter: u8) -> bool;
737
738        /// Apply environment variables, defaults and rules, and build the value.
739        fn finish(slots: Self::Slots, input: &Argv<'_>) -> Result<Self, Error>;
740    }
741
742    /// `help a b …`: the long help of the command the words name, below `root`.
743    pub fn help_word(root: &'static crate::help::Command, input: &mut Argv<'_>) -> Error {
744        let mut words = Vec::new();
745        while !input.is_empty() && !is_flag_like(input.front()) {
746            words.push(input.take_word());
747        }
748        let (command, path) = crate::help::resolve(root, words.iter().map(|w| &***w));
749        path.into_iter()
750            .rev()
751            .fold(Error::help(command, true), Error::within)
752    }
753
754    /// Whether every letter of the short bundle `token` (`-abc`) is `known`,
755    /// up to the first that takes a value: the rest is that value.
756    pub fn bundle_known(token: &[u8], known: impl Fn(char) -> Option<bool>) -> bool {
757        let Some(mut rest) = token.strip_prefix(b"-") else {
758            return false;
759        };
760        // Letters are ASCII in practice: decode only from the first byte that is not.
761        while let Some((&byte, tail)) = rest.split_first() {
762            if !byte.is_ascii() {
763                let Ok(letters) = core::str::from_utf8(rest) else {
764                    return false;
765                };
766                return letters.chars().try_for_each(|letter| match known(letter) {
767                    None => Err(false),
768                    Some(true) => Err(true),
769                    Some(false) => Ok(()),
770                }) != Err(false);
771            }
772            match known(byte as char) {
773                None => return false,
774                Some(true) => return true,
775                Some(false) => rest = tail,
776            }
777        }
778        true
779    }
780
781    /// A short bundle of two or more letters naming one no flag answers to,
782    /// taken whole as a word: `unknown_flags = "value"`. `None`, consuming
783    /// nothing, for anything else; a single unknown letter binds nothing before
784    /// it, so the derive's fallback arm handles it without this check.
785    #[inline]
786    pub fn unknown_bundle<'i>(
787        input: &mut Argv<'i>,
788        known: impl Fn(char) -> Option<bool>,
789    ) -> Option<Word<'i>> {
790        let front = input.front();
791        let short = input.mode() == crate::stream::Mode::Word
792            && front.len() > 2
793            && front[0] == b'-'
794            && front[1] != b'-';
795        if !short || bundle_known(front, known) {
796            return None;
797        }
798        let offset = input.offset();
799        Some(Word {
800            value: input.take_word(),
801            offset,
802            after_separator: false,
803        })
804    }
805
806    /// A `#[arg(keywords)]` field: each value `flag` took (`-z now`) parsed as
807    /// the `--now` flag of `T`, in order; an error is an invalid value of `flag`
808    /// naming the keyword.
809    pub fn keywords<T: crate::Args>(values: &[&BStr], flag: &str, at: usize) -> Result<T, Error> {
810        let spelled: Vec<Vec<u8>> = values
811            .iter()
812            .map(|v| [b"--".as_slice(), v].concat())
813            .collect();
814        let words: Vec<&BStr> = spelled.iter().map(|w| BStr::new(w.as_slice())).collect();
815        T::parse_words(&words).map_err(|e| {
816            let keyword = e.token().map_or_else(String::new, |t| {
817                t.strip_prefix("--").unwrap_or(t).to_owned()
818            });
819            Error::invalid_value(at, flag, keyword.as_bytes(), e)
820        })
821    }
822
823    /// The end of a unit subcommand: only inherited global flags may follow,
824    /// or a request for its `help`.
825    pub fn finish_with(
826        input: &mut Argv<'_>,
827        globals: &mut dyn Globals,
828        help: &'static crate::help::Command,
829    ) -> Result<(), Error> {
830        while !input.is_empty() {
831            let arg = arg(input)?;
832            match arg {
833                Arg::Separator { .. } => {}
834                Arg::Long(_) | Arg::Short(_) if globals.bind(&arg, input)? => {}
835                Arg::Long(flag) if flag.name == b"help" => return Err(Error::help(help, true)),
836                Arg::Short(flag) if flag.letter == 'h' => return Err(Error::help(help, false)),
837                _ => return Err(arg.unexpected()),
838            }
839        }
840        Ok(())
841    }
842}