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