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