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