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