Skip to main content

usage_argv/
lib.rs

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