Skip to main content

usage_argv/
lib.rs

1//! A zero-allocation argv parser for [usage](https://usage.jdx.dev) specs.
2//!
3//! This crate implements the binding rules of [the argv grammar]: which token
4//! becomes which flag or argument, when a word selects a subcommand, and what
5//! is an error. It does so without building a command tree, without allocating,
6//! and in one pass.
7//!
8//! It is the runtime half of a compiled parser. The tables it reads are meant to
9//! be emitted by a derive macro as `static` data, so that starting a parse costs
10//! nothing at all: there is no construction step to pay for, only the walk over
11//! `argv`.
12//!
13//! # Shape of the API
14//!
15//! Parsing yields [`Event`]s rather than a map. A map would have to allocate,
16//! and would then have to be read back out again — whereas generated code can
17//! assign an event straight into a struct field. This is the same reason serde
18//! deserializes into your type instead of into a `Value`.
19//!
20//! ```
21//! use usage_argv::{Arg, Command, Event, Flag, Parser};
22//!
23//! static FORCE: Flag = Flag { key: 0, longs: &["force"], shorts: b"f", ..Flag::BOOL };
24//! static FILE: Arg = Arg { key: 1, ..Arg::REQUIRED };
25//! static ROOT: Command = Command {
26//!     name: "ex",
27//!     flags: &[&FORCE],
28//!     args: &[&FILE],
29//!     ..Command::EMPTY
30//! };
31//!
32//! let argv = ["--force", "a.txt"].map(std::ffi::OsStr::new);
33//! let mut parser = Parser::new(&ROOT, &argv);
34//!
35//! let mut force = false;
36//! let mut file = None;
37//! while let Some(event) = parser.next_event() {
38//!     match event.expect("valid command line") {
39//!         Event::Flag { flag, .. } if flag.key == 0 => force = true,
40//!         Event::Arg { value, .. } => file = Some(value),
41//!         _ => {}
42//!     }
43//! }
44//! assert!(force);
45//! assert_eq!(file, Some(&b"a.txt"[..]));
46//! ```
47//!
48//! # Values are bytes
49//!
50//! An [`Event`] carries `&[u8]`, borrowed from `argv`. Converting to `&str` is
51//! the caller's step ([`as_str`]), and it is the right place for the only
52//! failure a value can have: a command line that is not valid UTF-8 still
53//! *parses* — flags match, subcommands route — and only the values that are
54//! actually looked at can fail to convert.
55//!
56//! Slicing an `OsStr` into `&str` pieces safely is not possible without
57//! allocating or `unsafe`. Bytes are what is left, and they turn out to be the
58//! honest interface anyway.
59//!
60//! The reverse conversion is [`os_string_from_bytes`], which lets a `PathBuf`
61//! field hold a filename that is not UTF-8 rather than a mangled copy of one. On
62//! Unix that is lossless and safe; on Windows, where WTF-8 makes it partial, a
63//! value that will not convert is reported. Either way this crate contains no
64//! `unsafe`, which a conversion that guessed would have cost.
65//!
66//! # What this crate does not do
67//!
68//! Only binding. Required-ness, `choices`, `env` fallback, defaults, `var_min`
69//! and `var_max` are all decided *after* the last token is read, and they need to
70//! know a value's type, so they belong to the layer that owns the target struct.
71//! Keeping them out is what makes this loop small.
72//!
73//! # Features
74//!
75//! - `spec` — a parallel tree of cold metadata (help text, choices, defaults,
76//!   effects) and a writer that emits it as a usage spec. Off by default: a
77//!   successful parse never reads any of it, so a CLI that only wants a parser
78//!   should not compile it.
79//! - `complete` — answering a partial command line ([`complete`]), the shell
80//!   scripts that ask ([`script`]), and putting one of those scripts where its
81//!   shell will look for it ([`install`]). Installing ships with the scripts
82//!   rather than behind a gate of its own: a script a CLI still has to tell its
83//!   users to redirect by hand is the unfinished half of shipping one.
84//!
85//! [the argv grammar]: https://usage.jdx.dev/spec/argv
86
87#![forbid(unsafe_code)]
88
89/// Terminate at the compiled CLI entry-point boundary.
90///
91/// Kept in the runtime rather than expanded into an adopter crate so a project that
92/// forbids direct `std::process::exit` calls does not attribute the derive's process
93/// boundary to application code. `Cli::parse_from*` continues to return errors.
94#[doc(hidden)]
95#[allow(clippy::disallowed_methods)]
96pub fn __usage_process_exit(status: i32) -> ! {
97    std::process::exit(status)
98}
99
100use std::ffi::{OsStr, OsString};
101
102/// A value's shell-native completion class for `#[usage(value_hint = ...)]`.
103///
104/// This lives in the runtime crate so a declaration never needs clap merely to describe what
105/// kind of path a shell should offer. It is metadata only and adds no work to a successful
106/// parse.
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub enum ValueHint {
109    /// Let the shell use its normal fallback behavior.
110    Unknown,
111    /// No structured hint applies; suppress the shell's path fallback.
112    Other,
113    /// A path to a file.
114    FilePath,
115    /// A path to either a file or a directory.
116    AnyPath,
117    /// A path to a directory.
118    DirPath,
119    /// A path to an executable file.
120    ExecutablePath,
121    /// A command name, resolved through the shell's command table and `PATH`.
122    CommandName,
123    /// One string containing a command and any arguments.
124    CommandString,
125    /// A trailing argv vector: complete the first value as a command, then its arguments.
126    CommandWithArguments,
127    /// A local operating-system user name.
128    Username,
129    /// A host name known to the shell or operating system.
130    Hostname,
131    /// A web address. This suppresses path fallback but offers no finite candidate set.
132    Url,
133    /// An email address. This suppresses path fallback but offers no finite candidate set.
134    EmailAddress,
135}
136
137#[cfg(feature = "complete")]
138pub mod complete;
139#[cfg(feature = "diagnostics")]
140pub mod diagnostic;
141#[cfg(feature = "spec")]
142pub mod embedded;
143#[cfg(feature = "complete")]
144pub mod install;
145#[cfg(feature = "complete")]
146pub mod script;
147
148/// Checks that the `complete` feature is on, with an explanation when it is not.
149///
150/// `#[usage(completion)]` generates code that reaches into [`complete`], which is behind a
151/// feature the *depending* crate enables — a derive cannot turn on a feature of another crate.
152/// Without this, the failure was `unresolved module complete`, which says nothing about the
153/// attribute that caused it.
154#[cfg(feature = "complete")]
155#[macro_export]
156macro_rules! __usage_needs_complete_feature {
157    () => {};
158}
159
160/// See [`__usage_needs_complete_feature`].
161#[cfg(not(feature = "complete"))]
162#[macro_export]
163macro_rules! __usage_needs_complete_feature {
164    () => {
165        ::core::compile_error!(
166            "`#[usage(completion)]` needs usage-argv's `complete` feature. Add it where \
167             usage-argv is depended on: usage-argv = { version = \"…\", features = \
168             [\"spec\", \"complete\"] }"
169        );
170    };
171}
172#[cfg(feature = "spec")]
173pub mod help;
174// Behind no feature: two traits and no code, so there is nothing here for a binary that
175// does not dispatch to pay for, and a hand-written CLI on the bare runtime can use them.
176pub mod run;
177#[cfg(feature = "spec")]
178pub mod spec;
179#[cfg(feature = "spec")]
180pub mod warn;
181
182pub use run::{Run, RunAsync, RunAsyncWith, RunWith};
183
184/// How deep a command tree this parser will descend.
185///
186/// The ancestor chain is kept in a fixed-size array so that a parse allocates
187/// nothing; this is that array's size. mise, the largest usage CLI, is four
188/// levels deep.
189pub const MAX_DEPTH: usize = 16;
190
191/// A command: its flags, its positional arguments, and its subcommands.
192///
193/// Every field is a borrowed slice so that a derive can emit the whole tree as
194/// `static` data. Use `..Command::EMPTY` to fill in the parts you do not need.
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct Command<'a> {
197    /// The canonical name, used to select this command.
198    pub name: &'a str,
199    /// Alternative names that also select it.
200    pub aliases: &'a [&'a str],
201    pub flags: &'a [&'a Flag<'a>],
202    /// Positional arguments, in the order they are filled.
203    pub args: &'a [&'a Arg<'a>],
204    /// A repeatable group of positional arguments, if this command has one.
205    pub clause: ::core::option::Option<Clause<'a>>,
206    pub subcommands: &'a [&'a Command<'a>],
207    /// Where a word goes when it names no subcommand of this one.
208    ///
209    /// The spec's `default_subcommand`. `mise build` means `mise run build`: the word names
210    /// no command, so the parser descends into `run` and lets *`run`* have it — even where
211    /// this command declares an argument of its own, which is what makes the property worth
212    /// having rather than a synonym for a positional.
213    ///
214    /// Applied at most once per parse, so a CLI cannot loop through it, and only where a
215    /// subcommand could still be selected.
216    ///
217    /// Resolve it with [`find_subcommand`], which turns a name that no subcommand answers to
218    /// into a compile error.
219    pub default_subcommand: ::core::option::Option<&'a Command<'a>>,
220    /// Whether an unmatched word is forwarded as an external command plus the rest of argv.
221    ///
222    /// clap's `allow_external_subcommands`. Known subcommands still win; a
223    /// [`default_subcommand`](Self::default_subcommand) still catches first. Once the
224    /// unmatched word is taken, remaining tokens — including `--help` — are not parsed
225    /// as this command's flags.
226    pub external_subcommand: bool,
227    /// Show this command's help when no argv token follows its name.
228    ///
229    /// This is clap's `arg_required_else_help`. It deliberately observes argv rather than
230    /// bound values: an environment variable or default may fill a field, but neither means
231    /// the user supplied an argument to this invocation.
232    pub arg_required_else_help: bool,
233    /// Selecting a subcommand suppresses this command's required arguments.
234    pub subcommand_negates_reqs: bool,
235    /// Once this command binds a flag or positional, selecting one of its
236    /// subcommands is an error.
237    pub args_conflicts_with_subcommands: bool,
238    /// Let a known subcommand interrupt a variadic argument that would otherwise consume it.
239    pub subcommand_precedence_over_arg: bool,
240    /// Let a later required positional take a word while an earlier optional positional
241    /// remains empty.
242    pub allow_missing_positional: bool,
243    /// Disable delimiter splitting for positional values after `--` or on an
244    /// automatic trailing argument. Inherited by subcommands.
245    pub dont_delimit_trailing_values: bool,
246    /// What an unrecognized flag-like token means here, or `None` to keep whatever the
247    /// enclosing command said. See [`UnknownFlags`].
248    ///
249    /// Inherited rather than resolved per command, which is what usage-lib does — its
250    /// `effective_unknown_flags` walks outward from the command that ran and falls back to
251    /// the spec's. Resolving it in the tables instead was possible only for a builder that
252    /// can see the whole tree: a derive expands one struct at a time and cannot see its
253    /// parent, so `#[usage(unknown_flags = "error")]` on the root reached the root alone and
254    /// a subcommand had no way to say it at all.
255    ///
256    /// The parser carries the effective value down as it descends, so a command that states
257    /// nothing costs nothing.
258    pub unknown_flags: ::core::option::Option<UnknownFlags>,
259    /// Whether this command answers to `--version` and `-V`.
260    ///
261    /// Set on the root, and only when the CLI declares a version: clap adds the flag exactly
262    /// then, and a `--version` that answers with nothing is worse than one that is not there.
263    /// A field rather than a rule about depth, so a CLI that wants it on a subcommand — clap's
264    /// `propagate_version` — has somewhere to say so.
265    pub version: bool,
266    /// Do not synthesize `--help` and `-h` for this command.
267    pub disable_help_flag: bool,
268    /// Do not synthesize the `help` subcommand route for this command.
269    pub disable_help_subcommand: bool,
270    /// Do not synthesize `--version` and `-V` for this command.
271    pub disable_version_flag: bool,
272    /// Caller-assigned identifier, echoed back in [`Event::Command`].
273    ///
274    /// Wide enough for a derive to make these unique without coordination: two
275    /// macro expansions cannot see each other, so the generated keys carry a hash
276    /// of the type they came from in the high half and a per-type index in the low
277    /// half. A parse dispatches on this, so a collision would bind the wrong field
278    /// — [`Spec::to_kdl`](crate::spec::Spec::to_kdl) checks the tree for duplicates
279    /// in debug builds.
280    pub key: u64,
281}
282
283impl Command<'_> {
284    /// A command with nothing declared, for use with struct update syntax.
285    pub const EMPTY: Command<'static> = Command {
286        name: "",
287        aliases: &[],
288        flags: &[],
289        args: &[],
290        clause: ::core::option::Option::None,
291        subcommands: &[],
292        default_subcommand: ::core::option::Option::None,
293        external_subcommand: false,
294        arg_required_else_help: false,
295        subcommand_negates_reqs: false,
296        args_conflicts_with_subcommands: false,
297        subcommand_precedence_over_arg: false,
298        allow_missing_positional: false,
299        dont_delimit_trailing_values: false,
300        unknown_flags: ::core::option::Option::None,
301        version: false,
302        disable_help_flag: false,
303        disable_help_subcommand: false,
304        disable_version_flag: false,
305        key: 0,
306    };
307}
308
309/// A separator-delimited positional group.
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub struct Clause<'a> {
312    pub key: u64,
313    pub name: &'a str,
314    pub separator: &'a [u8],
315    pub args: &'a [&'a Arg<'a>],
316}
317
318/// Basename of argv[0] for a multicall CLI: last path component, with a trailing
319/// `.exe` stripped so Windows and Unix agree.
320pub fn multicall_basename(argv0: &str) -> &str {
321    let name = argv0.rsplit(['/', '\\']).next().unwrap_or(argv0);
322    match name.get(name.len().saturating_sub(4)..) {
323        Some(ext) if ext.eq_ignore_ascii_case(".exe") => &name[..name.len() - 4],
324        _ => name,
325    }
326}
327
328/// The applet name to parse as the first word, when argv[0] is not the dispatcher.
329///
330/// `None` means a dispatcher invocation (`busybox ls`): skip argv[0] and parse the
331/// rest. `Some` is a symlink invocation (`ls -l`): inject the basename.
332pub fn multicall_applet<'a>(argv0: &'a str, name: &str, bin: Option<&str>) -> Option<&'a str> {
333    let base = multicall_basename(argv0);
334    if !name.is_empty() && base == multicall_basename(name) {
335        return None;
336    }
337    if let Some(bin) = bin {
338        if !bin.is_empty() && base == multicall_basename(bin) {
339            return None;
340        }
341    }
342    Some(base)
343}
344
345/// Resolved identity of a derive-generated binding type.
346#[derive(Clone, Copy)]
347pub struct BindingType(pub fn() -> &'static str);
348
349impl BindingType {
350    pub fn name(self) -> &'static str {
351        (self.0)()
352    }
353}
354
355impl ::core::fmt::Debug for BindingType {
356    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
357        f.debug_tuple("BindingType").field(&self.name()).finish()
358    }
359}
360
361impl PartialEq for BindingType {
362    fn eq(&self, other: &Self) -> bool {
363        self.name() == other.name()
364    }
365}
366
367impl Eq for BindingType {}
368
369/// A flag, addressed by any of its long or short forms.
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub struct Flag<'a> {
372    /// Caller-assigned identifier, echoed back in [`Event::Flag`]. This is how
373    /// generated code knows which field to assign without any string comparison.
374    /// See [`Command::key`] on why it is this wide.
375    pub key: u64,
376    /// Compatibility key for mirroring a redeclared child global into an ancestor field.
377    ///
378    /// Zero means no typed binding contract is declared. Derive-generated tables hash the
379    /// binding shape and portable metadata so only equivalent bindings receive the same event.
380    pub binding_key: u64,
381    /// Resolved Rust value type for a derive-generated binding.
382    ///
383    /// This is separate from [`Self::binding_key`] because token spellings are not type
384    /// identities: an imported alias and a fully qualified path can name the same type.
385    pub binding_type: Option<BindingType>,
386    /// Unused by binding, kept so a table entry can carry its own name for
387    /// diagnostics.
388    pub name: &'a str,
389    /// Long forms, written without the leading `--`.
390    pub longs: &'a [&'a str],
391    /// Short forms, as single bytes.
392    ///
393    /// **Should be ASCII.** A cluster like `-xyz` is walked one byte at a time, so a
394    /// non-ASCII short can never be matched, and the remainder after a value-taking one —
395    /// which becomes its value — would begin in the middle of a character.
396    /// `#[derive(Cli)]` rejects a non-ASCII `short`; a table written by hand should keep to
397    /// it. Nothing is unsound if it does not: the value would simply be cut in a place that
398    /// makes no sense, and on Windows would then fail to convert.
399    pub shorts: &'a [u8],
400    /// A long form that sets the flag to false, written without the `--`.
401    pub negate: Option<&'a str>,
402    /// Whether the flag takes a value.
403    pub takes_value: bool,
404    /// Whether one occurrence of this flag keeps taking values, until a flag-like
405    /// token or the end of the command line.
406    ///
407    /// This is the spec's variadic flag *argument* (`--include <pattern>...`). It
408    /// is not the spec's flag-level `var=#true`, which means the flag may be
409    /// repeated and takes one value each time — repetition needs nothing from the
410    /// parser, since it already reports every occurrence separately. Conflating
411    /// the two makes a merely repeatable flag greedy enough to eat a positional.
412    pub variadic: bool,
413    /// How many values one variadic occurrence may take, after which the next word
414    /// belongs to whatever comes next.
415    ///
416    /// Only for [`variadic`](Self::variadic). A merely repeatable flag — the spec's
417    /// `var=#true` — is bounded on how many times it was *given*, which no single token
418    /// can decide, so that bound stays with the metadata and is checked after the parse.
419    pub var_max: ::core::option::Option<u32>,
420    /// The byte that makes one word several values, if the flag declares one.
421    ///
422    /// Here rather than with the metadata for the same reason [`var_max`](Self::var_max)
423    /// is: it decides *where* a word lands. A bound counts values, and a delimiter is what
424    /// makes a word stop being one of them — `--include a,b,c` is three, so a `var_max` of
425    /// two is already past its bound on the single word it was entitled to take. Binding
426    /// cannot count without it.
427    pub delimiter: ::core::option::Option<u8>,
428    /// Whether a detached value may itself look like a flag.
429    ///
430    /// The default is to refuse: `--jobs --force` is far more likely a forgotten
431    /// value than a jobs of `"--force"`. Declared, the next token is taken
432    /// whatever it looks like — including `--` — which is clap's
433    /// `allow_hyphen_values` and the spec's property of the same name. A variadic
434    /// occurrence still stops collecting at a later flag-like token, so a second
435    /// occurrence of the flag is not eaten as a value.
436    pub allow_hyphen_values: bool,
437    /// Whether a detached value may be a negative number while other flag-like
438    /// tokens still stop collection or report as flags.
439    pub allow_negative_numbers: bool,
440    /// A token that ends one variadic occurrence without becoming a value.
441    pub value_terminator: ::core::option::Option<&'a [u8]>,
442    /// Whether the value must be attached with `=`.
443    ///
444    /// `--flag=value` is accepted and `--flag value` is not, which is clap's
445    /// `require_equals` and the spec's property of the same name. A short's
446    /// attached form (`-i9229`, `-i=9229`) still binds: only the following word
447    /// is refused.
448    pub require_equals: bool,
449    /// Whether this value-taking flag may be present without a value.
450    ///
451    /// A missing value emits the flag event with `value: None`; bindings such as
452    /// `Option<Option<T>>` can therefore distinguish an absent flag from a bare
453    /// flag and from a flag with an explicit value.
454    pub value_optional: bool,
455    /// Whether a boolean long flag accepts an attached `true` or `false` value.
456    ///
457    /// This does not make the flag value-taking in the ordinary sense: a detached
458    /// word is never consumed, and help keeps rendering a switch. Only
459    /// `--flag=true` and `--flag=false` opt into an explicit boolean value.
460    pub bool_value: bool,
461    /// Value used when the flag is present but no value is given.
462    ///
463    /// clap's `default_missing_value` and the spec's `default_missing`. `--color`
464    /// binds this, `--color=never` binds `never`, and an absent flag is not bound.
465    /// Combined with [`Self::require_equals`], a following word is still refused.
466    pub default_missing: ::core::option::Option<&'a [u8]>,
467    /// Whether the flag is recognized by every command beneath the one that
468    /// declares it.
469    pub global: bool,
470    /// Whether this declared flag binds a field or requests a built-in response.
471    pub action: ArgAction,
472}
473
474impl Flag<'_> {
475    /// A value-less flag, for use with struct update syntax.
476    pub const BOOL: Flag<'static> = Flag {
477        key: 0,
478        binding_key: 0,
479        binding_type: None,
480        name: "",
481        longs: &[],
482        shorts: &[],
483        negate: None,
484        takes_value: false,
485        variadic: false,
486        var_max: ::core::option::Option::None,
487        delimiter: ::core::option::Option::None,
488        allow_hyphen_values: false,
489        allow_negative_numbers: false,
490        value_terminator: ::core::option::Option::None,
491        require_equals: false,
492        value_optional: false,
493        bool_value: false,
494        default_missing: ::core::option::Option::None,
495        global: false,
496        action: ArgAction::Set,
497    };
498
499    /// A flag that takes a value, for use with struct update syntax.
500    pub const VALUE: Flag<'static> = Flag {
501        takes_value: true,
502        ..Flag::BOOL
503    };
504}
505
506/// What supplying a declared flag does.
507#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
508pub enum ArgAction {
509    /// Bind the flag to its declared field.
510    #[default]
511    Set,
512    /// Show help, choosing the long form for a long spelling and the short form otherwise.
513    Help,
514    /// Always show short help.
515    HelpShort,
516    /// Always show long help.
517    HelpLong,
518    /// Show long help for this command and every visible descendant.
519    HelpAll,
520    /// Show version information.
521    Version,
522}
523
524/// A positional argument.
525#[derive(Debug, Clone, Copy, PartialEq, Eq)]
526pub struct Arg<'a> {
527    /// Caller-assigned identifier, echoed back in [`Event::Arg`]. See
528    /// [`Command::key`] on why it is this wide.
529    pub key: u64,
530    /// Prefix that classifies this positional independently of declaration order.
531    /// The prefix is removed from the value emitted in [`Event::Arg`].
532    pub sigil: ::core::option::Option<&'a [u8]>,
533    /// Whether post-binding requires this positional to have a value. Kept in the hot
534    /// table because `allow_missing_positional` must reserve words for later required args.
535    pub required: bool,
536    /// Whether this argument keeps taking values once it has one.
537    pub var: bool,
538    /// How many words a variadic may take before the next argument gets the rest.
539    ///
540    /// A bound belongs here, in the table binding reads, rather than with the metadata:
541    /// it decides *where* a word lands, not whether what landed is acceptable. clap's
542    /// `num_args` works the same way, and every spec in the wild is generated from a clap
543    /// command. `u32` rather than `usize` because a CLI that bounds a variadic above four
544    /// billion has other problems, and this table is read on the hot path.
545    pub var_max: ::core::option::Option<u32>,
546    /// The byte that makes one word several values, if the argument declares one.
547    ///
548    /// See [`Flag::delimiter`]: a bound counts values, and only this says how many values a
549    /// word carries.
550    pub delimiter: ::core::option::Option<u8>,
551    /// Whether a negative-number token is accepted as this positional even in
552    /// strict flag mode.
553    pub allow_negative_numbers: bool,
554    /// A token that ends this variadic positional without becoming a value.
555    pub value_terminator: ::core::option::Option<&'a [u8]>,
556    /// This argument's relationship to the `--` separator.
557    pub double_dash: DoubleDash,
558    /// Unused by binding, kept so a table entry can carry its own name for
559    /// diagnostics.
560    pub name: &'a str,
561}
562
563impl Arg<'_> {
564    /// A single-value argument, for use with struct update syntax.
565    pub const REQUIRED: Arg<'static> = Arg {
566        key: 0,
567        sigil: ::core::option::Option::None,
568        required: true,
569        var: false,
570        var_max: ::core::option::Option::None,
571        delimiter: ::core::option::Option::None,
572        allow_negative_numbers: false,
573        value_terminator: ::core::option::Option::None,
574        double_dash: DoubleDash::Optional,
575        name: "",
576    };
577
578    /// A variadic argument, for use with struct update syntax.
579    pub const VAR: Arg<'static> = Arg {
580        var: true,
581        ..Arg::REQUIRED
582    };
583}
584
585/// What to do with a flag-like token that names no flag in scope.
586///
587/// The default is [`UnknownFlags::Value`]: the token carries on to the positional
588/// arguments, because a spec is often parsing a command line whose flags belong to
589/// something else — a wrapped tool, a task script. A CLI that owns all of its
590/// flags declares [`UnknownFlags::Error`] and gets typo detection instead.
591///
592/// Stored per command and already resolved: inheritance is a question for whoever
593/// builds the tables, and answering it at compile time keeps it out of the parse.
594#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
595pub enum UnknownFlags {
596    /// Offer the token to the positionals. If none can take it, it is an
597    /// unexpected argument.
598    #[default]
599    Value,
600    /// Reject the token.
601    Error,
602}
603
604/// How an argument relates to the `--` separator.
605#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
606pub enum DoubleDash {
607    /// Values may appear on either side of a `--`.
608    #[default]
609    Optional,
610    /// Values are accepted only after a `--`.
611    Required,
612    /// A `--` is kept as a value rather than consumed as a separator.
613    Preserve,
614    /// Once the argument takes a value, behave as if a `--` had been given, so
615    /// the rest of the command line is values. A wrapper can then forward flags
616    /// without its caller typing the separator.
617    Automatic,
618}
619
620/// Something the parser bound.
621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
622pub enum Event<'t, 'a, 'v> {
623    /// A subcommand was selected; parsing continues inside it.
624    Command(&'t Command<'t>),
625    /// A flag was given. `value` is `Some` for a flag that takes one, and
626    /// `negated` is true when the flag was set through its `negate` form.
627    Flag {
628        flag: &'t Flag<'t>,
629        value: Option<&'v [u8]>,
630        negated: bool,
631    },
632    /// A word was bound to a positional argument. A variadic argument produces
633    /// one event per value.
634    Arg {
635        arg: &'t Arg<'t>,
636        value: &'v [u8],
637        /// Whether this value should be split by the argument's declared delimiter.
638        delimit: bool,
639    },
640    /// Ended one clause instance and began the next.
641    ClauseSeparator { clause: Clause<'t> },
642    /// An unmatched word was forwarded as an external command: the name, then
643    /// every remaining token, including flags.
644    External { values: &'a [&'v OsStr] },
645}
646
647/// A binding failure.
648///
649/// Carries the offending token so a caller can render a good message, but no
650/// message of its own: rendering belongs to a cold path, and building a string
651/// here would allocate on the way to reporting that nothing was allocated.
652///
653/// `non_exhaustive`, because an error enum grows: a caller matching on it needs a
654/// fallback arm so that recognizing a new failure is never a breaking change.
655// No `Copy`: one variant owns its message. `Clone` stays, and the enum is still 40 bytes
656// because that variant is boxed, so nothing on the hot path grew.
657#[derive(Debug, Clone, PartialEq, Eq)]
658#[non_exhaustive]
659pub enum Error<'t, 'v> {
660    /// A flag-like token matched no flag in scope. `token` is the whole token as
661    /// typed, so a bundle containing an unrecognized letter reports `-fz` rather
662    /// than the letter alone — which is also the unit in which it is rejected.
663    UnknownFlag { token: &'v [u8] },
664    /// A flag that needs a value did not get one, either because the command
665    /// line ended or because the next token was flag-like.
666    MissingFlagValue { flag: &'t Flag<'t> },
667    /// A word arrived with no argument left to hold it.
668    UnexpectedArg { token: &'v [u8] },
669    /// A word was offered to a `double_dash = "required"` argument before any
670    /// `--` had been seen.
671    ArgRequiresDoubleDash { arg: &'t Arg<'t> },
672    /// A subcommand was selected after this command had already bound an argument.
673    SubcommandConflict { subcommand: &'t Command<'t> },
674    /// The command tree is deeper than [`MAX_DEPTH`].
675    TooDeep,
676
677    // The rest are raised *after* the parse, by whoever owns the target type: they
678    // need to know a value's declared type, which the parser deliberately does not.
679    // They share this enum so that a caller has one error to handle rather than two.
680    /// Something the command requires was never given.
681    MissingRequired {
682        /// The flag or argument's name, as the spec calls it.
683        name: &'t str,
684    },
685    /// A flag that is not repeatable was given more than once.
686    DuplicateFlag {
687        /// The flag's name, as the spec calls it.
688        name: &'t str,
689    },
690    /// A value was given that is not among the declared choices.
691    ///
692    /// Carries the choices rather than the offending value: rendering the value means
693    /// owning it, and an error that allocates on a path this crate promises not to
694    /// allocate on would be a poor trade for a better message. Diagnostics are a
695    /// separate layer.
696    InvalidChoice {
697        name: &'t str,
698        choices: &'t [&'t str],
699    },
700    /// Fewer values than `var_min`.
701    VarTooFew {
702        name: &'t str,
703        min: usize,
704        got: usize,
705    },
706    /// More values than `var_max`.
707    VarTooMany {
708        name: &'t str,
709        max: usize,
710        got: usize,
711    },
712    /// Two flags declared to conflict were both given.
713    ///
714    /// Carries both names because either one alone reads as a puzzle: which flag is
715    /// unwelcome depends entirely on what else is on the command line.
716    ConflictingFlags {
717        /// The flag whose declaration names the conflict.
718        name: &'t str,
719        /// The flag it cannot be given with, as the declaration spells it.
720        other: &'t str,
721    },
722    /// A value was given that the field's type could not be built from.
723    ///
724    /// Boxed, and the only error here that owns anything. Everything else borrows the
725    /// tables or argv, which is what keeps a *successful* parse allocation-free — and the
726    /// box keeps `Error` the size it was, so the `Result` this rides in on the hot path
727    /// does not grow. A value that will not convert has already failed, and a message
728    /// worth reading is worth one allocation.
729    InvalidValue(::std::boxed::Box<InvalidValue<'t>>),
730    /// A required group had none of its members given.
731    ///
732    /// Carries the members as members rather than as a rendered sentence: the caller
733    /// decides how to say it, and a completion asking what would satisfy this needs the
734    /// list rather than the prose.
735    MissingGroup {
736        /// The group's declared name, which appears in the message so a command with
737        /// several groups does not report the same sentence twice.
738        group: &'t str,
739        /// The flags that would satisfy it, as the declaration spells them.
740        members: &'t [&'t str],
741    },
742    /// A subcommand was required, and none was given.
743    MissingSubcommand,
744    /// `--help` or `-h` was given, and `cmd` is what it was asked about.
745    ///
746    /// Not a failure, and returned as one anyway: a parse that stops to print help has not
747    /// produced a value, and every caller already handles the "no value" shape. clap does the
748    /// same thing for the same reason.
749    ///
750    /// `long` distinguishes the two: `-h` prints the short form and `--help` the long one, as
751    /// clap has them. The caller renders — this crate does not print, because a library that
752    /// writes to stdout on its own is one an adopter cannot embed.
753    Help { cmd: &'t Command<'t>, long: bool },
754    /// `arg_required_else_help` found no command-line arguments for `cmd`.
755    ///
756    /// Unlike an explicit help request, this is a usage failure: clap prints the short help to
757    /// stderr and exits with status 2. Keeping the shape separate lets embedders preserve that
758    /// terminal contract without guessing why [`Error::Help`] was returned.
759    MissingArgsHelp { cmd: &'t Command<'t> },
760    /// Recursive long help was requested for `cmd` and every visible descendant.
761    HelpAll { cmd: &'t Command<'t> },
762    /// `--version` or `-V` was asked for. Not a failure either — the caller prints and leaves.
763    ///
764    /// The version string lives in the spec rather than the parse tables. `long` lets the
765    /// caller choose `long_version` for `--version` while `-V` retains the concise value.
766    Version { long: bool },
767}
768
769/// The high half of every key one declaration's items get.
770///
771/// A derive cannot see other expansions, so it cannot hand out keys from a shared
772/// counter: it hashes the declaration it was given instead. It cannot see a module path
773/// either, which is why the module is mixed in *here* — `module_path!()` is available to
774/// the generated code as a compile-time string, so two byte-identical declarations in
775/// different modules end up with different keys rather than colliding.
776///
777/// `declaration` is a hash the derive computed over the item's own tokens.
778pub const fn key_base(module: &str, declaration: u32) -> u64 {
779    // FNV-1a, continuing from the declaration's hash rather than starting over, so both
780    // halves contribute. Spelled out rather than taken from a `Hasher`, which is not
781    // guaranteed to be stable between compilations — and these are baked into a binary.
782    let mut hash: u32 = declaration;
783    let bytes = module.as_bytes();
784    let mut i = 0;
785    while i < bytes.len() {
786        hash ^= bytes[i] as u32;
787        hash = hash.wrapping_mul(0x0100_0193);
788        i += 1;
789    }
790    (hash as u64) << 32
791}
792
793/// Why a value would not convert into the type its field holds.
794///
795/// Separate from [`Error`] so that the enum stays small: this is reached through a `Box`.
796#[derive(Debug, Clone, PartialEq, Eq)]
797pub struct InvalidValue<'t> {
798    /// The flag or argument's name, as the spec calls it.
799    pub name: &'t str,
800    /// The text that would not convert.
801    pub value: ::std::string::String,
802    /// What the type's own conversion complained about.
803    pub reason: ::std::string::String,
804}
805
806/// A command-wide validation or finalization failure.
807///
808/// Return this from a `#[usage(validate_with = ...)]` hook or from the
809/// `TryFrom` implementation named by `#[usage(try_into = ...)]`. The derive
810/// turns it into the same [`Error::InvalidValue`] diagnostic used by field
811/// conversion, so callers keep one parse error type.
812#[derive(Debug, Clone, PartialEq, Eq)]
813pub struct ValidationError {
814    name: &'static str,
815    value: String,
816    reason: String,
817}
818
819impl ValidationError {
820    /// Start an error attributed to a flag, positional, or command name.
821    pub fn field(name: &'static str) -> Self {
822        Self {
823            name,
824            value: String::new(),
825            reason: String::new(),
826        }
827    }
828
829    /// Record the value that failed the command-wide invariant.
830    pub fn value(mut self, value: impl Into<String>) -> Self {
831        self.value = value.into();
832        self
833    }
834
835    /// Explain the invariant that the value did not satisfy.
836    pub fn reason(mut self, reason: impl Into<String>) -> Self {
837        self.reason = reason.into();
838        self
839    }
840
841    /// Convert this application-level failure into the parser's diagnostic.
842    pub fn into_parse_error<'v>(self) -> Error<'static, 'v> {
843        Error::InvalidValue(Box::new(InvalidValue {
844            name: self.name,
845            value: self.value,
846            reason: self.reason,
847        }))
848    }
849}
850
851/// Interpret a value as UTF-8.
852///
853/// The parser hands back bytes borrowed from `argv`; this is the conversion most
854/// callers want, and the point at which a non-UTF-8 command line is rejected —
855/// but only for the values actually inspected.
856pub fn as_str(value: &[u8]) -> Result<&str, std::str::Utf8Error> {
857    std::str::from_utf8(value)
858}
859
860/// How many entries a group of tables holds in total.
861///
862/// The length for [`concat_flags`] and [`concat_args`], which need it as a const generic — so
863/// it has to be computable separately from the concatenation itself.
864///
865/// ```
866/// use usage_argv::{table_len, Flag};
867///
868/// static A: Flag = Flag { name: "a", ..Flag::BOOL };
869/// static B: Flag = Flag { name: "b", ..Flag::BOOL };
870/// const GROUPS: &[&[&Flag]] = &[&[&A], &[], &[&B]];
871/// const N: usize = table_len(GROUPS);
872/// assert_eq!(N, 2);
873/// ```
874pub const fn table_len<T>(groups: &[&[T]]) -> usize {
875    let mut total = 0;
876    let mut i = 0;
877    while i < groups.len() {
878        total += groups[i].len();
879        i += 1;
880    }
881    total
882}
883
884/// Join groups of flag tables into one, at compile time.
885///
886/// This is how `#[usage(flatten)]` stays free. A flattened struct's flags have to appear in
887/// the parent's own table, and the parent's macro expansion cannot see them — it has only a
888/// type. But it can name that type's [`CommandArgs::COMMAND`](crate::spec::CommandArgs::COMMAND),
889/// and a `const fn` can read through it, so the two lists become one `static` array before the
890/// program runs. The parser then walks a single flat slice, exactly as it does for a command
891/// that declared everything itself: flatten costs nothing at run time.
892///
893/// Groups are laid out in the order given, which is what lets a flattened group sit *between*
894/// two of the parent's own declarations — necessary for positional arguments, where order is
895/// the meaning.
896///
897/// `N` must be [`table_len`] of the same groups. It cannot be inferred, and a wrong one fails
898/// to compile rather than leaving the difference filled with padding.
899///
900/// ```
901/// use usage_argv::{concat_flags, table_len, Flag};
902///
903/// static FORCE: Flag = Flag { name: "force", longs: &["force"], ..Flag::BOOL };
904/// static QUIET: Flag = Flag { name: "quiet", longs: &["quiet"], ..Flag::BOOL };
905/// static SHARED: &[&Flag] = &[&QUIET];
906///
907/// const GROUPS: &[&[&Flag]] = &[&[&FORCE], SHARED];
908/// static FLAGS: [&Flag; table_len(GROUPS)] = concat_flags(GROUPS);
909///
910/// assert_eq!(FLAGS.iter().map(|f| f.name).collect::<Vec<_>>(), ["force", "quiet"]);
911/// ```
912pub const fn concat_flags<const N: usize>(
913    groups: &[&[&'static Flag<'static>]],
914) -> [&'static Flag<'static>; N] {
915    // Every slot is written below, but an array has to start somewhere and `MaybeUninit`
916    // would mean `unsafe`. A `Flag` nobody can reach is cheaper than that.
917    static PLACEHOLDER: Flag<'static> = Flag::BOOL;
918    let mut out = [&PLACEHOLDER; N];
919    let mut at = 0;
920    let mut g = 0;
921    while g < groups.len() {
922        let group = groups[g];
923        let mut i = 0;
924        while i < group.len() {
925            out[at] = group[i];
926            at += 1;
927            i += 1;
928        }
929        g += 1;
930    }
931    assert!(
932        at == N,
933        "`N` must be `table_len` of the same groups, or the table would keep a placeholder \
934         that answers to nothing"
935    );
936    out
937}
938
939/// Join groups of argument tables into one, at compile time.
940///
941/// The positional counterpart of [`concat_flags`] — see there for why this exists. Order
942/// matters more here: an argument's position *is* its identity, so a flattened group has to
943/// land exactly where the field was written.
944///
945/// Two functions rather than one generic: each needs a value to fill an array with before
946/// overwriting it, and there is no way to ask a type parameter for one in a `const fn`.
947pub const fn concat_args<const N: usize>(
948    groups: &[&[&'static Arg<'static>]],
949) -> [&'static Arg<'static>; N] {
950    static PLACEHOLDER: Arg<'static> = Arg::REQUIRED;
951    let mut out = [&PLACEHOLDER; N];
952    let mut at = 0;
953    let mut g = 0;
954    while g < groups.len() {
955        let group = groups[g];
956        let mut i = 0;
957        while i < group.len() {
958            out[at] = group[i];
959            at += 1;
960            i += 1;
961        }
962        g += 1;
963    }
964    assert!(
965        at == N,
966        "`N` must be `table_len` of the same groups, or the table would keep a placeholder \
967         that answers to nothing"
968    );
969    out
970}
971
972/// The key `--help` answers to, and the one `-h` does.
973///
974/// Reserved rather than generated: a derive builds keys from a hash of the type they came from
975/// in the high half and an index in the low half, so the top of the range belongs to nobody.
976/// Generated code compares against these to tell a help request from a flag of its own.
977pub const HELP_LONG_KEY: u64 = u64::MAX;
978/// See [`HELP_LONG_KEY`].
979pub const HELP_SHORT_KEY: u64 = u64::MAX - 1;
980
981/// `--help`, which every command answers to.
982///
983/// In the parse table and *not* in the metadata, which is the whole trick: the parser has to
984/// recognise the flag, and help output must not list it — a spec does not declare `--help`, so
985/// showing one would make the rendered page disagree with the spec it came from.
986pub static HELP_LONG: Flag<'static> = Flag {
987    key: HELP_LONG_KEY,
988    name: "help",
989    longs: &["help"],
990    action: ArgAction::HelpLong,
991    ..Flag::BOOL
992};
993
994/// See [`HELP_LONG_KEY`].
995pub const VERSION_LONG_KEY: u64 = u64::MAX - 2;
996/// See [`HELP_LONG_KEY`].
997pub const VERSION_SHORT_KEY: u64 = u64::MAX - 3;
998
999/// `--version`, where the CLI declared one.
1000///
1001/// In the parse table and not in the metadata, exactly as `--help` is: a spec does not declare
1002/// `--version`, so listing one would make the rendered page disagree with the spec it came from.
1003pub static VERSION_LONG: Flag<'static> = Flag {
1004    key: VERSION_LONG_KEY,
1005    name: "version",
1006    longs: &["version"],
1007    action: ArgAction::Version,
1008    ..Flag::BOOL
1009};
1010
1011/// `-V`, which clap also supplies.
1012pub static VERSION_SHORT: Flag<'static> = Flag {
1013    key: VERSION_SHORT_KEY,
1014    name: "version",
1015    shorts: b"V",
1016    action: ArgAction::Version,
1017    ..Flag::BOOL
1018};
1019
1020/// `-h`, which prints the shorter form.
1021pub static HELP_SHORT: Flag<'static> = Flag {
1022    key: HELP_SHORT_KEY,
1023    name: "help",
1024    shorts: b"h",
1025    action: ArgAction::HelpShort,
1026    ..Flag::BOOL
1027};
1028
1029/// A named subcommand of a given command, by name or alias.
1030///
1031/// Free rather than a method because `help` resolves a path *without* descending: the words
1032/// after it are a question about a command rather than a walk into one.
1033///
1034/// Names across every subcommand before any alias, the precedence the grammar states — and
1035/// the reason this is the only implementation of it on argv's side. `ex run` and `ex help run`
1036/// selecting different commands would be exactly the divergence this rule was written to end.
1037pub(crate) fn find_named<'t>(cmd: &'t Command<'t>, name: &[u8]) -> Option<&'t Command<'t>> {
1038    let subcommands = || cmd.subcommands.iter().copied();
1039    subcommands()
1040        .find(|c| c.name.as_bytes() == name)
1041        .or_else(|| subcommands().find(|c| c.aliases.iter().any(|a| a.as_bytes() == name)))
1042}
1043
1044/// What a caller should print for a parse failure, and what to exit with.
1045///
1046/// The one entry point a generated `parse()` reaches for, and the reason it exists here rather
1047/// than in the derive: whether the good rendering is available is a *feature of this crate* in
1048/// the adopter's dependency graph, and a `#[cfg]` written into generated code is evaluated in
1049/// the adopter's crate, where the feature is not theirs to see. That is how a metadata field
1050/// once got silently dropped; the answer is that the cfg lives beside the thing it gates.
1051///
1052/// With `diagnostics` on, this is the clap-shaped message. Without it, the error's `Debug`
1053/// form — which is still better than nothing and is what a parser-only build asked for.
1054///
1055/// [`Error::Help`] and [`Error::Version`] are not failures and must be handled before this.
1056#[cfg(feature = "diagnostics")]
1057pub fn render_failure(spec: &spec::Spec<'_>, argv: &[&OsStr], error: &Error<'_, '_>) -> String {
1058    diagnostic::render(spec, argv, error, diagnostic::Style::auto())
1059}
1060
1061/// A parse failure, never coloured.
1062///
1063/// [`render_failure`] asks the environment whether to colour, which is right for a process and
1064/// wrong for anything that keeps the string: a test that asserts on a message, or a snapshot of
1065/// one, would pass or fail by whether stderr happened to be a terminal. The renderer is the
1066/// same; only the answer to that question is fixed.
1067#[cfg(feature = "diagnostics")]
1068pub fn render_failure_plain(
1069    spec: &spec::Spec<'_>,
1070    argv: &[&OsStr],
1071    error: &Error<'_, '_>,
1072) -> String {
1073    diagnostic::render(spec, argv, error, diagnostic::Style::PLAIN)
1074}
1075
1076/// Render a failure through a spec-declared executable view.
1077///
1078/// `argv` is the original full argv, including the view executable as argv0.
1079#[cfg(feature = "diagnostics")]
1080pub fn render_failure_view<'a>(
1081    spec: &'a spec::Spec<'a>,
1082    argv: &[&OsStr],
1083    error: &Error<'_, '_>,
1084    view: &'a spec::ViewMeta<'a>,
1085) -> String {
1086    diagnostic::render_view(spec, argv, error, diagnostic::Style::auto(), view)
1087}
1088
1089/// What a caller should print for a parse failure, without the renderer that makes it readable.
1090///
1091/// See the other half. A caller that wants the clap-shaped message turns on `diagnostics`;
1092/// this is what a parser-only build asked for, and it still says which error it was.
1093#[cfg(all(feature = "spec", not(feature = "diagnostics")))]
1094pub fn render_failure(spec: &spec::Spec<'_>, argv: &[&OsStr], error: &Error<'_, '_>) -> String {
1095    let _ = (spec, argv);
1096    ::std::format!("error: {error:?}\n")
1097}
1098
1099/// A parse failure without the renderer, which is plain either way.
1100#[cfg(all(feature = "spec", not(feature = "diagnostics")))]
1101pub fn render_failure_plain(
1102    spec: &spec::Spec<'_>,
1103    argv: &[&OsStr],
1104    error: &Error<'_, '_>,
1105) -> String {
1106    render_failure(spec, argv, error)
1107}
1108
1109/// Render a failure through a declared view without the optional diagnostics renderer.
1110#[cfg(all(feature = "spec", not(feature = "diagnostics")))]
1111pub fn render_failure_view(
1112    spec: &spec::Spec<'_>,
1113    argv: &[&OsStr],
1114    error: &Error<'_, '_>,
1115    view: &spec::ViewMeta<'_>,
1116) -> String {
1117    let _ = (spec, argv, view);
1118    ::std::format!("error: {error:?}\n")
1119}
1120
1121/// What a caller should print for the deprecations a command line used.
1122///
1123/// The same arrangement as [`render_failure`], and for the same reason: whether the coloured
1124/// rendering is available is a feature of *this* crate in the adopter's dependency graph, so the
1125/// `#[cfg]` lives beside the thing it gates rather than in generated code.
1126///
1127/// Warnings are not failures. A caller prints these to stderr and carries on.
1128#[cfg(feature = "diagnostics")]
1129pub fn render_warnings(warnings: &[warn::Warning<'_>]) -> String {
1130    diagnostic::render_warnings(warnings, diagnostic::Style::auto())
1131}
1132
1133/// The same wording without the renderer that colours it. See the other half.
1134#[cfg(all(feature = "spec", not(feature = "diagnostics")))]
1135pub fn render_warnings(warnings: &[warn::Warning<'_>]) -> String {
1136    warn::render_warnings(warnings)
1137}
1138
1139/// The word a tool sends to ask a binary for its own spec.
1140///
1141/// Not a flag and not a command: a spec request is not something this CLI *does*, so it is
1142/// answered before the parse and stays out of the tables — the same reason
1143/// `__complete_word__` is a word rather than a subcommand. It also keeps the endpoint from
1144/// perturbing the document it prints, which a declared flag would not.
1145pub const SPEC_REQUEST: &str = "__usage_spec__";
1146
1147/// Whether this argv asks for the spec rather than for the CLI to run.
1148///
1149/// Only the first word counts: `mycli build __usage_spec__` passes the word through as an
1150/// ordinary value, because a request is the whole invocation or it is nothing.
1151///
1152/// A root that declares a command of that name keeps it, which is the precedence the `help`
1153/// subcommand already has. The check is here rather than in the derive because a `Cli` derive
1154/// expands one struct and cannot see the variant names of a separate `Subcommands` enum — the
1155/// static tables can.
1156pub fn is_spec_request(root: &Command<'_>, argv: &[&OsStr]) -> bool {
1157    let [first, ..] = argv else { return false };
1158    first.as_encoded_bytes() == SPEC_REQUEST.as_bytes()
1159        && find_named(root, SPEC_REQUEST.as_bytes()).is_none()
1160}
1161
1162/// Whether a flag is one of the two the parser supplies rather than the CLI declaring it.
1163pub fn is_help_flag(flag: &Flag<'_>) -> bool {
1164    matches!(
1165        flag.action,
1166        ArgAction::Help | ArgAction::HelpShort | ArgAction::HelpLong | ArgAction::HelpAll
1167    )
1168}
1169
1170/// Whether a flag is one of the two the parser supplies for `--version`.
1171pub fn is_version_flag(flag: &Flag<'_>) -> bool {
1172    flag.action == ArgAction::Version
1173}
1174
1175/// Whether one exact root argument selects a declared or synthesized version action.
1176///
1177/// Declared flags are checked first because they shadow the built-in `--version` and `-V`
1178/// spellings. Executable views use this before projection so custom version spellings keep
1179/// reporting the package that owns the view.
1180pub fn is_version_arg(cmd: &Command<'_>, word: &OsStr) -> bool {
1181    let token = word.as_encoded_bytes();
1182    if let Some(long) = token.strip_prefix(b"--") {
1183        if let Some(flag) = cmd.flags.iter().find(|flag| {
1184            flag.longs
1185                .iter()
1186                .any(|spelling| spelling.as_bytes() == long)
1187        }) {
1188            return is_version_flag(flag);
1189        }
1190        if cmd.flags.iter().any(|flag| {
1191            flag.negate
1192                .is_some_and(|spelling| spelling.as_bytes() == long)
1193        }) {
1194            return false;
1195        }
1196        return long == b"version" && cmd.version && !cmd.disable_version_flag;
1197    }
1198    if let [b'-', short] = token {
1199        if let Some(flag) = cmd.flags.iter().find(|flag| flag.shorts.contains(short)) {
1200            return is_version_flag(flag);
1201        }
1202        return *short == b'V' && cmd.version && !cmd.disable_version_flag;
1203    }
1204    false
1205}
1206
1207/// Resolve a subcommand by name or alias, at compile time.
1208///
1209/// For [`Command::default_subcommand`], which names a command that a derive cannot see: the
1210/// variants of a subcommand enum are a different macro expansion, so the name is all the
1211/// parent has. Searching the list in a `const fn` closes that gap — the answer is the same
1212/// `&'static` the table already holds, found before the program runs.
1213///
1214/// A name no subcommand answers to is a **compile error**, since this panics during const
1215/// evaluation. That is the whole point of doing it here rather than at startup.
1216///
1217/// ```
1218/// use usage_argv::{find_subcommand, Command};
1219///
1220/// static RUN: Command = Command { name: "run", ..Command::EMPTY };
1221/// static SUBS: &[&Command] = &[&RUN];
1222/// static ROOT: Command = Command {
1223///     name: "ex",
1224///     subcommands: SUBS,
1225///     default_subcommand: Some(find_subcommand(SUBS, "run")),
1226///     ..Command::EMPTY
1227/// };
1228/// assert_eq!(ROOT.default_subcommand.unwrap().name, "run");
1229/// ```
1230pub const fn find_subcommand<'a>(
1231    subcommands: &'a [&'a Command<'a>],
1232    name: &str,
1233) -> &'a Command<'a> {
1234    // Names first, then aliases: a command's own name outranks another command's alias, so
1235    // the answer does not depend on the order the table happens to list them in. Checking
1236    // each candidate's name *and* aliases in one pass instead let whichever command came
1237    // first win, and usage-lib resolved the same spec to the last one.
1238    let mut i = 0;
1239    while i < subcommands.len() {
1240        if str_eq(subcommands[i].name, name) {
1241            return subcommands[i];
1242        }
1243        i += 1;
1244    }
1245    // Aliases answer too, because usage-lib resolves the name against names, aliases and
1246    // hidden aliases alike — so a spec may point `default_subcommand` at any of them.
1247    let mut i = 0;
1248    while i < subcommands.len() {
1249        let candidate = subcommands[i];
1250        let mut a = 0;
1251        while a < candidate.aliases.len() {
1252            if str_eq(candidate.aliases[a], name) {
1253                return candidate;
1254            }
1255            a += 1;
1256        }
1257        i += 1;
1258    }
1259    panic!("`default_subcommand` names a command that this one does not have")
1260}
1261
1262/// Refuse two subcommands that answer to the same name, aliases included.
1263///
1264/// A derive expansion can validate aliases written on one enum, but aliases may also live on
1265/// the independently expanded `Args` structs its variants wrap. This final, joined-table check
1266/// is where both declarations are visible.
1267pub const fn assert_unique_subcommand_names(subcommands: &[&Command<'_>]) {
1268    const fn form<'a>(cmd: &'a Command<'a>, at: usize) -> Option<&'a str> {
1269        if at == 0 {
1270            Some(cmd.name)
1271        } else if at <= cmd.aliases.len() {
1272            Some(cmd.aliases[at - 1])
1273        } else {
1274            None
1275        }
1276    }
1277
1278    let mut command = 0;
1279    while command < subcommands.len() {
1280        let mut at = 0;
1281        while let Some(name) = form(subcommands[command], at) {
1282            let mut other_command = command;
1283            while other_command < subcommands.len() {
1284                let mut other_at = if other_command == command { at + 1 } else { 0 };
1285                while let Some(other) = form(subcommands[other_command], other_at) {
1286                    assert!(
1287                        !str_eq(name, other),
1288                        "two subcommands answer to the same name, counting aliases"
1289                    );
1290                    other_at += 1;
1291                }
1292                other_command += 1;
1293            }
1294            at += 1;
1295        }
1296        command += 1;
1297    }
1298}
1299
1300/// `==` on strings, in a `const fn`.
1301const fn str_eq(a: &str, b: &str) -> bool {
1302    let (a, b) = (a.as_bytes(), b.as_bytes());
1303    if a.len() != b.len() {
1304        return false;
1305    }
1306    let mut i = 0;
1307    while i < a.len() {
1308        if a[i] != b[i] {
1309            return false;
1310        }
1311        i += 1;
1312    }
1313    true
1314}
1315
1316/// Rebuild an [`OsString`] from bytes the parser handed back.
1317///
1318/// This is the reverse of [`OsStr::as_encoded_bytes`], and it is how a `PathBuf` field
1319/// receives a filename the operating system accepts but UTF-8 does not — `/tmp/\xff` stays
1320/// `/tmp/\xff` rather than becoming a *different* filename with `U+FFFD` in it.
1321///
1322/// Where the platform cannot hold those bytes, they are handed back in the `Err` — as
1323/// `String::from_utf8` does — so the caller can name the value in its error without this
1324/// having to copy it for a case that is nearly never taken.
1325///
1326/// # Why this is not `unsafe`, and why it is not lossless everywhere
1327///
1328/// On **Unix** an `OsString` is an arbitrary byte sequence, so the conversion is total and
1329/// uses the safe [`OsStringExt::from_vec`]. Every byte survives, which is the case that
1330/// matters: non-UTF-8 filenames are ordinary there.
1331///
1332/// [`OsStringExt::from_vec`]: std::os::unix::ffi::OsStringExt::from_vec
1333///
1334/// On **Windows** the encoding is WTF-8, where not every byte sequence is valid, and the only
1335/// constructor that accepts one is `OsString::from_encoded_bytes_unchecked` — whose
1336/// precondition this function cannot enforce. It takes a `Vec<u8>` from a safe caller, so
1337/// there is no way to know the bytes came from `as_encoded_bytes` rather than from anywhere
1338/// else, and a safe function with a precondition that can be violated is unsound however
1339/// carefully its callers behave today.
1340///
1341/// So on Windows the bytes go through UTF-8, and one that is not valid UTF-8 is refused
1342/// rather than assumed. What that gives up is a Windows argument containing an unpaired
1343/// surrogate, which is reported instead of accepted; what it buys is that this crate needs no
1344/// `unsafe` at all.
1345pub fn os_string_from_bytes(value: Vec<u8>) -> Result<OsString, Vec<u8>> {
1346    #[cfg(unix)]
1347    {
1348        Ok(std::os::unix::ffi::OsStringExt::from_vec(value))
1349    }
1350    #[cfg(not(unix))]
1351    {
1352        match String::from_utf8(value) {
1353            Ok(text) => Ok(OsString::from(text)),
1354            Err(bad) => Err(bad.into_bytes()),
1355        }
1356    }
1357}
1358
1359/// One [`Error::InvalidValue`], built out of line.
1360///
1361/// Cold and never inlined on purpose: this is the failure path of every value
1362/// conversion in every generated `build`, and inlining it there is what made
1363/// those functions large.
1364#[cold]
1365#[inline(never)]
1366pub(crate) fn invalid_value_error<'t, 'v>(
1367    name: &'t str,
1368    value: String,
1369    reason: String,
1370) -> Error<'t, 'v> {
1371    Error::InvalidValue(Box::new(InvalidValue {
1372        name,
1373        value,
1374        reason,
1375    }))
1376}
1377
1378/// One [`Error::InvalidValue`] for a word that was not UTF-8.
1379///
1380/// The error half of what a generated `build` does per text field: the check stays
1381/// inline at the field, and this — the lossy rendering and the allocations — lives
1382/// here once instead of once per field.
1383#[cold]
1384#[inline(never)]
1385pub fn invalid_utf8_value<'t, 'v>(name: &'t str, bad: std::string::FromUtf8Error) -> Error<'t, 'v> {
1386    invalid_value_error(
1387        name,
1388        String::from_utf8_lossy(bad.as_bytes()).into_owned(),
1389        bad.utf8_error().to_string(),
1390    )
1391}
1392
1393/// One [`Error::InvalidValue`] for a value whose type would not build from it.
1394///
1395/// Takes the reason as `&dyn Display` so one copy serves every `FromStr` error type.
1396#[cold]
1397#[inline(never)]
1398pub fn invalid_parsed_value<'t, 'v>(
1399    name: &'t str,
1400    value: String,
1401    reason: &dyn std::fmt::Display,
1402) -> Error<'t, 'v> {
1403    invalid_value_error(name, value, reason.to_string())
1404}
1405
1406/// One [`Error::InvalidValue`] for a word that is not one of a value enum's choices.
1407#[cold]
1408#[inline(never)]
1409pub fn invalid_choice_value<'t, 'v>(name: &'t str, value: String) -> Error<'t, 'v> {
1410    invalid_value_error(name, value, String::from("not one of the declared values"))
1411}
1412
1413/// One [`Error::InvalidValue`] for bytes the platform cannot hold in a path.
1414#[cold]
1415#[inline(never)]
1416pub fn invalid_os_value<'t, 'v>(name: &'t str, bytes: Vec<u8>) -> Error<'t, 'v> {
1417    invalid_value_error(
1418        name,
1419        String::from_utf8_lossy(&bytes).into_owned(),
1420        "this platform cannot hold these bytes in a path".to_string(),
1421    )
1422}
1423
1424/// Convert every repeated value of one text field, reporting `name` for the
1425/// first that is not UTF-8.
1426///
1427/// The shared body of what a generated `build` does per collecting text field:
1428/// one loop in the binary rather than one per field. Converts element by
1429/// element rather than with `collect` so the error can carry the value that
1430/// failed rather than only that one did.
1431///
1432/// The empty case is answered here rather than in the shared loop, and this is
1433/// what keeps sharing the loop free: a command at mise's scale declares dozens
1434/// of collecting fields and a command line names one or two of them, so most of
1435/// these calls have nothing to convert. Testing that at the field costs a branch;
1436/// reaching the loop to learn it costs the call.
1437#[inline]
1438pub fn utf8_values<'t, 'v>(
1439    values: Vec<Vec<u8>>,
1440    name: &'t str,
1441) -> Result<Vec<String>, Error<'t, 'v>> {
1442    if values.is_empty() {
1443        return Ok(Vec::new());
1444    }
1445    utf8_values_given(values, name)
1446}
1447
1448#[inline(never)]
1449fn utf8_values_given<'t, 'v>(
1450    values: Vec<Vec<u8>>,
1451    name: &'t str,
1452) -> Result<Vec<String>, Error<'t, 'v>> {
1453    let mut out = Vec::with_capacity(values.len());
1454    for value in values {
1455        match String::from_utf8(value) {
1456            Ok(text) => out.push(text),
1457            Err(bad) => return Err(invalid_utf8_value(name, bad)),
1458        }
1459    }
1460    Ok(out)
1461}
1462
1463/// Convert every repeated value of one field through
1464/// [`FromStr`](std::str::FromStr), reporting `name` for the first that fails.
1465///
1466/// Monomorphized once per target type rather than expanded once per field, and
1467/// the empty case is answered at the field for the reason [`utf8_values`] gives.
1468#[inline]
1469pub fn parsed_values<'t, 'v, T>(
1470    values: Vec<Vec<u8>>,
1471    name: &'t str,
1472) -> Result<Vec<T>, Error<'t, 'v>>
1473where
1474    T: std::str::FromStr,
1475    T::Err: std::fmt::Display,
1476{
1477    if values.is_empty() {
1478        return Ok(Vec::new());
1479    }
1480    parsed_values_given(values, name)
1481}
1482
1483#[inline(never)]
1484fn parsed_values_given<'t, 'v, T>(
1485    values: Vec<Vec<u8>>,
1486    name: &'t str,
1487) -> Result<Vec<T>, Error<'t, 'v>>
1488where
1489    T: std::str::FromStr,
1490    T::Err: std::fmt::Display,
1491{
1492    let mut out = Vec::with_capacity(values.len());
1493    for value in values {
1494        let text = match String::from_utf8(value) {
1495            Ok(text) => text,
1496            Err(bad) => return Err(invalid_utf8_value(name, bad)),
1497        };
1498        match text.parse() {
1499            Ok(parsed) => out.push(parsed),
1500            Err(reason) => return Err(invalid_parsed_value(name, text, &reason)),
1501        }
1502    }
1503    Ok(out)
1504}
1505
1506/// Convert every repeated value of one path-like field, reporting `name` for
1507/// the first the platform cannot hold.
1508///
1509/// `T` is what the field collects — [`PathBuf`](std::path::PathBuf) or
1510/// [`OsString`] — so one body serves both. The same platform note as
1511/// [`os_string_from_bytes`] applies: lossless on Unix, partial on Windows. The
1512/// empty case is answered at the field for the reason [`utf8_values`] gives.
1513#[inline]
1514pub fn os_values<'t, 'v, T: From<OsString>>(
1515    values: Vec<Vec<u8>>,
1516    name: &'t str,
1517) -> Result<Vec<T>, Error<'t, 'v>> {
1518    if values.is_empty() {
1519        return Ok(Vec::new());
1520    }
1521    os_values_given(values, name)
1522}
1523
1524#[inline(never)]
1525fn os_values_given<'t, 'v, T: From<OsString>>(
1526    values: Vec<Vec<u8>>,
1527    name: &'t str,
1528) -> Result<Vec<T>, Error<'t, 'v>> {
1529    let mut out = Vec::with_capacity(values.len());
1530    for value in values {
1531        match os_string_from_bytes(value) {
1532            Ok(os) => out.push(T::from(os)),
1533            Err(bytes) => return Err(invalid_os_value(name, bytes)),
1534        }
1535    }
1536    Ok(out)
1537}
1538
1539/// A single-pass parse over `argv`.
1540///
1541/// Created with [`Parser::new`] and driven with [`Parser::next_event`].
1542pub struct Parser<'t, 'a, 'v> {
1543    argv: &'a [&'v OsStr],
1544    /// Index of the next token to read.
1545    pos: usize,
1546    /// The command currently in scope.
1547    cmd: &'t Command<'t>,
1548    /// The canonical root, used to hide root globals omitted by an executable view.
1549    #[cfg(feature = "spec")]
1550    root: &'t Command<'t>,
1551    /// The executable projection being parsed, if argv0 selected one.
1552    #[cfg(feature = "spec")]
1553    view: Option<&'t spec::ViewMeta<'t>>,
1554    /// What an unrecognized flag-like token means in the command currently in scope.
1555    ///
1556    /// Carried rather than looked up, because it is inherited: a command that states
1557    /// nothing keeps what the enclosing one said, and walking back up the ancestors on
1558    /// every unrecognized token would pay for the inheritance at the wrong moment.
1559    unknown_flags: UnknownFlags,
1560    /// Effective inherited trailing-delimiter policy.
1561    dont_delimit_trailing_values: bool,
1562    /// The chain above `cmd`, used to find inherited global flags. Fixed size so
1563    /// that nothing is allocated.
1564    ancestors: [Option<&'t Command<'t>>; MAX_DEPTH],
1565    depth: usize,
1566    /// Bytes left in a short-flag bundle, if one is partly read.
1567    bundle: &'v [u8],
1568    /// The whole token the current bundle came from, so an error raised part way
1569    /// through it can still name what the user typed.
1570    bundle_token: &'v [u8],
1571    /// A variadic flag that is still collecting values.
1572    collecting: Option<&'t Flag<'t>>,
1573    /// Where the command in scope began, as an index into `argv`.
1574    cmd_start: usize,
1575    /// Where each ancestor's own words began, in step with `ancestors`.
1576    starts: [usize; MAX_DEPTH],
1577    /// How many values it has taken, so a bound can stop it.
1578    collected: u32,
1579    /// Which of `cmd.args` is next to fill.
1580    arg_pos: usize,
1581    /// How many words the variadic at `arg_pos` has taken, for the same reason.
1582    arg_taken: u32,
1583    /// Whether any word has been bound to a positional of `cmd`. Once one has,
1584    /// no further word can select a subcommand.
1585    arg_filled: bool,
1586    /// Whether this command has bound any flag or positional. Unlike
1587    /// `arg_filled`, flags count because clap's command policy treats both as
1588    /// arguments that exclude a later subcommand.
1589    command_arg_found: bool,
1590    /// Whether flag interpretation has stopped. A `--` does this, and so does an
1591    /// `automatic` argument taking a value.
1592    flags_stopped: bool,
1593    /// Whether a `--` was actually consumed as a separator.
1594    ///
1595    /// Tracked apart from `flags_stopped` because the two can differ: an
1596    /// `automatic` argument stops flag interpretation without any separator being
1597    /// typed, and a `preserve` argument keeps one as a value rather than
1598    /// consuming it. Callers asking this question want to know what the user
1599    /// wrote, not what state the parser reached.
1600    separator_seen: bool,
1601    /// Whether the default subcommand has already been taken.
1602    ///
1603    /// Once, per parse: a default subcommand that itself declares one would otherwise
1604    /// descend on every word until the tree ran out.
1605    default_taken: bool,
1606    /// Set once a fatal error has been reported, so iteration stops.
1607    done: bool,
1608    /// Whether declared built-in actions stop parsing with their action error.
1609    ///
1610    /// Invocation parsing does; completion walking only needs the grammar position after the
1611    /// flag, and must not execute an action while inspecting a partial command line.
1612    action_errors: bool,
1613    /// The `argv` range the `help` *word* resolved as a command path, if one was typed.
1614    ///
1615    /// Empty for `--help`, which asks about wherever the parse had got to. For the word, the
1616    /// question is about a command deeper than the parse reached, and only this walk knows
1617    /// which tokens named it: a caller re-scanning `argv` would count a flag's detached value
1618    /// that happens to spell a sibling's name. Two indices rather than the commands
1619    /// themselves, so the parser keeps allocating nothing.
1620    help_span: (usize, usize),
1621}
1622
1623impl<'t: 'v, 'a, 'v> Parser<'t, 'a, 'v> {
1624    /// Begin parsing `argv` against `root`.
1625    ///
1626    /// `argv` excludes the program name.
1627    pub fn new(root: &'t Command<'t>, argv: &'a [&'v OsStr]) -> Self {
1628        Self::with_action_errors(root, argv, true)
1629    }
1630
1631    /// Begin a non-executing parse for completion walking.
1632    #[cfg(feature = "complete")]
1633    pub(crate) fn for_completion(root: &'t Command<'t>, argv: &'a [&'v OsStr]) -> Self {
1634        Self::with_action_errors(root, argv, false)
1635    }
1636
1637    fn with_action_errors(
1638        root: &'t Command<'t>,
1639        argv: &'a [&'v OsStr],
1640        action_errors: bool,
1641    ) -> Self {
1642        Parser {
1643            argv,
1644            pos: 0,
1645            cmd: root,
1646            #[cfg(feature = "spec")]
1647            root,
1648            #[cfg(feature = "spec")]
1649            view: None,
1650            unknown_flags: match root.unknown_flags {
1651                ::core::option::Option::Some(mode) => mode,
1652                // Nothing above the root to inherit from, so the default stands.
1653                ::core::option::Option::None => UnknownFlags::Value,
1654            },
1655            dont_delimit_trailing_values: root.dont_delimit_trailing_values,
1656            ancestors: [None; MAX_DEPTH],
1657            depth: 0,
1658            bundle: &[],
1659            bundle_token: &[],
1660            collecting: None,
1661            cmd_start: 0,
1662            starts: [0; MAX_DEPTH],
1663            collected: 0,
1664            arg_pos: 0,
1665            arg_taken: 0,
1666            arg_filled: false,
1667            command_arg_found: false,
1668            flags_stopped: false,
1669            separator_seen: false,
1670            default_taken: false,
1671            done: false,
1672            action_errors,
1673            help_span: (0, 0),
1674        }
1675    }
1676
1677    /// Restrict inherited root globals to those carried by an executable view.
1678    #[cfg(feature = "spec")]
1679    pub fn with_view(mut self, view: &'t spec::ViewMeta<'t>) -> Self {
1680        self.view = Some(view);
1681        self
1682    }
1683
1684    /// The command in scope: the root, or the deepest subcommand selected so far.
1685    pub fn command(&self) -> &'t Command<'t> {
1686        self.cmd
1687    }
1688
1689    /// Whether a `--` was consumed as a separator.
1690    ///
1691    /// False when flag interpretation stopped for another reason, such as an
1692    /// `automatic` argument taking a value, and false for a `--` that a
1693    /// `preserve` argument kept as a value.
1694    pub fn double_dash_seen(&self) -> bool {
1695        self.separator_seen
1696    }
1697
1698    /// Every command entered so far, and where each one's own words begin.
1699    ///
1700    /// The ancestors are already kept for flag scoping; this is the same chain with the offsets,
1701    /// which is what lets a completion hand a callback the words of *its* command rather than of
1702    /// the deepest one — a global flag is declared on an ancestor.
1703    pub fn command_path(&self) -> Vec<(&'t Command<'t>, usize)> {
1704        let mut out = Vec::with_capacity(self.depth + 1);
1705        for (i, ancestor) in self.ancestors[..self.depth].iter().enumerate() {
1706            if let Some(cmd) = ancestor {
1707                // An ancestor's own words start where the one before it descended, and the
1708                // root's start at the beginning.
1709                out.push((*cmd, self.starts[i]));
1710            }
1711        }
1712        out.push((self.cmd, self.cmd_start));
1713        out
1714    }
1715
1716    /// The `argv` range the `help` word resolved as a command path.
1717    ///
1718    /// Empty unless the word was typed. Every token in it named a subcommand of the one before
1719    /// it — the parser resolved them itself, so nothing here is a flag or a flag's value.
1720    pub fn help_span(&self) -> (usize, usize) {
1721        self.help_span
1722    }
1723
1724    /// Where the command in scope began: the index in `argv` just after its name, or at the
1725    /// unmatched word routed into a default subcommand.
1726    ///
1727    /// `argv[command_start()..]` is what that command was given, which is what a completion
1728    /// callback needs to be handed its own command's half-parsed struct rather than the root's.
1729    pub fn command_start(&self) -> usize {
1730        self.cmd_start
1731    }
1732
1733    /// Whether flag interpretation has stopped, for any reason.
1734    ///
1735    /// Wider than [`double_dash_seen`](Self::double_dash_seen), and the question completion
1736    /// asks: past a separator *or* past the first value of an `automatic` argument, a
1737    /// dash-prefixed word is a value, so there is no flag there to offer.
1738    pub fn flags_stopped(&self) -> bool {
1739        self.flags_stopped
1740    }
1741
1742    /// A variadic flag that is still claiming words.
1743    ///
1744    /// Asked *between* events, because the answer is gone by the end: the call that finds argv
1745    /// exhausted is the one that clears it. A completion needs it — the next word after
1746    /// `--tools a ⌶` is another tool, not the positional that follows.
1747    pub fn collecting(&self) -> Option<&'t Flag<'t>> {
1748        self.collecting
1749    }
1750
1751    /// The positional the next word would fill, if there is one left.
1752    ///
1753    /// A variadic stays here until it reaches its bound, which is what makes it the answer to
1754    /// "what could go where the cursor is" as many times as it can be filled.
1755    pub fn pending_arg(&self) -> Option<&'t Arg<'t>> {
1756        self.next_arg()
1757    }
1758
1759    /// Flags a word here could name: this command's own, then any ancestor's globals.
1760    ///
1761    /// The same set the parser itself would look in, so what is offered and what is accepted
1762    /// cannot disagree — including the shadowing rule, where a subcommand redeclaring an
1763    /// inherited name hides it.
1764    pub fn flags_in_scope(&self) -> impl Iterator<Item = &'t Flag<'t>> + '_ {
1765        self.in_scope()
1766    }
1767
1768    /// Read the next event.
1769    ///
1770    /// Returns `None` when `argv` is exhausted. An `Err` is terminal: the parse
1771    /// stops there, since continuing past a token that could not be understood
1772    /// would only produce bindings derived from a guess. Events already yielded
1773    /// before an error are therefore not a partial result to be used — a caller
1774    /// that assigned them into fields should discard the whole attempt.
1775    ///
1776    /// One case is stronger than that, because the grammar demands it: a short
1777    /// bundle containing an unrecognized letter yields the error *instead of*, not
1778    /// after, the letters that did match.
1779    #[allow(clippy::should_implement_trait)] // not an Iterator: items borrow from self's tables
1780    pub fn next_event(&mut self) -> Option<Result<Event<'t, 'a, 'v>, Error<'t, 'v>>> {
1781        if self.done {
1782            return None;
1783        }
1784        let event = self.step();
1785        if matches!(event, Some(Ok(Event::Flag { .. } | Event::Arg { .. }))) {
1786            self.command_arg_found = true;
1787        }
1788        if let Some(Err(_)) = event {
1789            self.done = true;
1790        }
1791        event
1792    }
1793
1794    fn step(&mut self) -> Option<Result<Event<'t, 'a, 'v>, Error<'t, 'v>>> {
1795        // A partly-read short bundle takes priority: its remaining bytes are
1796        // still part of the token being processed.
1797        if !self.bundle.is_empty() {
1798            return Some(self.short_flag());
1799        }
1800
1801        if self.cmd.subcommand_precedence_over_arg && !self.flags_stopped {
1802            if let Some(token) = self.argv.get(self.pos).map(bytes) {
1803                if let Some(sub) = self.find_subcommand(token) {
1804                    if self.cmd.args_conflicts_with_subcommands && self.command_arg_found {
1805                        return Some(Err(Error::SubcommandConflict { subcommand: sub }));
1806                    }
1807                    self.pos += 1;
1808                    return Some(self.descend(sub).map(|()| Event::Command(sub)));
1809                }
1810            }
1811        }
1812
1813        // A variadic flag keeps claiming tokens until one of them could be
1814        // something else.
1815        if let Some(flag) = self.collecting {
1816            match self.argv.get(self.pos) {
1817                Some(next)
1818                    if flag
1819                        .value_terminator
1820                        .is_some_and(|terminator| bytes(next) == terminator) =>
1821                {
1822                    self.pos += 1;
1823                    self.collecting = None;
1824                    return self.step();
1825                }
1826                Some(next)
1827                    if (!is_flag_like(bytes(next))
1828                        || (flag.allow_negative_numbers && is_negative_number(bytes(next))))
1829                        && bytes(next) != b"--" =>
1830                {
1831                    self.pos += 1;
1832                    self.collected += values_in(bytes(next), flag.delimiter);
1833                    // Same rule as a positional: a bounded occurrence takes that many and
1834                    // leaves the rest to whatever follows.
1835                    if flag.var_max.is_some_and(|max| self.collected >= max) {
1836                        self.collecting = None;
1837                    }
1838                    // Stopping is only the same as staying within the bound while one word
1839                    // is one value. A delimited word can carry the occurrence past it in a
1840                    // single step, and that word cannot be split between two owners, so the
1841                    // overshoot is an error rather than a place to stop.
1842                    if let Some(max) = flag.var_max.filter(|max| self.collected > *max) {
1843                        return Some(Err(Error::VarTooMany {
1844                            name: flag.name,
1845                            max: max as usize,
1846                            got: self.collected as usize,
1847                        }));
1848                    }
1849                    return Some(Ok(Event::Flag {
1850                        flag,
1851                        value: Some(bytes(next)),
1852                        negated: false,
1853                    }));
1854                }
1855                // A token that could be something else ends the run — but the *end of argv*
1856                // decides nothing. Clearing there threw away the answer to "would the next
1857                // word be claimed?", which is the question a completion asks and no parse
1858                // ever does: once argv is exhausted there are no more events either way.
1859                Some(_) => self.collecting = None,
1860                None => {}
1861            }
1862        }
1863
1864        let token = bytes(self.argv.get(self.pos)?);
1865        self.pos += 1;
1866
1867        // A clause separator remains syntax after an automatic positional stopped flags.
1868        // Only an explicit `--` protects a literal separator.
1869        if !self.separator_seen {
1870            if let Some(clause) = self.cmd.clause.filter(|clause| token == clause.separator) {
1871                self.arg_pos = 0;
1872                self.arg_taken = 0;
1873                self.arg_filled = false;
1874                self.collecting = None;
1875                self.flags_stopped = false;
1876                return Some(Ok(Event::ClauseSeparator { clause }));
1877            }
1878        }
1879
1880        // An automatic trailing argument stops flag interpretation without consuming an
1881        // explicit separator. A later `--` must still unlock a required trailing argument
1882        // (clap's `last`), while a separator already consumed makes every later `--` data.
1883        if self.flags_stopped && (token != b"--" || self.separator_seen) {
1884            return Some(self.word(token));
1885        }
1886
1887        if token == b"--" {
1888            // `preserve` wants the separator itself as a value, so ask the
1889            // argument that would receive it before treating it as syntax.
1890            if self
1891                .next_arg()
1892                .is_some_and(|a| a.double_dash == DoubleDash::Preserve)
1893            {
1894                return Some(self.word(token));
1895            }
1896            self.flags_stopped = true;
1897            self.separator_seen = true;
1898            // An explicit separator unlocks any argument that required one, even
1899            // if earlier arguments are still unfilled.
1900            if let Some(idx) = self.current_args()[self.arg_pos..]
1901                .iter()
1902                .position(|a| a.double_dash == DoubleDash::Required)
1903            {
1904                // The count belongs to the argument at `arg_pos`, so jumping past it has
1905                // to leave the count behind: a bounded variadic before the separator would
1906                // otherwise lend its total to the argument after it, which then stops
1907                // early or at once.
1908                self.arg_pos += idx;
1909                self.arg_taken = 0;
1910            }
1911            return self.step();
1912        }
1913
1914        if self.arg_taken > 0
1915            && self.next_arg().is_some_and(|arg| {
1916                arg.value_terminator
1917                    .is_some_and(|terminator| token == terminator)
1918            })
1919        {
1920            self.advance_arg();
1921            return self.step();
1922        }
1923
1924        // An exact declared short outranks the numeric shape. This keeps ordinary negative
1925        // numbers available as values while allowing clap-compatible spellings such as fd's
1926        // `-0` / `--print0` switch.
1927        let declared_numeric_short = matches!(token, [b'-', short]
1928            if short.is_ascii_digit() && self.find_short(*short).is_some());
1929
1930        if !declared_numeric_short
1931            && is_negative_number(token)
1932            && self
1933                .next_arg()
1934                .is_some_and(|arg| arg.allow_negative_numbers)
1935        {
1936            return Some(self.word(token));
1937        }
1938
1939        if !declared_numeric_short
1940            && is_negative_number(token)
1941            && self.cmd.external_subcommand
1942            && !self.arg_filled
1943        {
1944            return Some(self.word(token));
1945        }
1946
1947        if is_flag_like(token) {
1948            if token.starts_with(b"--") {
1949                return Some(self.long_flag(token));
1950            }
1951            // Check the whole bundle before emitting anything from it. Events go
1952            // out one at a time, so discovering an unknown letter half way
1953            // through would mean the earlier letters had already been applied —
1954            // and the grammar rejects the entire token, not the tail of it.
1955            match self.check_bundle(token) {
1956                Ok(()) => {}
1957                // Unrecognized, so it is a word unless this command wants it refused.
1958                Err(e) if self.unknown_flags == UnknownFlags::Error => {
1959                    return Some(Err(e));
1960                }
1961                Err(_) => return Some(self.word(token)),
1962            }
1963            self.bundle = &token[1..];
1964            self.bundle_token = token;
1965            return Some(self.short_flag());
1966        }
1967
1968        Some(self.word(token))
1969    }
1970
1971    fn long_flag(&mut self, token: &'v [u8]) -> Result<Event<'t, 'a, 'v>, Error<'t, 'v>> {
1972        let body = &token[2..];
1973        let (name, attached) = match body.iter().position(|&b| b == b'=') {
1974            Some(i) => (&body[..i], Some(&body[i + 1..])),
1975            None => (body, None),
1976        };
1977
1978        if let Some(flag) = self.find_long(name) {
1979            let value = if flag.takes_value {
1980                match attached {
1981                    Some(v) => Some(v),
1982                    None => self.take_detached_value(flag)?,
1983                }
1984            } else if flag.bool_value {
1985                validate_bool_value(flag, attached)?
1986            } else {
1987                None
1988            };
1989            if flag.variadic {
1990                if let Some(value) = value {
1991                    self.start_collecting(flag, value)?;
1992                }
1993            }
1994            if let Some(error) = self.flag_action(flag, true) {
1995                return Err(error);
1996            }
1997            return Ok(Event::Flag {
1998                flag,
1999                value,
2000                negated: false,
2001            });
2002        }
2003
2004        if let Some(flag) = self.find_negation(name) {
2005            return Ok(Event::Flag {
2006                flag,
2007                value: if flag.bool_value {
2008                    validate_bool_value(flag, attached)?
2009                } else {
2010                    None
2011                },
2012                negated: true,
2013            });
2014        }
2015
2016        // Where the CLI declared a version, `--version` answers with it — asked after the
2017        // command's own flags, so a CLI declaring its own keeps it.
2018        let version_command = self.version_command();
2019        if name == b"version" && version_command.version && !version_command.disable_version_flag {
2020            return Ok(Event::Flag {
2021                flag: &VERSION_LONG,
2022                value: None,
2023                negated: false,
2024            });
2025        }
2026
2027        // Every CLI answers to `--help`, and none of them declares it. Asked *after* the
2028        // command's own flags, so a CLI that declares its own `--help` keeps it.
2029        if name == b"help" && !self.cmd.disable_help_flag {
2030            return Ok(Event::Flag {
2031                flag: &HELP_LONG,
2032                value: None,
2033                negated: false,
2034            });
2035        }
2036
2037        if self.unknown_flags == UnknownFlags::Error {
2038            return Err(Error::UnknownFlag { token });
2039        }
2040        // Not a flag here, so it is a word like any other.
2041        self.word(token)
2042    }
2043
2044    /// Walk a short-flag token without binding anything, to find out whether all
2045    /// of it is recognized.
2046    ///
2047    /// Scanning stops at the first letter whose flag takes a value, because
2048    /// everything after it is that value rather than more letters.
2049    fn check_bundle(&self, token: &'v [u8]) -> Result<(), Error<'t, 'v>> {
2050        let mut rest = &token[1..];
2051        while let Some((&byte, tail)) = rest.split_first() {
2052            match self.find_short(byte) {
2053                None => return Err(Error::UnknownFlag { token }),
2054                Some(flag) if flag.takes_value => return Ok(()),
2055                Some(_) => rest = tail,
2056            }
2057        }
2058        Ok(())
2059    }
2060
2061    fn short_flag(&mut self) -> Result<Event<'t, 'a, 'v>, Error<'t, 'v>> {
2062        let byte = self.bundle[0];
2063        let rest = &self.bundle[1..];
2064
2065        let Some(flag) = self.find_short(byte) else {
2066            // check_bundle already rejected any token containing an unrecognized
2067            // letter, so this is unreachable — but a parser should report rather
2068            // than panic if that ever stops being true.
2069            self.bundle = &[];
2070            return Err(Error::UnknownFlag {
2071                token: self.bundle_token,
2072            });
2073        };
2074
2075        if !flag.takes_value {
2076            self.bundle = rest;
2077            if let Some(error) = self.flag_action(flag, false) {
2078                self.bundle = &[];
2079                return Err(error);
2080            }
2081            return Ok(Event::Flag {
2082                flag,
2083                value: None,
2084                negated: false,
2085            });
2086        }
2087
2088        // A value-taking short ends the token: everything after it is the value,
2089        // less one separating `=`.
2090        self.bundle = &[];
2091        let value = if rest.is_empty() {
2092            self.take_detached_value(flag)?
2093        } else if rest[0] == b'=' {
2094            Some(&rest[1..])
2095        } else {
2096            Some(rest)
2097        };
2098        if flag.variadic {
2099            if let Some(value) = value {
2100                self.start_collecting(flag, value)?;
2101            }
2102        }
2103        if let Some(error) = self.flag_action(flag, false) {
2104            return Err(error);
2105        }
2106        Ok(Event::Flag {
2107            flag,
2108            value,
2109            negated: false,
2110        })
2111    }
2112
2113    fn flag_action(&self, flag: &'t Flag<'t>, long_spelling: bool) -> Option<Error<'t, 'v>> {
2114        if matches!(
2115            flag.key,
2116            HELP_LONG_KEY | HELP_SHORT_KEY | VERSION_LONG_KEY | VERSION_SHORT_KEY
2117        ) || !self.action_errors
2118        {
2119            return None;
2120        }
2121        match flag.action {
2122            ArgAction::Set => None,
2123            ArgAction::Help => Some(Error::Help {
2124                cmd: self.cmd,
2125                long: long_spelling,
2126            }),
2127            ArgAction::HelpShort => Some(Error::Help {
2128                cmd: self.cmd,
2129                long: false,
2130            }),
2131            ArgAction::HelpLong => Some(Error::Help {
2132                cmd: self.cmd,
2133                long: true,
2134            }),
2135            ArgAction::HelpAll => Some(Error::HelpAll { cmd: self.cmd }),
2136            ArgAction::Version => Some(Error::Version {
2137                long: long_spelling,
2138            }),
2139        }
2140    }
2141
2142    /// Take the following token as a flag's value.
2143    ///
2144    /// Refuses a flag-like token unless [`Flag::allow_hyphen_values`] is set:
2145    /// `--jobs --force` is far more likely a forgotten value than a deliberate
2146    /// one, and the attached form is available for the deliberate case. Declared,
2147    /// the next token is taken whatever it looks like, including `--`.
2148    fn take_detached_value(
2149        &mut self,
2150        flag: &'t Flag<'t>,
2151    ) -> Result<Option<&'v [u8]>, Error<'t, 'v>> {
2152        if flag.require_equals {
2153            return self.missing_or_default(flag);
2154        }
2155        match self.argv.get(self.pos) {
2156            Some(next)
2157                if flag.allow_hyphen_values
2158                    || !is_flag_like(bytes(next))
2159                    || (flag.allow_negative_numbers && is_negative_number(bytes(next))) =>
2160            {
2161                self.pos += 1;
2162                Ok(Some(bytes(next)))
2163            }
2164            _ => self.missing_or_default(flag),
2165        }
2166    }
2167
2168    fn missing_or_default(&self, flag: &'t Flag<'t>) -> Result<Option<&'v [u8]>, Error<'t, 'v>> {
2169        match flag.default_missing {
2170            Some(value) => Ok(Some(value)),
2171            None if flag.value_optional => Ok(None),
2172            None => Err(Error::MissingFlagValue { flag }),
2173        }
2174    }
2175
2176    fn word(&mut self, token: &'v [u8]) -> Result<Event<'t, 'a, 'v>, Error<'t, 'v>> {
2177        // Subcommands are only matched where descent is still possible: once a
2178        // positional of this command has taken a word, a later word that happens
2179        // to equal a subcommand name is just a value.
2180        if !self.arg_filled && !self.flags_stopped {
2181            if let Some(sub) = self.find_subcommand(token) {
2182                if self.cmd.args_conflicts_with_subcommands && self.command_arg_found {
2183                    return Err(Error::SubcommandConflict { subcommand: sub });
2184                }
2185                self.descend(sub)?;
2186                return Ok(Event::Command(sub));
2187            }
2188
2189            // `ex help config ls` — the line every page with a Commands section has printed
2190            // all along ("help  Print this message or the help of the given subcommand(s)"),
2191            // and which until now did nothing. The page is what decides the condition here:
2192            // it prints that line where there are subcommands, so that is where the word is
2193            // answered, and to a leaf `help` is a word like any other.
2194            //
2195            // Asked *after* the subcommand lookup, so a CLI that declares a `help` of its own
2196            // keeps it — the same rule the two help flags follow.
2197            //
2198            // The words after it name a command, resolved here rather than descended into:
2199            // descending would bind them, and they are a question rather than an invocation.
2200            if token == b"help"
2201                && !self.cmd.disable_help_subcommand
2202                && !self.cmd.subcommands.is_empty()
2203            {
2204                let mut cmd = self.cmd;
2205                let from = self.pos;
2206                while let Some(next) = self.argv.get(self.pos) {
2207                    let Some(sub) = find_named(cmd, bytes(next)) else {
2208                        break;
2209                    };
2210                    cmd = sub;
2211                    self.pos += 1;
2212                }
2213                // Kept for `help::route_to`: which mount was asked about is not recoverable
2214                // from `cmd`, since two mounts of one `Subcommands` type are one address.
2215                self.help_span = (from, self.pos);
2216                // The long form, as `ex config --help` gives: someone who typed a whole word to
2217                // ask for help wants the fuller answer.
2218                return Err(Error::Help { cmd, long: true });
2219            }
2220
2221            // A word that names no subcommand goes to the default one, if there is one.
2222            //
2223            // Only a word, though. A dash-prefixed token that named no flag arrives here as a
2224            // value — that is what `unknown_flags = value` means — and it was never a
2225            // candidate to *select* anything, so it binds where it was typed. usage-lib stops
2226            // looking for subcommands at an unrecognised flag for the same reason. `--` is
2227            // excluded on the same grounds: it reaches this function only when a `preserve`
2228            // argument wants it as a value.
2229            //
2230            // The token is *not* consumed: the cursor steps back so the next event reads it
2231            // again, now against the command just descended into. That is what lets it be a
2232            // subcommand of the default (`mise build` where `build` is a task the mount
2233            // added) as easily as an argument of it, without this function having to decide
2234            // which — and without yielding two events for one word.
2235            if let Some(default) = self.cmd.default_subcommand {
2236                // `-` joins `--` in being excluded, and for the reason already written above:
2237                // a value was never a candidate to *select* anything. `is_flag_like` calls a
2238                // lone `-` a value — conventionally stdin — so it passed this guard and
2239                // descended, where mise's `run` has no positional and the parse failed.
2240                // usage-lib and clap both bind it to the root's own `[TASK]` instead.
2241                let default_accepts_negative = is_negative_number(token)
2242                    && default
2243                        .args
2244                        .first()
2245                        .is_some_and(|arg| arg.allow_negative_numbers);
2246                if !self.default_taken
2247                    && (!is_flag_like(token) || default_accepts_negative)
2248                    && token != b"--"
2249                    && token != b"-"
2250                {
2251                    self.default_taken = true;
2252                    self.descend(default)?;
2253                    self.pos -= 1;
2254                    // Unlike an explicitly named command, the default command receives the
2255                    // word that caused descent. Keep its argv boundary at that word so
2256                    // command-level policies and completion callbacks see the same input the
2257                    // command parser is about to re-read.
2258                    self.cmd_start = self.pos;
2259                    return Ok(Event::Command(default));
2260                }
2261            }
2262
2263            // Known subcommands and the default route keep precedence. A sigil positional
2264            // then claims its classified word before an external-subcommand catch-all can.
2265            if let Some((arg, sigil)) = self.match_sigil_arg(token) {
2266                if token.len() == sigil.len() {
2267                    return Err(invalid_value_error(
2268                        arg.name,
2269                        as_str(token).unwrap_or_default().to_string(),
2270                        format!(
2271                            "expected a value after sigil {:?}",
2272                            as_str(sigil).unwrap_or_default()
2273                        ),
2274                    ));
2275                }
2276                return Ok(Event::Arg {
2277                    arg,
2278                    value: &token[sigil.len()..],
2279                    delimit: true,
2280                });
2281            }
2282
2283            // An unmatched word that names no subcommand is forwarded as an external
2284            // command: this word, then every token after it, including flags. Known
2285            // subcommands already won above, and a default_subcommand already caught.
2286            if self.cmd.external_subcommand
2287                && (!is_flag_like(token) || is_negative_number(token))
2288                && token != b"--"
2289                && token != b"-"
2290            {
2291                let from = self.pos - 1;
2292                self.pos = self.argv.len();
2293                return Ok(Event::External {
2294                    values: &self.argv[from..],
2295                });
2296            }
2297        }
2298
2299        if self.arg_filled && !self.flags_stopped {
2300            if let Some((arg, sigil)) = self.match_sigil_arg(token) {
2301                if token.len() == sigil.len() {
2302                    return Err(invalid_value_error(
2303                        arg.name,
2304                        as_str(token).unwrap_or_default().to_string(),
2305                        format!(
2306                            "expected a value after sigil {:?}",
2307                            as_str(sigil).unwrap_or_default()
2308                        ),
2309                    ));
2310                }
2311                return Ok(Event::Arg {
2312                    arg,
2313                    value: &token[sigil.len()..],
2314                    delimit: true,
2315                });
2316            }
2317        }
2318
2319        self.skip_sigil_args();
2320        self.reserve_for_required_positionals();
2321        let Some(arg) = self.next_arg() else {
2322            return Err(Error::UnexpectedArg { token });
2323        };
2324
2325        if arg.double_dash == DoubleDash::Required && !self.separator_seen {
2326            return Err(Error::ArgRequiresDoubleDash { arg });
2327        }
2328
2329        self.arg_filled = true;
2330        // An `automatic` argument stops flag interpretation from here on, as
2331        // though the caller had typed the separator themselves.
2332        let trailing_value = self.separator_seen || arg.double_dash == DoubleDash::Automatic;
2333        let delimit = !(self.dont_delimit_trailing_values && trailing_value);
2334        if arg.double_dash == DoubleDash::Automatic {
2335            self.flags_stopped = true;
2336        }
2337        // A variadic keeps taking values, so the cursor stays put — until it reaches its
2338        // bound, at which point the words after it belong to whatever comes next. That is
2339        // what makes `[a]… [b]` expressible at all.
2340        if arg.var {
2341            self.arg_taken += values_in(token, delimit.then_some(arg.delimiter).flatten());
2342            // Before advancing, which resets the count: as with a variadic flag, reaching
2343            // the bound and passing it are the same event once a word can carry several
2344            // values, and only the second is a mistake.
2345            if let Some(max) = arg.var_max.filter(|max| self.arg_taken > *max) {
2346                return Err(Error::VarTooMany {
2347                    name: arg.name,
2348                    max: max as usize,
2349                    got: self.arg_taken as usize,
2350                });
2351            }
2352            if arg.var_max.is_some_and(|max| self.arg_taken >= max) {
2353                self.advance_arg();
2354            }
2355        } else {
2356            self.advance_arg();
2357        }
2358        Ok(Event::Arg {
2359            arg,
2360            value: token,
2361            delimit,
2362        })
2363    }
2364
2365    fn descend(&mut self, sub: &'t Command<'t>) -> Result<(), Error<'t, 'v>> {
2366        if self.depth >= MAX_DEPTH {
2367            return Err(Error::TooDeep);
2368        }
2369        self.ancestors[self.depth] = Some(self.cmd);
2370        self.starts[self.depth] = self.cmd_start;
2371        self.depth += 1;
2372        self.cmd = sub;
2373        // Only a command that says something changes it, which is what inheriting means.
2374        if let ::core::option::Option::Some(mode) = sub.unknown_flags {
2375            self.unknown_flags = mode;
2376        }
2377        self.dont_delimit_trailing_values |= sub.dont_delimit_trailing_values;
2378        // Where this command's own words start, which is what lets a completion hand a callback
2379        // the half-parsed struct of the command it was declared on rather than of the root.
2380        self.cmd_start = self.pos;
2381        self.arg_pos = 0;
2382        self.arg_taken = 0;
2383        self.arg_filled = false;
2384        self.command_arg_found = false;
2385        Ok(())
2386    }
2387
2388    /// Move to the next positional, forgetting what the last one took.
2389    fn advance_arg(&mut self) {
2390        self.skip_sigil_args();
2391        self.arg_pos += 1;
2392        self.arg_taken = 0;
2393        self.skip_sigil_args();
2394    }
2395
2396    /// A variadic flag occurrence begins, counting from zero.
2397    ///
2398    /// The value it was given on the same token counts, which is why this starts at what
2399    /// that value holds: `--include a b` with `var_max=2` takes `a` and `b`, not three
2400    /// words — and `--include a,b` has already taken both on the one token.
2401    fn start_collecting(&mut self, flag: &'t Flag<'t>, first: &[u8]) -> Result<(), Error<'t, 'v>> {
2402        self.collected = values_in(first, flag.delimiter);
2403        if let Some(max) = flag.var_max.filter(|max| self.collected > *max) {
2404            return Err(Error::VarTooMany {
2405                name: flag.name,
2406                max: max as usize,
2407                got: self.collected as usize,
2408            });
2409        }
2410        self.collecting = if flag.var_max.is_some_and(|max| self.collected >= max) {
2411            None
2412        } else {
2413            Some(flag)
2414        };
2415        Ok(())
2416    }
2417
2418    fn next_arg(&self) -> Option<&'t Arg<'t>> {
2419        self.current_args()[self.arg_pos..]
2420            .iter()
2421            .find(|arg| arg.sigil.is_none())
2422            .copied()
2423    }
2424
2425    fn skip_sigil_args(&mut self) {
2426        while self
2427            .current_args()
2428            .get(self.arg_pos)
2429            .is_some_and(|arg| arg.sigil.is_some())
2430        {
2431            self.arg_pos += 1;
2432        }
2433    }
2434
2435    fn match_sigil_arg(&self, token: &[u8]) -> Option<(&'t Arg<'t>, &'t [u8])> {
2436        if self.flags_stopped {
2437            return None;
2438        }
2439        let own = self.current_args().iter().copied();
2440        let inherited = self.ancestors[..self.depth]
2441            .iter()
2442            .rev()
2443            .filter_map(|cmd| *cmd)
2444            .flat_map(|cmd| cmd.args.iter().copied());
2445        own.chain(inherited)
2446            .filter_map(|arg| {
2447                let sigil = arg.sigil?;
2448                (token.len() >= sigil.len() && token.starts_with(sigil)).then_some((arg, sigil))
2449            })
2450            .max_by_key(|(_, sigil)| sigil.len())
2451    }
2452
2453    fn current_args(&self) -> &'t [&'t Arg<'t>] {
2454        self.cmd
2455            .clause
2456            .map(|clause| clause.args)
2457            .unwrap_or(self.cmd.args)
2458    }
2459
2460    /// Skip empty optional positionals when every remaining value is needed by a later
2461    /// required positional. This is clap's opt-in `allow_missing_positional` policy.
2462    fn reserve_for_required_positionals(&mut self) {
2463        if !self.cmd.allow_missing_positional || self.arg_taken != 0 {
2464            return;
2465        }
2466        loop {
2467            let Some(current) = self.next_arg() else {
2468                return;
2469            };
2470            if current.required {
2471                return;
2472            }
2473            let required_after = self.current_args()[self.arg_pos + 1..]
2474                .iter()
2475                .filter(|arg| arg.required && arg.sigil.is_none())
2476                .count();
2477            if required_after == 0 {
2478                return;
2479            }
2480            let remaining_values = 1 + self.argv[self.pos..]
2481                .iter()
2482                .filter(|word| {
2483                    (self.flags_stopped || !is_flag_like(bytes(word)))
2484                        && self.match_sigil_arg(bytes(word)).is_none()
2485                })
2486                .count();
2487            if remaining_values > required_after {
2488                return;
2489            }
2490            self.advance_arg();
2491        }
2492    }
2493
2494    #[cfg(feature = "spec")]
2495    fn view_allows_own_flag(&self, flag: &Flag<'_>) -> bool {
2496        match self.view {
2497            None => true,
2498            // The promoted command keeps its own surface. While the injected path is
2499            // still at the host root, however, only explicitly carried globals belong
2500            // to the view; root-local flags are not part of the projected executable.
2501            Some(view) => {
2502                !core::ptr::eq(self.cmd, self.root) || is_version_flag(flag) || view.carries(flag)
2503            }
2504        }
2505    }
2506
2507    #[cfg(not(feature = "spec"))]
2508    fn view_allows_own_flag(&self, _flag: &Flag<'_>) -> bool {
2509        true
2510    }
2511
2512    #[cfg(feature = "spec")]
2513    fn view_allows_inherited_flag(&self, flag: &Flag<'_>) -> bool {
2514        match self.view {
2515            None => true,
2516            // A portable view carries selected host globals, not globals declared
2517            // by intermediate commands on a multi-segment promoted path.
2518            Some(view) => {
2519                self.root
2520                    .flags
2521                    .iter()
2522                    .any(|root| core::ptr::eq(*root, flag))
2523                    && (is_version_flag(flag) || view.carries(flag))
2524            }
2525        }
2526    }
2527
2528    #[cfg(not(feature = "spec"))]
2529    fn view_allows_inherited_flag(&self, _flag: &Flag<'_>) -> bool {
2530        true
2531    }
2532
2533    #[cfg(feature = "spec")]
2534    fn inherited_flag_is_in_scope(&self, flag: &Flag<'_>) -> bool {
2535        flag.global || (self.view.is_some() && is_version_flag(flag))
2536    }
2537
2538    #[cfg(not(feature = "spec"))]
2539    fn inherited_flag_is_in_scope(&self, flag: &Flag<'_>) -> bool {
2540        flag.global
2541    }
2542
2543    /// Flags in scope: this command's own, then any ancestor's globals.
2544    ///
2545    /// Own flags come first so that a subcommand redeclaring an inherited name
2546    /// shadows it, which is what mise relies on when it redeclares root globals
2547    /// on `run` with different shorts.
2548    fn in_scope(&self) -> impl Iterator<Item = &'t Flag<'t>> + '_ {
2549        let own = self
2550            .cmd
2551            .flags
2552            .iter()
2553            .copied()
2554            .filter(|flag| self.view_allows_own_flag(flag));
2555        let inherited = self.ancestors[..self.depth]
2556            .iter()
2557            .rev()
2558            .filter_map(|c| *c)
2559            .flat_map(|c| c.flags.iter().copied())
2560            .filter(|flag| self.inherited_flag_is_in_scope(flag))
2561            .filter(|flag| self.view_allows_inherited_flag(flag));
2562        own.chain(inherited)
2563    }
2564
2565    fn find_long(&self, name: &[u8]) -> Option<&'t Flag<'t>> {
2566        self.in_scope()
2567            .find(|f| f.longs.iter().any(|l| l.as_bytes() == name))
2568    }
2569
2570    fn find_negation(&self, name: &[u8]) -> Option<&'t Flag<'t>> {
2571        self.in_scope()
2572            .find(|f| f.negate.is_some_and(|n| n.as_bytes() == name))
2573    }
2574
2575    fn find_short(&self, byte: u8) -> Option<&'t Flag<'t>> {
2576        self.in_scope()
2577            .find(|f| f.shorts.contains(&byte))
2578            // As for `--help`: supplied by the parser, and only where the command has not
2579            // declared a `-h` of its own.
2580            .or(if byte == b'h' && !self.cmd.disable_help_flag {
2581                Some(&HELP_SHORT)
2582            } else if byte == b'V'
2583                && self.version_command().version
2584                && !self.version_command().disable_version_flag
2585            {
2586                Some(&VERSION_SHORT)
2587            } else {
2588                None
2589            })
2590    }
2591
2592    #[cfg(feature = "spec")]
2593    fn version_command(&self) -> &'t Command<'t> {
2594        if self.view.is_some() {
2595            self.root
2596        } else {
2597            self.cmd
2598        }
2599    }
2600
2601    #[cfg(not(feature = "spec"))]
2602    fn version_command(&self) -> &'t Command<'t> {
2603        self.cmd
2604    }
2605
2606    fn find_subcommand(&self, name: &[u8]) -> Option<&'t Command<'t>> {
2607        // Shared with `help` rather than spelled out again, so descending into a command and
2608        // asking about one cannot drift apart.
2609        find_named(self.cmd, name)
2610    }
2611}
2612
2613/// View a token as bytes.
2614///
2615/// `as_encoded_bytes` is a plain accessor with no conversion and no allocation.
2616/// The reverse direction is the one with a cost — see [`os_string_from_bytes`] —
2617/// which is why values come back as bytes.
2618fn bytes<'v>(s: &&'v OsStr) -> &'v [u8] {
2619    s.as_encoded_bytes()
2620}
2621
2622/// How many values one word carries.
2623///
2624/// One, until a delimiter is declared — and then one per separator, counting the same way
2625/// splitting on it does: `a,b` is two, `a,` is two with an empty second, and `` is one.
2626/// Counted rather than split because binding only needs the number, and the split itself
2627/// belongs to the layer that owns the values.
2628fn values_in(word: &[u8], delimiter: ::core::option::Option<u8>) -> u32 {
2629    match delimiter {
2630        Some(d) => 1 + word.iter().filter(|b| **b == d).count() as u32,
2631        None => 1,
2632    }
2633}
2634
2635/// Whether a token should be read as a flag.
2636///
2637/// `-` alone is a value, conventionally stdin. Other dash-prefixed tokens are
2638/// flag-like; a field may make the narrower negative-number exception.
2639fn is_flag_like(token: &[u8]) -> bool {
2640    matches!(token, [b'-', rest @ ..] if !rest.is_empty())
2641}
2642
2643fn is_negative_number(token: &[u8]) -> bool {
2644    token.strip_prefix(b"-").is_some_and(is_number)
2645}
2646
2647/// Whether the text after a `-` is a number, so `-1`, `-2.5`, and `-1e5` are values
2648/// while `-1x` is a flag-shaped token that names nothing.
2649///
2650/// Digits, at most one `.`, and an optional exponent. Deliberately narrower than
2651/// `f64::from_str`, which also accepts `inf` and `NaN` — `-inf` is far likelier to be
2652/// a misspelled flag than a number somebody meant to pass.
2653///
2654/// usage-lib applies the same rule, and the corpus pins the edges so the two cannot
2655/// drift apart: they disagreed about `-1e5` when this was a hand-rolled scanner on
2656/// one side and a float parse on the other.
2657///
2658/// Written out rather than deferred to `f64::from_str` because this runs on the hot
2659/// path, and a parse would mean a UTF-8 check on a slice already decided by its
2660/// bytes.
2661fn is_number(rest: &[u8]) -> bool {
2662    let (mantissa, exponent) = match rest.iter().position(|b| matches!(b, b'e' | b'E')) {
2663        Some(at) => (&rest[..at], Some(&rest[at + 1..])),
2664        None => (rest, None),
2665    };
2666
2667    let mut seen_digit = false;
2668    let mut seen_dot = false;
2669    for &b in mantissa {
2670        match b {
2671            b'0'..=b'9' => seen_digit = true,
2672            b'.' if !seen_dot => seen_dot = true,
2673            _ => return false,
2674        }
2675    }
2676    if !seen_digit {
2677        return false;
2678    }
2679
2680    match exponent {
2681        None => true,
2682        // An exponent needs digits of its own, and may carry a sign.
2683        Some(exp) => {
2684            let digits = exp
2685                .strip_prefix(b"+")
2686                .or_else(|| exp.strip_prefix(b"-"))
2687                .unwrap_or(exp);
2688            !digits.is_empty() && digits.iter().all(|b| b.is_ascii_digit())
2689        }
2690    }
2691}
2692
2693fn validate_bool_value<'t, 'v>(
2694    flag: &'t Flag<'t>,
2695    value: Option<&'v [u8]>,
2696) -> Result<Option<&'v [u8]>, Error<'t, 'v>> {
2697    match value {
2698        None | Some(b"true" | b"false") => Ok(value),
2699        Some(_) => Err(Error::InvalidChoice {
2700            name: flag.name,
2701            choices: &["true", "false"],
2702        }),
2703    }
2704}
2705
2706#[cfg(test)]
2707mod tests {
2708    use super::*;
2709
2710    #[test]
2711    fn clause_separator_resets_inner_args_and_survives_automatic_mode() {
2712        static TASK: Arg = Arg {
2713            key: 91,
2714            name: "task",
2715            ..Arg::REQUIRED
2716        };
2717        static REST: Arg = Arg {
2718            key: 92,
2719            name: "args",
2720            double_dash: DoubleDash::Automatic,
2721            ..Arg::VAR
2722        };
2723        static ROOT: Command = Command {
2724            name: "ex",
2725            clause: Some(Clause {
2726                key: 90,
2727                name: "tasks",
2728                separator: b":::",
2729                args: &[&TASK, &REST],
2730            }),
2731            ..Command::EMPTY
2732        };
2733        let argv = [
2734            OsStr::new("lint"),
2735            OsStr::new("--fix"),
2736            OsStr::new(":::"),
2737            OsStr::new("test"),
2738        ];
2739        let mut parser = Parser::new(&ROOT, &argv);
2740        let mut seen = Vec::new();
2741        while let Some(event) = parser.next_event() {
2742            match event.expect("valid clause") {
2743                Event::Arg { arg, value, .. } => {
2744                    seen.push((arg.name, String::from_utf8_lossy(value).into_owned()))
2745                }
2746                Event::ClauseSeparator { clause } => seen.push((clause.name, ":::".into())),
2747                _ => {}
2748            }
2749        }
2750        assert_eq!(
2751            seen,
2752            [
2753                ("task", "lint".into()),
2754                ("args", "--fix".into()),
2755                ("tasks", ":::".into()),
2756                ("task", "test".into())
2757            ]
2758        );
2759    }
2760
2761    static FORCE: Flag = Flag {
2762        key: 1,
2763        longs: &["force"],
2764        shorts: b"f",
2765        ..Flag::BOOL
2766    };
2767    static EXPLICIT_BOOL: Flag = Flag {
2768        key: 20,
2769        name: "color",
2770        longs: &["color"],
2771        negate: Some("no-color"),
2772        bool_value: true,
2773        ..Flag::BOOL
2774    };
2775    static EXPLICIT_BOOL_ROOT: Command = Command {
2776        name: "ex",
2777        flags: &[&EXPLICIT_BOOL],
2778        ..Command::EMPTY
2779    };
2780    static JOBS: Flag = Flag {
2781        key: 2,
2782        longs: &["jobs"],
2783        shorts: b"j",
2784        allow_negative_numbers: true,
2785        ..Flag::VALUE
2786    };
2787    static COLOR: Flag = Flag {
2788        key: 3,
2789        longs: &["color"],
2790        negate: Some("no-color"),
2791        ..Flag::BOOL
2792    };
2793    static VERBOSE: Flag = Flag {
2794        key: 4,
2795        longs: &["verbose"],
2796        shorts: b"v",
2797        global: true,
2798        ..Flag::BOOL
2799    };
2800    static FILE: Arg = Arg {
2801        key: 10,
2802        name: "file",
2803        allow_negative_numbers: true,
2804        ..Arg::REQUIRED
2805    };
2806    static REST: Arg = Arg {
2807        key: 11,
2808        name: "rest",
2809        ..Arg::VAR
2810    };
2811    static INSTALL: Command = Command {
2812        name: "install",
2813        aliases: &["i"],
2814        flags: &[&FORCE],
2815        key: 100,
2816        ..Command::EMPTY
2817    };
2818    /// Same shape as ROOT, but a CLI that owns all of its flags. The subcommand says
2819    /// nothing and inherits it, which is the point: only the root declares the mode.
2820    static STRICT_INSTALL: Command = Command {
2821        name: "install",
2822        aliases: &["i"],
2823        flags: &[&FORCE],
2824        key: 100,
2825        ..Command::EMPTY
2826    };
2827    static STRICT: Command = Command {
2828        name: "ex",
2829        flags: &[&FORCE, &JOBS, &COLOR, &VERBOSE],
2830        args: &[&FILE, &REST],
2831        subcommands: &[&STRICT_INSTALL],
2832        unknown_flags: Some(UnknownFlags::Error),
2833        ..Command::EMPTY
2834    };
2835    static ROOT: Command = Command {
2836        name: "ex",
2837        flags: &[&FORCE, &JOBS, &COLOR, &VERBOSE],
2838        args: &[&FILE, &REST],
2839        subcommands: &[&INSTALL],
2840        ..Command::EMPTY
2841    };
2842    static ARGUMENT_CONFLICT: Command = Command {
2843        name: "ex",
2844        flags: &[&FORCE],
2845        subcommands: &[&INSTALL],
2846        args_conflicts_with_subcommands: true,
2847        ..Command::EMPTY
2848    };
2849
2850    // A CLI shaped exactly like mise's root: a default subcommand, a positional of its own,
2851    // and a subcommand under the default — which is the arrangement that tells routing from
2852    // a plain positional.
2853    static TASK: Arg = Arg {
2854        key: 20,
2855        name: "task",
2856        ..Arg::REQUIRED
2857    };
2858    static RUN_TASK: Arg = Arg {
2859        key: 21,
2860        name: "run_task",
2861        ..Arg::REQUIRED
2862    };
2863    static DEEP: Command = Command {
2864        name: "deep",
2865        args: &[&RUN_TASK],
2866        key: 203,
2867        ..Command::EMPTY
2868    };
2869    static LINT: Command = Command {
2870        name: "lint",
2871        subcommands: &[&DEEP],
2872        // A default of its own, so that a parse which forgot it had already taken one would
2873        // have somewhere to go. Nothing else in these fixtures can show the latch working.
2874        default_subcommand: Some(&DEEP),
2875        key: 202,
2876        ..Command::EMPTY
2877    };
2878    static RUN: Command = Command {
2879        name: "run",
2880        args: &[&RUN_TASK],
2881        subcommands: &[&LINT],
2882        key: 200,
2883        ..Command::EMPTY
2884    };
2885    static DEFAULTING: Command = Command {
2886        name: "mise",
2887        flags: &[&VERBOSE],
2888        args: &[&TASK],
2889        subcommands: &[&RUN, &INSTALL],
2890        default_subcommand: Some(find_subcommand(&[&RUN, &INSTALL], "run")),
2891        ..Command::EMPTY
2892    };
2893
2894    /// Collect every event, or the first error.
2895    fn parse<'t: 'v, 'v>(
2896        root: &'t Command<'t>,
2897        argv: &'v [&'v OsStr],
2898    ) -> Result<Vec<Event<'t, 'v, 'v>>, Error<'t, 'v>> {
2899        let mut parser = Parser::new(root, argv);
2900        let mut events = Vec::new();
2901        while let Some(event) = parser.next_event() {
2902            events.push(event?);
2903        }
2904        Ok(events)
2905    }
2906
2907    fn argv<const N: usize>(tokens: [&str; N]) -> [&OsStr; N] {
2908        tokens.map(OsStr::new)
2909    }
2910
2911    #[test]
2912    fn long_boolean() {
2913        let a = argv(["--force"]);
2914        assert_eq!(
2915            parse(&ROOT, &a).unwrap(),
2916            vec![Event::Flag {
2917                flag: &FORCE,
2918                value: None,
2919                negated: false
2920            }]
2921        );
2922    }
2923
2924    #[test]
2925    fn long_boolean_accepts_only_opted_in_attached_values() {
2926        for (token, negated, value) in [
2927            ("--color=false", false, b"false".as_slice()),
2928            ("--color=true", false, b"true".as_slice()),
2929            ("--no-color=false", true, b"false".as_slice()),
2930        ] {
2931            let a = argv([token]);
2932            assert_eq!(
2933                parse(&EXPLICIT_BOOL_ROOT, &a).unwrap(),
2934                vec![Event::Flag {
2935                    flag: &EXPLICIT_BOOL,
2936                    value: Some(value),
2937                    negated,
2938                }]
2939            );
2940        }
2941
2942        let a = argv(["--color=maybe"]);
2943        assert!(matches!(
2944            parse(&EXPLICIT_BOOL_ROOT, &a),
2945            Err(Error::InvalidChoice { name: "color", .. })
2946        ));
2947
2948        let a = argv(["--force=false"]);
2949        assert_eq!(
2950            parse(&ROOT, &a).unwrap(),
2951            vec![Event::Flag {
2952                flag: &FORCE,
2953                value: None,
2954                negated: false,
2955            }]
2956        );
2957    }
2958
2959    #[test]
2960    fn long_value_forms() {
2961        for tokens in [vec!["--jobs=8"], vec!["--jobs", "8"]] {
2962            let a: Vec<&OsStr> = tokens.iter().map(|t| OsStr::new(*t)).collect();
2963            assert_eq!(
2964                parse(&ROOT, &a).unwrap(),
2965                vec![Event::Flag {
2966                    flag: &JOBS,
2967                    value: Some(b"8"),
2968                    negated: false
2969                }],
2970                "{tokens:?}"
2971            );
2972        }
2973    }
2974
2975    #[test]
2976    fn long_value_keeps_later_equals() {
2977        let a = argv(["--jobs=a=b"]);
2978        let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else {
2979            panic!("expected a flag");
2980        };
2981        assert_eq!(value, Some(&b"a=b"[..]));
2982    }
2983
2984    #[test]
2985    fn long_value_attached_empty_is_empty_not_absent() {
2986        let a = argv(["--jobs="]);
2987        let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else {
2988            panic!("expected a flag");
2989        };
2990        assert_eq!(value, Some(&b""[..]));
2991    }
2992
2993    #[test]
2994    fn long_value_refuses_flaglike_next_word() {
2995        let a = argv(["--jobs", "--force"]);
2996        assert_eq!(
2997            parse(&ROOT, &a),
2998            Err(Error::MissingFlagValue { flag: &JOBS })
2999        );
3000    }
3001
3002    #[test]
3003    fn long_value_accepts_negative_number() {
3004        let a = argv(["--jobs", "-1"]);
3005        let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else {
3006            panic!("expected a flag");
3007        };
3008        assert_eq!(value, Some(&b"-1"[..]));
3009    }
3010
3011    #[test]
3012    fn missing_optional_positional_reserves_the_last_word() {
3013        static OPTIONAL: Arg = Arg {
3014            key: 90,
3015            name: "optional",
3016            required: false,
3017            ..Arg::REQUIRED
3018        };
3019        static REQUIRED: Arg = Arg {
3020            key: 91,
3021            name: "required",
3022            ..Arg::REQUIRED
3023        };
3024        static CMD: Command = Command {
3025            name: "ex",
3026            args: &[&OPTIONAL, &REQUIRED],
3027            allow_missing_positional: true,
3028            ..Command::EMPTY
3029        };
3030
3031        let one = argv(["value"]);
3032        assert_eq!(
3033            parse(&CMD, &one).unwrap(),
3034            vec![Event::Arg {
3035                arg: &REQUIRED,
3036                value: b"value",
3037                delimit: true
3038            }]
3039        );
3040        let two = argv(["optional", "required"]);
3041        assert_eq!(
3042            parse(&CMD, &two).unwrap(),
3043            vec![
3044                Event::Arg {
3045                    arg: &OPTIONAL,
3046                    value: b"optional",
3047                    delimit: true
3048                },
3049                Event::Arg {
3050                    arg: &REQUIRED,
3051                    value: b"required",
3052                    delimit: true
3053                },
3054            ]
3055        );
3056    }
3057
3058    #[test]
3059    fn negative_numbers_are_narrowly_opted_in() {
3060        static PLAIN: Flag = Flag {
3061            key: 90,
3062            name: "plain",
3063            longs: &["plain"],
3064            ..Flag::VALUE
3065        };
3066        static VALUE: Arg = Arg {
3067            key: 91,
3068            name: "value",
3069            ..Arg::REQUIRED
3070        };
3071        static CMD: Command = Command {
3072            name: "ex",
3073            flags: &[&PLAIN],
3074            args: &[&VALUE],
3075            unknown_flags: Some(UnknownFlags::Error),
3076            ..Command::EMPTY
3077        };
3078
3079        let flag = argv(["--plain", "-1"]);
3080        assert_eq!(
3081            parse(&CMD, &flag),
3082            Err(Error::MissingFlagValue { flag: &PLAIN })
3083        );
3084        let positional = argv(["-1"]);
3085        assert_eq!(
3086            parse(&CMD, &positional),
3087            Err(Error::UnknownFlag { token: b"-1" })
3088        );
3089    }
3090
3091    #[test]
3092    fn an_exact_declared_digit_short_outranks_a_negative_number() {
3093        static PRINT0: Flag = Flag {
3094            key: 92,
3095            name: "print0",
3096            shorts: b"0",
3097            ..Flag::BOOL
3098        };
3099        static VALUE: Arg = Arg {
3100            key: 93,
3101            name: "value",
3102            required: false,
3103            allow_negative_numbers: true,
3104            ..Arg::REQUIRED
3105        };
3106        static CMD: Command = Command {
3107            name: "fd",
3108            flags: &[&PRINT0],
3109            args: &[&VALUE],
3110            unknown_flags: Some(UnknownFlags::Error),
3111            ..Command::EMPTY
3112        };
3113
3114        assert_eq!(
3115            parse(&CMD, &argv(["-0"])),
3116            Ok(vec![Event::Flag {
3117                flag: &PRINT0,
3118                value: None,
3119                negated: false,
3120            }])
3121        );
3122        assert!(matches!(
3123            parse(&CMD, &argv(["-1"])),
3124            Ok(events) if matches!(events.as_slice(), [Event::Arg { value: b"-1", .. }])
3125        ));
3126    }
3127
3128    #[test]
3129    fn negation_of_value_flag_does_not_consume_a_value() {
3130        static MODE: Flag = Flag {
3131            key: 9,
3132            name: "mode",
3133            longs: &["mode"],
3134            negate: Some("no-mode"),
3135            ..Flag::VALUE
3136        };
3137        static NEGATED_VALUE: Command = Command {
3138            name: "ex",
3139            flags: &[&MODE],
3140            args: &[&FILE],
3141            ..Command::EMPTY
3142        };
3143
3144        let a = argv(["--no-mode", "input"]);
3145        assert_eq!(
3146            parse(&NEGATED_VALUE, &a).unwrap(),
3147            vec![
3148                Event::Flag {
3149                    flag: &MODE,
3150                    value: None,
3151                    negated: true
3152                },
3153                Event::Arg {
3154                    arg: &FILE,
3155                    value: b"input",
3156                    delimit: true,
3157                }
3158            ]
3159        );
3160    }
3161
3162    #[test]
3163    fn no_abbreviation() {
3164        // A prefix names no flag, so by default it is a value like any other word.
3165        let a = argv(["--forc"]);
3166        assert_eq!(
3167            parse(&ROOT, &a).unwrap(),
3168            vec![Event::Arg {
3169                arg: &FILE,
3170                value: b"--forc",
3171                delimit: true,
3172            }]
3173        );
3174
3175        // And a CLI that owns its flags hears about it, which is the whole reason
3176        // the strict mode exists.
3177        assert!(matches!(
3178            parse(&STRICT, &a),
3179            Err(Error::UnknownFlag { token: b"--forc" })
3180        ));
3181    }
3182
3183    #[test]
3184    fn an_unknown_flag_is_a_value_by_default() {
3185        // The default, and the case it is for: a command line being forwarded to
3186        // something whose flags this spec does not know.
3187        let a = argv(["--wat", "keep"]);
3188        assert_eq!(
3189            parse(&ROOT, &a).unwrap(),
3190            vec![
3191                Event::Arg {
3192                    arg: &FILE,
3193                    value: b"--wat",
3194                    delimit: true,
3195                },
3196                Event::Arg {
3197                    arg: &REST,
3198                    value: b"keep",
3199                    delimit: true,
3200                },
3201            ]
3202        );
3203
3204        // With nowhere to put it, it is an unexpected argument — the same error an
3205        // extra word gets, rather than a special one about flags.
3206        static ONE: Command = Command {
3207            name: "ex",
3208            args: &[&FILE],
3209            ..Command::EMPTY
3210        };
3211        let a = argv(["a", "--wat"]);
3212        assert_eq!(
3213            parse(&ONE, &a),
3214            Err(Error::UnexpectedArg { token: b"--wat" })
3215        );
3216    }
3217
3218    #[test]
3219    fn negation() {
3220        let a = argv(["--no-color"]);
3221        assert_eq!(
3222            parse(&ROOT, &a).unwrap(),
3223            vec![Event::Flag {
3224                flag: &COLOR,
3225                value: None,
3226                negated: true
3227            }]
3228        );
3229    }
3230
3231    #[test]
3232    fn short_bundle_and_attached_value() {
3233        let a = argv(["-fj8"]);
3234        assert_eq!(
3235            parse(&ROOT, &a).unwrap(),
3236            vec![
3237                Event::Flag {
3238                    flag: &FORCE,
3239                    value: None,
3240                    negated: false
3241                },
3242                Event::Flag {
3243                    flag: &JOBS,
3244                    value: Some(b"8"),
3245                    negated: false
3246                },
3247            ]
3248        );
3249    }
3250
3251    #[test]
3252    fn short_value_strips_one_equals() {
3253        for (tokens, want) in [(["-j=8"], &b"8"[..]), (["-j==8"], &b"=8"[..])] {
3254            let a = argv(tokens);
3255            let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else {
3256                panic!("expected a flag");
3257            };
3258            assert_eq!(value, Some(want), "{tokens:?}");
3259        }
3260    }
3261
3262    #[test]
3263    fn bare_dash_is_a_value() {
3264        let a = argv(["-"]);
3265        assert_eq!(
3266            parse(&ROOT, &a).unwrap(),
3267            vec![Event::Arg {
3268                arg: &FILE,
3269                value: b"-",
3270                delimit: true,
3271            }]
3272        );
3273    }
3274
3275    #[test]
3276    fn positionals_then_variadic() {
3277        let a = argv(["one", "two", "three"]);
3278        assert_eq!(
3279            parse(&ROOT, &a).unwrap(),
3280            vec![
3281                Event::Arg {
3282                    arg: &FILE,
3283                    value: b"one",
3284                    delimit: true,
3285                },
3286                Event::Arg {
3287                    arg: &REST,
3288                    value: b"two",
3289                    delimit: true,
3290                },
3291                Event::Arg {
3292                    arg: &REST,
3293                    value: b"three",
3294                    delimit: true,
3295                },
3296            ]
3297        );
3298    }
3299
3300    #[test]
3301    fn subcommand_and_alias_route_the_same() {
3302        for token in ["install", "i"] {
3303            let a = argv([token]);
3304            assert_eq!(
3305                parse(&ROOT, &a).unwrap(),
3306                vec![Event::Command(&INSTALL)],
3307                "{token}"
3308            );
3309        }
3310    }
3311
3312    #[test]
3313    fn a_parent_argument_can_exclude_a_later_subcommand() {
3314        let a = argv(["--force", "install"]);
3315        assert!(matches!(
3316            parse(&ARGUMENT_CONFLICT, &a),
3317            Err(Error::SubcommandConflict { subcommand }) if subcommand.name == "install"
3318        ));
3319    }
3320
3321    #[test]
3322    fn subcommand_only_routes_before_a_positional_is_filled() {
3323        let a = argv(["other", "install"]);
3324        assert_eq!(
3325            parse(&ROOT, &a).unwrap(),
3326            vec![
3327                Event::Arg {
3328                    arg: &FILE,
3329                    value: b"other",
3330                    delimit: true,
3331                },
3332                Event::Arg {
3333                    arg: &REST,
3334                    value: b"install",
3335                    delimit: true,
3336                },
3337            ]
3338        );
3339    }
3340
3341    #[test]
3342    fn a_word_naming_no_subcommand_goes_to_the_default_one() {
3343        // usage-lib's answer, which this reproduces: `mise build` comes back as commands
3344        // `["mise", "run"]` with the word bound to *run's* argument — not to `mise`'s own
3345        // `[TASK]`, which is what makes this more than a synonym for a positional.
3346        let a = argv(["build"]);
3347        assert_eq!(
3348            parse(&DEFAULTING, &a).unwrap(),
3349            vec![
3350                Event::Command(&RUN),
3351                Event::Arg {
3352                    arg: &RUN_TASK,
3353                    value: b"build",
3354                    delimit: true,
3355                },
3356            ]
3357        );
3358    }
3359
3360    // A shared table, as a flattened struct's would be. Declared outside the tests so both
3361    // can splice it, which is the arrangement it exists to model.
3362    static SHARED_QUIET: Flag = Flag {
3363        key: 300,
3364        name: "quiet",
3365        longs: &["quiet"],
3366        ..Flag::BOOL
3367    };
3368    static SHARED_FLAGS: &[&Flag] = &[&SHARED_QUIET];
3369    static SHARED_WHAT: Arg = Arg {
3370        key: 301,
3371        name: "what",
3372        ..Arg::REQUIRED
3373    };
3374    static SHARED_ARGS: &[&Arg] = &[&SHARED_WHAT];
3375
3376    #[test]
3377    fn concatenating_tables_keeps_the_order_they_were_given_in() {
3378        // The property positional arguments depend on: a flattened group lands where the
3379        // field was written, not at the end. `[&FILE], SHARED, [&REST]` has to stay in that
3380        // order or `ex a b c` binds the wrong words.
3381        const ARGS: &[&[&Arg]] = &[&[&FILE], SHARED_ARGS, &[&REST]];
3382        static TABLE: [&Arg; table_len(ARGS)] = concat_args(ARGS);
3383        assert_eq!(
3384            TABLE.iter().map(|a| a.name).collect::<Vec<_>>(),
3385            ["file", "what", "rest"]
3386        );
3387
3388        // Empty groups contribute nothing and disturb nothing, which is what lets the derive
3389        // emit a group per field without checking whether it is empty first.
3390        const WITH_GAPS: &[&[&Flag]] = &[&[], &[&FORCE], &[], SHARED_FLAGS, &[]];
3391        static FLAGS: [&Flag; table_len(WITH_GAPS)] = concat_flags(WITH_GAPS);
3392        // By long form: these fixtures do not all set `name`, and the placeholder's is also
3393        // empty — so comparing names could not tell a real entry from a leftover slot.
3394        assert_eq!(
3395            FLAGS.iter().map(|f| f.longs).collect::<Vec<_>>(),
3396            [&["force"], &["quiet"]]
3397        );
3398    }
3399
3400    #[test]
3401    fn a_concatenated_table_parses_like_a_declared_one() {
3402        // The point of doing this at compile time: what the parser walks is one flat slice,
3403        // indistinguishable from a command that declared everything itself.
3404        const FLAG_GROUPS: &[&[&Flag]] = &[&[&FORCE], SHARED_FLAGS];
3405        const ARG_GROUPS: &[&[&Arg]] = &[SHARED_ARGS, &[&REST]];
3406        static FLAGS: [&Flag; table_len(FLAG_GROUPS)] = concat_flags(FLAG_GROUPS);
3407        static ARGS: [&Arg; table_len(ARG_GROUPS)] = concat_args(ARG_GROUPS);
3408        static JOINED: Command = Command {
3409            name: "joined",
3410            flags: &FLAGS,
3411            args: &ARGS,
3412            ..Command::EMPTY
3413        };
3414
3415        let a = argv(["--quiet", "one", "two", "--force"]);
3416        assert_eq!(
3417            parse(&JOINED, &a).unwrap(),
3418            vec![
3419                Event::Flag {
3420                    flag: &SHARED_QUIET,
3421                    value: None,
3422                    negated: false
3423                },
3424                Event::Arg {
3425                    arg: &SHARED_WHAT,
3426                    value: b"one",
3427                    delimit: true,
3428                },
3429                Event::Arg {
3430                    arg: &REST,
3431                    value: b"two",
3432                    delimit: true,
3433                },
3434                Event::Flag {
3435                    flag: &FORCE,
3436                    value: None,
3437                    negated: false
3438                },
3439            ]
3440        );
3441    }
3442
3443    #[test]
3444    fn an_unknown_flag_is_not_routed() {
3445        // A dash-prefixed token that names no flag becomes a value here (the default for
3446        // `unknown_flags`), and it must not thereby become a *subcommand* word: usage-lib
3447        // stops looking for subcommands at an unrecognised flag, and binds it to the command
3448        // still in scope. Verified against usage-lib, where `ex --wat` comes back as commands
3449        // `["ex"]` with `ROOT_TASK = "--wat"`.
3450        for token in ["--wat", "-x"] {
3451            let a = argv([token]);
3452            assert_eq!(
3453                parse(&DEFAULTING, &a).unwrap(),
3454                vec![Event::Arg {
3455                    arg: &TASK,
3456                    value: token.as_bytes(),
3457                    delimit: true,
3458                }],
3459                "{token} should bind where it was typed, not in the default subcommand"
3460            );
3461        }
3462    }
3463
3464    #[test]
3465    fn a_named_subcommand_is_not_routed() {
3466        // The default is for words that name nothing. A word that names a sibling still
3467        // selects it, and the root's own argument is still reachable behind one.
3468        let a = argv(["install"]);
3469        assert_eq!(
3470            parse(&DEFAULTING, &a).unwrap(),
3471            vec![Event::Command(&INSTALL)]
3472        );
3473    }
3474
3475    #[test]
3476    fn an_unmatched_word_is_forwarded_when_external_subcommand_is_set() {
3477        static CATCH: Command = Command {
3478            name: "ex",
3479            flags: &[&VERBOSE],
3480            subcommands: &[&INSTALL],
3481            external_subcommand: true,
3482            unknown_flags: Some(UnknownFlags::Error),
3483            ..Command::EMPTY
3484        };
3485        let a = argv(["foo", "--help", "bar"]);
3486        assert_eq!(
3487            parse(&CATCH, &a).unwrap(),
3488            vec![Event::External { values: &a[..] }]
3489        );
3490
3491        let a = argv(["install"]);
3492        assert_eq!(parse(&CATCH, &a).unwrap(), vec![Event::Command(&INSTALL)]);
3493
3494        let a = argv(["--verbose", "foo", "--verbose"]);
3495        assert_eq!(
3496            parse(&CATCH, &a).unwrap(),
3497            vec![
3498                Event::Flag {
3499                    flag: &VERBOSE,
3500                    value: None,
3501                    negated: false
3502                },
3503                Event::External { values: &a[1..] }
3504            ]
3505        );
3506
3507        let a = argv(["--wat"]);
3508        assert_eq!(
3509            parse(&CATCH, &a),
3510            Err(Error::UnknownFlag { token: b"--wat" })
3511        );
3512
3513        // A negative number is a value, not a flag, so it can be the unmatched word.
3514        let a = argv(["-1", "rest"]);
3515        assert_eq!(
3516            parse(&CATCH, &a).unwrap(),
3517            vec![Event::External { values: &a[..] }]
3518        );
3519    }
3520
3521    #[test]
3522    fn a_default_subcommand_outranks_an_external_one() {
3523        static CATCH_DEFAULT: Command = Command {
3524            name: "ex",
3525            subcommands: &[&RUN],
3526            default_subcommand: Some(&RUN),
3527            external_subcommand: true,
3528            ..Command::EMPTY
3529        };
3530        let a = argv(["build"]);
3531        assert_eq!(
3532            parse(&CATCH_DEFAULT, &a).unwrap(),
3533            vec![
3534                Event::Command(&RUN),
3535                Event::Arg {
3536                    arg: &RUN_TASK,
3537                    value: b"build",
3538                    delimit: true,
3539                }
3540            ]
3541        );
3542    }
3543
3544    #[test]
3545    fn a_default_subcommand_starts_at_the_word_it_receives() {
3546        let a = argv(["build"]);
3547        let mut parser = Parser::new(&DEFAULTING, &a);
3548        assert_eq!(parser.next_event(), Some(Ok(Event::Command(&RUN))));
3549        assert_eq!(parser.command_start(), 0);
3550        assert_eq!(
3551            parser.next_event(),
3552            Some(Ok(Event::Arg {
3553                arg: &RUN_TASK,
3554                value: b"build",
3555                delimit: true,
3556            }))
3557        );
3558    }
3559
3560    #[test]
3561    fn the_default_can_be_named_by_an_alias() {
3562        // usage-lib resolves the name against subcommand names, aliases and hidden aliases
3563        // alike, so a spec may point `default_subcommand` at any of them.
3564        static BY_ALIAS: Command = Command {
3565            name: "mise",
3566            args: &[&TASK],
3567            subcommands: &[&INSTALL],
3568            // `INSTALL` answers to "i" as well as to its name.
3569            default_subcommand: Some(find_subcommand(&[&INSTALL], "i")),
3570            ..Command::EMPTY
3571        };
3572        assert!(::core::ptr::eq(
3573            BY_ALIAS.default_subcommand.expect("declared"),
3574            &INSTALL
3575        ));
3576    }
3577
3578    #[test]
3579    fn a_name_outranks_another_commands_alias() {
3580        // A spec `assert_unique_subcommand_names` would reject, resolved anyway: a parser
3581        // handed a table nothing validated still has to answer, and the answer is the
3582        // command whose own name it is. Both orders, because taking the first candidate
3583        // that matched on either name or alias made this depend on which was listed first
3584        // — and usage-lib, building a map, took the last.
3585        static ALPHA: Command = Command {
3586            name: "alpha",
3587            aliases: &["run"],
3588            key: 300,
3589            ..Command::EMPTY
3590        };
3591        static PLAIN_RUN: Command = Command {
3592            name: "run",
3593            key: 301,
3594            ..Command::EMPTY
3595        };
3596        for subcommands in [&[&ALPHA, &PLAIN_RUN] as &[&Command], &[&PLAIN_RUN, &ALPHA]] {
3597            assert!(::core::ptr::eq(
3598                find_subcommand(subcommands, "run"),
3599                &PLAIN_RUN
3600            ));
3601            let root: Command = Command {
3602                name: "ex",
3603                subcommands,
3604                ..Command::EMPTY
3605            };
3606            let a = argv(["run"]);
3607            assert_eq!(parse(&root, &a).unwrap(), vec![Event::Command(&PLAIN_RUN)]);
3608            // The alias still reaches its own command by every name it does not share.
3609            let a = argv(["alpha"]);
3610            assert_eq!(parse(&root, &a).unwrap(), vec![Event::Command(&ALPHA)]);
3611            // `ex help run` asks about the command `ex run` selects. These are separate
3612            // lookups — help resolves a path without descending — and answering differently
3613            // for a colliding word is the divergence this rule exists to end.
3614            let a = argv(["help", "run"]);
3615            match parse(&root, &a) {
3616                Err(Error::Help { cmd, .. }) => {
3617                    assert!(
3618                        ::core::ptr::eq(cmd, &PLAIN_RUN),
3619                        "got help for {}",
3620                        cmd.name
3621                    )
3622                }
3623                other => panic!("expected a help request, got {other:?}"),
3624            }
3625        }
3626    }
3627
3628    #[test]
3629    #[should_panic(expected = "two subcommands answer to the same name")]
3630    fn an_alias_cannot_shadow_a_sibling_command() {
3631        static ADD: Command = Command {
3632            name: "add",
3633            aliases: &["install"],
3634            ..Command::EMPTY
3635        };
3636        assert_unique_subcommand_names(&[&INSTALL, &ADD]);
3637    }
3638
3639    #[test]
3640    fn the_word_is_re_examined_against_the_command_it_reached() {
3641        // The reason the cursor steps back rather than the token being consumed: `lint` names
3642        // nothing at the root, and once inside `run` it names a subcommand. mise's mounted
3643        // task names arrive exactly this way.
3644        let a = argv(["lint"]);
3645        assert_eq!(
3646            parse(&DEFAULTING, &a).unwrap(),
3647            vec![Event::Command(&RUN), Event::Command(&LINT)]
3648        );
3649    }
3650
3651    #[test]
3652    fn the_default_is_taken_at_most_once_per_parse() {
3653        // usage-lib latches this for the whole parse rather than per command, and the shape
3654        // that shows the difference needs two of them: `lint` routes through `run`, and `lint`
3655        // declares a default too. A second word there would descend again — walking a CLI
3656        // deeper than anything the user typed — so the answer is that it does not.
3657        let a = argv(["lint", "zzz"]);
3658        assert_eq!(
3659            parse(&DEFAULTING, &a),
3660            Err(Error::UnexpectedArg { token: b"zzz" }),
3661            "the second word must not reach `deep`"
3662        );
3663
3664        // Reached explicitly, the same command still takes it: the latch bounds routing, not
3665        // the tree.
3666        let a = argv(["lint", "deep", "zzz"]);
3667        assert_eq!(
3668            parse(&DEFAULTING, &a).unwrap(),
3669            vec![
3670                Event::Command(&RUN),
3671                Event::Command(&LINT),
3672                Event::Command(&DEEP),
3673                Event::Arg {
3674                    arg: &RUN_TASK,
3675                    value: b"zzz",
3676                    delimit: true,
3677                },
3678            ]
3679        );
3680    }
3681
3682    #[test]
3683    fn a_flag_before_the_word_still_belongs_to_the_root() {
3684        // Routing happens at the word, so anything typed before it was addressed to the
3685        // command the user was actually at.
3686        let a = argv(["--verbose", "build"]);
3687        assert_eq!(
3688            parse(&DEFAULTING, &a).unwrap(),
3689            vec![
3690                Event::Flag {
3691                    flag: &VERBOSE,
3692                    value: None,
3693                    negated: false
3694                },
3695                Event::Command(&RUN),
3696                Event::Arg {
3697                    arg: &RUN_TASK,
3698                    value: b"build",
3699                    delimit: true,
3700                },
3701            ]
3702        );
3703    }
3704
3705    #[test]
3706    fn nothing_routes_after_the_separator() {
3707        // Past `--` there are no subcommands left to select, so there is no default to reach
3708        // either: the words are values of whatever the command declares.
3709        let a = argv(["--", "build"]);
3710        assert_eq!(
3711            parse(&DEFAULTING, &a).unwrap(),
3712            vec![Event::Arg {
3713                arg: &TASK,
3714                value: b"build",
3715                delimit: true,
3716            }]
3717        );
3718    }
3719
3720    #[test]
3721    fn globals_are_inherited_but_plain_flags_are_not() {
3722        let a = argv(["install", "--verbose"]);
3723        assert_eq!(
3724            parse(&ROOT, &a).unwrap(),
3725            vec![
3726                Event::Command(&INSTALL),
3727                Event::Flag {
3728                    flag: &VERBOSE,
3729                    value: None,
3730                    negated: false
3731                }
3732            ]
3733        );
3734
3735        // `--jobs` belongs to the root and is not global, so it is not a flag here.
3736        // Strictly that is an unknown flag; leniently it is a word, and `install`
3737        // declares no argument to hold one — either way it is never read as the
3738        // root's flag, which is what this test is about.
3739        let a = argv(["install", "--jobs", "8"]);
3740        assert!(matches!(parse(&STRICT, &a), Err(Error::UnknownFlag { .. })));
3741        assert!(matches!(
3742            parse(&ROOT, &a),
3743            Err(Error::UnexpectedArg { token: b"--jobs" })
3744        ));
3745    }
3746
3747    #[test]
3748    fn double_dash_protects_flaglike_values() {
3749        let a = argv(["--", "--force", "-x"]);
3750        assert_eq!(
3751            parse(&ROOT, &a).unwrap(),
3752            vec![
3753                Event::Arg {
3754                    arg: &FILE,
3755                    value: b"--force",
3756                    delimit: true,
3757                },
3758                Event::Arg {
3759                    arg: &REST,
3760                    value: b"-x",
3761                    delimit: true,
3762                },
3763            ]
3764        );
3765    }
3766
3767    #[test]
3768    fn second_double_dash_is_a_value() {
3769        let a = argv(["--", "a", "--", "b"]);
3770        let values: Vec<&[u8]> = parse(&ROOT, &a)
3771            .unwrap()
3772            .iter()
3773            .filter_map(|e| match e {
3774                Event::Arg { value, .. } => Some(*value),
3775                _ => None,
3776            })
3777            .collect();
3778        assert_eq!(values, vec![&b"a"[..], &b"--"[..], &b"b"[..]]);
3779    }
3780
3781    #[test]
3782    fn allow_hyphen_values_takes_a_flaglike_detached_value() {
3783        static ARGS: Flag = Flag {
3784            key: 6,
3785            name: "args",
3786            longs: &["args"],
3787            shorts: b"a",
3788            takes_value: true,
3789            allow_hyphen_values: true,
3790            ..Flag::BOOL
3791        };
3792        static DIR: Flag = Flag {
3793            key: 7,
3794            name: "working-dir",
3795            longs: &["working-dir"],
3796            shorts: b"d",
3797            ..Flag::VALUE
3798        };
3799        static HYPHEN: Command = Command {
3800            name: "ex",
3801            flags: &[&ARGS, &DIR],
3802            args: &[&REST],
3803            ..Command::EMPTY
3804        };
3805
3806        let a = argv(["-a", "-destroy"]);
3807        assert_eq!(
3808            parse(&HYPHEN, &a).unwrap(),
3809            vec![Event::Flag {
3810                flag: &ARGS,
3811                value: Some(b"-destroy"),
3812                negated: false
3813            }]
3814        );
3815
3816        let a = argv(["--args", "--", "-x"]);
3817        assert_eq!(
3818            parse(&HYPHEN, &a).unwrap(),
3819            vec![
3820                Event::Flag {
3821                    flag: &ARGS,
3822                    value: Some(b"--"),
3823                    negated: false
3824                },
3825                Event::Arg {
3826                    arg: &REST,
3827                    value: b"-x",
3828                    delimit: true,
3829                },
3830            ]
3831        );
3832    }
3833
3834    #[test]
3835    fn require_equals_refuses_a_detached_value() {
3836        static INSPECT: Flag = Flag {
3837            key: 8,
3838            name: "inspect",
3839            longs: &["inspect"],
3840            shorts: b"i",
3841            takes_value: true,
3842            require_equals: true,
3843            ..Flag::BOOL
3844        };
3845        static EQ: Command = Command {
3846            name: "ex",
3847            flags: &[&INSPECT],
3848            ..Command::EMPTY
3849        };
3850
3851        let a = argv(["--inspect=9229"]);
3852        assert_eq!(
3853            parse(&EQ, &a).unwrap(),
3854            vec![Event::Flag {
3855                flag: &INSPECT,
3856                value: Some(b"9229"),
3857                negated: false
3858            }]
3859        );
3860
3861        let a = argv(["--inspect", "9229"]);
3862        assert!(matches!(
3863            parse(&EQ, &a),
3864            Err(Error::MissingFlagValue { .. })
3865        ));
3866
3867        let a = argv(["-i9229"]);
3868        assert_eq!(
3869            parse(&EQ, &a).unwrap(),
3870            vec![Event::Flag {
3871                flag: &INSPECT,
3872                value: Some(b"9229"),
3873                negated: false
3874            }]
3875        );
3876
3877        static ALL: Flag = Flag {
3878            key: 9,
3879            name: "all",
3880            longs: &["all"],
3881            shorts: b"a",
3882            ..Flag::BOOL
3883        };
3884        static BUNDLE: Command = Command {
3885            name: "ex",
3886            flags: &[&ALL, &INSPECT],
3887            ..Command::EMPTY
3888        };
3889        let a = argv(["-ai", "9229"]);
3890        assert!(
3891            matches!(parse(&BUNDLE, &a), Err(Error::MissingFlagValue { .. })),
3892            "a require_equals short reached through a bundle still refuses the following word"
3893        );
3894    }
3895
3896    #[test]
3897    fn default_missing_binds_when_the_value_is_left_off() {
3898        static COLOR: Flag = Flag {
3899            key: 9,
3900            name: "color",
3901            longs: &["color"],
3902            takes_value: true,
3903            default_missing: Some(b"always"),
3904            ..Flag::BOOL
3905        };
3906        static VERBOSE: Flag = Flag {
3907            key: 10,
3908            name: "verbose",
3909            longs: &["verbose"],
3910            ..Flag::BOOL
3911        };
3912        static MISSING: Command = Command {
3913            name: "ex",
3914            flags: &[&COLOR, &VERBOSE],
3915            ..Command::EMPTY
3916        };
3917
3918        let a = argv(["--color"]);
3919        assert_eq!(
3920            parse(&MISSING, &a).unwrap(),
3921            vec![Event::Flag {
3922                flag: &COLOR,
3923                value: Some(b"always"),
3924                negated: false
3925            }]
3926        );
3927
3928        let a = argv(["--color=never"]);
3929        assert_eq!(
3930            parse(&MISSING, &a).unwrap(),
3931            vec![Event::Flag {
3932                flag: &COLOR,
3933                value: Some(b"never"),
3934                negated: false
3935            }]
3936        );
3937
3938        let a = argv(["--color", "--verbose"]);
3939        assert_eq!(
3940            parse(&MISSING, &a).unwrap(),
3941            vec![
3942                Event::Flag {
3943                    flag: &COLOR,
3944                    value: Some(b"always"),
3945                    negated: false
3946                },
3947                Event::Flag {
3948                    flag: &VERBOSE,
3949                    value: None,
3950                    negated: false
3951                },
3952            ]
3953        );
3954
3955        let a = argv(["--color="]);
3956        assert_eq!(
3957            parse(&MISSING, &a).unwrap(),
3958            vec![Event::Flag {
3959                flag: &COLOR,
3960                value: Some(b""),
3961                negated: false
3962            }]
3963        );
3964    }
3965
3966    #[test]
3967    fn optional_flag_value_distinguishes_bare_and_explicit_forms() {
3968        static BUMP: Flag = Flag {
3969            key: 11,
3970            name: "bump",
3971            longs: &["bump"],
3972            takes_value: true,
3973            value_optional: true,
3974            ..Flag::BOOL
3975        };
3976        static OPTIONAL: Command = Command {
3977            name: "ex",
3978            flags: &[&BUMP],
3979            ..Command::EMPTY
3980        };
3981
3982        assert_eq!(parse(&OPTIONAL, &argv([])).unwrap(), vec![]);
3983        assert_eq!(
3984            parse(&OPTIONAL, &argv(["--bump"])).unwrap(),
3985            vec![Event::Flag {
3986                flag: &BUMP,
3987                value: None,
3988                negated: false,
3989            }]
3990        );
3991        assert_eq!(
3992            parse(&OPTIONAL, &argv(["--bump=5"])).unwrap(),
3993            vec![Event::Flag {
3994                flag: &BUMP,
3995                value: Some(b"5"),
3996                negated: false,
3997            }]
3998        );
3999
4000        static INCLUDE: Flag = Flag {
4001            key: 12,
4002            name: "include",
4003            longs: &["include"],
4004            takes_value: true,
4005            variadic: true,
4006            value_optional: true,
4007            ..Flag::BOOL
4008        };
4009        static VERBOSE: Flag = Flag {
4010            key: 13,
4011            name: "verbose",
4012            longs: &["verbose"],
4013            ..Flag::BOOL
4014        };
4015        static VARIADIC: Command = Command {
4016            name: "ex",
4017            flags: &[&INCLUDE, &VERBOSE],
4018            args: &[&REST],
4019            ..Command::EMPTY
4020        };
4021        assert_eq!(
4022            parse(&VARIADIC, &argv(["--include", "--verbose", "file"])).unwrap(),
4023            vec![
4024                Event::Flag {
4025                    flag: &INCLUDE,
4026                    value: None,
4027                    negated: false,
4028                },
4029                Event::Flag {
4030                    flag: &VERBOSE,
4031                    value: None,
4032                    negated: false,
4033                },
4034                Event::Arg {
4035                    arg: &REST,
4036                    value: b"file",
4037                    delimit: true,
4038                },
4039            ]
4040        );
4041    }
4042
4043    #[test]
4044    fn default_missing_with_require_equals_leaves_the_following_word() {
4045        static INSPECT: Flag = Flag {
4046            key: 11,
4047            name: "inspect",
4048            longs: &["inspect"],
4049            takes_value: true,
4050            require_equals: true,
4051            default_missing: Some(b"9229"),
4052            ..Flag::BOOL
4053        };
4054        static BOTH: Command = Command {
4055            name: "ex",
4056            flags: &[&INSPECT],
4057            args: &[&REST],
4058            ..Command::EMPTY
4059        };
4060
4061        let a = argv(["--inspect"]);
4062        assert_eq!(
4063            parse(&BOTH, &a).unwrap(),
4064            vec![Event::Flag {
4065                flag: &INSPECT,
4066                value: Some(b"9229"),
4067                negated: false
4068            }]
4069        );
4070
4071        let a = argv(["--inspect", "80"]);
4072        assert_eq!(
4073            parse(&BOTH, &a).unwrap(),
4074            vec![
4075                Event::Flag {
4076                    flag: &INSPECT,
4077                    value: Some(b"9229"),
4078                    negated: false
4079                },
4080                Event::Arg {
4081                    arg: &REST,
4082                    value: b"80",
4083                    delimit: true,
4084                },
4085            ]
4086        );
4087
4088        let a = argv(["--inspect="]);
4089        assert_eq!(
4090            parse(&BOTH, &a).unwrap(),
4091            vec![Event::Flag {
4092                flag: &INSPECT,
4093                value: Some(b""),
4094                negated: false
4095            }]
4096        );
4097    }
4098
4099    #[test]
4100    fn variadic_flag_collects_until_a_flaglike_token() {
4101        static INCLUDE: Flag = Flag {
4102            key: 5,
4103            name: "include",
4104            longs: &["include"],
4105            shorts: b"i",
4106            takes_value: true,
4107            variadic: true,
4108            ..Flag::BOOL
4109        };
4110        static GREEDY: Command = Command {
4111            name: "ex",
4112            flags: &[&INCLUDE, &FORCE],
4113            args: &[&FILE],
4114            ..Command::EMPTY
4115        };
4116
4117        let a = argv(["--include", "x", "y", "--force"]);
4118        assert_eq!(
4119            parse(&GREEDY, &a).unwrap(),
4120            vec![
4121                Event::Flag {
4122                    flag: &INCLUDE,
4123                    value: Some(b"x"),
4124                    negated: false
4125                },
4126                Event::Flag {
4127                    flag: &INCLUDE,
4128                    value: Some(b"y"),
4129                    negated: false
4130                },
4131                Event::Flag {
4132                    flag: &FORCE,
4133                    value: None,
4134                    negated: false
4135                },
4136            ]
4137        );
4138    }
4139
4140    #[test]
4141    fn value_terminators_end_variadic_owners_without_binding() {
4142        static INCLUDE: Flag = Flag {
4143            key: 92,
4144            name: "include",
4145            longs: &["include"],
4146            takes_value: true,
4147            variadic: true,
4148            value_terminator: Some(b";"),
4149            ..Flag::BOOL
4150        };
4151        static ITEMS: Arg = Arg {
4152            key: 93,
4153            name: "items",
4154            var: true,
4155            value_terminator: Some(b";"),
4156            ..Arg::REQUIRED
4157        };
4158        static AFTER: Arg = Arg {
4159            key: 94,
4160            name: "after",
4161            ..Arg::REQUIRED
4162        };
4163        static FLAG_CMD: Command = Command {
4164            name: "ex",
4165            flags: &[&INCLUDE],
4166            args: &[&AFTER],
4167            ..Command::EMPTY
4168        };
4169        static ARG_CMD: Command = Command {
4170            name: "ex",
4171            args: &[&ITEMS, &AFTER],
4172            ..Command::EMPTY
4173        };
4174
4175        let flag = argv(["--include", "a", ";", "tail"]);
4176        assert_eq!(
4177            parse(&FLAG_CMD, &flag).unwrap(),
4178            vec![
4179                Event::Flag {
4180                    flag: &INCLUDE,
4181                    value: Some(b"a"),
4182                    negated: false,
4183                },
4184                Event::Arg {
4185                    arg: &AFTER,
4186                    value: b"tail",
4187                    delimit: true,
4188                },
4189            ]
4190        );
4191
4192        let positional = argv(["a", ";", "tail"]);
4193        assert_eq!(
4194            parse(&ARG_CMD, &positional).unwrap(),
4195            vec![
4196                Event::Arg {
4197                    arg: &ITEMS,
4198                    value: b"a",
4199                    delimit: true,
4200                },
4201                Event::Arg {
4202                    arg: &AFTER,
4203                    value: b"tail",
4204                    delimit: true,
4205                },
4206            ]
4207        );
4208    }
4209
4210    #[test]
4211    fn a_non_variadic_flag_leaves_the_next_word_alone() {
4212        // The counterpart to the test above: a flag that takes one value must not
4213        // swallow the word after it, which would silently steal a positional.
4214        let a = argv(["--jobs", "8", "keep-me"]);
4215        assert_eq!(
4216            parse(&ROOT, &a).unwrap(),
4217            vec![
4218                Event::Flag {
4219                    flag: &JOBS,
4220                    value: Some(b"8"),
4221                    negated: false
4222                },
4223                Event::Arg {
4224                    arg: &FILE,
4225                    value: b"keep-me",
4226                    delimit: true,
4227                },
4228            ]
4229        );
4230    }
4231
4232    #[test]
4233    fn double_dash_seen_means_a_separator_was_typed() {
4234        static FILES: Arg = Arg {
4235            key: 23,
4236            name: "files",
4237            double_dash: DoubleDash::Automatic,
4238            ..Arg::VAR
4239        };
4240        static AUTO: Command = Command {
4241            name: "ex",
4242            flags: &[&FORCE],
4243            args: &[&FILES],
4244            ..Command::EMPTY
4245        };
4246
4247        let a = argv(["--", "x"]);
4248        let mut parser = Parser::new(&ROOT, &a);
4249        while parser.next_event().is_some() {}
4250        assert!(parser.double_dash_seen(), "a real separator was consumed");
4251
4252        // `automatic` stops flag interpretation without a separator being typed,
4253        // and reporting one would be a lie to any caller that forwards argv.
4254        let a = argv(["x", "--force"]);
4255        let mut parser = Parser::new(&AUTO, &a);
4256        while parser.next_event().is_some() {}
4257        assert!(
4258            !parser.double_dash_seen(),
4259            "automatic mode must not claim a separator was given"
4260        );
4261    }
4262
4263    #[test]
4264    fn a_wrapper_still_forwards_a_help_flag() {
4265        // Supplying `--help` must not take the two forwarding mechanisms away from a wrapper,
4266        // which is the one place a CLI means to hand the token on rather than answer it.
4267        static ARGS: Arg = Arg {
4268            key: 24,
4269            name: "args",
4270            ..Arg::VAR
4271        };
4272        static WRAP: Command = Command {
4273            name: "wrap",
4274            args: &[&ARGS],
4275            ..Command::EMPTY
4276        };
4277
4278        // A typed separator: everything after it is a value, `--help` included.
4279        let a = argv(["--", "--help", "-h"]);
4280        assert_eq!(
4281            parse(&WRAP, &a).unwrap(),
4282            vec![
4283                Event::Arg {
4284                    arg: &ARGS,
4285                    value: b"--help",
4286                    delimit: true,
4287                },
4288                Event::Arg {
4289                    arg: &ARGS,
4290                    value: b"-h",
4291                    delimit: true,
4292                },
4293            ]
4294        );
4295
4296        // And `automatic`, for the wrapper whose caller should not have to type one: the
4297        // first value stops flag interpretation, so the flags after it forward.
4298        static AUTO_ARGS: Arg = Arg {
4299            key: 25,
4300            name: "args",
4301            double_dash: DoubleDash::Automatic,
4302            ..Arg::VAR
4303        };
4304        static AUTO_WRAP: Command = Command {
4305            name: "wrap",
4306            args: &[&AUTO_ARGS],
4307            ..Command::EMPTY
4308        };
4309
4310        let a = argv(["node", "--help"]);
4311        assert_eq!(
4312            parse(&AUTO_WRAP, &a).unwrap(),
4313            vec![
4314                Event::Arg {
4315                    arg: &AUTO_ARGS,
4316                    value: b"node",
4317                    delimit: true,
4318                },
4319                Event::Arg {
4320                    arg: &AUTO_ARGS,
4321                    value: b"--help",
4322                    delimit: true,
4323                },
4324            ]
4325        );
4326
4327        // Before either takes effect, though, the wrapper's own help is what `--help` asks
4328        // for — `mise run --help` is a question about `run`, not a value for it.
4329        let a = argv(["--help"]);
4330        assert_eq!(
4331            parse(&AUTO_WRAP, &a).unwrap(),
4332            vec![Event::Flag {
4333                flag: &HELP_LONG,
4334                value: None,
4335                negated: false
4336            }]
4337        );
4338    }
4339
4340    #[test]
4341    fn double_dash_required_arg() {
4342        static CMD: Arg = Arg {
4343            key: 20,
4344            name: "cmd",
4345            double_dash: DoubleDash::Required,
4346            ..Arg::REQUIRED
4347        };
4348        static EXEC: Command = Command {
4349            name: "ex",
4350            args: &[&CMD],
4351            ..Command::EMPTY
4352        };
4353
4354        let a = argv(["--", "ls"]);
4355        assert_eq!(
4356            parse(&EXEC, &a).unwrap(),
4357            vec![Event::Arg {
4358                arg: &CMD,
4359                value: b"ls",
4360                delimit: true,
4361            }]
4362        );
4363
4364        let a = argv(["ls"]);
4365        assert_eq!(
4366            parse(&EXEC, &a),
4367            Err(Error::ArgRequiresDoubleDash { arg: &CMD })
4368        );
4369    }
4370
4371    #[test]
4372    fn double_dash_preserve_keeps_the_separator() {
4373        static ARGS: Arg = Arg {
4374            key: 21,
4375            name: "args",
4376            double_dash: DoubleDash::Preserve,
4377            ..Arg::VAR
4378        };
4379        static WRAP: Command = Command {
4380            name: "ex",
4381            args: &[&ARGS],
4382            ..Command::EMPTY
4383        };
4384
4385        let a = argv(["a", "--", "b"]);
4386        let values: Vec<&[u8]> = parse(&WRAP, &a)
4387            .unwrap()
4388            .iter()
4389            .filter_map(|e| match e {
4390                Event::Arg { value, .. } => Some(*value),
4391                _ => None,
4392            })
4393            .collect();
4394        assert_eq!(values, vec![&b"a"[..], &b"--"[..], &b"b"[..]]);
4395    }
4396
4397    #[test]
4398    fn double_dash_automatic_stops_flag_interpretation() {
4399        static FILES: Arg = Arg {
4400            key: 22,
4401            name: "files",
4402            double_dash: DoubleDash::Automatic,
4403            ..Arg::VAR
4404        };
4405        static AUTO: Command = Command {
4406            name: "ex",
4407            flags: &[&FORCE],
4408            args: &[&FILES],
4409            ..Command::EMPTY
4410        };
4411
4412        // The flag before the first value is still a flag; the one after it is a
4413        // value.
4414        let a = argv(["-f", "one", "--force"]);
4415        assert_eq!(
4416            parse(&AUTO, &a).unwrap(),
4417            vec![
4418                Event::Flag {
4419                    flag: &FORCE,
4420                    value: None,
4421                    negated: false
4422                },
4423                Event::Arg {
4424                    arg: &FILES,
4425                    value: b"one",
4426                    delimit: true,
4427                },
4428                Event::Arg {
4429                    arg: &FILES,
4430                    value: b"--force",
4431                    delimit: true,
4432                },
4433            ]
4434        );
4435    }
4436
4437    #[test]
4438    fn too_many_words() {
4439        static ONE: Command = Command {
4440            name: "ex",
4441            args: &[&FILE],
4442            ..Command::EMPTY
4443        };
4444        let a = argv(["a", "b"]);
4445        assert_eq!(parse(&ONE, &a), Err(Error::UnexpectedArg { token: b"b" }));
4446    }
4447
4448    #[test]
4449    fn unknown_letter_rejects_the_whole_bundle() {
4450        // `-f` is real and `-z` is not. The first event must be the error: if the
4451        // flag event came out first, a caller would have applied `-f` from a
4452        // command line that was rejected.
4453        let a = argv(["-fz"]);
4454        let mut parser = Parser::new(&STRICT, &a);
4455        assert_eq!(
4456            parser.next_event(),
4457            Some(Err(Error::UnknownFlag { token: b"-fz" })),
4458            "an unknown letter must reject the token before any of it is applied"
4459        );
4460        assert!(parser.next_event().is_none());
4461
4462        // Leniently, the same token is a value — and `-f` is *not* applied, since
4463        // the token was never a bundle at all.
4464        let a = argv(["-fz"]);
4465        assert_eq!(
4466            parse(&ROOT, &a).unwrap(),
4467            vec![Event::Arg {
4468                arg: &FILE,
4469                value: b"-fz",
4470                delimit: true,
4471            }]
4472        );
4473    }
4474
4475    #[test]
4476    fn unknown_short_error_names_the_whole_token() {
4477        for (tokens, want) in [(["-z"], &b"-z"[..]), (["-fz"], &b"-fz"[..])] {
4478            let a = argv(tokens);
4479            assert_eq!(
4480                parse(&STRICT, &a),
4481                Err(Error::UnknownFlag { token: want }),
4482                "{tokens:?}"
4483            );
4484        }
4485    }
4486
4487    #[test]
4488    fn errors_are_terminal() {
4489        let a = argv(["--wat", "--force"]);
4490        let mut parser = Parser::new(&STRICT, &a);
4491        assert!(parser.next_event().unwrap().is_err());
4492        assert!(parser.next_event().is_none());
4493    }
4494
4495    #[test]
4496    fn non_utf8_values_still_parse() {
4497        // A value that is not valid UTF-8 binds; only converting it fails, and
4498        // only if a caller asks.
4499        let raw = OsStr::new("--force");
4500        let a = [raw];
4501        assert!(parse(&ROOT, &a).is_ok());
4502
4503        assert!(as_str(b"ok").is_ok());
4504        assert!(as_str(&[0xff, 0xfe]).is_err());
4505    }
4506
4507    #[test]
4508    fn a_multicall_applet_is_the_basename_unless_it_is_the_dispatcher() {
4509        assert_eq!(multicall_basename("/usr/bin/ls"), "ls");
4510        assert_eq!(multicall_basename(r"C:\busybox\ls.exe"), "ls");
4511        assert_eq!(
4512            multicall_applet("/usr/bin/ls", "busybox", Some("busybox")),
4513            Some("ls")
4514        );
4515        assert_eq!(
4516            multicall_applet("/usr/bin/busybox", "busybox", Some("busybox")),
4517            None
4518        );
4519        assert_eq!(
4520            multicall_applet("ls.exe", "busybox", Some("busybox")),
4521            Some("ls")
4522        );
4523        assert_eq!(
4524            multicall_applet("/usr/bin/busybox", "BusyBox", Some("/opt/bin/busybox")),
4525            None
4526        );
4527        assert_eq!(
4528            multicall_applet("busybox.exe", "BusyBox", Some("busybox.exe")),
4529            None
4530        );
4531    }
4532
4533    #[test]
4534    fn a_spec_request_is_the_first_word_and_nothing_else() {
4535        let request = [OsStr::new(SPEC_REQUEST)];
4536        assert!(is_spec_request(&ROOT, &request));
4537
4538        // Anywhere but the front it is an ordinary value, which is what makes the endpoint
4539        // safe for a CLI whose arguments are arbitrary text.
4540        let later = ["install", SPEC_REQUEST].map(OsStr::new);
4541        assert!(!is_spec_request(&ROOT, &later));
4542        assert!(!is_spec_request(&ROOT, &[]));
4543        assert!(!is_spec_request(&ROOT, &[OsStr::new("--help")]));
4544    }
4545
4546    #[test]
4547    fn a_declared_command_of_that_name_keeps_it() {
4548        static DECLARED: Command = Command {
4549            name: SPEC_REQUEST,
4550            key: 200,
4551            ..Command::EMPTY
4552        };
4553        static ALIASED: Command = Command {
4554            name: "describe",
4555            aliases: &[SPEC_REQUEST],
4556            key: 201,
4557            ..Command::EMPTY
4558        };
4559        static DECLARES_IT: Command = Command {
4560            name: "ex",
4561            subcommands: &[&DECLARED],
4562            ..Command::EMPTY
4563        };
4564        static ALIASES_IT: Command = Command {
4565            name: "ex",
4566            subcommands: &[&ALIASED],
4567            ..Command::EMPTY
4568        };
4569
4570        let request = [OsStr::new(SPEC_REQUEST)];
4571        assert!(!is_spec_request(&DECLARES_IT, &request));
4572        // An alias selects a command just as its name does, so it wins here too.
4573        assert!(!is_spec_request(&ALIASES_IT, &request));
4574    }
4575}