Skip to main content

usage/
parse.rs

1use crate::miette::{self, bail};
2use indexmap::IndexMap;
3use itertools::Itertools;
4use log::trace;
5use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
6use std::fmt::{Debug, Display, Formatter};
7use std::sync::Arc;
8
9#[cfg(feature = "cli-help")]
10use crate::docs;
11use crate::error::UsageErr;
12use crate::spec::arg::SpecDoubleDashChoices;
13use crate::spec::unknown_flags::UnknownFlags;
14use crate::warn::Warning;
15use crate::{Spec, SpecArg, SpecChoices, SpecCommand, SpecFlag};
16
17/// Merge a subcommand's flags into the currently available flags when descending
18/// into that subcommand.
19///
20/// On descent we drop the parent's non-global flags (they are scoped to the parent)
21/// but keep its global flags so they remain recognized further down. A subcommand may
22/// re-declare a flag that the parent exposed as global (e.g. `-C/--cd`) but mark its own
23/// copy as non-global. In that case we must NOT let the non-global re-declaration shadow
24/// the inherited global flag, otherwise the next descent's `retain(global)` would drop it
25/// entirely and later parsing would treat the already-consumed global token as an
26/// unexpected positional/flag value.
27///
28/// Descending into a *mounted* subcommand (`crossing_mount`) is different: the mounted
29/// command describes another program, which does not accept the mounting CLI's globals.
30/// Those globals stay recognized (they may appear before the mounted command, and Phase 2
31/// re-parses them), but the mounted command's own flags take precedence over them, so its
32/// choices/completions are not replaced by a global's. Which flags a completion may offer
33/// there is a separate question, answered by [`ParseOutput::completion_flags`].
34fn merge_subcommand_flags(
35    available: &mut BTreeMap<String, Arc<SpecFlag>>,
36    new_flags: BTreeMap<String, Arc<SpecFlag>>,
37    crossing_mount: bool,
38) {
39    // Keep only inherited global flags from the parent.
40    available.retain(|_, f| f.global);
41
42    if crossing_mount {
43        // A mounted command owns its flags outright, including names an inherited global also
44        // uses: a word after the mounted command belongs to the mounted program. Words before
45        // it keep resolving to the global they were read as, via `Token::binding`. Aliases the
46        // mounted command does not declare (e.g. a global's short) stay inherited.
47        for (key, flag) in new_flags {
48            available.insert(key, flag);
49        }
50        return;
51    }
52
53    // Cache the merged (global ∪ orphan-alias) flag per re-declared child so every alias key of
54    // that flag ends up sharing one `Arc`. Keyed by the child `Arc`'s identity.
55    let mut merged_cache: HashMap<usize, Arc<SpecFlag>> = HashMap::new();
56    // Maps each merged flag produced below back to the inherited global it was merged from, so
57    // the collision check can compare *origins*: a flag this loop already merged is not a
58    // different global, even though it is a different `Arc`.
59    let mut merged_origin: HashMap<usize, usize> = HashMap::new();
60    // The inherited global a flag stands for: itself, or — for a merged flag — its source global.
61    fn origin_of(merged_origin: &HashMap<usize, usize>, flag: &Arc<SpecFlag>) -> usize {
62        let ptr = Arc::as_ptr(flag) as usize;
63        *merged_origin.get(&ptr).unwrap_or(&ptr)
64    }
65
66    // Iterate the *flattened* child map directly (one entry per alias key). This preserves the
67    // map's existing intra-subcommand collision resolution: when two flags in the same command
68    // share an alias (e.g. `-x --alpha` then `-x --beta`), the BTreeMap already collapsed `-x`
69    // to its last-declared owner, and we must not change which flag owns it.
70    for (key, flag) in new_flags {
71        if flag.global {
72            // A child that re-declares (or adds) a global flag stays recognized everywhere.
73            available.insert(key, flag);
74            continue;
75        }
76
77        // A non-global re-declaration that shares a LONG name with an inherited global flag is
78        // the SAME logical flag (e.g. mise's `-r --raw` re-declaring the long-only `--raw`
79        // global). Keep the global flag (global precedence, so it survives the next descent's
80        // `retain`), but union in any short/long aliases that exist only on the re-declaration,
81        // otherwise those orphan aliases would be silently dropped. Matching on a shared long is
82        // deliberate: a re-declaration sharing only a short letter with an unrelated global
83        // (`-q --quiet` vs `-q --quoting`) is a genuine collision, not an alias addition, and is
84        // handled by the `contains_key` skip below instead.
85        let inherited_global = flag.long.iter().find_map(|l| {
86            available
87                .get(&format!("--{l}"))
88                .filter(|f| f.global)
89                .cloned()
90        });
91        if let Some(global_flag) = inherited_global {
92            // Never clobber a *different* inherited global's alias. If this re-declaration's
93            // orphan alias (e.g. `-r`) is already owned by some other global (e.g. an unrelated
94            // `-r --restrict`), that is a genuine collision: keep the existing global, as global
95            // precedence dictates, instead of stealing the alias for the merged flag.
96            //
97            // Compare origins, not `Arc`s: when the global has several aliases of its own, an
98            // earlier key of this same child already replaced some of them with the merged flag,
99            // which the lookups above may now resolve to. That is the same logical flag, so it
100            // must not read as a collision and leave this key on the pre-merge global.
101            let global_origin = origin_of(&merged_origin, &global_flag);
102            if available.get(&key).is_some_and(|existing| {
103                existing.global && origin_of(&merged_origin, existing) != global_origin
104            }) {
105                continue;
106            }
107            let merged = match merged_cache.get(&(Arc::as_ptr(&flag) as usize)) {
108                Some(merged) => merged.clone(),
109                None => {
110                    let mut merged = (*global_flag).clone();
111                    // `exclusive` is deliberately *not* reconciled here, in either direction.
112                    // One object now answers to two alias sets that may disagree: the child
113                    // owns the spellings it declared, the ancestor keeps the ones only it
114                    // declared. A single bool cannot hold both, so the merged flag carries the
115                    // ancestor's and validation resolves the occurrence by the spelling that
116                    // was typed — the ledger it already consults to decide whether selecting
117                    // the child is company.
118                    for s in &flag.short {
119                        if !merged.short.contains(s) {
120                            merged.short.push(*s);
121                        }
122                    }
123                    for l in &flag.long {
124                        if !merged.long.contains(l) {
125                            merged.long.push(l.clone());
126                        }
127                    }
128                    // A child may deliberately promote one of the ancestor's hidden aliases.
129                    // Hidden lists are subsets of the accepted spellings, so a spelling present
130                    // on the child but absent from its hidden subset is visible at this level.
131                    merged.hidden_short_aliases.retain(|alias| {
132                        !flag.short.contains(alias) || flag.hidden_short_aliases.contains(alias)
133                    });
134                    merged.hidden_aliases.retain(|alias| {
135                        !flag.long.contains(alias) || flag.hidden_aliases.contains(alias)
136                    });
137                    for s in &flag.hidden_short_aliases {
138                        if !merged.hidden_short_aliases.contains(s) {
139                            merged.hidden_short_aliases.push(*s);
140                        }
141                    }
142                    for l in &flag.hidden_aliases {
143                        if !merged.hidden_aliases.contains(l) {
144                            merged.hidden_aliases.push(l.clone());
145                        }
146                    }
147                    let merged = Arc::new(merged);
148                    merged_cache.insert(Arc::as_ptr(&flag) as usize, Arc::clone(&merged));
149                    merged_origin.insert(Arc::as_ptr(&merged) as usize, global_origin);
150                    // Rebind the global's *other* aliases onto the merged flag. The loop only
151                    // visits keys the child declared, so an alias the child left out (the `-y` of
152                    // a `-y --yes` global re-declared as just `--yes`) would otherwise keep
153                    // pointing at the pre-merge flag and miss the aliases just unioned in. One
154                    // logical flag must be one object under every key it answers to.
155                    for existing in available.values_mut() {
156                        if origin_of(&merged_origin, existing) == global_origin {
157                            *existing = Arc::clone(&merged);
158                        }
159                    }
160                    merged
161                }
162            };
163            available.insert(key, merged);
164            continue;
165        }
166
167        // Purely-local flag (shares nothing with an inherited global), or one that collides only
168        // on a short with an unrelated global. Insert this alias but never shadow an inherited
169        // global flag. Such non-global flags are dropped by the next descent's `retain`.
170        if available.contains_key(&key) {
171            continue;
172        }
173        available.insert(key, flag);
174    }
175}
176
177/// Build the lookup keys a flag is registered under in `available_flags`:
178/// `--<long>` for each long name, `-<short>` for each short char, plus the `negate` token.
179fn flag_keys(flag: &SpecFlag) -> Vec<String> {
180    let mut keys: Vec<String> = flag
181        .long
182        .iter()
183        .map(|l| format!("--{l}"))
184        .chain(flag.short.iter().map(|s| format!("-{s}")))
185        .collect();
186    if let Some(negate) = &flag.negate {
187        keys.push(negate.clone());
188    }
189    keys
190}
191
192/// The flags a command declares, keyed by each of their aliases.
193fn gather_flags(cmd: &SpecCommand) -> BTreeMap<String, Arc<SpecFlag>> {
194    cmd.flags
195        .iter()
196        .chain(cmd.clause.iter().flat_map(|clause| &clause.flags))
197        .flat_map(|f| {
198            let f = Arc::new(f.clone()); // One clone per flag, then cheap Arc refs
199            flag_keys(&f)
200                .into_iter()
201                .map(|key| (key, Arc::clone(&f)))
202                .collect::<Vec<_>>()
203        })
204        .collect()
205}
206
207fn unique_flags<'a>(
208    flags: impl IntoIterator<Item = &'a Arc<SpecFlag>>,
209) -> impl Iterator<Item = &'a Arc<SpecFlag>> {
210    let mut seen = HashSet::new();
211    flags
212        .into_iter()
213        .filter(move |flag| seen.insert(Arc::as_ptr(flag) as usize))
214}
215
216/// Every flag a command accepts, resolved the way parsing an invocation of it
217/// resolves them.
218///
219/// `chain` runs from the root command (`spec.cmd`) down to the command in
220/// question; an empty chain yields no flags.
221///
222/// This is not "the command's flags plus its ancestors' globals". A subcommand
223/// that re-declares a global's long name is describing the *same* flag rather
224/// than a new one, so the global's help, argument and effect survive and only
225/// the re-declaration's extra aliases are added — see
226/// [`merge_subcommand_flags`]. Anything that reports a command's flags without
227/// going through this will disagree with what the parser actually accepts.
228pub fn available_flags(chain: &[&SpecCommand]) -> Vec<Arc<SpecFlag>> {
229    let Some((root, rest)) = chain.split_first() else {
230        return vec![];
231    };
232    let mut available = gather_flags(root);
233    for cmd in rest {
234        merge_subcommand_flags(&mut available, gather_flags(cmd), false);
235    }
236
237    // Deduplicating by `Arc` identity is not enough. When a child re-declares a
238    // global that has both a short and a long, the merged flag is written under
239    // the long key while the short key keeps pointing at the pre-merge `Arc` —
240    // two objects for one logical flag. That is harmless for parsing, which
241    // looks flags up by key, but a caller listing flags would see it twice.
242    //
243    // Names break the tie because a long key always sorts before a short one
244    // (`--x` < `-y` at the second byte), so the merged declaration is the one
245    // reached first. Two genuinely distinct flags sharing a name is a spec bug
246    // that `usage lint` reports as a duplicate flag.
247    let mut seen_names = HashSet::new();
248    unique_flags(available.values())
249        .filter(|f| seen_names.insert(f.name.clone()))
250        .cloned()
251        .collect()
252}
253
254/// Extract the flag key from a flag word for lookup in available_flags map
255/// Handles both long flags (--flag, --flag=value) and short flags (-f)
256fn get_flag_key(word: &str) -> &str {
257    if word.starts_with("--") {
258        // Long flag: strip =value if present
259        word.split_once('=').map(|(k, _)| k).unwrap_or(word)
260    } else if let Some((end, _)) = word.char_indices().nth(2) {
261        // Short flag: the dash and one letter, which is one character and not
262        // necessarily one byte.
263        &word[0..end]
264    } else {
265        word
266    }
267}
268
269/// Where a value came from, when it did not come from the command line.
270///
271/// About the *value*, not the flag. `--color` typed bare with `default_missing` has a
272/// token for the flag and none for the value, and that distinction is the whole question
273/// a spec author is asking when they ask why `--color` came out `always`. Values that were
274/// typed are attributed to the token that carried them instead — see [`TokenRole::Value`].
275#[derive(Debug, Clone, PartialEq, Eq)]
276#[non_exhaustive]
277pub enum ValueOrigin {
278    /// A flag that takes a value was given without one, so the declaration supplied it:
279    /// `default_missing`, or the empty tri-state a bare `value_optional` flag records.
280    /// One variant for both, because from argv's side the same thing happened — the flag
281    /// was typed and the value was not.
282    DefaultMissing,
283    /// An environment variable, named.
284    ///
285    /// Named because a flag may list several — `env`, `env_fallback` and `deprecated_env`,
286    /// folded together by [`SpecFlag::env_names`] — and "it came from the environment" does
287    /// not say which declaration fired or which one to delete.
288    Env(String),
289    /// A declared `default`, on the flag or on the flag's argument.
290    ///
291    /// Not two variants: the precedence between them is a spec-authoring oddity rather than
292    /// a fact about the value, and `usage lint` is the place to complain about declaring
293    /// both.
294    Default,
295    /// A `default_if` whose condition matched, with the condition that decided it. The
296    /// selector alone is ambiguous — several conditions may name it with different `when`
297    /// values.
298    DefaultIf {
299        selector: String,
300        when: Option<String>,
301    },
302}
303
304/// What one word of the command line became.
305///
306/// Several because a single token can do more than one thing: `-abc` sets three flags,
307/// `-j8` is a flag and its value.
308#[derive(Debug, Clone)]
309#[non_exhaustive]
310pub enum TokenRole {
311    /// argv[0]. Also a `Command` when a multicall symlink makes the basename a word.
312    Program,
313    /// Selected a subcommand.
314    Command { name: String },
315    /// Named a flag, in this spelling. `negated` for the `negate` form.
316    Flag {
317        flag: Arc<SpecFlag>,
318        spelling: String,
319        negated: bool,
320    },
321    /// Supplied a flag's value. Several values when a `delimiter` split the word.
322    Value {
323        flag: Arc<SpecFlag>,
324        values: Vec<String>,
325        /// Whether the value rode along on the flag's own token (`--env=prod`, `-j8`)
326        /// rather than following it as its own word.
327        attached: bool,
328    },
329    /// Filled a positional argument. Several values when a `delimiter` split the word.
330    Arg {
331        arg: Arc<SpecArg>,
332        values: Vec<String>,
333    },
334    /// An explicit `--`, consumed as a separator.
335    Separator,
336    /// A word the parser answers itself rather than binding: `--help`, `-h`, `--version`,
337    /// `-V`. The parse stops here and the answer travels as an error carrying the text, so
338    /// without a role the word reads as having done nothing while a whole help page arrives
339    /// in the error list.
340    Builtin { spelling: String },
341    /// A declared `value_terminator`, consumed to end a run of values. `ends` names the
342    /// declaration whose run it closed — the word is not one of that run's values, which is
343    /// the whole reason it was declared.
344    ValueTerminator { ends: String },
345    /// A declared `restart_token`: the positional cursor and the values it had filled start
346    /// over here. Recorded because the words before it are still in the report, and without
347    /// this row they look like they filled arguments that then came back empty.
348    Restart,
349    /// A flag-like word no declaration matched. `bound_as` is the positional that took it
350    /// under `unknown_flags="value"`, and `None` when the word was refused.
351    UnknownFlag { bound_as: Option<Arc<SpecArg>> },
352    /// The word reached a declaration that would not take it, and was dropped. Without this
353    /// the token reads as having done nothing, which is the one thing it did not do.
354    Refused { reason: String },
355    /// Forwarded to an external subcommand.
356    External,
357    /// The parser stopped before this word — a help request, a refused value.
358    Unread,
359    /// Filled a sigil-classified positional after removing its declared prefix.
360    Sigil {
361        arg: Arc<SpecArg>,
362        sigil: String,
363        values: Vec<String>,
364    },
365    /// Ended one instance of a repeatable clause and began the next.
366    ClauseSeparator { name: String },
367}
368
369/// One word of the command line, and what it became.
370#[derive(Debug, Clone)]
371#[non_exhaustive]
372pub struct TokenBinding {
373    /// Position in the argv slice the parse was given, argv[0] included.
374    pub index: usize,
375    pub word: String,
376    /// Roles a word the parser made up contributed, folded onto the token it was derived
377    /// from: the tail of a short bundle onto the bundle, a multicall applet name onto
378    /// argv[0]. `word` is what the caller wrote, not what the parser read.
379    pub synthesized: bool,
380    pub roles: Vec<TokenRole>,
381}
382
383#[non_exhaustive]
384pub struct ParseOutput {
385    pub cmd: SpecCommand,
386    pub cmds: Vec<SpecCommand>,
387    pub args: IndexMap<Arc<SpecArg>, ParseValue>,
388    /// Separator-delimited positional instances, keyed by clause name.
389    pub clauses: IndexMap<String, Vec<IndexMap<Arc<SpecArg>, ParseValue>>>,
390    /// Per-instance flags belonging to a repeatable clause, keyed by clause name.
391    pub clause_flags: IndexMap<String, Vec<IndexMap<Arc<SpecFlag>, ParseValue>>>,
392    pub flags: IndexMap<Arc<SpecFlag>, ParseValue>,
393    /// What each word of the command line became, in argv order, one entry per word.
394    ///
395    /// The token half of provenance; [`ParseOutput::flag_origins`] and
396    /// [`ParseOutput::arg_origins`] are the other half. A table keyed by token cannot show
397    /// a value that came from nowhere in argv, and a table keyed by declaration cannot show
398    /// a token that bound to nothing, so both exist.
399    pub tokens: Vec<TokenBinding>,
400    /// Where a flag's value came from when it did not come from argv, in the order the
401    /// fallbacks fired. Keyed as [`ParseOutput::flags`] is.
402    ///
403    /// A list rather than one origin: repeated bare occurrences of a `var` flag each take a
404    /// `default_missing` value, so one flag can have several.
405    pub flag_origins: IndexMap<Arc<SpecFlag>, Vec<ValueOrigin>>,
406    /// Where an argument's value came from when it did not come from argv. Keyed as
407    /// [`ParseOutput::args`] is.
408    pub arg_origins: IndexMap<Arc<SpecArg>, Vec<ValueOrigin>>,
409    /// Flags a later occurrence removed, and the flag that removed them.
410    ///
411    /// The overriding name is the half a caller needs: the fallback phase silently declines
412    /// to fill an overridden flag, so "why is `--quiet` unset when its default says
413    /// otherwise" has no answer without it.
414    pub overridden_flags: BTreeMap<String, String>,
415    /// Every flag the parser recognizes at this point, keyed by each of its aliases
416    /// (`--long`, `-s`, negations).
417    ///
418    /// This includes flags that only remain recognized because they may appear *before* a
419    /// mounted command — see [`ParseOutput::completion_flags`] for the set a completion
420    /// should offer.
421    pub available_flags: BTreeMap<String, Arc<SpecFlag>>,
422    pub flag_awaiting_value: Vec<Arc<SpecFlag>>,
423    pub errors: Vec<UsageErr>,
424    /// Deprecated declarations this command line used, for the caller to render when its
425    /// logging is up. Empty from [`parse_partial`]: a half-typed line being completed has
426    /// not used anything yet.
427    pub warnings: Vec<Warning>,
428    /// The positional argument the next word would have filled, i.e. where the parser's
429    /// cursor stopped. `None` once every argument is satisfied.
430    ///
431    /// Completions need exactly this: the parser already accounts for `var_max`, for
432    /// `restart_token` rewinds, and for the jump an explicit `--` performs onto a
433    /// `double_dash="required"` argument, so re-deriving it from `args` would disagree.
434    pub next_arg: Option<Arc<SpecArg>>,
435    /// Whether an explicit `--` was consumed *as a separator*.
436    ///
437    /// A `--` that `double_dash="preserve"` keeps as a value does not count: it is a value
438    /// of the variadic argument collecting it, not a separator, so it does not unlock a
439    /// `double_dash="required"` argument.
440    pub double_dash_seen: bool,
441    /// Remaining argv captured when an unmatched word was forwarded as an external
442    /// subcommand: the command name first, then every token after it.
443    ///
444    /// Absent when no external command was selected. See [`SpecCommand::external_subcommand`].
445    pub external: Option<Vec<String>>,
446}
447
448impl ParseOutput {
449    /// The flags a completion should offer for the parsed command.
450    ///
451    /// Usually every recognized flag, i.e. [`ParseOutput::available_flags`]. Once a mounted
452    /// command has been reached, though, the commands above it belong to the mounting CLI and
453    /// their flags are not accepted there — mise, for example, forwards everything after a task
454    /// name to the task itself — so only the flags declared from the mount boundary down are
455    /// offered. Those globals stay in `available_flags` because they may legitimately appear
456    /// *before* the mounted command.
457    pub fn completion_flags(&self) -> BTreeMap<String, Arc<SpecFlag>> {
458        let Some(boundary) = self.cmds.iter().position(|cmd| cmd.mounted) else {
459            return self.available_flags.clone();
460        };
461        // A mount can also merge flags from its spec's root into the command it is mounted on
462        // (`SpecCommand::flags_from_mount`). Those describe the mounted program too, so the
463        // replay starts one level up to inherit its globals.
464        let start = match boundary.checked_sub(1) {
465            Some(prev) if self.cmds[prev].flags_from_mount => prev,
466            _ => boundary,
467        };
468        // Re-run the descent from there, which starts with no inherited flags. Below the
469        // boundary the mounted program's commands are ordinary commands, so the descents use
470        // the same merge as the real parse.
471        let mut offered = gather_flags(&self.cmds[start]);
472        for cmd in &self.cmds[start + 1..] {
473            merge_subcommand_flags(&mut offered, gather_flags(cmd), false);
474        }
475        offered
476    }
477}
478
479#[derive(Debug, Clone)]
480pub enum ParseValue {
481    Bool(bool),
482    String(String),
483    MultiBool(Vec<bool>),
484    MultiString(Vec<String>),
485}
486
487impl ParseValue {
488    pub fn try_as_bool(self) -> Option<bool> {
489        match self {
490            Self::Bool(value) => Some(value),
491            _ => None,
492        }
493    }
494
495    pub const fn try_as_bool_ref(&self) -> Option<&bool> {
496        match self {
497            Self::Bool(value) => Some(value),
498            _ => None,
499        }
500    }
501
502    pub fn try_as_bool_mut(&mut self) -> Option<&mut bool> {
503        match self {
504            Self::Bool(value) => Some(value),
505            _ => None,
506        }
507    }
508
509    pub fn try_as_string(self) -> Option<String> {
510        match self {
511            Self::String(value) => Some(value),
512            _ => None,
513        }
514    }
515
516    pub const fn try_as_string_ref(&self) -> Option<&String> {
517        match self {
518            Self::String(value) => Some(value),
519            _ => None,
520        }
521    }
522
523    pub fn try_as_string_mut(&mut self) -> Option<&mut String> {
524        match self {
525            Self::String(value) => Some(value),
526            _ => None,
527        }
528    }
529
530    pub fn try_as_multi_bool(self) -> Option<Vec<bool>> {
531        match self {
532            Self::MultiBool(value) => Some(value),
533            _ => None,
534        }
535    }
536
537    pub const fn try_as_multi_bool_ref(&self) -> Option<&Vec<bool>> {
538        match self {
539            Self::MultiBool(value) => Some(value),
540            _ => None,
541        }
542    }
543
544    pub fn try_as_multi_bool_mut(&mut self) -> Option<&mut Vec<bool>> {
545        match self {
546            Self::MultiBool(value) => Some(value),
547            _ => None,
548        }
549    }
550
551    pub fn try_as_multi_string(self) -> Option<Vec<String>> {
552        match self {
553            Self::MultiString(value) => Some(value),
554            _ => None,
555        }
556    }
557
558    pub const fn try_as_multi_string_ref(&self) -> Option<&Vec<String>> {
559        match self {
560            Self::MultiString(value) => Some(value),
561            _ => None,
562        }
563    }
564
565    pub fn try_as_multi_string_mut(&mut self) -> Option<&mut Vec<String>> {
566        match self {
567            Self::MultiString(value) => Some(value),
568            _ => None,
569        }
570    }
571}
572
573/// The deprecated declarations argv itself named: the commands it descended through, and the
574/// flags it bound.
575///
576/// Called before the environment and defaults have filled anything, because afterwards nothing
577/// distinguishes a flag the user typed from one a variable supplied — and the two are reported
578/// differently, at the point where each is applied.
579///
580/// The root is skipped. A `deprecated` root would otherwise warn on every invocation of the CLI,
581/// including `--help`, and the compiled parser reports selected commands rather than the one the
582/// process already is.
583fn collect_deprecations(out: &mut ParseOutput) {
584    for cmd in out.cmds.iter().skip(1) {
585        if cmd.deprecated.is_none()
586            && cmd.deprecated_warn_at.is_none()
587            && cmd.deprecated_remove_at.is_none()
588        {
589            continue;
590        }
591        out.warnings.push(Warning::command(
592            cmd.name.clone(),
593            cmd.deprecated.clone(),
594            cmd.deprecated_warn_at.clone(),
595            cmd.deprecated_remove_at.clone(),
596        ));
597    }
598    for flag in out.flags.keys() {
599        if let Some(warning) = flag_deprecation(flag) {
600            out.warnings.push(warning);
601        }
602    }
603}
604
605/// A warning for a flag that was used, if its declaration is deprecated at all.
606fn flag_deprecation(flag: &SpecFlag) -> Option<Warning> {
607    if flag.deprecated.is_none()
608        && flag.deprecated_warn_at.is_none()
609        && flag.deprecated_remove_at.is_none()
610    {
611        return None;
612    }
613    Some(Warning::flag(
614        flag_spelling(flag),
615        flag.deprecated.clone(),
616        flag.deprecated_warn_at.clone(),
617        flag.deprecated_remove_at.clone(),
618    ))
619}
620
621/// A flag named the way the user names it. The spec's name for it has no dashes, and a warning
622/// about `old-flag` would be about a word nobody typed.
623fn flag_spelling(flag: &SpecFlag) -> String {
624    flag.long
625        .first()
626        .map(|long| format!("--{long}"))
627        .or_else(|| flag.short.first().map(|short| format!("-{short}")))
628        .unwrap_or_else(|| flag.name.clone())
629}
630
631/// The name this flag reads first, which is what to use instead of a deprecated alias.
632fn flag_current_env(flag: &SpecFlag) -> Option<String> {
633    flag.env
634        .clone()
635        .or_else(|| flag.env_fallback.first().cloned())
636}
637
638fn flag_env_is_deprecated(flag: &SpecFlag, name: &str) -> bool {
639    flag.deprecated_env.iter().any(|declared| declared == name)
640}
641
642/// The same two questions for a positional, which has aliases but no `deprecated` of its own.
643fn arg_current_env(arg: &SpecArg) -> Option<String> {
644    arg.env
645        .clone()
646        .or_else(|| arg.env_fallback.first().cloned())
647}
648
649fn arg_env_is_deprecated(arg: &SpecArg, name: &str) -> bool {
650    arg.deprecated_env.iter().any(|declared| declared == name)
651}
652
653/// The first of `names` that is set, and which one it was.
654///
655/// `env_names()` yields the current name, then the declared fallbacks, then the deprecated
656/// aliases, so the winner's identity is what says whether a value arrived through an alias.
657/// Deciding that a second time, from the outside, would be a copy of this precedence rule free to
658/// disagree with it.
659fn first_set_env<'a>(
660    mut names: impl Iterator<Item = &'a str>,
661    get_env: &impl Fn(&str) -> Option<String>,
662) -> Option<(&'a str, String)> {
663    names.find_map(|name| get_env(name).map(|value| (name, value)))
664}
665
666/// Builder for parsing command-line arguments with custom options.
667///
668/// Use this when you need to customize parsing behavior, such as providing
669/// a custom environment variable map instead of using the process environment.
670///
671/// # Example
672/// ```
673/// use std::collections::HashMap;
674/// use usage::Spec;
675/// use usage::parse::Parser;
676///
677/// let spec: Spec = r#"flag "--name <name>" env="NAME""#.parse().unwrap();
678/// let env: HashMap<String, String> = [("NAME".into(), "john".into())].into();
679///
680/// let result = Parser::new(&spec)
681///     .with_env(env)
682///     .parse(&["cmd".into()])
683///     .unwrap();
684/// ```
685#[non_exhaustive]
686pub struct Parser<'a> {
687    spec: &'a Spec,
688    env: Option<HashMap<String, String>>,
689    mount_outputs: Option<HashMap<String, String>>,
690}
691
692impl<'a> Parser<'a> {
693    /// Create a new parser for the given spec.
694    pub fn new(spec: &'a Spec) -> Self {
695        Self {
696            spec,
697            env: None,
698            mount_outputs: None,
699        }
700    }
701
702    /// Use a custom environment variable map instead of the process environment.
703    ///
704    /// This is useful when parsing for tasks in a monorepo where the env vars
705    /// come from a child config file rather than the current process environment.
706    pub fn with_env(mut self, env: HashMap<String, String>) -> Self {
707        self.env = Some(env);
708        self
709    }
710
711    /// Inject deterministic outputs for mount commands instead of executing them.
712    ///
713    /// Keys are the exact `run` strings declared by mount nodes and values are the
714    /// usage specs those commands would print. When this is set, every encountered
715    /// mount must have an entry. Production parsing remains process-backed unless a
716    /// caller explicitly opts into injection.
717    pub fn with_mount_outputs(mut self, outputs: HashMap<String, String>) -> Self {
718        self.mount_outputs = Some(outputs);
719        self
720    }
721
722    /// Parse the input arguments.
723    ///
724    /// Returns the parsed arguments and flags, with defaults and env vars applied.
725    pub fn parse(self, input: &[String]) -> Result<ParseOutput, miette::Error> {
726        let out = self.parse_collecting(input)?;
727        if let Some(err) = out
728            .errors
729            .iter()
730            .find(|e| matches!(e, UsageErr::Help(_) | UsageErr::Version(_)))
731        {
732            bail!("{err}");
733        }
734        if !out.errors.is_empty() {
735            bail!("{}", out.errors.iter().map(|e| e.to_string()).join("\n"));
736        }
737        Ok(out)
738    }
739
740    /// Everything the parse learned, whether or not it succeeded.
741    ///
742    /// [`Parser::parse`] wants the first error and nothing else, which is right for a
743    /// caller about to act on a command line. A caller that wants to *explain* one wants
744    /// the opposite: the bindings that worked and every complaint about the rest, since a
745    /// report saying only "missing required <src>" is the report you already had.
746    ///
747    /// Failures that stop the parse dead — a mount that will not run, a word no
748    /// declaration can take — still come back as `Err`. There is no output to describe in
749    /// those cases; see [`Parser::explain`] for what to do about it.
750    pub fn explain(self, input: &[String]) -> Result<ParseOutput, miette::Error> {
751        self.parse_collecting(input)
752    }
753
754    /// The binding phase's own answer for a line [`Parser::explain`] refused.
755    ///
756    /// `Ok` when the binding phase finished and the failure came after it — a flag left
757    /// waiting for a value, say. Everything argv supplied is there and only the
758    /// environment-and-defaults phase is missing.
759    ///
760    /// `Err` when the binding phase is where it died, leaving the tokens it had attributed
761    /// by then. Those words are most of what a report is for: "no declaration takes `bogus`"
762    /// is more useful next to the three tokens that did bind than on its own. The word that
763    /// caused the failure carries a role saying so, and everything still queued behind it is
764    /// [`TokenRole::Unread`], for the two failures a command line reaches on its own — a
765    /// word nothing declares, and a flag a strict spec refuses. A failure in the spec rather
766    /// than in the line, such as a mount that will not run, stops the trace where it stopped
767    /// and the words past it carry no role.
768    pub fn explain_refused(self, input: &[String]) -> Result<ParseOutput, Vec<TokenBinding>> {
769        let mut trace = Trace::new(input);
770        match parse_partial_traced(
771            self.spec,
772            input,
773            self.env.as_ref(),
774            self.mount_outputs.as_ref(),
775            MountTiming::WhenAWordIsUnknown,
776            true,
777            &mut trace,
778        ) {
779            // A parse that got as far as stopping normally already moved its tokens onto the
780            // output, which is where a caller should read them from.
781            Ok((out, _)) => Ok(out),
782            Err(_) => Err(trace.tokens),
783        }
784    }
785
786    fn parse_collecting(self, input: &[String]) -> Result<ParseOutput, miette::Error> {
787        let custom_env = self.env.as_ref();
788        let (mut out, overridden_flags) = parse_partial_with_env(
789            self.spec,
790            input,
791            custom_env,
792            self.mount_outputs.as_ref(),
793            MountTiming::WhenAWordIsUnknown,
794            false,
795        )?;
796        restore_current_clause(&mut out);
797        trace!("{out:?}");
798
799        // A flag still waiting for a value never got one, so the command line ended
800        // mid-flag. `parse_partial` leaves this for completions to look at — a
801        // half-typed `--jobs ` is exactly what a completion is asked about — but a
802        // full parse has nothing left to wait for, and dropping the flag silently
803        // made a forgotten value look like a working command.
804        while try_bind_default_missing(
805            &mut out.flags,
806            &mut out.flag_awaiting_value,
807            custom_env,
808            &mut out.flag_origins,
809        )? {}
810        if let Some(flag) = out.flag_awaiting_value.first() {
811            let token = flag
812                .long
813                .first()
814                .map(|l| format!("--{l}"))
815                .or_else(|| flag.short.first().map(|s| format!("-{s}")))
816                .unwrap_or_else(|| flag.name.clone());
817            let rendered = input.join(" ");
818            let span = rendered
819                .rfind(&token)
820                .map(|at| (at, token.len()))
821                .unwrap_or((0, 0));
822            return Err(UsageErr::InvalidFlag {
823                token,
824                reason: "requires an argument".to_string(),
825                span: span.into(),
826                input: rendered,
827            }
828            .into());
829        }
830
831        // Before the environment and defaults have their turn, because both mark a field as
832        // filled and only argv can be reported as something the user typed. Env is reported
833        // where it is applied, below; a default is nobody's request and reports nothing.
834        collect_deprecations(&mut out);
835
836        let get_env = |key: &str| -> Option<String> {
837            if let Some(env_map) = custom_env {
838                env_map.get(key).cloned()
839            } else {
840                std::env::var(key).ok()
841            }
842        };
843
844        // Apply env vars and defaults for args
845        //
846        // Not `skip(out.args.len())`: an explicit `--` can jump the parser's cursor past an arg
847        // that stayed empty, leaving a gap that makes the fill count a wrong starting offset.
848        for arg in active_args(&out.cmd) {
849            // Clause instances contain argv only: defaults and environment values do not
850            // manufacture fields inside a repeated group.
851            if out.cmd.clause.is_some() {
852                break;
853            }
854            if out.args.contains_key(arg) {
855                continue;
856            }
857            if let Some((env_name, env_value)) = first_set_env(arg.env_names(), &get_env) {
858                if arg_env_is_deprecated(arg, env_name) {
859                    out.warnings
860                        .push(Warning::env(env_name, arg_current_env(arg)));
861                }
862                let values = split_fallback_values(std::slice::from_ref(&env_value), arg.delimiter);
863                validate_choice_values(
864                    ChoiceTarget::arg(arg),
865                    &values,
866                    arg.choices.as_ref(),
867                    custom_env,
868                )?;
869                let parsed = if arg.var {
870                    validate_arg_fallback_count(arg, values.len(), &mut out.errors);
871                    ParseValue::MultiString(values)
872                } else {
873                    ParseValue::String(values.into_iter().next().unwrap_or_default())
874                };
875                out.args.insert(Arc::new(arg.clone()), parsed);
876                out.arg_origins
877                    .entry(Arc::new(arg.clone()))
878                    .or_default()
879                    .push(ValueOrigin::Env(env_name.to_string()));
880                continue;
881            }
882            if !arg.default.is_empty() {
883                // Consider var when deciding the type of default return value
884                if arg.var {
885                    let values = split_fallback_values(&arg.default, arg.delimiter);
886                    validate_arg_fallback_count(arg, values.len(), &mut out.errors);
887                    validate_choice_values(
888                        ChoiceTarget::arg(arg),
889                        &values,
890                        arg.choices.as_ref(),
891                        custom_env,
892                    )?;
893                    // For var=true, always return a vec (MultiString)
894                    out.args
895                        .insert(Arc::new(arg.clone()), ParseValue::MultiString(values));
896                    out.arg_origins
897                        .entry(Arc::new(arg.clone()))
898                        .or_default()
899                        .push(ValueOrigin::Default);
900                } else {
901                    validate_choice_value(
902                        ChoiceTarget::arg(arg),
903                        &arg.default[0],
904                        arg.choices.as_ref(),
905                        custom_env,
906                    )?;
907                    // For var=false, return the first default value as String
908                    out.args.insert(
909                        Arc::new(arg.clone()),
910                        ParseValue::String(arg.default[0].clone()),
911                    );
912                    out.arg_origins
913                        .entry(Arc::new(arg.clone()))
914                        .or_default()
915                        .push(ValueOrigin::Default);
916                }
917            }
918        }
919
920        // Environment first, for every flag, so a `default_if` can see a sibling
921        // that was filled from env. Applying both in one pass would make the
922        // answer depend on declaration order: `--bin-names` before `--json`
923        // would miss `EX_JSON=1`.
924        let flags: Vec<Arc<SpecFlag>> = out
925            .available_flags
926            .values()
927            .filter(|flag| !is_clause_scoped_flag(&out, flag))
928            .cloned()
929            .collect();
930        for flag in &flags {
931            if out.flags.contains_key(flag) || overridden_flags.contains(&flag.name) {
932                continue;
933            }
934            if let Some((env_name, env_value)) = first_set_env(flag.env_names(), &get_env) {
935                // The flag's own deprecation before the alias's, which is the order the
936                // compiled parser reports them in: it walks a command's flags and then its
937                // aliases. Using a deprecated flag through a variable is still using it.
938                if let Some(warning) = flag_deprecation(flag) {
939                    out.warnings.push(warning);
940                }
941                if flag_env_is_deprecated(flag, env_name) {
942                    out.warnings
943                        .push(Warning::env(env_name, flag_current_env(flag)));
944                }
945                if let Some(arg) = flag.arg.as_ref() {
946                    let values =
947                        split_fallback_values(std::slice::from_ref(&env_value), arg.delimiter);
948                    validate_choice_values(
949                        ChoiceTarget::option(flag),
950                        &values,
951                        arg.choices.as_ref(),
952                        custom_env,
953                    )?;
954                    let parsed = if flag.var || arg.var {
955                        if flag.var {
956                            validate_flag_fallback_count(flag, values.len(), &mut out.errors);
957                        }
958                        if arg.var {
959                            validate_flag_arg_fallback_count(
960                                flag,
961                                arg,
962                                values.len(),
963                                &mut out.errors,
964                            );
965                        }
966                        ParseValue::MultiString(values)
967                    } else {
968                        ParseValue::String(values.into_iter().next().unwrap_or_default())
969                    };
970                    out.flags.insert(Arc::clone(flag), parsed);
971                } else {
972                    let is_true = matches!(env_value.as_str(), "1" | "true" | "True" | "TRUE");
973                    out.flags
974                        .insert(Arc::clone(flag), ParseValue::Bool(is_true));
975                }
976                out.flag_origins
977                    .entry(Arc::clone(flag))
978                    .or_default()
979                    .push(ValueOrigin::Env(env_name.to_string()));
980            }
981        }
982        // Decide every `default_if` against argv+env only. Binding as we go would put
983        // a default into `out.flags` and make the next flag's condition treat it as
984        // explicit — Go's `Given()` and the derive's `__given_*` both ignore defaults
985        // here, so an unconditional `default` on `--json` must not fire
986        // `default_if "--json"`.
987        let mut from_default_if: Vec<(Arc<SpecFlag>, crate::SpecDefaultIf)> = Vec::new();
988        for flag in &flags {
989            if out.flags.contains_key(flag) || overridden_flags.contains(&flag.name) {
990                continue;
991            }
992            if let Some(condition) = flag.default_if.iter().find(|condition| {
993                default_if_condition_matches(condition, &out, &overridden_flags, custom_env)
994            }) {
995                from_default_if.push((Arc::clone(flag), condition.clone()));
996            }
997        }
998        for (flag, condition) in &from_default_if {
999            // The whole condition, not just the value: several conditions may name the same
1000            // selector with different `when` values, so the selector alone does not say
1001            // which one fired.
1002            bind_flag_fallback(
1003                flag,
1004                std::slice::from_ref(&condition.value),
1005                &mut out,
1006                custom_env,
1007                ValueOrigin::DefaultIf {
1008                    selector: condition.selector.clone(),
1009                    when: condition.when.clone(),
1010                },
1011            )?;
1012        }
1013        for flag in &flags {
1014            if out.flags.contains_key(flag) || overridden_flags.contains(&flag.name) {
1015                continue;
1016            }
1017            if !flag.default.is_empty() {
1018                bind_flag_fallback(
1019                    flag,
1020                    &flag.default,
1021                    &mut out,
1022                    custom_env,
1023                    ValueOrigin::Default,
1024                )?;
1025                continue;
1026            }
1027            if let Some(arg) = flag.arg.as_ref() {
1028                if !arg.default.is_empty() {
1029                    bind_flag_fallback(
1030                        flag,
1031                        &arg.default,
1032                        &mut out,
1033                        custom_env,
1034                        ValueOrigin::Default,
1035                    )?;
1036                }
1037            }
1038        }
1039        // The binding phase leaves the last clause instance in `args`/`flags` so
1040        // completion can inspect it. A full parse closes it before applying scoped
1041        // fallbacks: defaults and environment values fill instances that argv made,
1042        // but never manufacture a repeated group on their own.
1043        finalize_current_clause(&mut out);
1044        let clause_explicit = apply_clause_flag_fallbacks(&mut out, &overridden_flags, custom_env)?;
1045        validate_clause_relationships(
1046            &mut out,
1047            &overridden_flags,
1048            custom_env,
1049            clause_explicit.as_deref(),
1050        );
1051        // Declarative value validation is deliberately post-binding. Defaults and
1052        // environment fallbacks have landed by here, and delimiters were already split
1053        // while binding. Like clap's value parsers, a declaration judges each resulting
1054        // raw value independently.
1055        for (arg, parsed) in &out.args {
1056            validate_expression(
1057                &arg.name,
1058                arg.validate.as_deref(),
1059                arg.validate_error.as_deref(),
1060                parsed,
1061                &mut out.errors,
1062            );
1063        }
1064        if let Some(clause) = &out.cmd.clause {
1065            let mut clause_errors = Vec::new();
1066            for (index, instance) in out
1067                .clauses
1068                .get(&clause.name)
1069                .into_iter()
1070                .flatten()
1071                .enumerate()
1072            {
1073                for arg in &clause.args {
1074                    let Some(value) = instance.get(arg) else {
1075                        if arg.required {
1076                            clause_errors.push(UsageErr::MissingClauseArg {
1077                                clause: clause.name.clone(),
1078                                instance: index + 1,
1079                                arg: arg.name.clone(),
1080                            });
1081                        }
1082                        continue;
1083                    };
1084                    if let (true, ParseValue::MultiString(values)) = (arg.var, value) {
1085                        if let Some(min) = arg.var_min {
1086                            if values.len() < min {
1087                                clause_errors.push(UsageErr::VarArgTooFew {
1088                                    name: format!(
1089                                        "{} instance {}: {}",
1090                                        clause.name,
1091                                        index + 1,
1092                                        arg.name
1093                                    ),
1094                                    min,
1095                                    got: values.len(),
1096                                });
1097                            }
1098                        }
1099                        if let Some(max) = arg.var_max {
1100                            if values.len() > max {
1101                                clause_errors.push(UsageErr::VarArgTooMany {
1102                                    name: format!(
1103                                        "{} instance {}: {}",
1104                                        clause.name,
1105                                        index + 1,
1106                                        arg.name
1107                                    ),
1108                                    max,
1109                                    got: values.len(),
1110                                });
1111                            }
1112                        }
1113                    }
1114                }
1115            }
1116            out.errors.extend(clause_errors);
1117        }
1118        let clause_flag_names = out
1119            .cmd
1120            .clause
1121            .iter()
1122            .flat_map(|clause| &clause.flags)
1123            .map(|flag| flag.name.as_str())
1124            .collect::<HashSet<_>>();
1125        for (flag, parsed) in out
1126            .flags
1127            .iter()
1128            .filter(|(flag, _)| !clause_flag_names.contains(flag.name.as_str()))
1129        {
1130            if let Some(arg) = &flag.arg {
1131                validate_expression(
1132                    &flag.name,
1133                    arg.validate.as_deref(),
1134                    arg.validate_error.as_deref(),
1135                    parsed,
1136                    &mut out.errors,
1137                );
1138            }
1139        }
1140        // Applied once, here, because this is where the CLI's own version is known: a
1141        // `deprecated_warn_at` the spec has not reached yet is an author saying *not yet*.
1142        crate::warn::retain_reached(&mut out.warnings, self.spec.version.as_deref());
1143        Ok(out)
1144    }
1145}
1146
1147/// Parse command-line arguments according to a spec.
1148///
1149/// Returns the parsed arguments and flags, with defaults and env vars applied.
1150/// Uses `std::env::var` for environment variable lookups.
1151///
1152/// For custom environment variable handling, use [`Parser`] instead.
1153#[must_use = "parsing result should be used"]
1154pub fn parse(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
1155    Parser::new(spec).parse(input)
1156}
1157
1158/// Parse command-line arguments without applying defaults.
1159///
1160/// Use this for help text generation or when you need the raw parsed values.
1161#[must_use = "parsing result should be used"]
1162pub fn parse_partial(spec: &Spec, input: &[String]) -> Result<ParseOutput, miette::Error> {
1163    parse_partial_with_env(spec, input, None, None, MountTiming::Eager, true).map(|(out, _)| out)
1164}
1165
1166/// Basename of argv[0] for a multicall CLI: last path component, with a trailing
1167/// `.exe` stripped so Windows and Unix agree.
1168pub fn multicall_basename(argv0: &str) -> &str {
1169    let name = argv0.rsplit(['/', '\\']).next().unwrap_or(argv0);
1170    match name.get(name.len().saturating_sub(4)..) {
1171        Some(ext) if ext.eq_ignore_ascii_case(".exe") => &name[..name.len() - 4],
1172        _ => name,
1173    }
1174}
1175
1176/// The applet name to parse as the first word, when argv[0] is not the dispatcher.
1177///
1178/// `None` means a dispatcher invocation (`busybox ls`): skip argv[0] and parse the
1179/// rest. `Some` is a symlink invocation (`ls -l`): inject the basename.
1180pub fn multicall_applet<'a>(argv0: &'a str, name: &str, bin: Option<&str>) -> Option<&'a str> {
1181    let base = multicall_basename(argv0);
1182    if !name.is_empty() && base == multicall_basename(name) {
1183        return None;
1184    }
1185    if let Some(bin) = bin {
1186        if !bin.is_empty() && base == multicall_basename(bin) {
1187            return None;
1188        }
1189    }
1190    Some(base)
1191}
1192
1193/// Internal version of parse_partial that accepts an optional custom env map.
1194/// When a command's own `mount` runs, for the root — which nothing descends into.
1195///
1196/// A completion has to know every command before it can offer one, even with
1197/// nothing typed yet, so it resolves up front. An execution knows the word it was
1198/// given, so it only pays for discovery when that word matches nothing declared —
1199/// and a CLI that declares its commands and mounts a few more does not spawn a
1200/// process on every invocation.
1201#[derive(Clone, Copy, PartialEq, Eq)]
1202enum MountTiming {
1203    Eager,
1204    WhenAWordIsUnknown,
1205}
1206
1207/// One word on its way through the parser, with what the parser has learned about it.
1208///
1209/// This holds what a side queue used to: the flag Phase 1 read a word as, previously a
1210/// `VecDeque` popped in step with the words. Two queues staying aligned is an invariant
1211/// nothing checks, and it was delicate enough to need explaining at three call sites; on
1212/// the word itself there is nothing to keep aligned. The argv position is here for the
1213/// same reason: the queue is popped, re-queued, split on `=`, and has subcommand words
1214/// removed from the middle, so position in the queue stops meaning position in argv on the
1215/// first descent.
1216struct Token {
1217    word: String,
1218    /// Where in the caller's argv this word came from.
1219    ///
1220    /// A word the parser made up points at the token it was derived from — the tail of a
1221    /// short bundle at the bundle, a multicall applet name at argv[0] — because that is the
1222    /// token a reader would point at, and there is nothing else to point at.
1223    argv: usize,
1224    /// The flag Phase 1 read this word as, and the command level it read it at.
1225    ///
1226    /// `Some((flag, command_level))` for a flag word, `None` for its value, for anything
1227    /// unresolved, and for every word Phase 1 never reached. The words stay in the queue
1228    /// for Phase 2 to re-parse — that is how they reach `out.flags` and `as_env()` — but by
1229    /// then the recognized flags have changed, because each descent drops the parent's
1230    /// non-global flags and a mounted command may declare the same name as a global seen
1231    /// here. Recording the owner keeps a word bound to the flag it was read as.
1232    ///
1233    /// The level matters to strict parsing: clap permits an inherited global once on each
1234    /// side of a subcommand boundary.
1235    binding: Option<(Arc<SpecFlag>, usize)>,
1236}
1237
1238/// Flag scope for the one token straddling an implicit default-command boundary.
1239struct DefaultBundle {
1240    argv: usize,
1241    flags: BTreeMap<String, Arc<SpecFlag>>,
1242    parent_keys: HashSet<String>,
1243}
1244
1245impl DefaultBundle {
1246    /// Preserve the declaration and command level when a short tail is requeued.
1247    fn binding(&self, key: &str) -> Option<(Arc<SpecFlag>, usize)> {
1248        self.flags.get(key).map(|flag| {
1249            (
1250                Arc::clone(flag),
1251                usize::from(!self.parent_keys.contains(key)),
1252            )
1253        })
1254    }
1255}
1256
1257impl Token {
1258    fn new(word: String, argv: usize) -> Self {
1259        Self {
1260            word,
1261            argv,
1262            binding: None,
1263        }
1264    }
1265}
1266
1267/// The token trace, while it is being built.
1268///
1269/// One row per word of the caller's argv, so a role can be recorded against a position
1270/// without the recorder having to know how many words came before it. Words the parser
1271/// made up have no row of their own and fold onto the row they were derived from.
1272struct Trace {
1273    tokens: Vec<TokenBinding>,
1274}
1275
1276impl Trace {
1277    fn new(input: &[String]) -> Self {
1278        Self {
1279            tokens: input
1280                .iter()
1281                .enumerate()
1282                .map(|(index, word)| TokenBinding {
1283                    index,
1284                    word: word.clone(),
1285                    synthesized: false,
1286                    roles: vec![],
1287                })
1288                .collect(),
1289        }
1290    }
1291
1292    fn record(&mut self, argv: usize, role: TokenRole) {
1293        if let Some(token) = self.tokens.get_mut(argv) {
1294            token.roles.push(role);
1295        }
1296    }
1297
1298    /// Note that what was read at this position is not what the caller wrote there.
1299    fn note_synthesized(&mut self, argv: usize) {
1300        if let Some(token) = self.tokens.get_mut(argv) {
1301            token.synthesized = true;
1302        }
1303    }
1304
1305    /// Every word the parse never reached, once it has stopped.
1306    fn close(&mut self, unread: &VecDeque<Token>) {
1307        for token in unread {
1308            self.record(token.argv, TokenRole::Unread);
1309        }
1310    }
1311}
1312
1313fn parse_partial_with_env(
1314    spec: &Spec,
1315    input: &[String],
1316    custom_env: Option<&HashMap<String, String>>,
1317    mount_outputs: Option<&HashMap<String, String>>,
1318    mount_timing: MountTiming,
1319    validate_clauses: bool,
1320) -> Result<(ParseOutput, HashSet<String>), miette::Error> {
1321    let mut trace = Trace::new(input);
1322    parse_partial_traced(
1323        spec,
1324        input,
1325        custom_env,
1326        mount_outputs,
1327        mount_timing,
1328        validate_clauses,
1329        &mut trace,
1330    )
1331}
1332
1333/// Locate the first default-only flag, while preserving an explicit sibling selector.
1334/// This only reads declarations: it neither binds values nor executes mount commands.
1335fn default_flag_route(spec: &Spec, root: &SpecCommand, input: &VecDeque<Token>) -> Option<usize> {
1336    let default = root.find_subcommand(spec.default_subcommand.as_deref()?)?;
1337    let parent = gather_flags(root);
1338    let child = gather_flags(default);
1339    let path = std::slice::from_ref(root);
1340    let mut at = None;
1341    let mut i = 0;
1342    while let Some(token) = input.get(i).map(|t| t.word.as_str()) {
1343        let scope = if at.is_some() { default } else { root };
1344        if token == "--"
1345            || token == "-"
1346            || scope
1347                .clause
1348                .as_ref()
1349                .is_some_and(|c| c.separator.as_deref() == Some(token))
1350        {
1351            return at;
1352        }
1353        if !is_flag_like(token) {
1354            return if root.find_subcommand(token).is_some()
1355                || (token == "help"
1356                    && spec.disable_help != Some(true)
1357                    && !root.disable_help_subcommand)
1358            {
1359                None
1360            } else {
1361                at
1362            };
1363        }
1364        let mut value_flag = None;
1365        let mut attached = None;
1366        if token.starts_with("--") {
1367            let (name, value) = token
1368                .split_once('=')
1369                .map_or((token, None), |(n, v)| (n, Some(v)));
1370            if !parent.contains_key(name)
1371                && (is_help_arg(spec, root, name) || is_version_arg(spec, path, name))
1372            {
1373                i += 1;
1374                continue;
1375            }
1376            let flag = parent.get(name).or_else(|| child.get(name));
1377            let Some(flag) = flag else {
1378                return at;
1379            };
1380            if !parent.contains_key(name) {
1381                at.get_or_insert(i);
1382            }
1383            if flag.arg.is_some() && flag.negate.as_deref() != Some(name) {
1384                value_flag = Some(flag);
1385                attached = value;
1386            }
1387        } else {
1388            for (offset, letter) in token.char_indices().skip(1) {
1389                let key = format!("-{letter}");
1390                if !parent.contains_key(&key) && supplied_short(spec, path, letter).is_some() {
1391                    continue;
1392                }
1393                let flag = parent.get(&key).or_else(|| child.get(&key));
1394                let Some(flag) = flag else {
1395                    return at;
1396                };
1397                if !parent.contains_key(&key) {
1398                    at.get_or_insert(i);
1399                }
1400                if flag.arg.is_some() {
1401                    value_flag = Some(flag);
1402                    let rest = &token[offset + letter.len_utf8()..];
1403                    attached = (!rest.is_empty()).then_some(rest.strip_prefix('=').unwrap_or(rest));
1404                    break;
1405                }
1406            }
1407        }
1408        i += 1;
1409        if let Some(flag) = value_flag {
1410            let scope = if at.is_some() { default } else { root };
1411            let is_separator = |next: &str| {
1412                scope
1413                    .clause
1414                    .as_ref()
1415                    .is_some_and(|c| c.separator.as_deref() == Some(next))
1416            };
1417            let first = if attached.is_some() {
1418                attached
1419            } else {
1420                input
1421                    .get(i)
1422                    .map(|t| t.word.as_str())
1423                    .filter(|next| !is_separator(next) && accepts_detached_flag_value(flag, next))
1424                    .inspect(|_| i += 1)
1425            };
1426            if first.is_none() && !flag.value_optional && flag.default_missing.is_none() {
1427                return at;
1428            }
1429            let arg = flag.arg.as_ref().expect("value flag");
1430            if arg.var {
1431                let count_values = |v: &str| arg.delimiter.map_or(1, |d| v.split(d).count());
1432                let mut count = first.map_or(0, count_values);
1433                while arg.var_max.is_none_or(|max| count < max) {
1434                    let Some(next) = input.get(i).map(|t| t.word.as_str()) else {
1435                        break;
1436                    };
1437                    if is_separator(next) {
1438                        break;
1439                    }
1440                    if next == "--" || arg.value_terminator.as_deref() == Some(next) {
1441                        if next != "--" {
1442                            i += 1;
1443                        }
1444                        break;
1445                    }
1446                    if is_flag_like(next)
1447                        && !(arg.allow_negative_numbers && is_negative_number(next))
1448                    {
1449                        break;
1450                    }
1451                    if root.subcommand_precedence_over_arg && root.find_subcommand(next).is_some() {
1452                        return None;
1453                    }
1454                    count += count_values(next);
1455                    i += 1;
1456                }
1457            }
1458        }
1459    }
1460    at
1461}
1462
1463/// The binding phase, with the trace left somewhere the caller can still read it.
1464///
1465/// A failure this phase cannot continue past — a word no declaration can take, a flag a
1466/// strict spec refuses — leaves through `?`, and a trace owned by the loop goes with it. The
1467/// words read before the failure are most of what a report wants, so the caller owns the
1468/// trace instead and keeps them. See [`Parser::explain_refused`].
1469fn parse_partial_traced(
1470    spec: &Spec,
1471    input: &[String],
1472    custom_env: Option<&HashMap<String, String>>,
1473    mount_outputs: Option<&HashMap<String, String>>,
1474    mount_timing: MountTiming,
1475    validate_clauses: bool,
1476    trace: &mut Trace,
1477) -> Result<(ParseOutput, HashSet<String>), miette::Error> {
1478    if let Some(view) = input.first().and_then(|argv0| spec.view_for_program(argv0)) {
1479        let viewed = spec.for_view(view)?;
1480        return parse_partial_traced(
1481            &viewed,
1482            input,
1483            custom_env,
1484            mount_outputs,
1485            mount_timing,
1486            validate_clauses,
1487            trace,
1488        );
1489    }
1490    trace!("parse_partial: {input:?}");
1491    let mut input = input
1492        .iter()
1493        .enumerate()
1494        .map(|(argv, word)| Token::new(word.clone(), argv))
1495        .collect::<VecDeque<_>>();
1496    let argv0 = input.pop_front();
1497    if let Some(argv0) = argv0.as_ref() {
1498        trace.record(argv0.argv, TokenRole::Program);
1499    }
1500    if spec.multicall {
1501        if let Some(raw) = argv0 {
1502            if let Some(applet) = multicall_applet(&raw.word, &spec.name, Some(spec.bin.as_str())) {
1503                // A symlink invocation reads a word the caller never typed — the basename of
1504                // the program itself — so argv[0] is both the program and, below, whatever
1505                // that word selects.
1506                trace.note_synthesized(raw.argv);
1507                input.push_front(Token::new(applet.to_string(), raw.argv));
1508            }
1509        }
1510    }
1511    // The policy observes the selected command's own argv, not values eventually filled from
1512    // env/default. Start at the root, then reset on every explicit descent. A default
1513    // subcommand receives the unmatched word that selected it, so it is necessarily non-bare.
1514    let mut command_has_argv = !input.is_empty();
1515
1516    let mut out = ParseOutput {
1517        cmd: spec.cmd.clone(),
1518        cmds: vec![spec.cmd.clone()],
1519        args: IndexMap::new(),
1520        clauses: IndexMap::new(),
1521        clause_flags: IndexMap::new(),
1522        flags: IndexMap::new(),
1523        tokens: vec![],
1524        flag_origins: IndexMap::new(),
1525        arg_origins: IndexMap::new(),
1526        overridden_flags: BTreeMap::new(),
1527        available_flags: gather_flags(&spec.cmd),
1528        flag_awaiting_value: vec![],
1529        errors: vec![],
1530        warnings: vec![],
1531        next_arg: None,
1532        double_dash_seen: false,
1533        external: None,
1534    };
1535    // Keep this internal so adding relationship support remains semver-compatible. The full
1536    // parser uses it to prevent defaults and environment values from restoring overridden flags.
1537    let mut overridden_flags = HashSet::new();
1538    // Which spelling supplied each parsed flag. A child may re-declare one long form of an
1539    // inherited global while the merge keeps the ancestor's other aliases on the same `Arc`.
1540    // The declaration object alone then cannot answer whether `--clean` belonged to the child
1541    // or an inherited `-c` belonged to the ancestor.
1542    let mut parsed_flag_spellings: HashMap<usize, HashSet<String>> = HashMap::new();
1543
1544    // Phase 1: Scan for subcommands and collect global flags
1545    //
1546    // This phase identifies subcommands early because they may have mount points
1547    // that need to be executed with the global flags that appeared before them.
1548    //
1549    // Example: "usage --verbose run task"
1550    //   -> finds "run" subcommand, passes ["--verbose"] to its mount command
1551    //   -> then finds "task" as a subcommand of "run" (if it exists)
1552    //
1553    // We only collect global flags for mounts because:
1554    // - Non-global flags are specific to the current command, not subcommands
1555    // - Global flags affect all commands and should be passed to mount points
1556    let mut prefix_flags: Vec<(Arc<SpecFlag>, Vec<String>)> = vec![];
1557    // Which flag each word skipped here belongs to is recorded on the word — see
1558    // `Token::binding`.
1559    let mut command_arg_found = false;
1560    let mut variadic_flag_active = false;
1561    let mut idx = 0;
1562    // Track whether we've already applied the default_subcommand to prevent
1563    // multiple switches (e.g., if default is "run" and there's a task named "run")
1564    let mut used_default_subcommand = false;
1565    let mut default_bundle: Option<DefaultBundle> = None;
1566    // Whether the command in scope has had its own mounts run. A mount on the root
1567    // is the case that needs this: a subcommand's mounts are run when the parser
1568    // descends into it, but nothing descends into the root.
1569    let mut mounts_resolved = false;
1570    // A completion needs the whole command list before it can offer anything, and
1571    // `mycli <tab>` has no word to trigger discovery with — so waiting for one would
1572    // mean a root mount never contributed to the very thing it exists for.
1573    //
1574    // The default-subcommand gate applies here too, and has to: offering a discovered
1575    // command that a real parse would hand to the default instead would be worse than
1576    // not offering it. A root mount under a `default_subcommand` that does not say
1577    // `overrides_default` therefore contributes nothing anywhere, which is what
1578    // "the default outranks discovery" means.
1579    let default_outranks_mounts =
1580        spec.default_subcommand.is_some() && !out.cmd.mounts.iter().any(|m| m.overrides_default);
1581    if mount_timing == MountTiming::Eager && !default_outranks_mounts && !out.cmd.mounts.is_empty()
1582    {
1583        mounts_resolved = true;
1584        let mut mounted = out.cmd.clone();
1585        mounted.mount(&[], mount_outputs)?;
1586        merge_subcommand_flags(&mut out.available_flags, gather_flags(&mounted), false);
1587        if let Some(last) = out.cmds.last_mut() {
1588            *last = mounted.clone();
1589        }
1590        out.cmd = mounted;
1591    }
1592
1593    let default_flag_at = spec
1594        .default_subcommand_flags
1595        .then(|| default_flag_route(spec, &out.cmd, &input))
1596        .flatten();
1597
1598    while idx < input.len() {
1599        // Only for a word that could name a command, and only when it matches
1600        // nothing already declared. A CLI that declares its commands and mounts more
1601        // does not spawn a process for every invocation, and a flag — `--help`, or
1602        // anything unrecognized — never triggers discovery at all, which it would
1603        // otherwise do simply by not being a subcommand.
1604        // A declared `default_subcommand` already says what an unmatched word means,
1605        // and it costs nothing — so discovery waits behind it unless a mount asks to
1606        // outrank it. Without this, a task runner would spawn its discovery process
1607        // once per task invocation.
1608        let default_catches_it = spec.default_subcommand.as_deref().is_some_and(|name| {
1609            default_accepts_word(&out.cmd, name, &input[idx].word)
1610                && !out.cmd.mounts.iter().any(|m| m.overrides_default)
1611        });
1612        if !mounts_resolved
1613            && !out.cmd.mounts.is_empty()
1614            && !default_catches_it
1615            && is_command_word(&input[idx].word)
1616            && !is_negative_number(&input[idx].word)
1617            && out.cmd.find_subcommand(&input[idx].word).is_none()
1618        {
1619            mounts_resolved = true;
1620            let mut mounted = out.cmd.clone();
1621            mounted.mount(&mount_prefix_words(&prefix_flags), mount_outputs)?;
1622            merge_subcommand_flags(&mut out.available_flags, gather_flags(&mounted), false);
1623            if let Some(last) = out.cmds.last_mut() {
1624                *last = mounted.clone();
1625            }
1626            out.cmd = mounted;
1627        }
1628        if variadic_flag_active
1629            && out.cmd.find_subcommand(&input[idx].word).is_some()
1630            && !out.cmd.subcommand_precedence_over_arg
1631        {
1632            break;
1633        }
1634        let implicit_default = !used_default_subcommand && default_flag_at == Some(idx);
1635        let selected_command = if implicit_default {
1636            out.cmd
1637                .find_subcommand(spec.default_subcommand.as_deref().expect("default route"))
1638        } else {
1639            out.cmd.find_subcommand(&input[idx].word)
1640        };
1641        if let Some(subcommand) = selected_command {
1642            let boundary_has_parent = out.cmd.args_conflicts_with_subcommands
1643                && implicit_default
1644                && input[idx].word.starts_with('-')
1645                && !input[idx].word.starts_with("--")
1646                && {
1647                    let child_flags = gather_flags(subcommand);
1648                    let mut found = false;
1649                    for short in input[idx].word[1..].chars() {
1650                        let key = format!("-{short}");
1651                        if out.available_flags.contains_key(&key)
1652                            || supplied_short(spec, &out.cmds, short).is_some()
1653                        {
1654                            found = true;
1655                            break;
1656                        }
1657                        match child_flags.get(&key) {
1658                            Some(flag) if flag.arg.is_none() => {}
1659                            _ => break,
1660                        }
1661                    }
1662                    found
1663                };
1664            if out.cmd.args_conflicts_with_subcommands && (command_arg_found || boundary_has_parent)
1665            {
1666                bail!(
1667                    "subcommand '{}' cannot be used with arguments on its parent command",
1668                    subcommand.name
1669                );
1670            }
1671            let boundary_parent = (implicit_default
1672                && input[idx].word.starts_with('-')
1673                && !input[idx].word.starts_with("--"))
1674            .then(|| out.available_flags.clone());
1675            let mut subcommand = subcommand.clone();
1676            // Pass prefix words (global flags before this subcommand) to mount
1677            subcommand.mount(&mount_prefix_words(&prefix_flags), mount_outputs)?;
1678            // Only the *boundary* is a mount crossing: below it, the mounted program's own
1679            // commands are ordinary commands relative to each other.
1680            let crossing_mount = subcommand.mounted && !out.cmd.mounted;
1681            merge_subcommand_flags(
1682                &mut out.available_flags,
1683                gather_flags(&subcommand),
1684                crossing_mount,
1685            );
1686            if let Some(parent) = boundary_parent {
1687                let parent_keys = parent.keys().cloned().collect();
1688                let mut flags = out.available_flags.clone();
1689                flags.extend(parent);
1690                default_bundle = Some(DefaultBundle {
1691                    argv: input[idx].argv,
1692                    flags,
1693                    parent_keys,
1694                });
1695            }
1696            // Remove subcommand from input
1697            let selected = if implicit_default {
1698                used_default_subcommand = true;
1699                None
1700            } else {
1701                input.remove(idx)
1702            };
1703            if let Some(selected) = selected {
1704                trace.record(
1705                    selected.argv,
1706                    TokenRole::Command {
1707                        name: subcommand.name.clone(),
1708                    },
1709                );
1710            }
1711            command_has_argv = idx < input.len();
1712            out.cmds.push(subcommand.clone());
1713            out.cmd = subcommand.clone();
1714            // A descent already ran the new command's mounts, above.
1715            mounts_resolved = true;
1716            prefix_flags.clear();
1717            command_arg_found = false;
1718            variadic_flag_active = false;
1719            // Continue from current position (don't reset to 0)
1720            // After remove(), idx now points to the next element
1721        } else if !is_command_word(&input[idx].word)
1722            || declared_numeric_short(&out.available_flags, &input[idx].word)
1723        {
1724            // Check if this is a known flag
1725            let word = input[idx].word.clone();
1726            let flag_key = get_flag_key(&word);
1727
1728            // A short token keys on its first letter, so `-az` would be recorded as
1729            // `-a` and its tail left over. Check the whole token here, where it is
1730            // first read: a token containing an unrecognized letter is not a bundle,
1731            // and recording it as one is what let `-a` be applied from a token that
1732            // never named it.
1733            let boundary = default_bundle
1734                .as_ref()
1735                .filter(|bundle| bundle.argv == input[idx].argv);
1736            let flags = boundary.map_or(&out.available_flags, |bundle| &bundle.flags);
1737            let is_bundle =
1738                word.starts_with("--") || short_bundle_is_known(spec, &out.cmds, flags, &word);
1739            if let Some(f) = flags.get(flag_key).cloned().filter(|_| is_bundle) {
1740                command_arg_found = true;
1741                variadic_flag_active = f.arg.as_ref().is_some_and(|arg| arg.var);
1742                // Skip the flag and keep scanning. Both global and non-global flags may precede
1743                // a subcommand (`mycli --verbose run task`, `mycli run --force task`), and
1744                // stopping at one would hide the subcommand — and any mount on it — from the
1745                // parse, leaving the subcommand name to be mis-read as a positional argument.
1746                //
1747                // Only globals are forwarded to mounts: a non-global flag belongs to the
1748                // command that declared it, not to what is mounted below it.
1749                input[idx].binding = boundary
1750                    .and_then(|bundle| bundle.binding(flag_key))
1751                    .or_else(|| Some((Arc::clone(&f), out.cmds.len() - 1)));
1752                let mut forwarded = f.global.then(|| vec![word.clone()]);
1753                idx += 1;
1754
1755                // Only consume next word if flag takes an argument AND value isn't embedded
1756                // Example: "--dir foo" consumes "foo", but "--dir=foo" or "--verbose" do not
1757                if f.arg.is_some()
1758                    && !word.contains('=')
1759                    && idx < input.len()
1760                    && accepts_detached_flag_value(&f, &input[idx].word)
1761                {
1762                    if let Some(words) = forwarded.as_mut() {
1763                        words.push(input[idx].word.clone());
1764                    }
1765                    idx += 1;
1766                }
1767                if let Some(words) = forwarded {
1768                    apply_prefix_flag_overrides(&mut prefix_flags, Arc::clone(&f));
1769                    prefix_flags.push((f, words));
1770                }
1771            } else {
1772                // Unknown flag - stop looking for subcommands
1773                // Let the main parsing phase handle the error
1774                break;
1775            }
1776        } else {
1777            if variadic_flag_active && out.cmd.subcommand_precedence_over_arg {
1778                idx += 1;
1779                continue;
1780            }
1781            // Found a word that's not a flag or subcommand
1782            // Check if we should use the default_subcommand (only once, and only at the
1783            // root, which is the only place a spec can declare one — `out.cmds` holds just
1784            // the root until something descends). Without that second condition the one
1785            // declared name is looked up wherever the parser happens to be standing, so an
1786            // unrelated command acquires a default because a name matched one level down:
1787            // with `default_subcommand "ls"` at the top, `ex config zzz` descended into
1788            // `config ls`.
1789            if !used_default_subcommand && out.cmds.len() == 1 {
1790                if let Some(default_name) = &spec.default_subcommand {
1791                    if let Some(subcommand) = out
1792                        .cmd
1793                        .find_subcommand(default_name)
1794                        .filter(|_| default_accepts_word(&out.cmd, default_name, &input[idx].word))
1795                    {
1796                        if out.cmd.args_conflicts_with_subcommands && command_arg_found {
1797                            bail!(
1798                                "subcommand '{}' cannot be used with arguments on its parent command",
1799                                subcommand.name
1800                            );
1801                        }
1802                        let mut subcommand = subcommand.clone();
1803                        // Pass prefix words (global flags before this) to mount
1804                        subcommand.mount(&mount_prefix_words(&prefix_flags), mount_outputs)?;
1805                        let crossing_mount = subcommand.mounted && !out.cmd.mounted;
1806                        merge_subcommand_flags(
1807                            &mut out.available_flags,
1808                            gather_flags(&subcommand),
1809                            crossing_mount,
1810                        );
1811                        out.cmds.push(subcommand.clone());
1812                        out.cmd = subcommand.clone();
1813                        command_has_argv = true;
1814                        prefix_flags.clear();
1815                        command_arg_found = false;
1816                        variadic_flag_active = false;
1817                        // This descent ran the new command's mounts, so lazy
1818                        // discovery must not run them a second time.
1819                        mounts_resolved = true;
1820                        used_default_subcommand = true;
1821                        // Continue the loop to check if this word is a subcommand of the
1822                        // default subcommand (e.g., a task name added via mount).
1823                        // If it's not a subcommand, the next iteration will break and
1824                        // Phase 2 will handle it as a positional arg.
1825                        continue;
1826                    }
1827                }
1828            }
1829            // Sigil-classified positionals do not occupy the ordinary positional cursor and
1830            // therefore do not close subcommand routing. Phase 2 binds and strips them. A
1831            // default subcommand gets first refusal so interpreted and compiled routing agree
1832            // when the root sigil and the default command can both accept this word.
1833            if match_sigil_arg_chain(&out.cmds, &input[idx].word).is_some() {
1834                idx += 1;
1835                continue;
1836            }
1837            // An unmatched word that names no subcommand is forwarded as an external
1838            // command: this word, then every token after it, including flags. Known
1839            // subcommands already won above, and a default_subcommand already caught.
1840            // clap's `allow_external_subcommands` is this, not `unknown_flags=value`.
1841            if out.cmd.external_subcommand {
1842                let rest: Vec<Token> = input.drain(idx..).collect();
1843                for token in &rest {
1844                    trace.record(token.argv, TokenRole::External);
1845                }
1846                out.external = Some(rest.into_iter().map(|t| t.word).collect());
1847                break;
1848            }
1849            // This could be a positional argument, so stop subcommand search
1850            break;
1851        }
1852    }
1853
1854    // Phase 2: Main argument and flag parsing
1855    //
1856    // Now that we've identified all subcommands and executed their mounts,
1857    // we can parse the remaining arguments, flags, and their values.
1858
1859    // The cursor into the active positional arguments, kept as an index rather than a reference
1860    // because an explicit `--` may jump it *past* arguments that stay empty (see the `w == "--"`
1861    // arm).
1862    // With such a gap `out.args.len()` no longer equals the cursor, so anything asking "is this
1863    // argument filled?" has to consult `out.args` by key instead of counting.
1864    let mut next_arg_idx = cursor_skip_sigils(&out.cmd, 0);
1865    let mut enable_flags = true;
1866    let mut grouped_flag = false;
1867    // Whether an explicit `--` has been consumed *as a separator* (as opposed to being kept as a
1868    // value by `double_dash="preserve"`). Args declared `double_dash="required"` only accept
1869    // words that come after it — see `report_double_dash_violation`.
1870    let mut seen_double_dash = false;
1871    // Sigils are a leading-segment grammar. A restart begins a later segment but does not
1872    // reopen sigil classification for this invocation.
1873    let mut restart_seen = false;
1874    // Args already reported as having been offered a word before the `--` they require, so a
1875    // variadic one does not report the same violation for every word it is offered.
1876    let mut double_dash_violations: HashSet<String> = HashSet::new();
1877    // Scalar occurrences are scoped to the command level where they were written. Inherited
1878    // globals may therefore appear once before and once after a subcommand under clap's strict
1879    // `args_override_self(false)` policy. The bitset also keeps both forms of a negatable flag:
1880    // opposite forms may override one another, while repeating either spelling is an error.
1881    let mut scalar_occurrences: HashMap<(usize, usize), u8> = HashMap::new();
1882
1883    while !input.is_empty() {
1884        let token = input.pop_front().unwrap();
1885        // The flag this word was read as in Phase 1, if it skipped it (see `Token::binding`).
1886        let binding = token.binding;
1887        let argv = token.argv;
1888        let mut w = token.word;
1889        // A short's attached value is re-queued with `grouped_flag` set, and that
1890        // continuation is not a following word. `require_equals` refuses only the
1891        // following word; `-i9229` and `-i=9229` still bind. `default_missing` binds
1892        // only when the value is actually missing, so `-cnever` is still `never`.
1893        let attached_continuation = grouped_flag;
1894        // The opt-in lookahead uses declared value arity, so binding must consume
1895        // the same attached value even after the default changed flag scope.
1896        // Other specs retain the reference parser's legacy short-bundle rules.
1897        if spec.default_subcommand_flags
1898            && attached_continuation
1899            && !out.flag_awaiting_value.is_empty()
1900        {
1901            grouped_flag = false;
1902            w.remove(0);
1903            if w.starts_with('=') {
1904                w.remove(0);
1905            }
1906            if bind_pending_flag_value(
1907                spec,
1908                &out.cmd,
1909                &mut out.errors,
1910                &mut out.flags,
1911                &mut out.flag_awaiting_value,
1912                &mut w,
1913                &mut input,
1914                custom_env,
1915                trace,
1916                argv,
1917                true,
1918            )? {
1919                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
1920                return Ok((out, overridden_flags));
1921            }
1922            continue;
1923        }
1924
1925        // A clause boundary is syntax even after an automatic trailing argument disabled
1926        // flags. Only an explicit `--` protects a literal separator.
1927        if !seen_double_dash {
1928            if let Some(clause) = out.cmd.clause.as_ref() {
1929                if clause.separator.as_deref() == Some(w.as_str()) {
1930                    while try_bind_default_missing(
1931                        &mut out.flags,
1932                        &mut out.flag_awaiting_value,
1933                        custom_env,
1934                        &mut out.flag_origins,
1935                    )? {}
1936                    if let Some(flag) = out.flag_awaiting_value.first() {
1937                        let spelling = flag
1938                            .long
1939                            .first()
1940                            .map(|long| format!("--{long}"))
1941                            .or_else(|| flag.short.first().map(|short| format!("-{short}")))
1942                            .unwrap_or_else(|| flag.name.clone());
1943                        return Err(UsageErr::InvalidFlag {
1944                            token: spelling.clone(),
1945                            reason: "requires an argument".to_string(),
1946                            span: (0, spelling.len()).into(),
1947                            input: spelling,
1948                        }
1949                        .into());
1950                    }
1951                    let name = clause.name.clone();
1952                    finalize_current_clause(&mut out);
1953                    out.arg_origins.clear();
1954                    trace.record(argv, TokenRole::ClauseSeparator { name });
1955                    next_arg_idx = 0;
1956                    out.flag_awaiting_value.clear();
1957                    reset_clause_scalar_occurrences(&out, &mut scalar_occurrences);
1958                    enable_flags = true;
1959                    seen_double_dash = false;
1960                    continue;
1961                }
1962            }
1963        }
1964
1965        // Check for restart_token - resets argument parsing for multiple command invocations
1966        // e.g., `mise run lint ::: test ::: check` with restart_token=":::"
1967        if let Some(ref restart_token) = out.cmd.restart_token {
1968            if w == *restart_token {
1969                // Reset argument parsing state for a fresh command invocation, keeping the
1970                // flags. `double_dash_violations` is deliberately *not* cleared: `out.errors`
1971                // is not cleared here either, so clearing it would let one arg report the same
1972                // violation once per invocation.
1973                out.args.clear();
1974                // With the values gone, so is where they came from — otherwise the second
1975                // invocation of `run lint ::: test` reports the first one's provenance. The
1976                // token trace is *not* cleared: those words were read, and a report that
1977                // dropped them would show a command line with a hole in it.
1978                out.arg_origins.clear();
1979                trace.record(argv, TokenRole::Restart);
1980                next_arg_idx = cursor_skip_sigils(&out.cmd, 0);
1981                restart_seen = true;
1982                out.flag_awaiting_value.clear(); // Clear any pending flag values
1983                enable_flags = true; // Reset -- separator effect
1984                seen_double_dash = false; // The next invocation needs its own `--`
1985                continue;
1986            }
1987        }
1988
1989        // A flag declared `allow_hyphen_values` takes the next token whatever it looks
1990        // like, and that has to be asked before the separator arm below rather than
1991        // after it. Asked after, a `--` was consumed as a separator while the flag
1992        // stayed hungry, and the flag then ate the word past it: `ex -a -- -x` bound
1993        // `-x` and the separator was simply gone. Asked here, the flag takes the `--`
1994        // itself, which is what clap does with the same declaration — and no flag can
1995        // still be waiting once the separator has done its job, so the starvation rule
1996        // below has no path around it.
1997        if enable_flags
1998            && !attached_continuation
1999            && w.starts_with('-')
2000            && out
2001                .flag_awaiting_value
2002                .last()
2003                .is_some_and(|flag| accepts_detached_flag_value(flag, &w))
2004        {
2005            // A variadic argument collects here too: which token supplied its first
2006            // value says nothing about how many it takes.
2007            let should_return = bind_pending_flag_value(
2008                spec,
2009                &out.cmd,
2010                &mut out.errors,
2011                &mut out.flags,
2012                &mut out.flag_awaiting_value,
2013                &mut w,
2014                &mut input,
2015                custom_env,
2016                trace,
2017                argv,
2018                // The token a hyphen-valued flag takes is the following word, never attached.
2019                false,
2020            )?;
2021            if should_return {
2022                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2023                return Ok((out, overridden_flags));
2024            }
2025            continue;
2026        }
2027
2028        // A flag whose value may be omitted that cannot take this token as a detached
2029        // value finishes bare and leaves the token for whatever comes next:
2030        // `--color --verbose` colours with the missing value and still sets verbose,
2031        // and `--inspect 9229` with `require_equals` binds the missing value rather
2032        // than treating 9229 as the port.
2033        if enable_flags
2034            && !attached_continuation
2035            && !out.flag_awaiting_value.is_empty()
2036            && out.flag_awaiting_value.last().is_some_and(|flag| {
2037                (flag.default_missing.is_some() || flag.value_optional)
2038                    && !accepts_detached_flag_value(flag, &w)
2039            })
2040        {
2041            try_bind_default_missing(
2042                &mut out.flags,
2043                &mut out.flag_awaiting_value,
2044                custom_env,
2045                &mut out.flag_origins,
2046            )?;
2047        }
2048
2049        // The first explicit `--` is still a separator after an `automatic` argument has
2050        // stopped flag parsing. Once an explicit separator has done its job, a second one is
2051        // an ordinary value: every parser worth comparing against keeps it (POSIX getopt,
2052        // argparse, clap, commander, yargs), and jdx/usage#229 was a user reporting the old
2053        // behavior as the bug it is.
2054        if w == "--" && !seen_double_dash {
2055            enable_flags = false;
2056
2057            // Only preserve the double dash token if we're collecting values for a variadic arg
2058            // in double_dash == `preserve` mode
2059            let should_preserve = active_args(&out.cmd)
2060                .get(next_arg_idx)
2061                .map(|arg| arg.var && arg.double_dash == SpecDoubleDashChoices::Preserve)
2062                .unwrap_or(false);
2063
2064            if should_preserve {
2065                // Fall through to arg parsing. This `--` is a *value*, not a separator, so it
2066                // neither counts as one nor unlocks a `double_dash="required"` arg.
2067            } else {
2068                seen_double_dash = true;
2069                trace.record(argv, TokenRole::Separator);
2070
2071                // Everything after an explicit `--` belongs to the arg that requires one, so
2072                // jump the cursor there — past any earlier arg, including a greedy variadic
2073                // that would otherwise swallow the rest. This mirrors clap's `Arg::last(true)`,
2074                // which is what `double_dash="required"` is generated from. Specs without such
2075                // an arg find nothing and keep the cursor where it was.
2076                let target = active_args(&out.cmd).iter().position(|arg| {
2077                    arg.double_dash == SpecDoubleDashChoices::Required
2078                        && !out.args.contains_key(arg)
2079                });
2080                if let Some(target) = target {
2081                    // Forward only. An unfilled required arg declared *before* the cursor is
2082                    // left where it is rather than rewound to — words already assigned to
2083                    // later args would have to be taken back for that to mean anything, and
2084                    // the arg keeps its `MissingArg`. `double_dash="required"` mirrors clap's
2085                    // `Arg::last(true)`, which is the final positional, so a spec that puts
2086                    // one ahead of others is already outside what this models.
2087                    if target > next_arg_idx {
2088                        next_arg_idx = target;
2089                    }
2090                }
2091                continue;
2092            }
2093        }
2094
2095        // long flags
2096        if enable_flags && w.starts_with("--") {
2097            grouped_flag = false;
2098            // `Some` only when an `=` was actually written, so `--jobs=` can supply
2099            // an empty value while `--jobs` supplies none. Collapsing the two lost
2100            // the flag entirely.
2101            let split = w.split_once('=');
2102            let word = split.map(|(word, _)| word).unwrap_or(&w);
2103            let bound_flag = binding.as_ref().map(|(flag, _)| flag);
2104            if let Some(f) = bound_flag.or_else(|| out.available_flags.get(word)) {
2105                let command_level = binding
2106                    .as_ref()
2107                    .map(|(_, level)| *level)
2108                    .unwrap_or(out.cmds.len() - 1);
2109                parsed_flag_spellings
2110                    .entry(Arc::as_ptr(f) as usize)
2111                    .or_default()
2112                    .insert(word.to_string());
2113                // Recorded before the action check below: a token that named a flag named it
2114                // whether or not the parse can carry on afterwards.
2115                trace.record(
2116                    argv,
2117                    TokenRole::Flag {
2118                        flag: Arc::clone(f),
2119                        spelling: word.to_string(),
2120                        negated: f.negate.as_deref() == Some(word),
2121                    },
2122                );
2123                if f.action != crate::SpecFlagAction::Set {
2124                    out.errors.push(render_action_err(spec, &out.cmd, f, word));
2125                    record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2126                    return Ok((out, overridden_flags));
2127                }
2128                apply_flag_overrides(
2129                    f,
2130                    &out.available_flags,
2131                    &mut out.flags,
2132                    &mut out.flag_awaiting_value,
2133                    &mut overridden_flags,
2134                    &mut out.overridden_flags,
2135                );
2136                if let Some(pending) = out.flag_awaiting_value.first() {
2137                    out.errors.push(render_missing_flag_value(pending, &w));
2138                    record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2139                    return Ok((out, overridden_flags));
2140                }
2141                // An attached value only means something to a flag that takes one:
2142                // `--jobs=` is an empty string, while `--force=yes` has nothing to
2143                // give a flag that holds no value. Handing that leftover to the
2144                // positionals would re-split one token into two, so `ex --force=yes`
2145                // would fill an argument the caller never typed a word for.
2146                if f.arg.is_some() {
2147                    record_scalar_flag_occurrence(
2148                        &out.cmds,
2149                        f,
2150                        command_level,
2151                        None,
2152                        &mut scalar_occurrences,
2153                        &mut out.errors,
2154                    );
2155                    let f = Arc::clone(f);
2156                    out.flag_awaiting_value.push(Arc::clone(&f));
2157                    // The `=` has already settled that this text is the value, so it
2158                    // binds here rather than going back on the queue to be read as a
2159                    // token again — where `--jobs=--force` looked like a flag of its
2160                    // own and bound `force`, leaving `jobs` unset.
2161                    if let Some((_, val)) = split {
2162                        // The `=` settles where the *first* value came from and nothing
2163                        // more, so a variadic argument goes on collecting from the words
2164                        // after it exactly as the detached form does.
2165                        let mut val = val.to_string();
2166                        let should_return = bind_pending_flag_value(
2167                            spec,
2168                            &out.cmd,
2169                            &mut out.errors,
2170                            &mut out.flags,
2171                            &mut out.flag_awaiting_value,
2172                            &mut val,
2173                            &mut input,
2174                            custom_env,
2175                            trace,
2176                            argv,
2177                            // The `=` settled that this text is the value, so it rode in on
2178                            // the flag's own token.
2179                            true,
2180                        )?;
2181                        if should_return {
2182                            record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2183                            return Ok((out, overridden_flags));
2184                        }
2185                    }
2186                } else if f.count {
2187                    let arr = out
2188                        .flags
2189                        .entry(Arc::clone(f))
2190                        .or_insert_with(|| ParseValue::MultiBool(vec![]))
2191                        .try_as_multi_bool_mut()
2192                        .unwrap();
2193                    arr.push(true);
2194                } else {
2195                    let negate = f.negate.clone().unwrap_or_default();
2196                    let negated_form = word == negate;
2197                    let value = if f.bool_value {
2198                        match split.map(|(_, value)| value) {
2199                            Some("true") => !negated_form,
2200                            Some("false") => negated_form,
2201                            Some(value) => {
2202                                out.errors.push(UsageErr::InvalidValue {
2203                                    name: f.name.clone(),
2204                                    value: value.to_string(),
2205                                    reason: "expected `true` or `false`".to_string(),
2206                                });
2207                                continue;
2208                            }
2209                            None => !negated_form,
2210                        }
2211                    } else {
2212                        !negated_form
2213                    };
2214                    // Which form was typed is a question about the name, so it is
2215                    // asked of `word` rather than the whole token: the attached value
2216                    // is dropped just above, and comparing `--no-color=yes` against
2217                    // `--no-color` would take the negation down with it.
2218                    record_scalar_flag_occurrence(
2219                        &out.cmds,
2220                        f,
2221                        command_level,
2222                        Some(!negated_form),
2223                        &mut scalar_occurrences,
2224                        &mut out.errors,
2225                    );
2226                    out.flags.insert(Arc::clone(f), ParseValue::Bool(value));
2227                }
2228                continue;
2229            }
2230            if is_help_arg(spec, &out.cmd, &w) {
2231                out.errors
2232                    .push(render_help_err(spec, &out.cmd, w.len() > 2));
2233                trace.record(
2234                    argv,
2235                    TokenRole::Builtin {
2236                        spelling: w.clone(),
2237                    },
2238                );
2239                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2240                return Ok((out, overridden_flags));
2241            }
2242            if is_version_arg(spec, &out.cmds, &w) {
2243                out.errors.push(render_version_err(spec, w.len() > 2));
2244                trace.record(
2245                    argv,
2246                    TokenRole::Builtin {
2247                        spelling: w.clone(),
2248                    },
2249                );
2250                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2251                return Ok((out, overridden_flags));
2252            }
2253            if let Err(refused) = reject_unknown_flag_if_asked(spec, &out.cmds, &w) {
2254                trace.record(argv, TokenRole::UnknownFlag { bound_as: None });
2255                trace.close(&input);
2256                return Err(refused.into());
2257            }
2258        }
2259
2260        // short flags
2261        //
2262        // A fresh token is checked whole before any of it is applied: `-az` with only
2263        // `-a` declared is not a bundle at all, so it must not set `a` on the way to
2264        // discovering that `z` names nothing. A grouped continuation is exempt — its
2265        // token was already checked when it arrived.
2266        let declared_numeric_short = declared_numeric_short(&out.available_flags, &w);
2267        let positional_negative_number = !declared_numeric_short
2268            && is_negative_number(&w)
2269            && active_args(&out.cmd)
2270                .get(next_arg_idx)
2271                .is_some_and(|arg| arg.allow_negative_numbers);
2272        if enable_flags
2273            && !grouped_flag
2274            // A word phase 1 already resolved to a flag needs no re-checking, and
2275            // the flags in scope have changed since, so re-checking would be wrong.
2276            && binding.is_none()
2277            && w.starts_with('-')
2278            && w.len() > 1
2279            && is_flag_like(&w)
2280            && !positional_negative_number
2281            && !short_bundle_is_known(spec, &out.cmds, &out.available_flags, &w)
2282        {
2283            // Refused if this command asked for that; otherwise it carries on below
2284            // as one word, with none of its letters applied.
2285            if let Err(refused) = reject_unknown_flag_if_asked(spec, &out.cmds, &w) {
2286                trace.record(argv, TokenRole::UnknownFlag { bound_as: None });
2287                trace.close(&input);
2288                return Err(refused.into());
2289            }
2290        } else if enable_flags && !positional_negative_number && w.starts_with('-') && w.len() > 1 {
2291            let short = w.chars().nth(1).unwrap();
2292            if let Some(f) = binding
2293                .as_ref()
2294                .map(|(flag, _)| flag)
2295                .or_else(|| out.available_flags.get(&format!("-{short}")))
2296            {
2297                let command_level = binding
2298                    .as_ref()
2299                    .map(|(_, level)| *level)
2300                    .unwrap_or(out.cmds.len() - 1);
2301                if f.action != crate::SpecFlagAction::Set {
2302                    out.errors
2303                        .push(render_action_err(spec, &out.cmd, f, &format!("-{short}")));
2304                    record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2305                    return Ok((out, overridden_flags));
2306                }
2307                parsed_flag_spellings
2308                    .entry(Arc::as_ptr(f) as usize)
2309                    .or_default()
2310                    .insert(format!("-{short}"));
2311                trace.record(
2312                    argv,
2313                    TokenRole::Flag {
2314                        flag: Arc::clone(f),
2315                        spelling: format!("-{short}"),
2316                        // A short spelling is never the negated form: `negate` is a long.
2317                        negated: false,
2318                    },
2319                );
2320                apply_flag_overrides(
2321                    f,
2322                    &out.available_flags,
2323                    &mut out.flags,
2324                    &mut out.flag_awaiting_value,
2325                    &mut overridden_flags,
2326                    &mut out.overridden_flags,
2327                );
2328                if !attached_continuation {
2329                    if let Some(pending) = out.flag_awaiting_value.first() {
2330                        out.errors.push(render_missing_flag_value(pending, &w));
2331                        record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2332                        return Ok((out, overridden_flags));
2333                    }
2334                }
2335                let rest = &w[1 + short.len_utf8()..];
2336                if !rest.is_empty() {
2337                    // `-abc` is one token that names three flags, so the tail is read at the
2338                    // bundle's own position rather than at one of its own.
2339                    let mut tail = Token::new(format!("-{rest}"), argv);
2340                    if f.arg.is_none() {
2341                        tail.binding = default_bundle
2342                            .as_ref()
2343                            .filter(|bundle| bundle.argv == argv)
2344                            .and_then(|bundle| bundle.binding(get_flag_key(&tail.word)));
2345                    }
2346                    input.push_front(tail);
2347                }
2348                // A fully consumed short is no longer a grouped continuation.
2349                // Leaving this set after `-ai` made `-i` skip `require_equals`
2350                // and bind the following word.
2351                grouped_flag = !rest.is_empty();
2352                if f.arg.is_some() {
2353                    record_scalar_flag_occurrence(
2354                        &out.cmds,
2355                        f,
2356                        command_level,
2357                        None,
2358                        &mut scalar_occurrences,
2359                        &mut out.errors,
2360                    );
2361                    out.flag_awaiting_value.push(Arc::clone(f));
2362                } else if f.count {
2363                    let arr = out
2364                        .flags
2365                        .entry(Arc::clone(f))
2366                        .or_insert_with(|| ParseValue::MultiBool(vec![]))
2367                        .try_as_multi_bool_mut()
2368                        .unwrap();
2369                    arr.push(true);
2370                } else {
2371                    let negate = f.negate.clone().unwrap_or_default();
2372                    let value = w != negate;
2373                    record_scalar_flag_occurrence(
2374                        &out.cmds,
2375                        f,
2376                        command_level,
2377                        Some(value),
2378                        &mut scalar_occurrences,
2379                        &mut out.errors,
2380                    );
2381                    out.flags.insert(Arc::clone(f), ParseValue::Bool(value));
2382                }
2383                continue;
2384            }
2385            // The letter nothing declared may still be one the parser supplies, and it may
2386            // sit anywhere in the token: `-hv` asks for help as surely as `-vh` does, and
2387            // neither reaches the whole-token spellings below.
2388            if let Some(err) = supplied_short(spec, &out.cmds, short) {
2389                out.errors.push(err);
2390                trace.record(
2391                    argv,
2392                    TokenRole::Builtin {
2393                        spelling: format!("-{short}"),
2394                    },
2395                );
2396                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2397                return Ok((out, overridden_flags));
2398            }
2399            if is_help_arg(spec, &out.cmd, &w) {
2400                out.errors
2401                    .push(render_help_err(spec, &out.cmd, w.len() > 2));
2402                trace.record(
2403                    argv,
2404                    TokenRole::Builtin {
2405                        spelling: w.clone(),
2406                    },
2407                );
2408                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2409                return Ok((out, overridden_flags));
2410            }
2411            if is_version_arg(spec, &out.cmds, &w) {
2412                out.errors.push(render_version_err(spec, w.len() > 2));
2413                trace.record(
2414                    argv,
2415                    TokenRole::Builtin {
2416                        spelling: w.clone(),
2417                    },
2418                );
2419                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2420                return Ok((out, overridden_flags));
2421            }
2422            if let Err(refused) = reject_unknown_flag_if_asked(spec, &out.cmds, &w) {
2423                trace.record(argv, TokenRole::UnknownFlag { bound_as: None });
2424                trace.close(&input);
2425                return Err(refused.into());
2426            }
2427            if grouped_flag {
2428                grouped_flag = false;
2429                w.remove(0);
2430                // What is left is a short flag's attached value, and one `=` between
2431                // the letter and the value is a separator: `-j=8` means 8. Only one,
2432                // so `-j==8` still means `=8`.
2433                if !out.flag_awaiting_value.is_empty() && w.starts_with('=') {
2434                    w.remove(0);
2435                }
2436            }
2437        }
2438
2439        // Only while flags are still being read. A flag still waiting when the separator
2440        // was consumed is starved: its value would have to come from after the `--`,
2441        // where every token is data. Draining there gave `ex --jobs -- x` the word after
2442        // the separator, so the command line quietly meant `ex --jobs=x` and the `--`
2443        // was gone. Left waiting, it is reported as the missing value it is.
2444        // `require_equals` refuses a detached value: `--flag value` is a missing
2445        // value, not a flag of `"value"`. The attached form is still bound above.
2446        // Reported here rather than left waiting until the end of the line: falling
2447        // through would offer `value` to the positionals and call it an unexpected
2448        // word, which is the wrong error and a different one from usage-argv.
2449        if enable_flags
2450            && !attached_continuation
2451            && !out.flag_awaiting_value.is_empty()
2452            && out
2453                .flag_awaiting_value
2454                .last()
2455                .is_some_and(|flag| flag.require_equals)
2456        {
2457            let flag = out.flag_awaiting_value.last().unwrap();
2458            let token = flag
2459                .long
2460                .first()
2461                .map(|l| format!("--{l}"))
2462                .or_else(|| flag.short.first().map(|s| format!("-{s}")))
2463                .unwrap_or_else(|| flag.name.clone());
2464            out.errors.push(UsageErr::InvalidFlag {
2465                token: token.clone(),
2466                reason: "requires an argument".to_string(),
2467                span: (0, 0).into(),
2468                input: format!("{token} {w}"),
2469            });
2470            record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2471            return Ok((out, overridden_flags));
2472        }
2473        if enable_flags
2474            && !out.flag_awaiting_value.is_empty()
2475            && (attached_continuation
2476                || out
2477                    .flag_awaiting_value
2478                    .last()
2479                    .is_some_and(|flag| accepts_detached_flag_value(flag, &w)))
2480        {
2481            // Held before the drain pops it: a flag whose argument is variadic keeps
2482            // taking values after this first one.
2483            let should_return = bind_pending_flag_value(
2484                spec,
2485                &out.cmd,
2486                &mut out.errors,
2487                &mut out.flags,
2488                &mut out.flag_awaiting_value,
2489                &mut w,
2490                &mut input,
2491                custom_env,
2492                trace,
2493                argv,
2494                attached_continuation,
2495            )?;
2496            if should_return {
2497                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2498                return Ok((out, overridden_flags));
2499            }
2500            continue;
2501        }
2502
2503        if let Some((arg, sigil, value)) = (enable_flags && !restart_seen)
2504            .then(|| match_sigil_arg_chain(&out.cmds, &w))
2505            .flatten()
2506        {
2507            if value.is_empty() {
2508                out.errors.push(UsageErr::InvalidValue {
2509                    name: arg.name.clone(),
2510                    value: w.clone(),
2511                    reason: format!("expected a value after sigil {sigil:?}"),
2512                });
2513                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2514                return Ok((out, overridden_flags));
2515            }
2516            let trailing_value = arg.double_dash == SpecDoubleDashChoices::Automatic;
2517            let suppress_trailing_delimiter =
2518                out.cmds.iter().any(|cmd| cmd.dont_delimit_trailing_values);
2519            let delimiter = if suppress_trailing_delimiter && trailing_value {
2520                None
2521            } else {
2522                arg.delimiter
2523            };
2524            let parts = match delimiter {
2525                Some(delimiter) => value
2526                    .split(delimiter)
2527                    .map(str::to_string)
2528                    .collect::<Vec<_>>(),
2529                None => vec![value.to_string()],
2530            };
2531            let mut refused = false;
2532            for part in &parts {
2533                if validate_choices(
2534                    spec,
2535                    &out.cmd,
2536                    &mut out.errors,
2537                    ChoiceTarget::arg(arg),
2538                    part,
2539                    arg.choices.as_ref(),
2540                    custom_env,
2541                )? {
2542                    refused = true;
2543                    break;
2544                }
2545            }
2546            if refused {
2547                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2548                return Ok((out, overridden_flags));
2549            }
2550            trace.record(
2551                argv,
2552                TokenRole::Sigil {
2553                    arg: Arc::new(arg.clone()),
2554                    sigil: sigil.to_string(),
2555                    values: parts.clone(),
2556                },
2557            );
2558            let key = Arc::new(arg.clone());
2559            if arg.var {
2560                let arr = out
2561                    .args
2562                    .entry(key)
2563                    .or_insert_with(|| ParseValue::MultiString(vec![]))
2564                    .try_as_multi_string_mut()
2565                    .unwrap();
2566                arr.extend(parts);
2567            } else {
2568                out.args.insert(key, ParseValue::String(value.to_string()));
2569            }
2570            continue;
2571        }
2572
2573        if out.cmd.allow_missing_positional {
2574            next_arg_idx = cursor_skip_sigils(&out.cmd, next_arg_idx);
2575            while let Some(current) = active_args(&out.cmd).get(next_arg_idx) {
2576                if current.required || out.args.contains_key(current) {
2577                    break;
2578                }
2579                let required_after = active_args(&out.cmd)[next_arg_idx + 1..]
2580                    .iter()
2581                    .filter(|arg| arg.required && arg.sigil.is_none())
2582                    .count();
2583                if required_after == 0 {
2584                    break;
2585                }
2586                let remaining_values = 1 + input
2587                    .iter()
2588                    .filter(|token| {
2589                        (!enable_flags || !is_flag_like(&token.word))
2590                            && (!enable_flags
2591                                || restart_seen
2592                                || match_sigil_arg_chain(&out.cmds, &token.word).is_none())
2593                    })
2594                    .count();
2595                if remaining_values > required_after {
2596                    break;
2597                }
2598                next_arg_idx = cursor_skip_sigils(&out.cmd, next_arg_idx + 1);
2599            }
2600        }
2601
2602        if let Some(arg) = active_args(&out.cmd).get(next_arg_idx) {
2603            if arg.var
2604                && out.args.contains_key(arg)
2605                && arg.value_terminator.as_deref() == Some(w.as_str())
2606            {
2607                trace.record(
2608                    argv,
2609                    TokenRole::ValueTerminator {
2610                        ends: arg.name.clone(),
2611                    },
2612                );
2613                next_arg_idx += 1;
2614                continue;
2615            }
2616            // Before anything else: an arg that requires `--` accepts nothing until one has been
2617            // seen. Checking ahead of `validate_choices` keeps a discarded word from also being
2618            // reported as an invalid choice, and from reaching that function's help escape.
2619            if arg.double_dash == SpecDoubleDashChoices::Required && !seen_double_dash {
2620                report_double_dash_violation(arg, &mut out.errors, &mut double_dash_violations);
2621                trace.record(
2622                    argv,
2623                    TokenRole::Refused {
2624                        reason: format!("{} only accepts words after `--`", arg.name),
2625                    },
2626                );
2627                // Drop the word without filling the arg or advancing the cursor: every later
2628                // word hits the same arg and is rejected the same way, so the parse still ends
2629                // in an error rather than in `unexpected word`.
2630                continue;
2631            }
2632            // Split before judging, as the flag path does: after the split the word is
2633            // no longer one value, and `choices` has to be asked about each. Judging
2634            // first rejects `src:docs` against a list that both halves are on, and
2635            // names the whole word rather than the half that was wrong.
2636            let trailing_value =
2637                seen_double_dash || arg.double_dash == SpecDoubleDashChoices::Automatic;
2638            let suppress_trailing_delimiter =
2639                out.cmds.iter().any(|cmd| cmd.dont_delimit_trailing_values);
2640            let delimiter = if suppress_trailing_delimiter && trailing_value {
2641                None
2642            } else {
2643                arg.delimiter
2644            };
2645            let parts: Vec<String> = match delimiter {
2646                Some(delimiter) => w.split(delimiter).map(str::to_string).collect(),
2647                None => vec![w.clone()],
2648            };
2649            let mut refused = false;
2650            for part in &parts {
2651                if validate_choices(
2652                    spec,
2653                    &out.cmd,
2654                    &mut out.errors,
2655                    ChoiceTarget::arg(arg),
2656                    part,
2657                    arg.choices.as_ref(),
2658                    custom_env,
2659                )? {
2660                    refused = true;
2661                    break;
2662                }
2663            }
2664            if refused {
2665                record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2666                return Ok((out, overridden_flags));
2667            }
2668            // `double_dash="automatic"` means the first value this arg takes is the last
2669            // token read as anything but data: a wrapper declaring it can forward flags
2670            // without its caller typing a `--`. Set before the value is stored, so the
2671            // rest of the command line is already past flag parsing.
2672            if arg.double_dash == SpecDoubleDashChoices::Automatic {
2673                enable_flags = false;
2674            }
2675            // A flag-like word reaching a positional while flags are still being read was
2676            // offered to every declaration and matched none: under the default
2677            // `unknown_flags="value"` it becomes data, and saying so is the difference
2678            // between "you have a typo" and "this argument took your typo".
2679            let unknown_flag = enable_flags
2680                && !positional_negative_number
2681                && is_flag_like(&w)
2682                && binding.is_none();
2683            trace.record(
2684                argv,
2685                if unknown_flag {
2686                    TokenRole::UnknownFlag {
2687                        bound_as: Some(Arc::new(arg.clone())),
2688                    }
2689                } else {
2690                    TokenRole::Arg {
2691                        arg: Arc::new(arg.clone()),
2692                        values: parts.clone(),
2693                    }
2694                },
2695            );
2696            if arg.var {
2697                let arr = out
2698                    .args
2699                    .entry(Arc::new(arg.clone()))
2700                    .or_insert_with(|| ParseValue::MultiString(vec![]))
2701                    .try_as_multi_string_mut()
2702                    .unwrap();
2703                // The values this word carried, split above so that everything
2704                // downstream — `choices`, `var_max` stopping the collection, `var_min` —
2705                // counts the values the user meant rather than the words they typed.
2706                arr.extend(parts.iter().cloned());
2707                if arr.len() >= arg.var_max.unwrap_or(usize::MAX) {
2708                    next_arg_idx += 1;
2709                }
2710            } else {
2711                out.args
2712                    .insert(Arc::new(arg.clone()), ParseValue::String(w));
2713                next_arg_idx += 1;
2714            }
2715            if out
2716                .cmd
2717                .clause
2718                .as_ref()
2719                .is_some_and(|clause| clause.separator.is_none())
2720                && next_arg_idx >= active_args(&out.cmd).len()
2721            {
2722                while try_bind_default_missing(
2723                    &mut out.flags,
2724                    &mut out.flag_awaiting_value,
2725                    custom_env,
2726                    &mut out.flag_origins,
2727                )? {}
2728                if let Some(flag) = out.flag_awaiting_value.first() {
2729                    let spelling = flag
2730                        .long
2731                        .first()
2732                        .map(|long| format!("--{long}"))
2733                        .or_else(|| flag.short.first().map(|short| format!("-{short}")))
2734                        .unwrap_or_else(|| flag.name.clone());
2735                    return Err(UsageErr::InvalidFlag {
2736                        token: spelling.clone(),
2737                        reason: "requires an argument".to_string(),
2738                        span: (0, spelling.len()).into(),
2739                        input: spelling,
2740                    }
2741                    .into());
2742                }
2743                let name = out.cmd.clause.as_ref().unwrap().name.clone();
2744                finalize_current_clause(&mut out);
2745                out.arg_origins.clear();
2746                trace.record(argv, TokenRole::ClauseSeparator { name });
2747                next_arg_idx = 0;
2748                out.flag_awaiting_value.clear();
2749                reset_clause_scalar_occurrences(&out, &mut scalar_occurrences);
2750                enable_flags = true;
2751                seen_double_dash = false;
2752            }
2753            continue;
2754        }
2755        if is_help_arg(spec, &out.cmd, &w) {
2756            out.errors
2757                .push(render_help_err(spec, &out.cmd, w.len() > 2));
2758            trace.record(
2759                argv,
2760                TokenRole::Builtin {
2761                    spelling: w.clone(),
2762                },
2763            );
2764            record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2765            return Ok((out, overridden_flags));
2766        }
2767        if is_version_arg(spec, &out.cmds, &w) {
2768            out.errors.push(render_version_err(spec, w.len() > 2));
2769            trace.record(
2770                argv,
2771                TokenRole::Builtin {
2772                    spelling: w.clone(),
2773                },
2774            );
2775            record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2776            return Ok((out, overridden_flags));
2777        }
2778        trace.record(
2779            argv,
2780            TokenRole::Refused {
2781                reason: "no declaration takes this word".to_string(),
2782            },
2783        );
2784        trace.close(&input);
2785        bail!("unexpected word: {w}");
2786    }
2787
2788    record_stop(&mut out, next_arg_idx, seen_double_dash, trace, &input);
2789    if validate_clauses {
2790        validate_clause_relationships(&mut out, &overridden_flags, custom_env, None);
2791    }
2792    let clause_flag_names = out
2793        .cmd
2794        .clause
2795        .iter()
2796        .flat_map(|clause| &clause.flags)
2797        .map(|flag| flag.name.clone())
2798        .collect::<HashSet<_>>();
2799
2800    // `out.flags` is keyed by `SpecFlag`, whose equality is intentionally name-only. Two
2801    // declarations with the same canonical name therefore share one public value entry even
2802    // when both were typed. The spelling ledger is keyed by declaration identity and retains
2803    // both, which is what exclusivity needs.
2804    let flag_was_parsed =
2805        |flag: &Arc<SpecFlag>| parsed_flag_spellings.contains_key(&(Arc::as_ptr(flag) as usize));
2806
2807    // The spellings the selected command's own declaration speaks for, on this object.
2808    //
2809    // Empty unless that declaration really is this object's: a parent and child may each
2810    // declare `--clean` without merging, leaving two flags that share a name, and the
2811    // ancestor's must not be read as the child's. The test is whether every spelling the child
2812    // declared resolves back here — true of a merged flag, and of a plain local one, but not of
2813    // an ancestor whose long form the child took over.
2814    let child_spellings = |flag: &Arc<SpecFlag>| -> HashSet<String> {
2815        let declared: HashSet<String> = out
2816            .cmd
2817            .flags
2818            .iter()
2819            .filter(|declared| declared.name == flag.name)
2820            .flat_map(flag_keys)
2821            .collect();
2822        // *Any* of them, not all. All was too strong: a child may declare a spelling that
2823        // some other inherited global already owns — `-c --clean` beside an inherited
2824        // `-c --config` — and that collision is resolved in the other global's favor, so the
2825        // child's `-c` resolves elsewhere. Requiring every spelling to land here let one
2826        // unrelated collision disown the child from the `--clean` it plainly does own.
2827        //
2828        // Still enough to tell the two-object case apart, which is what this guards: when a
2829        // child re-declares a global as global, the child's own spellings resolve to the
2830        // child's separate flag, so none of them lands on the ancestor's.
2831        let speaks_for_this_flag = declared.iter().any(|spelling| {
2832            out.available_flags
2833                .get(spelling)
2834                .is_some_and(|available| Arc::ptr_eq(available, flag))
2835        });
2836        if speaks_for_this_flag {
2837            declared
2838        } else {
2839            HashSet::new()
2840        }
2841    };
2842
2843    // Whose `exclusive` an occurrence activates, as `(the child's, an ancestor's)`.
2844    //
2845    // A child that re-declares an inherited global merges into one object answering to two
2846    // alias sets whose declarations may disagree, so there is no single owner to name: the
2847    // child owns the spellings it declared and the ancestor keeps the ones only it declared.
2848    // Both sides can be in play at once — `run -c --clean` is the ancestor's alias and the
2849    // child's in one invocation — and each carries its own declaration's answer.
2850    let exclusivity_in_play = |flag: &Arc<SpecFlag>| -> (bool, bool) {
2851        let child = child_spellings(flag);
2852        let child_exclusive = !child.is_empty()
2853            && out
2854                .cmd
2855                .flags
2856                .iter()
2857                .any(|declared| declared.name == flag.name && declared.exclusive);
2858        match parsed_flag_spellings.get(&(Arc::as_ptr(flag) as usize)) {
2859            Some(spellings) => (
2860                child_exclusive && spellings.iter().any(|s| child.contains(s)),
2861                flag.exclusive && spellings.iter().any(|s| !child.contains(s)),
2862            ),
2863            // An environment value has no spelling to attribute it by. The declaration the
2864            // selected command has in scope is the one that answers — which is the child's
2865            // when it re-declared the flag, and the ancestor's when it did not.
2866            None => (child_exclusive, flag.exclusive && child.is_empty()),
2867        }
2868    };
2869
2870    let exclusive_occurrence = |flag: &Arc<SpecFlag>| {
2871        let (child, ancestor) = exclusivity_in_play(flag);
2872        child || ancestor
2873    };
2874
2875    // clap's `exclusive` is also an escape from requiredness: `--version` has to work on a
2876    // command that otherwise needs an input. Companions are still diagnosed below, but an
2877    // exclusive occurrence suppresses the missing-value checks that would make it unusable
2878    // whether it was alone or not.
2879    let exclusive_present = unique_flags(out.available_flags.values().chain(out.flags.keys()))
2880        .filter(|flag| !clause_flag_names.contains(&flag.name))
2881        .any(|flag| {
2882            exclusive_occurrence(flag)
2883                && !overridden_flags.contains(&flag.name)
2884                && (flag_was_parsed(flag) || flag_has_env(flag, custom_env))
2885        });
2886    let requirements_apply = |command_index: usize| {
2887        command_index + 1 == out.cmds.len() || !out.cmds[command_index].subcommand_negates_reqs
2888    };
2889
2890    if out.cmd.arg_required_else_help && !command_has_argv {
2891        out.errors.push(render_help_err(spec, &out.cmd, false));
2892    }
2893
2894    // A command that says it needs a subcommand, given none. Checked on `out.cmd` and nowhere
2895    // else, because `out.cmd` *is* the command the words reached: had a subcommand been taken,
2896    // the child would be here instead. The spec has carried `subcommand_required` since it was
2897    // added for the derive, and this parser never read it — so `mise generate` parsed as a
2898    // complete invocation while usage-argv and clap both refused it.
2899    if out.cmd.subcommand_required && !out.cmd.subcommands.is_empty() && out.external.is_none() {
2900        let mut names: Vec<&str> = out
2901            .cmd
2902            .subcommands
2903            .iter()
2904            // Aliases share a map entry with the name they point at; listing both would offer
2905            // the same command twice under two spellings.
2906            .filter(|(name, sub)| sub.name == **name && !sub.hide)
2907            .map(|(name, _)| name.as_str())
2908            .collect();
2909        names.sort_unstable();
2910        out.errors.push(UsageErr::MissingSubcommand(
2911            out.cmd.name.clone(),
2912            names.join(", "),
2913        ));
2914    }
2915
2916    // Not `skip(out.args.len())`: a `--` may have jumped the cursor past an arg that stayed
2917    // empty, so position and fill count can disagree. Ask `out.args` which args it holds.
2918    if !exclusive_present {
2919        for arg in out
2920            .cmds
2921            .iter()
2922            .enumerate()
2923            .filter(|(index, _)| requirements_apply(*index))
2924            .flat_map(|(_, cmd)| &cmd.args)
2925        {
2926            if out.args.contains_key(arg) {
2927                continue;
2928            }
2929            // Already reported as needing a `--`; one mistake should not yield two messages.
2930            if double_dash_violations.contains(&arg.name) {
2931                continue;
2932            }
2933            let required_if = arg.required_if.iter().any(|selector| {
2934                selector_is_explicit(selector, &out, &overridden_flags, custom_env)
2935            });
2936            let required_if_eq = arg.required_if_eq.iter().any(|condition| {
2937                selector_explicit_has_value(
2938                    &condition.selector,
2939                    &condition.value,
2940                    &out,
2941                    &overridden_flags,
2942                    custom_env,
2943                )
2944            });
2945            let required_if_eq_all = !arg.required_if_eq_all.is_empty()
2946                && arg.required_if_eq_all.iter().all(|condition| {
2947                    selector_explicit_has_value(
2948                        &condition.selector,
2949                        &condition.value,
2950                        &out,
2951                        &overridden_flags,
2952                        custom_env,
2953                    )
2954                });
2955            let unless_any = arg.required_unless.iter().any(|selector| {
2956                selector_is_explicit(selector, &out, &overridden_flags, custom_env)
2957            });
2958            let unless_all = !arg.required_unless_all.is_empty()
2959                && arg.required_unless_all.iter().all(|selector| {
2960                    selector_is_explicit(selector, &out, &overridden_flags, custom_env)
2961                });
2962            let required_unless = !(unless_any
2963                || unless_all
2964                || (arg.required_unless.is_empty() && arg.required_unless_all.is_empty()));
2965            if (arg.required
2966                || required_if
2967                || required_if_eq
2968                || required_if_eq_all
2969                || required_unless)
2970                && arg.default.is_empty()
2971            {
2972                // Check if there's an env var available (custom env map takes precedence)
2973                let has_env = arg
2974                    .env
2975                    .as_ref()
2976                    .is_some_and(|env_var| env_contains(custom_env, env_var));
2977                if !has_env {
2978                    out.errors.push(UsageErr::MissingArg(arg.name.clone()));
2979                }
2980            }
2981        }
2982    }
2983
2984    // Conflicts are a question about the invocation as a whole rather than about any one
2985    // token, so they are checked here beside the requirement checks rather than at the
2986    // point a flag is matched — the flag it conflicts with may still be ahead of it.
2987    // Its own loop: the requirement loop below skips the flags that *were* given, which
2988    // is exactly the set this needs.
2989    //
2990    // A value from the environment counts on both sides, matching what
2991    // `selector_is_explicit` says about the other flag: the question is whether a flag
2992    // has a value, not how it got one. That is what clap does, and an asymmetric rule
2993    // would make the same pair of flags a conflict or not depending on which one
2994    // happened to be typed.
2995    for flag in unique_flags(out.available_flags.values())
2996        .filter(|flag| !clause_flag_names.contains(&flag.name))
2997    {
2998        let given = out.flags.contains_key(flag) || flag_has_env(flag, custom_env);
2999        if !given || overridden_flags.contains(&flag.name) {
3000            continue;
3001        }
3002        for other in &flag.conflicts {
3003            if selector_is_explicit(other, &out, &overridden_flags, custom_env) {
3004                out.errors.push(UsageErr::InvalidFlag {
3005                    token: format!("--{}", flag.name),
3006                    reason: format!("conflicts with {other}"),
3007                    span: (0, 0).into(),
3008                    input: format!("--{} {other}", flag.name),
3009                });
3010            }
3011        }
3012        // The positive form, checked in the same pass and under the same rule: a value
3013        // from the environment satisfies a requirement, because the question is whether
3014        // the other flag has a value rather than how it got one. A flag that was
3015        // overridden away has not been given, so it cannot satisfy anything either —
3016        // which is what `selector_is_explicit` already accounts for.
3017        //
3018        // Reported as the missing flag rather than as something wrong with the flag that
3019        // named it, which is what clap says too: an unmet `requires` is a required
3020        // argument that was not provided. Named by its own name, resolved through the
3021        // same matcher, so a `requires="-f"` reports `--force` rather than the selector.
3022        let owner = out
3023            .cmds
3024            .iter()
3025            .rposition(|cmd| cmd.flags.iter().any(|declared| declared.name == flag.name))
3026            .unwrap_or(out.cmds.len() - 1);
3027        if !exclusive_present && requirements_apply(owner) {
3028            for other in &flag.requires {
3029                if !selector_is_satisfied(other, &out, &overridden_flags, custom_env) {
3030                    let name = selector_flag_name(other, &out).unwrap_or_else(|| other.clone());
3031                    if other.starts_with('-') {
3032                        out.errors.push(UsageErr::MissingFlag(name));
3033                    } else {
3034                        out.errors.push(UsageErr::MissingArg(name));
3035                    }
3036                }
3037            }
3038            for condition in &flag.requires_if {
3039                if explicit_flag_has_value(flag, &condition.value, &out, custom_env)
3040                    && !selector_is_satisfied(
3041                        &condition.requires,
3042                        &out,
3043                        &overridden_flags,
3044                        custom_env,
3045                    )
3046                {
3047                    let name = selector_flag_name(&condition.requires, &out)
3048                        .unwrap_or_else(|| condition.requires.clone());
3049                    out.errors.push(UsageErr::MissingFlag(name));
3050                }
3051            }
3052        }
3053    }
3054
3055    // Positionals can declare the same pairwise conflict as flags. Their selector is
3056    // the bare argument name, while a flag keeps its dashed spelling.
3057    for (command_index, arg) in out
3058        .cmds
3059        .iter()
3060        .enumerate()
3061        .flat_map(|(index, cmd)| cmd.args.iter().map(move |arg| (index, arg)))
3062    {
3063        let given = arg_is_explicit(arg, &out, custom_env);
3064        if !given {
3065            continue;
3066        }
3067        for other in &arg.conflicts {
3068            if selector_is_explicit(other, &out, &overridden_flags, custom_env) {
3069                out.errors.push(UsageErr::InvalidFlag {
3070                    token: arg.name.clone(),
3071                    reason: format!("conflicts with {other}"),
3072                    span: (0, 0).into(),
3073                    input: format!("{} {other}", arg.name),
3074                });
3075            }
3076        }
3077        if !exclusive_present && requirements_apply(command_index) {
3078            for other in &arg.requires {
3079                if !selector_is_satisfied(other, &out, &overridden_flags, custom_env) {
3080                    let name = selector_flag_name(other, &out).unwrap_or_else(|| other.clone());
3081                    if other.starts_with('-') {
3082                        out.errors.push(UsageErr::MissingFlag(name));
3083                    } else {
3084                        out.errors.push(UsageErr::MissingArg(name));
3085                    }
3086                }
3087            }
3088        }
3089    }
3090
3091    // An exclusive flag is the whole-command form of a conflict: `--version` means the
3092    // rest of the line has nothing to act on. Everything the invocation supplied counts,
3093    // positionals included, which is what distinguishes it from being in a group with
3094    // every other flag.
3095    //
3096    // Only what was *given*, as `conflicts` reads it: a defaulted flag standing beside an
3097    // exclusive one is nobody saying anything, and counting it would make the exclusive
3098    // flag unusable on any command that has a default. Environment values do count, also as
3099    // `conflicts` reads them, so the spec parser and the derive agree.
3100    for flag in unique_flags(out.available_flags.values().chain(out.flags.keys()))
3101        .filter(|flag| !clause_flag_names.contains(&flag.name))
3102    {
3103        // `SpecFlag` equality is intentionally name-only for the public parsed-value map,
3104        // but re-declared aliases can leave distinct declarations with that same name in
3105        // scope. Exclusivity is about the declaration the typed spelling resolved to, so
3106        // compare the parser's `Arc`s by identity here.
3107        let given = flag_was_parsed(flag) || flag_has_env(flag, custom_env);
3108        if !exclusive_occurrence(flag) || !given || overridden_flags.contains(&flag.name) {
3109            continue;
3110        }
3111        let other_flag = unique_flags(out.available_flags.values().chain(out.flags.keys()))
3112            .filter(|other| !clause_flag_names.contains(&other.name))
3113            .find(|other| {
3114                !Arc::ptr_eq(other, flag)
3115                    && !overridden_flags.contains(&other.name)
3116                    && (flag_was_parsed(other) || flag_has_env(other, custom_env))
3117            })
3118            .map(|other| format!("--{}", other.name));
3119        let other_arg = active_args(&out.cmd).iter().find(|arg| {
3120            out.args.keys().any(|given| given.name == arg.name)
3121                || out
3122                    .clauses
3123                    .values()
3124                    .flatten()
3125                    .any(|instance| instance.keys().any(|given| given.name == arg.name))
3126                || arg
3127                    .env
3128                    .as_ref()
3129                    .is_some_and(|env| env_contains(custom_env, env))
3130        });
3131        // Selecting a child is company for an exclusive flag declared by an ancestor. An
3132        // exclusive flag belonging to the child itself does not conflict with the command word
3133        // needed to reach that child — so the question is not who owns the flag but whose
3134        // exclusivity is the one being enforced, which is what `exclusivity_in_play` already
3135        // separated.
3136        let (_, ancestor_exclusivity) = exclusivity_in_play(flag);
3137        let selected_subcommand =
3138            (out.cmds.len() > 1 && ancestor_exclusivity).then(|| out.cmd.name.clone());
3139        let other = other_flag
3140            .or_else(|| other_arg.map(|arg| format!("<{}>", arg.name)))
3141            .or(selected_subcommand);
3142        if let Some(other) = other {
3143            out.errors.push(UsageErr::InvalidFlag {
3144                token: format!("--{}", flag.name),
3145                reason: format!("must be given on its own, and {other} was given too"),
3146                span: (0, 0).into(),
3147                input: format!("--{} {other}", flag.name),
3148            });
3149        }
3150    }
3151
3152    // Groups, checked once per group rather than per flag: both questions a group asks —
3153    // how many members were given, and whether that is enough — are about the set, which
3154    // is the whole reason a group exists rather than a pile of pairwise conflicts.
3155    //
3156    // The same "given" rule as everything else here, so a member filled from the
3157    // environment or a default counts.
3158    // Every command in the chain, not only the selected one: a group may name global
3159    // flags, which belong to an ancestor and are declared there.
3160    let mut group_errors: Vec<UsageErr> = Vec::new();
3161    for (command_index, group) in out
3162        .cmds
3163        .iter()
3164        .enumerate()
3165        .flat_map(|(index, cmd)| cmd.groups.iter().map(move |group| (index, group)))
3166    {
3167        // Counted by the *flag* a selector resolves to, not by the selector. `-f` and
3168        // `--file` are two spellings of one flag, and a group naming both — or naming one
3169        // flag twice — would otherwise report that flag as conflicting with itself the
3170        // moment it was given. Deduplicated rather than refused where the group is
3171        // written, because listing both spellings is redundant, not wrong.
3172        let mut given: Vec<&str> = Vec::new();
3173        let mut seen: Vec<String> = Vec::new();
3174        for selector in &group.members {
3175            if !selector_is_explicit(selector, &out, &overridden_flags, custom_env) {
3176                continue;
3177            }
3178            let name = selector_flag_name(selector, &out).unwrap_or_else(|| selector.clone());
3179            if seen.contains(&name) {
3180                continue;
3181            }
3182            seen.push(name);
3183            given.push(selector.as_str());
3184        }
3185        if !group.multiple && given.len() > 1 {
3186            group_errors.push(UsageErr::InvalidFlag {
3187                token: given[1].to_string(),
3188                reason: format!("cannot be used with {} in group {}", given[0], group.name),
3189                span: (0, 0).into(),
3190                input: format!("{} {}", given[0], given[1]),
3191            });
3192        }
3193        // Requiredness is a *positive* rule, so it reads a default as filling a member —
3194        // the rule `requires` follows. That is also why it cannot reuse `given` above:
3195        // exclusivity must count only what was supplied, or a defaulted member would
3196        // collide with the sibling the user actually typed.
3197        let satisfied = group
3198            .members
3199            .iter()
3200            .any(|selector| selector_is_satisfied(selector, &out, &overridden_flags, custom_env));
3201        if group.required && requirements_apply(command_index) && !satisfied && !exclusive_present {
3202            // The members are what a user has to type, so they are in the message; the
3203            // group's name is there too, since a command with several groups would
3204            // otherwise report the same sentence twice with nothing to tell them apart.
3205            group_errors.push(UsageErr::MissingGroup {
3206                group: group.name.clone(),
3207                members: group.members.join(", "),
3208            });
3209        }
3210    }
3211    out.errors.extend(group_errors);
3212
3213    if !exclusive_present {
3214        for flag in unique_flags(out.available_flags.values())
3215            .filter(|flag| !clause_flag_names.contains(&flag.name))
3216        {
3217            let owner = out
3218                .cmds
3219                .iter()
3220                .rposition(|cmd| cmd.flags.iter().any(|declared| declared.name == flag.name))
3221                .unwrap_or(out.cmds.len() - 1);
3222            if !requirements_apply(owner) {
3223                continue;
3224            }
3225            if out.flags.contains_key(flag) || overridden_flags.contains(&flag.name) {
3226                continue;
3227            }
3228            let has_default =
3229                !flag.default.is_empty() || flag.arg.iter().any(|a| !a.default.is_empty());
3230            let has_env = flag_has_env(flag, custom_env);
3231            let required_if = flag.required_if.iter().any(|selector| {
3232                selector_is_explicit(selector, &out, &overridden_flags, custom_env)
3233            });
3234            let required_if_eq = flag.required_if_eq.iter().any(|condition| {
3235                selector_explicit_has_value(
3236                    &condition.selector,
3237                    &condition.value,
3238                    &out,
3239                    &overridden_flags,
3240                    custom_env,
3241                )
3242            });
3243            let required_if_eq_all = !flag.required_if_eq_all.is_empty()
3244                && flag.required_if_eq_all.iter().all(|condition| {
3245                    selector_explicit_has_value(
3246                        &condition.selector,
3247                        &condition.value,
3248                        &out,
3249                        &overridden_flags,
3250                        custom_env,
3251                    )
3252                });
3253            let unless_any = flag.required_unless.iter().any(|selector| {
3254                selector_is_explicit(selector, &out, &overridden_flags, custom_env)
3255            });
3256            let unless_all = !flag.required_unless_all.is_empty()
3257                && flag.required_unless_all.iter().all(|selector| {
3258                    selector_is_explicit(selector, &out, &overridden_flags, custom_env)
3259                });
3260            let required_unless = !(unless_any
3261                || unless_all
3262                || (flag.required_unless.is_empty() && flag.required_unless_all.is_empty()));
3263            if (flag.required
3264                || required_if
3265                || required_if_eq
3266                || required_if_eq_all
3267                || required_unless)
3268                && !has_default
3269                && !has_env
3270            {
3271                out.errors.push(UsageErr::MissingFlag(flag.name.clone()));
3272            }
3273        }
3274    }
3275
3276    // Validate var_min/var_max constraints for variadic args
3277    for (arg, value) in &out.args {
3278        if arg.var {
3279            if let ParseValue::MultiString(values) = value {
3280                if let Some(min) = arg.var_min {
3281                    if values.len() < min {
3282                        out.errors.push(UsageErr::VarArgTooFew {
3283                            name: arg.name.clone(),
3284                            min,
3285                            got: values.len(),
3286                        });
3287                    }
3288                }
3289                if let Some(max) = arg.var_max {
3290                    if values.len() > max {
3291                        out.errors.push(UsageErr::VarArgTooMany {
3292                            name: arg.name.clone(),
3293                            max,
3294                            got: values.len(),
3295                        });
3296                    }
3297                }
3298            }
3299        }
3300    }
3301
3302    // Validate var_min/var_max constraints for variadic flags. These are bounds on
3303    // repeated occurrences of the flag itself. Bounds on its nested argument are enforced
3304    // by binding once per occurrence, where the per-occurrence count is still available.
3305    for flag in unique_flags(out.available_flags.values())
3306        .filter(|flag| !clause_flag_names.contains(&flag.name))
3307    {
3308        if flag.var {
3309            let bound = match out.flags.get(flag) {
3310                Some(ParseValue::MultiString(values)) => values.len(),
3311                Some(ParseValue::MultiBool(values)) => values.len(),
3312                Some(_) => 1,
3313                None => 0,
3314            };
3315            // A partial parse deliberately leaves the final value-optional flag pending so
3316            // completion can still answer for it. It is nevertheless a real occurrence for
3317            // the repeated flag's bounds; the full parser closes it just after this phase.
3318            let pending = out
3319                .flag_awaiting_value
3320                .iter()
3321                .filter(|pending| {
3322                    Arc::ptr_eq(pending, flag)
3323                        && (pending.value_optional || pending.default_missing.is_some())
3324                })
3325                .count();
3326            let count = bound + pending;
3327            if count == 0 {
3328                continue;
3329            }
3330            if let Some(min) = flag.var_min {
3331                if count < min {
3332                    out.errors.push(UsageErr::VarFlagTooFew {
3333                        name: flag.name.clone(),
3334                        min,
3335                        got: count,
3336                    });
3337                }
3338            }
3339            if let Some(max) = flag.var_max {
3340                if count > max {
3341                    out.errors.push(UsageErr::VarFlagTooMany {
3342                        name: flag.name.clone(),
3343                        max,
3344                        got: count,
3345                    });
3346                }
3347            }
3348        }
3349    }
3350
3351    Ok((out, overridden_flags))
3352}
3353
3354fn validate_expression(
3355    name: &str,
3356    expression: Option<&str>,
3357    message: Option<&str>,
3358    parsed: &ParseValue,
3359    errors: &mut Vec<UsageErr>,
3360) {
3361    let Some(expression) = expression else {
3362        return;
3363    };
3364    #[cfg(not(feature = "validation"))]
3365    let _ = expression;
3366    let values: &[String] = match parsed {
3367        ParseValue::String(value) => std::slice::from_ref(value),
3368        ParseValue::MultiString(values) => values,
3369        ParseValue::Bool(_) | ParseValue::MultiBool(_) => return,
3370    };
3371    #[cfg(feature = "validation")]
3372    for value in values {
3373        let reason = match usage_validation::validate(expression, value) {
3374            Ok(true) => continue,
3375            Ok(false) => message
3376                .unwrap_or("does not satisfy the validation expression")
3377                .to_string(),
3378            Err(error) => format!("validation expression failed: {error}"),
3379        };
3380        errors.push(UsageErr::InvalidValue {
3381            name: name.to_string(),
3382            value: value.clone(),
3383            reason,
3384        });
3385        break;
3386    }
3387    #[cfg(not(feature = "validation"))]
3388    if let Some(value) = values.first() {
3389        let _ = message;
3390        errors.push(UsageErr::InvalidValue {
3391            name: name.to_string(),
3392            value: value.clone(),
3393            reason: "expression validation requires the `validation` feature".to_string(),
3394        });
3395    }
3396}
3397
3398#[cfg(all(test, not(feature = "validation")))]
3399mod optional_validation_tests {
3400    use crate::{parse, Spec};
3401
3402    #[test]
3403    fn validation_declarations_require_the_opt_in_runtime_feature() {
3404        let spec: Spec = r#"
3405name "ex"
3406bin "ex"
3407arg "<port>" validate="int(value) > 0"
3408        "#
3409        .parse()
3410        .unwrap();
3411        let error = parse(&spec, &["ex".to_string(), "1".to_string()]).unwrap_err();
3412        assert!(
3413            error
3414                .to_string()
3415                .contains("requires the `validation` feature"),
3416            "{error:?}"
3417        );
3418    }
3419}
3420
3421fn flag_matches_selector(flag: &SpecFlag, selector: &str) -> bool {
3422    flag.name == selector || flag_keys(flag).iter().any(|key| key == selector)
3423}
3424
3425fn flags_override(overrider: &SpecFlag, overridden: &SpecFlag) -> bool {
3426    overrider
3427        .overrides
3428        .iter()
3429        .any(|selector| flag_matches_selector(overridden, selector))
3430}
3431
3432fn apply_prefix_flag_overrides(
3433    prefix_flags: &mut Vec<(Arc<SpecFlag>, Vec<String>)>,
3434    flag: Arc<SpecFlag>,
3435) {
3436    prefix_flags
3437        .retain(|(other, _)| !(flags_override(&flag, other) || flags_override(other, &flag)));
3438}
3439
3440fn mount_prefix_words(prefix_flags: &[(Arc<SpecFlag>, Vec<String>)]) -> Vec<String> {
3441    prefix_flags
3442        .iter()
3443        .flat_map(|(_, words)| words.iter().cloned())
3444        .collect()
3445}
3446
3447fn env_contains(custom_env: Option<&HashMap<String, String>>, env_var: &str) -> bool {
3448    match custom_env {
3449        Some(env) => env.contains_key(env_var),
3450        None => std::env::var(env_var).is_ok(),
3451    }
3452}
3453
3454fn flag_has_env(flag: &SpecFlag, custom_env: Option<&HashMap<String, String>>) -> bool {
3455    flag.env_names()
3456        .any(|env_var| env_contains(custom_env, env_var))
3457}
3458
3459fn fallback_is_true(value: &str) -> bool {
3460    matches!(value, "1" | "true" | "True" | "TRUE")
3461}
3462
3463fn split_fallback_values(values: &[String], delimiter: Option<char>) -> Vec<String> {
3464    match delimiter {
3465        Some(delimiter) => values
3466            .iter()
3467            .flat_map(|value| value.split(delimiter).map(str::to_string))
3468            .collect(),
3469        None => values.to_vec(),
3470    }
3471}
3472
3473fn validate_arg_fallback_count(arg: &SpecArg, count: usize, errors: &mut Vec<UsageErr>) {
3474    if let Some(min) = arg.var_min {
3475        if count < min {
3476            errors.push(UsageErr::VarArgTooFew {
3477                name: arg.name.clone(),
3478                min,
3479                got: count,
3480            });
3481        }
3482    }
3483    if let Some(max) = arg.var_max {
3484        if count > max {
3485            errors.push(UsageErr::VarArgTooMany {
3486                name: arg.name.clone(),
3487                max,
3488                got: count,
3489            });
3490        }
3491    }
3492}
3493
3494fn validate_flag_fallback_count(flag: &SpecFlag, count: usize, errors: &mut Vec<UsageErr>) {
3495    if let Some(min) = flag.var_min {
3496        if count < min {
3497            errors.push(UsageErr::VarFlagTooFew {
3498                name: flag.name.clone(),
3499                min,
3500                got: count,
3501            });
3502        }
3503    }
3504    if let Some(max) = flag.var_max {
3505        if count > max {
3506            errors.push(UsageErr::VarFlagTooMany {
3507                name: flag.name.clone(),
3508                max,
3509                got: count,
3510            });
3511        }
3512    }
3513}
3514
3515fn validate_flag_arg_fallback_count(
3516    flag: &SpecFlag,
3517    arg: &SpecArg,
3518    count: usize,
3519    errors: &mut Vec<UsageErr>,
3520) {
3521    if let Some(min) = arg.var_min {
3522        if count < min {
3523            errors.push(UsageErr::VarFlagTooFew {
3524                name: flag.name.clone(),
3525                min,
3526                got: count,
3527            });
3528        }
3529    }
3530    if let Some(max) = arg.var_max {
3531        if count > max {
3532            errors.push(UsageErr::VarFlagTooMany {
3533                name: flag.name.clone(),
3534                max,
3535                got: count,
3536            });
3537        }
3538    }
3539}
3540
3541/// Bind a fallback the way an unconditional `default` does: one value, or several
3542/// for `var`, and choices checked the same way.
3543fn bind_flag_fallback(
3544    flag: &Arc<SpecFlag>,
3545    values: &[String],
3546    out: &mut ParseOutput,
3547    custom_env: Option<&HashMap<String, String>>,
3548    origin: ValueOrigin,
3549) -> Result<(), miette::Error> {
3550    let Some(value) = flag_fallback_value(flag, values, &mut out.errors, custom_env)? else {
3551        return Ok(());
3552    };
3553    out.flags.insert(Arc::clone(flag), value);
3554    out.flag_origins
3555        .entry(Arc::clone(flag))
3556        .or_default()
3557        .push(origin);
3558    Ok(())
3559}
3560
3561fn flag_fallback_value(
3562    flag: &SpecFlag,
3563    values: &[String],
3564    errors: &mut Vec<UsageErr>,
3565    custom_env: Option<&HashMap<String, String>>,
3566) -> Result<Option<ParseValue>, miette::Error> {
3567    if values.is_empty() {
3568        return Ok(None);
3569    }
3570    if let Some(arg) = flag.arg.as_ref() {
3571        let values = split_fallback_values(values, arg.delimiter);
3572        if flag.var || arg.var {
3573            if flag.var {
3574                validate_flag_fallback_count(flag, values.len(), errors);
3575            }
3576            if arg.var {
3577                validate_flag_arg_fallback_count(flag, arg, values.len(), errors);
3578            }
3579            validate_choice_values(
3580                ChoiceTarget::option(flag),
3581                &values,
3582                arg.choices.as_ref(),
3583                custom_env,
3584            )?;
3585            Ok(Some(ParseValue::MultiString(values)))
3586        } else {
3587            let value = values.into_iter().next().unwrap_or_default();
3588            validate_choice_value(
3589                ChoiceTarget::option(flag),
3590                &value,
3591                arg.choices.as_ref(),
3592                custom_env,
3593            )?;
3594            Ok(Some(ParseValue::String(value)))
3595        }
3596    } else if flag.var {
3597        validate_flag_fallback_count(flag, values.len(), errors);
3598        let bools: Vec<bool> = values.iter().map(|s| fallback_is_true(s)).collect();
3599        Ok(Some(ParseValue::MultiBool(bools)))
3600    } else {
3601        Ok(Some(ParseValue::Bool(fallback_is_true(&values[0]))))
3602    }
3603}
3604
3605/// Fill scoped flags inside the clause instances argv created.
3606///
3607/// The returned sets contain argv and environment sources only. Relationship
3608/// validation uses them to keep defaults from becoming explicit conflicts while
3609/// still allowing a fallback value to satisfy a required scoped flag.
3610fn apply_clause_flag_fallbacks(
3611    out: &mut ParseOutput,
3612    overridden_flags: &HashSet<String>,
3613    custom_env: Option<&HashMap<String, String>>,
3614) -> Result<Option<Vec<HashSet<String>>>, miette::Error> {
3615    let Some(clause) = out.cmd.clause.clone() else {
3616        return Ok(None);
3617    };
3618    let Some(positional_instances) = out.clauses.get(&clause.name) else {
3619        return Ok(None);
3620    };
3621    let positional_instances = positional_instances.clone();
3622    let mut flag_instances = out
3623        .clause_flags
3624        .shift_remove(&clause.name)
3625        .unwrap_or_default();
3626    flag_instances.resize_with(positional_instances.len(), IndexMap::new);
3627    let get_env = |key: &str| -> Option<String> {
3628        custom_env
3629            .and_then(|values| values.get(key).cloned())
3630            .or_else(|| {
3631                custom_env
3632                    .is_none()
3633                    .then(|| std::env::var(key).ok())
3634                    .flatten()
3635            })
3636    };
3637    let mut explicit_instances = Vec::with_capacity(flag_instances.len());
3638
3639    for (instance_index, scoped) in flag_instances.iter_mut().enumerate() {
3640        let positional = &positional_instances[instance_index];
3641        let mut explicit = scoped
3642            .keys()
3643            .map(|flag| flag.name.clone())
3644            .collect::<HashSet<_>>();
3645
3646        // Environment first so every default_if sees all explicit sources,
3647        // independent of declaration order.
3648        for declared in &clause.flags {
3649            let flag = Arc::new(declared.clone());
3650            if scoped.contains_key(&flag) {
3651                continue;
3652            }
3653            let Some((env_name, env_value)) = first_set_env(declared.env_names(), &get_env) else {
3654                continue;
3655            };
3656            if let Some(warning) = flag_deprecation(declared) {
3657                out.warnings.push(warning);
3658            }
3659            if flag_env_is_deprecated(declared, env_name) {
3660                out.warnings
3661                    .push(Warning::env(env_name, flag_current_env(declared)));
3662            }
3663            if let Some(value) = flag_fallback_value(
3664                declared,
3665                std::slice::from_ref(&env_value),
3666                &mut out.errors,
3667                custom_env,
3668            )? {
3669                scoped.insert(flag, value);
3670                explicit.insert(declared.name.clone());
3671            }
3672        }
3673
3674        let condition_matches =
3675            |condition: &crate::SpecDefaultIf| {
3676                if let Some(flag) = clause
3677                    .flags
3678                    .iter()
3679                    .find(|flag| flag_matches_selector(flag, &condition.selector))
3680                {
3681                    if !explicit.contains(&flag.name) {
3682                        return false;
3683                    }
3684                    return condition.when.as_ref().is_none_or(|expected| {
3685                        scoped
3686                            .iter()
3687                            .find(|(present, _)| present.name == flag.name)
3688                            .is_some_and(|(_, value)| parse_value_has(value, expected))
3689                    });
3690                }
3691                if let Some(arg) = clause.args.iter().find(|arg| {
3692                    !condition.selector.starts_with('-') && arg.name == condition.selector
3693                }) {
3694                    return positional.get(arg).is_some_and(|value| {
3695                        condition
3696                            .when
3697                            .as_ref()
3698                            .is_none_or(|expected| parse_value_has(value, expected))
3699                    });
3700                }
3701                let command_flag = out
3702                    .available_flags
3703                    .values()
3704                    .chain(out.flags.keys())
3705                    .filter(|flag| !is_clause_scoped_flag(out, flag))
3706                    .find(|flag| flag_matches_selector(flag, &condition.selector));
3707                command_flag.is_some_and(|flag| {
3708                    !overridden_flags.contains(&flag.name)
3709                        && command_flag_has_explicit_source(flag, out)
3710                        && condition.when.as_ref().is_none_or(|expected| {
3711                            out.flags
3712                                .get(flag)
3713                                .is_some_and(|value| parse_value_has(value, expected))
3714                        })
3715                })
3716            };
3717
3718        let conditional = clause
3719            .flags
3720            .iter()
3721            .filter(|flag| !scoped.keys().any(|present| present.name == flag.name))
3722            .filter_map(|flag| {
3723                flag.default_if
3724                    .iter()
3725                    .find(|condition| condition_matches(condition))
3726                    .map(|condition| (flag.clone(), condition.value.clone()))
3727            })
3728            .collect::<Vec<_>>();
3729        for (declared, value) in conditional {
3730            if let Some(value) = flag_fallback_value(
3731                &declared,
3732                std::slice::from_ref(&value),
3733                &mut out.errors,
3734                custom_env,
3735            )? {
3736                scoped.insert(Arc::new(declared), value);
3737            }
3738        }
3739
3740        for declared in &clause.flags {
3741            let flag = Arc::new(declared.clone());
3742            if scoped.contains_key(&flag) {
3743                continue;
3744            }
3745            let values = if !declared.default.is_empty() {
3746                &declared.default
3747            } else if let Some(arg) = declared.arg.as_ref().filter(|arg| !arg.default.is_empty()) {
3748                &arg.default
3749            } else {
3750                continue;
3751            };
3752            if let Some(value) = flag_fallback_value(declared, values, &mut out.errors, custom_env)?
3753            {
3754                scoped.insert(flag, value);
3755            }
3756        }
3757        explicit_instances.push(explicit);
3758    }
3759
3760    out.clause_flags.insert(clause.name, flag_instances);
3761    Ok(Some(explicit_instances))
3762}
3763
3764fn command_flag_has_explicit_source(flag: &SpecFlag, out: &ParseOutput) -> bool {
3765    out.flags.contains_key(flag)
3766        && out.flag_origins.get(flag).is_none_or(|origins| {
3767            origins
3768                .iter()
3769                .any(|origin| matches!(origin, ValueOrigin::DefaultMissing | ValueOrigin::Env(_)))
3770        })
3771}
3772
3773fn default_if_condition_matches(
3774    condition: &crate::SpecDefaultIf,
3775    out: &ParseOutput,
3776    overridden_flags: &HashSet<String>,
3777    custom_env: Option<&HashMap<String, String>>,
3778) -> bool {
3779    match &condition.when {
3780        None => selector_is_explicit(&condition.selector, out, overridden_flags, custom_env),
3781        Some(when) => {
3782            let Some(flag) = out
3783                .available_flags
3784                .values()
3785                .chain(out.flags.keys())
3786                .find(|flag| flag_matches_selector(flag, &condition.selector))
3787            else {
3788                return false;
3789            };
3790            if overridden_flags.contains(&flag.name) {
3791                return false;
3792            }
3793            explicit_flag_has_value(flag, when, out, custom_env)
3794        }
3795    }
3796}
3797
3798/// Whether an explicitly supplied value of `flag` equals `expected`.
3799///
3800/// clap treats command-line and environment values as explicit for `requires_if`, but
3801/// not defaults. Keep that source distinction here instead of consulting the flag's
3802/// defaults through `selector_is_satisfied`.
3803fn explicit_flag_has_value(
3804    flag: &SpecFlag,
3805    expected: &str,
3806    out: &ParseOutput,
3807    custom_env: Option<&HashMap<String, String>>,
3808) -> bool {
3809    let parsed_matches = out.flags.get(flag).is_some_and(|value| match value {
3810        ParseValue::Bool(value) => value.to_string() == expected,
3811        ParseValue::String(value) => value == expected,
3812        ParseValue::MultiBool(values) => values.iter().any(|value| value.to_string() == expected),
3813        ParseValue::MultiString(values) => values.iter().any(|value| value == expected),
3814    });
3815    if out.flags.contains_key(flag) {
3816        return parsed_matches;
3817    }
3818
3819    let value = flag.env_names().find_map(|env| match custom_env {
3820        Some(values) => values.get(env).cloned(),
3821        None => std::env::var(env).ok(),
3822    });
3823    value.is_some_and(
3824        |value| match flag.arg.as_ref().and_then(|arg| arg.delimiter) {
3825            Some(delimiter) => value.split(delimiter).any(|value| value == expected),
3826            None if flag.arg.is_none() => {
3827                matches!(value.as_str(), "1" | "true" | "True" | "TRUE").to_string() == expected
3828            }
3829            None => value == expected,
3830        },
3831    )
3832}
3833
3834fn parse_value_has(value: &ParseValue, expected: &str) -> bool {
3835    match value {
3836        ParseValue::Bool(value) => value.to_string() == expected,
3837        ParseValue::String(value) => value == expected,
3838        ParseValue::MultiBool(values) => values.iter().any(|value| value.to_string() == expected),
3839        ParseValue::MultiString(values) => values.iter().any(|value| value == expected),
3840    }
3841}
3842
3843/// Clause flags share the command's spelling table so they can be recognized while parsing,
3844/// but their values and validation belong to one clause instance rather than the command.
3845fn is_clause_scoped_flag(out: &ParseOutput, flag: &SpecFlag) -> bool {
3846    out.cmd
3847        .clause
3848        .as_ref()
3849        .is_some_and(|clause| clause.flags.iter().any(|scoped| scoped.name == flag.name))
3850}
3851
3852fn selected_clause_flag<'a>(out: &'a ParseOutput, selector: &str) -> Option<&'a SpecFlag> {
3853    out.cmd
3854        .clause
3855        .as_ref()?
3856        .flags
3857        .iter()
3858        .find(|flag| flag_matches_selector(flag, selector))
3859}
3860
3861fn clause_flag_is_explicit(out: &ParseOutput, flag: &SpecFlag) -> bool {
3862    out.clause_flags
3863        .values()
3864        .flatten()
3865        .any(|instance| instance.keys().any(|present| present.name == flag.name))
3866        || out.flags.keys().any(|present| present.name == flag.name)
3867}
3868
3869fn clause_flag_has_value(out: &ParseOutput, flag: &SpecFlag, expected: &str) -> bool {
3870    out.clause_flags.values().flatten().any(|instance| {
3871        instance
3872            .iter()
3873            .any(|(present, value)| present.name == flag.name && parse_value_has(value, expected))
3874    }) || out
3875        .flags
3876        .iter()
3877        .any(|(present, value)| present.name == flag.name && parse_value_has(value, expected))
3878}
3879
3880fn validate_clause_relationships(
3881    out: &mut ParseOutput,
3882    overridden_flags: &HashSet<String>,
3883    custom_env: Option<&HashMap<String, String>>,
3884    explicit_instances: Option<&[HashSet<String>]>,
3885) {
3886    let Some(clause) = out.cmd.clause.as_ref() else {
3887        return;
3888    };
3889    let Some(instances) = out.clauses.get(&clause.name) else {
3890        return;
3891    };
3892    let command_flag_is_explicit = |selector: &str| {
3893        out.available_flags
3894            .values()
3895            .chain(out.flags.keys())
3896            .filter(|flag| !is_clause_scoped_flag(out, flag))
3897            .any(|flag| {
3898                flag_matches_selector(flag, selector)
3899                    && !overridden_flags.contains(&flag.name)
3900                    && (out.flags.contains_key(flag) || flag_has_env(flag, custom_env))
3901            })
3902    };
3903    let command_flag_matches_value = |selector: &str, expected: &str| {
3904        out.available_flags
3905            .values()
3906            .chain(out.flags.keys())
3907            .filter(|flag| !is_clause_scoped_flag(out, flag))
3908            .find(|flag| flag_matches_selector(flag, selector))
3909            .is_some_and(|flag| {
3910                !overridden_flags.contains(&flag.name)
3911                    && explicit_flag_has_value(flag, expected, out, custom_env)
3912            })
3913    };
3914    let command_flag_is_satisfied = |selector: &str| {
3915        out.available_flags
3916            .values()
3917            .chain(out.flags.keys())
3918            .filter(|flag| !is_clause_scoped_flag(out, flag))
3919            .any(|flag| flag_matches_selector(flag, selector))
3920            && selector_is_satisfied(selector, out, overridden_flags, custom_env)
3921    };
3922    let mut errors = Vec::new();
3923    for (instance_index, instance) in instances.iter().enumerate() {
3924        let explicit = explicit_instances.and_then(|instances| instances.get(instance_index));
3925        let scoped = out
3926            .clause_flags
3927            .get(&clause.name)
3928            .and_then(|instances| instances.get(instance_index));
3929        let scoped_flag = |selector: &str| {
3930            clause
3931                .flags
3932                .iter()
3933                .find(|flag| flag_matches_selector(flag, selector))
3934        };
3935        let scoped_value = |flag: &SpecFlag| {
3936            scoped.and_then(|values| {
3937                values
3938                    .iter()
3939                    .find(|(present, _)| present.name == flag.name)
3940                    .map(|(_, value)| value)
3941            })
3942        };
3943        let arg_is_explicit = |selector: &str| {
3944            instance
3945                .keys()
3946                .any(|arg| !selector.starts_with('-') && arg.name == selector)
3947        };
3948        let selector_is_explicit = |selector: &str| {
3949            scoped_flag(selector).is_some_and(|flag| {
3950                explicit
3951                    .map(|given| given.contains(&flag.name))
3952                    .unwrap_or_else(|| scoped_value(flag).is_some())
3953            }) || command_flag_is_explicit(selector)
3954                || arg_is_explicit(selector)
3955        };
3956        let selector_has_value = |selector: &str, expected: &str| {
3957            scoped_flag(selector)
3958                .filter(|flag| {
3959                    explicit
3960                        .map(|given| given.contains(&flag.name))
3961                        .unwrap_or_else(|| scoped_value(flag).is_some())
3962                })
3963                .and_then(scoped_value)
3964                .is_some_and(|value| parse_value_has(value, expected))
3965                || command_flag_matches_value(selector, expected)
3966                || instance.iter().any(|(arg, value)| {
3967                    !selector.starts_with('-')
3968                        && arg.name == selector
3969                        && parse_value_has(value, expected)
3970                })
3971        };
3972        let selector_is_satisfied = |selector: &str| {
3973            scoped_flag(selector).is_some_and(|flag| scoped_value(flag).is_some())
3974                || command_flag_is_satisfied(selector)
3975                || arg_is_explicit(selector)
3976        };
3977        let selector_name = |selector: &str| {
3978            scoped_flag(selector)
3979                .map(|flag| flag.name.clone())
3980                .or_else(|| selector_flag_name(selector, out))
3981                .unwrap_or_else(|| selector.to_string())
3982        };
3983
3984        for flag in &clause.flags {
3985            let value = scoped_value(flag);
3986            if value.is_none() {
3987                let required_if = flag
3988                    .required_if
3989                    .iter()
3990                    .any(|selector| selector_is_explicit(selector));
3991                let required_if_eq = flag
3992                    .required_if_eq
3993                    .iter()
3994                    .any(|condition| selector_has_value(&condition.selector, &condition.value));
3995                let required_if_eq_all = !flag.required_if_eq_all.is_empty()
3996                    && flag
3997                        .required_if_eq_all
3998                        .iter()
3999                        .all(|condition| selector_has_value(&condition.selector, &condition.value));
4000                let unless_any = flag
4001                    .required_unless
4002                    .iter()
4003                    .any(|selector| selector_is_explicit(selector));
4004                let unless_all = !flag.required_unless_all.is_empty()
4005                    && flag
4006                        .required_unless_all
4007                        .iter()
4008                        .all(|selector| selector_is_explicit(selector));
4009                let required_unless = !(unless_any
4010                    || unless_all
4011                    || (flag.required_unless.is_empty() && flag.required_unless_all.is_empty()));
4012                if flag.required
4013                    || required_if
4014                    || required_if_eq
4015                    || required_if_eq_all
4016                    || required_unless
4017                {
4018                    errors.push(UsageErr::MissingFlag(flag.name.clone()));
4019                }
4020                continue;
4021            }
4022
4023            let explicitly_provided = explicit
4024                .map(|given| given.contains(&flag.name))
4025                .unwrap_or(true);
4026
4027            if explicitly_provided {
4028                for other in &flag.conflicts {
4029                    if selector_is_explicit(other) {
4030                        errors.push(UsageErr::InvalidFlag {
4031                            token: format!("--{}", flag.name),
4032                            reason: format!("conflicts with {other}"),
4033                            span: (0, 0).into(),
4034                            input: format!("--{} {other}", flag.name),
4035                        });
4036                    }
4037                }
4038                for other in &flag.requires {
4039                    if !selector_is_satisfied(other) {
4040                        errors.push(UsageErr::MissingFlag(selector_name(other)));
4041                    }
4042                }
4043                for condition in &flag.requires_if {
4044                    if value.is_some_and(|value| parse_value_has(value, &condition.value))
4045                        && !selector_is_satisfied(&condition.requires)
4046                    {
4047                        errors.push(UsageErr::MissingFlag(selector_name(&condition.requires)));
4048                    }
4049                }
4050            }
4051            if let (true, Some(value)) = (flag.var, value) {
4052                let count = match value {
4053                    ParseValue::MultiString(values) => values.len(),
4054                    ParseValue::MultiBool(values) => values.len(),
4055                    _ => 1,
4056                };
4057                if let Some(min) = flag.var_min.filter(|min| count < *min) {
4058                    errors.push(UsageErr::VarFlagTooFew {
4059                        name: flag.name.clone(),
4060                        min,
4061                        got: count,
4062                    });
4063                }
4064                if let Some(max) = flag.var_max.filter(|max| count > *max) {
4065                    errors.push(UsageErr::VarFlagTooMany {
4066                        name: flag.name.clone(),
4067                        max,
4068                        got: count,
4069                    });
4070                }
4071            }
4072            if let (Some(arg), Some(value)) = (&flag.arg, value) {
4073                validate_expression(
4074                    &flag.name,
4075                    arg.validate.as_deref(),
4076                    arg.validate_error.as_deref(),
4077                    value,
4078                    &mut errors,
4079                );
4080            }
4081        }
4082        for arg in &clause.args {
4083            let given = instance.keys().any(|present| present.name == arg.name);
4084            if !given {
4085                let required_if = arg
4086                    .required_if
4087                    .iter()
4088                    .any(|selector| selector_is_explicit(selector));
4089                let required_if_eq = arg
4090                    .required_if_eq
4091                    .iter()
4092                    .any(|condition| selector_has_value(&condition.selector, &condition.value));
4093                let required_if_eq_all = !arg.required_if_eq_all.is_empty()
4094                    && arg
4095                        .required_if_eq_all
4096                        .iter()
4097                        .all(|condition| selector_has_value(&condition.selector, &condition.value));
4098                let unless_any = arg
4099                    .required_unless
4100                    .iter()
4101                    .any(|selector| selector_is_explicit(selector));
4102                let unless_all = !arg.required_unless_all.is_empty()
4103                    && arg
4104                        .required_unless_all
4105                        .iter()
4106                        .all(|selector| selector_is_explicit(selector));
4107                let required_unless = !(unless_any
4108                    || unless_all
4109                    || (arg.required_unless.is_empty() && arg.required_unless_all.is_empty()));
4110                if required_if || required_if_eq || required_if_eq_all || required_unless {
4111                    errors.push(UsageErr::MissingClauseArg {
4112                        clause: clause.name.clone(),
4113                        instance: instance_index + 1,
4114                        arg: arg.name.clone(),
4115                    });
4116                }
4117                continue;
4118            }
4119            for other in &arg.conflicts {
4120                if selector_is_explicit(other) {
4121                    errors.push(UsageErr::InvalidFlag {
4122                        token: arg.name.clone(),
4123                        reason: format!("conflicts with {other}"),
4124                        span: (0, 0).into(),
4125                        input: format!("{} {other}", arg.name),
4126                    });
4127                }
4128            }
4129            for other in &arg.requires {
4130                if selector_is_satisfied(other) {
4131                    continue;
4132                }
4133                if other.starts_with('-') {
4134                    errors.push(UsageErr::MissingFlag(selector_name(other)));
4135                } else {
4136                    errors.push(UsageErr::MissingClauseArg {
4137                        clause: clause.name.clone(),
4138                        instance: instance_index + 1,
4139                        arg: other.clone(),
4140                    });
4141                }
4142            }
4143        }
4144    }
4145    out.errors.extend(errors);
4146}
4147
4148fn selector_explicit_has_value(
4149    selector: &str,
4150    expected: &str,
4151    out: &ParseOutput,
4152    overridden_flags: &HashSet<String>,
4153    custom_env: Option<&HashMap<String, String>>,
4154) -> bool {
4155    if let Some(flag) = selected_clause_flag(out, selector) {
4156        return clause_flag_has_value(out, flag, expected);
4157    }
4158    if let Some(flag) = out
4159        .available_flags
4160        .values()
4161        .chain(out.flags.keys())
4162        .filter(|flag| !is_clause_scoped_flag(out, flag))
4163        .find(|flag| flag_matches_selector(flag, selector))
4164    {
4165        return !overridden_flags.contains(&flag.name)
4166            && explicit_flag_has_value(flag, expected, out, custom_env);
4167    }
4168    let Some(arg) = selector_arg(selector, out) else {
4169        return false;
4170    };
4171    let parsed = out
4172        .args
4173        .iter()
4174        .find(|(given, _)| given.name == arg.name)
4175        .map(|(_, value)| value)
4176        .or_else(|| {
4177            out.clauses.values().flatten().find_map(|instance| {
4178                instance
4179                    .iter()
4180                    .find(|(given, _)| given.name == arg.name)
4181                    .map(|(_, value)| value)
4182            })
4183        });
4184    if let Some(value) = parsed {
4185        return match value {
4186            ParseValue::String(value) => value == expected,
4187            ParseValue::MultiString(values) => values.iter().any(|value| value == expected),
4188            ParseValue::Bool(value) => value.to_string() == expected,
4189            ParseValue::MultiBool(values) => {
4190                values.iter().any(|value| value.to_string() == expected)
4191            }
4192        };
4193    }
4194    let value = arg.env_names().find_map(|env| match custom_env {
4195        Some(values) => values.get(env).cloned(),
4196        None => std::env::var(env).ok(),
4197    });
4198    value.is_some_and(|value| match arg.delimiter {
4199        Some(delimiter) => value.split(delimiter).any(|value| value == expected),
4200        None => value == expected,
4201    })
4202}
4203
4204fn selector_is_explicit(
4205    selector: &str,
4206    out: &ParseOutput,
4207    overridden_flags: &HashSet<String>,
4208    custom_env: Option<&HashMap<String, String>>,
4209) -> bool {
4210    let scoped_flag_is_explicit =
4211        selected_clause_flag(out, selector).is_some_and(|flag| clause_flag_is_explicit(out, flag));
4212    let flag_is_explicit = out
4213        .available_flags
4214        .values()
4215        .chain(out.flags.keys())
4216        .filter(|flag| !is_clause_scoped_flag(out, flag))
4217        .any(|flag| {
4218            flag_matches_selector(flag, selector)
4219                && !overridden_flags.contains(&flag.name)
4220                && (out.flags.contains_key(flag) || flag_has_env(flag, custom_env))
4221        });
4222    scoped_flag_is_explicit
4223        || flag_is_explicit
4224        || selector_arg(selector, out).is_some_and(|arg| arg_is_explicit(arg, out, custom_env))
4225}
4226
4227/// The name of the flag a selector points at, for an error that has to name it.
4228///
4229/// `selector_is_explicit` only answers yes or no, which is all a check needs; a message
4230/// about a flag that is *missing* has to say which one, and the selector may be a short
4231/// form or an alias rather than the name.
4232fn selector_flag_name(selector: &str, out: &ParseOutput) -> Option<String> {
4233    if let Some(flag) = selected_clause_flag(out, selector) {
4234        return Some(flag.name.clone());
4235    }
4236    out.available_flags
4237        .values()
4238        .chain(out.flags.keys())
4239        .filter(|flag| !is_clause_scoped_flag(out, flag))
4240        .find(|flag| flag_matches_selector(flag, selector))
4241        .map(|flag| flag.name.clone())
4242        .or_else(|| selector_arg(selector, out).map(|arg| arg.name.clone()))
4243}
4244
4245/// Whether a selector's flag ended up with a value, however it got one.
4246///
4247/// The rule for a *positive* relationship, and the difference from
4248/// [`selector_is_explicit`] is deliberate. A negative rule — `conflicts`, or a group's
4249/// exclusivity — has to count only what was given, or a flag with a default would
4250/// conflict with everything and no command line would parse. A positive one asks whether
4251/// the flag it names has a value, and a default is a value: that is already how plain
4252/// `required`, `required_if` and `required_unless` read a default, and `requires` saying
4253/// otherwise would have made the same flag missing here and present ten lines below.
4254fn selector_is_satisfied(
4255    selector: &str,
4256    out: &ParseOutput,
4257    overridden_flags: &HashSet<String>,
4258    custom_env: Option<&HashMap<String, String>>,
4259) -> bool {
4260    if selector_is_explicit(selector, out, overridden_flags, custom_env) {
4261        return true;
4262    }
4263    let flag_is_satisfied = out
4264        .available_flags
4265        .values()
4266        .chain(out.flags.keys())
4267        .filter(|flag| !is_clause_scoped_flag(out, flag))
4268        .filter(|flag| flag_matches_selector(flag, selector))
4269        .any(|flag| {
4270            !overridden_flags.contains(&flag.name)
4271                && (!flag.default.is_empty()
4272                    || flag.arg.iter().any(|a| !a.default.is_empty())
4273                    || flag.default_if.iter().any(|condition| {
4274                        default_if_condition_matches(condition, out, overridden_flags, custom_env)
4275                    }))
4276        });
4277    flag_is_satisfied || selector_arg(selector, out).is_some_and(|arg| !arg.default.is_empty())
4278}
4279
4280fn selector_arg<'a>(selector: &str, out: &'a ParseOutput) -> Option<&'a SpecArg> {
4281    // Bare words are positional selectors. Keep accepting a flag's internal name above
4282    // for existing specs; when both exist, the dashed flag spelling removes ambiguity.
4283    if selector.starts_with('-') {
4284        return None;
4285    }
4286    out.cmds
4287        .iter()
4288        .flat_map(active_args)
4289        .find(|arg| arg.name == selector)
4290}
4291
4292fn arg_is_explicit(
4293    arg: &SpecArg,
4294    out: &ParseOutput,
4295    custom_env: Option<&HashMap<String, String>>,
4296) -> bool {
4297    out.args.keys().any(|given| given.name == arg.name)
4298        || out
4299            .clauses
4300            .values()
4301            .flatten()
4302            .any(|instance| instance.keys().any(|given| given.name == arg.name))
4303        || arg
4304            .env
4305            .as_ref()
4306            .is_some_and(|env| env_contains(custom_env, env))
4307}
4308
4309fn apply_flag_overrides(
4310    flag: &Arc<SpecFlag>,
4311    available_flags: &BTreeMap<String, Arc<SpecFlag>>,
4312    parsed_flags: &mut IndexMap<Arc<SpecFlag>, ParseValue>,
4313    pending_flags: &mut Vec<Arc<SpecFlag>>,
4314    overridden_flags: &mut HashSet<String>,
4315    // The reportable half of the same fact: which flag did the overriding. The set above
4316    // only stops a default or an environment value restoring what was overridden, and
4317    // "`--quiet` is unset despite its default" has no answer without the name.
4318    attributed: &mut BTreeMap<String, String>,
4319) {
4320    let overridden_names: HashSet<String> = available_flags
4321        .values()
4322        .chain(parsed_flags.keys())
4323        .filter(|other| flags_override(flag, other) || flags_override(other, flag))
4324        .map(|other| other.name.clone())
4325        .collect();
4326
4327    parsed_flags.retain(|parsed, _| !overridden_names.contains(&parsed.name));
4328    pending_flags.retain(|pending| !overridden_names.contains(&pending.name));
4329    for name in &overridden_names {
4330        attributed.insert(name.clone(), flag.name.clone());
4331    }
4332    overridden_flags.extend(overridden_names);
4333    // An explicit occurrence always restores this flag, including self-overrides.
4334    overridden_flags.remove(&flag.name);
4335    attributed.remove(&flag.name);
4336}
4337
4338#[cfg(feature = "cli-help")]
4339fn render_help_err(spec: &Spec, cmd: &SpecCommand, long: bool) -> UsageErr {
4340    UsageErr::Help(docs::cli::render_help(spec, cmd, long))
4341}
4342
4343#[cfg(feature = "cli-help")]
4344fn render_help_all_err(spec: &Spec, cmd: &SpecCommand) -> UsageErr {
4345    fn append(out: &mut String, spec: &Spec, cmd: &SpecCommand) {
4346        if !out.is_empty() {
4347            out.push('\n');
4348        }
4349        out.push_str(&docs::cli::render_help(spec, cmd, true));
4350        let mut children: Vec<_> = cmd
4351            .subcommands
4352            .values()
4353            .filter(|child| !child.hide)
4354            .collect();
4355        children.sort_by_key(|child| (child.display_order.unwrap_or(999), child.name.as_str()));
4356        for child in children {
4357            append(out, spec, child);
4358        }
4359    }
4360
4361    let mut out = String::new();
4362    append(&mut out, spec, cmd);
4363    UsageErr::Help(out)
4364}
4365
4366#[cfg(not(feature = "cli-help"))]
4367fn render_help_err(_spec: &Spec, _cmd: &SpecCommand, _long: bool) -> UsageErr {
4368    UsageErr::Help("help".to_string())
4369}
4370
4371#[cfg(not(feature = "cli-help"))]
4372fn render_help_all_err(_spec: &Spec, _cmd: &SpecCommand) -> UsageErr {
4373    UsageErr::Help("help".to_string())
4374}
4375
4376/// The version to answer with. `--version` prefers the long text and `-V` the concise
4377/// one, each falling back to the other when only one is declared.
4378fn render_version_err(spec: &Spec, long: bool) -> UsageErr {
4379    let value = if long {
4380        spec.long_version.as_ref().or(spec.version.as_ref())
4381    } else {
4382        spec.version.as_ref().or(spec.long_version.as_ref())
4383    };
4384    UsageErr::Version(value.cloned().unwrap_or_default())
4385}
4386
4387fn render_action_err(spec: &Spec, cmd: &SpecCommand, flag: &SpecFlag, spelling: &str) -> UsageErr {
4388    use crate::SpecFlagAction;
4389    match flag.action {
4390        SpecFlagAction::Help => render_help_err(spec, cmd, spelling.starts_with("--")),
4391        SpecFlagAction::HelpShort => render_help_err(spec, cmd, false),
4392        SpecFlagAction::HelpLong => render_help_err(spec, cmd, true),
4393        SpecFlagAction::HelpAll => render_help_all_err(spec, cmd),
4394        SpecFlagAction::Version => render_version_err(spec, spelling.starts_with("--")),
4395        SpecFlagAction::Set => unreachable!("binding actions are handled before this helper"),
4396    }
4397}
4398
4399/// Report a required flag value that was displaced by a later option.
4400fn render_missing_flag_value(flag: &SpecFlag, following: &str) -> UsageErr {
4401    let token = flag
4402        .long
4403        .first()
4404        .map(|long| format!("--{long}"))
4405        .or_else(|| flag.short.first().map(|short| format!("-{short}")))
4406        .unwrap_or_else(|| flag.name.clone());
4407    UsageErr::InvalidFlag {
4408        token: token.clone(),
4409        reason: "requires an argument".to_string(),
4410        span: (0, 0).into(),
4411        input: format!("{token} {following}"),
4412    }
4413}
4414
4415#[derive(Copy, Clone)]
4416struct ChoiceTarget<'a> {
4417    kind: &'a str,
4418    name: &'a str,
4419}
4420
4421impl<'a> ChoiceTarget<'a> {
4422    fn arg(arg: &'a SpecArg) -> Self {
4423        Self {
4424            kind: "arg",
4425            name: &arg.name,
4426        }
4427    }
4428
4429    fn option(flag: &'a SpecFlag) -> Self {
4430        Self {
4431            kind: "option",
4432            name: &flag.name,
4433        }
4434    }
4435}
4436
4437/// Whether every letter of a short token names a flag in scope.
4438///
4439/// Scanning stops at the first letter whose flag takes a value, because everything
4440/// after it is that value rather than more letters.
4441fn short_bundle_is_known(
4442    spec: &Spec,
4443    cmds: &[SpecCommand],
4444    available: &BTreeMap<String, Arc<SpecFlag>>,
4445    token: &str,
4446) -> bool {
4447    for c in token.chars().skip(1) {
4448        match available.get(&format!("-{c}")) {
4449            // `-h` and `-V` are recognized letters even though no spec declares them, so a
4450            // bundle containing one is a bundle. Without this `-vh` was not read as one at
4451            // all and fell through to `unexpected word`, while usage-argv, usage-go and
4452            // clap all answer it with help.
4453            None if supplied_short(spec, cmds, c).is_some() => {}
4454            None => return false,
4455            Some(f) if f.arg.is_some() => return true,
4456            Some(_) => {}
4457        }
4458    }
4459    true
4460}
4461
4462/// The response `-h` or `-V` produces where nothing declares that letter.
4463///
4464/// The letter form of the flags the parser supplies rather than a spec declaring them,
4465/// under exactly the conditions [`is_help_arg`] and [`is_version_arg`] state — asked
4466/// about here one letter at a time, because a bundle is read one letter at a time.
4467///
4468/// Always the short response: `-h` is short help however many letters share its token,
4469/// and `-V` the concise version. The long forms belong to the long spellings. `-?` is not
4470/// here — it is a whole-token spelling of `-h` rather than a letter anyone bundles.
4471fn supplied_short(spec: &Spec, cmds: &[SpecCommand], letter: char) -> Option<UsageErr> {
4472    let cmd = cmds.last()?;
4473    match letter {
4474        'h' if is_help_arg(spec, cmd, "-h") => Some(render_help_err(spec, cmd, false)),
4475        'V' if is_version_arg(spec, cmds, "-V") => Some(render_version_err(spec, false)),
4476        _ => None,
4477    }
4478}
4479
4480/// Refuse a flag-like token that named nothing, if this command asked for that.
4481///
4482/// Called from the flag branches, where the lookup has just failed and nothing from
4483/// the token has been applied yet — so a bundle like `-az` is refused whole rather
4484/// than after setting `-a`.
4485fn reject_unknown_flag_if_asked(
4486    spec: &Spec,
4487    path: &[SpecCommand],
4488    token: &str,
4489) -> Result<(), UsageErr> {
4490    // A lone `-` is a value by convention. A negative number reaches this only
4491    // when no pending value opted into the narrower exception.
4492    if !is_flag_like(token) {
4493        return Ok(());
4494    }
4495    if effective_unknown_flags(spec, path) != UnknownFlags::Error {
4496        return Ok(());
4497    }
4498    Err(UsageErr::InvalidFlag {
4499        token: token.to_string(),
4500        reason: "no such flag".to_string(),
4501        span: (0, 0).into(),
4502        input: token.to_string(),
4503    })
4504}
4505
4506/// Whether a flag-like token that matches nothing is a value or an error, here.
4507///
4508/// The nearest enclosing command that stated a preference wins, then the spec,
4509/// then the default. Inherited, unlike `effect`: it describes how a command line
4510/// is read, and a CLI that forwards options tends to forward them at every level.
4511fn effective_unknown_flags(spec: &Spec, path: &[SpecCommand]) -> UnknownFlags {
4512    path.iter()
4513        .rev()
4514        .find_map(|cmd| cmd.unknown_flags)
4515        .or(spec.unknown_flags)
4516        .unwrap_or_default()
4517}
4518
4519/// Whether a token would be read as a flag, for the purpose of rejecting unknown
4520/// ones.
4521///
4522/// A lone `-` is a value by convention. Other dash-prefixed tokens are flag-like;
4523/// a field may make the narrower negative-number exception.
4524fn is_flag_like(token: &str) -> bool {
4525    match token.strip_prefix('-') {
4526        None | Some("") => false,
4527        Some(_) => true,
4528    }
4529}
4530
4531fn is_negative_number(token: &str) -> bool {
4532    token.strip_prefix('-').is_some_and(is_number)
4533}
4534
4535/// Whether a flag may claim its following token as a detached value.
4536///
4537/// A required value keeps the historical negative-number exception. A value
4538/// that may be omitted needs an explicit opt-in to distinguish a negative value
4539/// from the flag's bare form.
4540fn accepts_detached_flag_value(flag: &SpecFlag, token: &str) -> bool {
4541    !flag.require_equals
4542        && (!is_flag_like(token)
4543            || flag.allow_hyphen_values()
4544            || (is_negative_number(token)
4545                && (flag
4546                    .arg
4547                    .as_ref()
4548                    .is_some_and(|arg| arg.allow_negative_numbers)
4549                    || (flag.default_missing.is_none() && !flag.value_optional))))
4550}
4551
4552fn record_scalar_flag_occurrence(
4553    cmds: &[SpecCommand],
4554    flag: &Arc<SpecFlag>,
4555    command_level: usize,
4556    bool_value: Option<bool>,
4557    occurrences: &mut HashMap<(usize, usize), u8>,
4558    errors: &mut Vec<UsageErr>,
4559) {
4560    let strict = cmds.get(command_level).is_some_and(|cmd| {
4561        !cmd.args_override_self
4562            || cmd
4563                .clause
4564                .as_ref()
4565                .is_some_and(|clause| clause.flags.iter().any(|candidate| candidate == &**flag))
4566    });
4567    let collects_values = flag.var || flag.arg.as_ref().is_some_and(|arg| arg.var);
4568    if !strict || flag.count || collects_values {
4569        return;
4570    }
4571
4572    let bit = match bool_value {
4573        Some(false) if flag.negate.is_some() => 0b10,
4574        _ => 0b01,
4575    };
4576    let key = (Arc::as_ptr(flag) as usize, command_level);
4577    let seen = occurrences.entry(key).or_default();
4578    if *seen & bit != 0 {
4579        errors.push(UsageErr::DuplicateFlag(flag.name.clone()));
4580    }
4581    *seen |= bit;
4582}
4583
4584/// A token that can select a subcommand, trigger a mount, or be forwarded as an
4585/// external command.
4586///
4587/// Flag-like tokens are not words. A lone `-` is a value — conventionally stdin —
4588/// so it was never a candidate to *select* anything either. usage-argv uses the
4589/// same rule; without it, `-1` skipped the external-subcommand path because Phase 1
4590/// treated every token that `starts_with('-')` as a flag.
4591fn is_command_word(token: &str) -> bool {
4592    (!is_flag_like(token) || is_negative_number(token)) && token != "-"
4593}
4594
4595/// Whether an otherwise numeric-looking token is an exact declared short flag.
4596///
4597/// This check belongs in both parse phases: phase 1 must skip the flag while it
4598/// searches for a later subcommand, and phase 2 must bind it instead of offering
4599/// it to an `allow_negative_numbers` positional.
4600fn declared_numeric_short(available_flags: &BTreeMap<String, Arc<SpecFlag>>, token: &str) -> bool {
4601    token.len() == 2 && token.as_bytes()[1].is_ascii_digit() && available_flags.contains_key(token)
4602}
4603
4604/// Whether an unmatched word belongs to the root's default command.
4605///
4606/// Ordinary words always do. A negative number only does when the default command's
4607/// first positional explicitly accepts one; otherwise it stays at the root, matching
4608/// usage-argv and generated Go.
4609fn default_accepts_word(cmd: &SpecCommand, default_name: &str, token: &str) -> bool {
4610    !is_negative_number(token)
4611        || cmd
4612            .find_subcommand(default_name)
4613            .and_then(|default| default.args.first())
4614            .is_some_and(|arg| arg.allow_negative_numbers)
4615}
4616
4617/// Digits, at most one `.`, and an optional exponent.
4618///
4619/// Spelled out rather than deferred to `f64::from_str`, which also accepts `inf` and
4620/// `NaN`: `-inf` is far likelier to be a misspelled flag than a number somebody meant
4621/// to pass. usage-argv implements the same rule, and the corpus pins the edges — the
4622/// two disagreed about `-1e5` when one used a float parse and the other did not.
4623fn is_number(rest: &str) -> bool {
4624    let (mantissa, exponent) = match rest.find(['e', 'E']) {
4625        Some(at) => (&rest[..at], Some(&rest[at + 1..])),
4626        None => (rest, None),
4627    };
4628
4629    let mut seen_digit = false;
4630    let mut seen_dot = false;
4631    for c in mantissa.chars() {
4632        match c {
4633            '0'..='9' => seen_digit = true,
4634            '.' if !seen_dot => seen_dot = true,
4635            _ => return false,
4636        }
4637    }
4638    if !seen_digit {
4639        return false;
4640    }
4641
4642    match exponent {
4643        None => true,
4644        Some(exp) => {
4645            let digits = exp
4646                .strip_prefix('+')
4647                .or_else(|| exp.strip_prefix('-'))
4648                .unwrap_or(exp);
4649            !digits.is_empty() && digits.chars().all(|c| c.is_ascii_digit())
4650        }
4651    }
4652}
4653
4654/// Bind one value to the flag waiting for it, and let a variadic argument go on
4655/// collecting from the words that follow.
4656///
4657/// Every route to a flag's value comes through here — the following word, the text
4658/// after an `=`, and the token a `allow_hyphen_values` flag takes whatever it looks
4659/// like — so that all three agree on how many values the flag ends up with.
4660#[allow(clippy::too_many_arguments)]
4661fn bind_pending_flag_value(
4662    spec: &Spec,
4663    cmd: &SpecCommand,
4664    errors: &mut Vec<UsageErr>,
4665    flags: &mut IndexMap<Arc<SpecFlag>, ParseValue>,
4666    flag_awaiting_value: &mut Vec<Arc<SpecFlag>>,
4667    word: &mut String,
4668    input: &mut VecDeque<Token>,
4669    custom_env: Option<&HashMap<String, String>>,
4670    trace: &mut Trace,
4671    // Which token supplied `word`, and whether it rode along on the flag's own token
4672    // (`--jobs=8`, `-j8`) rather than following it. A variadic run's later words carry
4673    // their own positions and are recorded where they are read.
4674    argv: usize,
4675    attached: bool,
4676) -> miette::Result<bool> {
4677    // Held before the drain pops it, along with what the flag is already carrying: a
4678    // `var_max` bounds the values this occurrence takes, not the list they are appended
4679    // to, so a second `--include` starts counting again.
4680    let collecting = flag_awaiting_value
4681        .last()
4682        .filter(|flag| flag.arg.as_ref().is_some_and(|arg| arg.var))
4683        .cloned()
4684        .map(|flag| {
4685            let carried = flags.get(&flag).map(value_count).unwrap_or(0);
4686            (flag, carried)
4687        });
4688    let mut bound = vec![];
4689    let refused = drain_pending_flag_values(
4690        spec,
4691        cmd,
4692        errors,
4693        flags,
4694        flag_awaiting_value,
4695        word,
4696        custom_env,
4697        &mut bound,
4698    )?;
4699    for (flag, values) in bound {
4700        trace.record(
4701            argv,
4702            TokenRole::Value {
4703                flag,
4704                values,
4705                attached,
4706            },
4707        );
4708    }
4709    if refused {
4710        return Ok(true);
4711    }
4712    let Some((flag, carried)) = collecting else {
4713        return Ok(false);
4714    };
4715    collect_variadic_flag_values(
4716        spec,
4717        cmd,
4718        errors,
4719        flags,
4720        flag_awaiting_value,
4721        &flag,
4722        carried,
4723        input,
4724        custom_env,
4725        trace,
4726    )
4727}
4728
4729/// Keep feeding a flag whose argument is variadic from the words that follow it.
4730///
4731/// `--include <pattern>...` collects from a single occurrence, so it takes tokens until
4732/// one is flag-like, a `--` arrives, its `var_max` is reached, or the command line ends.
4733/// This is greedy by design — a command declaring both such a flag and positionals will
4734/// find the flag eating them, and `--` or a `var_max` is how the run is stopped.
4735///
4736/// `carried` is what the flag already held when this occurrence began, so the bound
4737/// counts this run rather than everything the flag has collected across the command
4738/// line. Each value goes through the same drain as the first, so choices are checked
4739/// and the value lands in the same list rather than by a second route that could
4740/// disagree.
4741#[allow(clippy::too_many_arguments)]
4742fn collect_variadic_flag_values(
4743    spec: &Spec,
4744    cmd: &SpecCommand,
4745    errors: &mut Vec<UsageErr>,
4746    flags: &mut IndexMap<Arc<SpecFlag>, ParseValue>,
4747    flag_awaiting_value: &mut Vec<Arc<SpecFlag>>,
4748    flag: &Arc<SpecFlag>,
4749    carried: usize,
4750    input: &mut VecDeque<Token>,
4751    custom_env: Option<&HashMap<String, String>>,
4752    trace: &mut Trace,
4753) -> miette::Result<bool> {
4754    let max = flag
4755        .arg
4756        .as_ref()
4757        .and_then(|arg| arg.var_max)
4758        .unwrap_or(usize::MAX);
4759    while flags
4760        .get(flag)
4761        .map(value_count)
4762        .unwrap_or(0)
4763        .saturating_sub(carried)
4764        < max
4765    {
4766        let Some(next) = input.front().map(|token| token.word.as_str()) else {
4767            break;
4768        };
4769        if flag
4770            .arg
4771            .as_ref()
4772            .and_then(|arg| arg.value_terminator.as_deref())
4773            == Some(next)
4774        {
4775            let terminator = input.pop_front().unwrap();
4776            trace.record(
4777                terminator.argv,
4778                TokenRole::ValueTerminator {
4779                    ends: flag.name.clone(),
4780                },
4781            );
4782            break;
4783        }
4784        // The separator is left where it is: stopping here hands it to the arm that
4785        // knows what it means, rather than reading it as one more value.
4786        if next == "--"
4787            || (is_flag_like(next)
4788                && !(flag
4789                    .arg
4790                    .as_ref()
4791                    .is_some_and(|arg| arg.allow_negative_numbers)
4792                    && is_negative_number(next)))
4793        {
4794            break;
4795        }
4796        let taken = input.pop_front().unwrap();
4797        let argv = taken.argv;
4798        let mut word = taken.word;
4799        flag_awaiting_value.push(Arc::clone(flag));
4800        let mut bound = vec![];
4801        let refused = drain_pending_flag_values(
4802            spec,
4803            cmd,
4804            errors,
4805            flags,
4806            flag_awaiting_value,
4807            &mut word,
4808            custom_env,
4809            &mut bound,
4810        )?;
4811        for (flag, values) in bound {
4812            // A later word of the same occurrence is its own token, and never attached:
4813            // only the first value can ride along on the flag.
4814            trace.record(
4815                argv,
4816                TokenRole::Value {
4817                    flag,
4818                    values,
4819                    attached: false,
4820                },
4821            );
4822        }
4823        if refused {
4824            return Ok(true);
4825        }
4826    }
4827    // The loop stops once the occurrence has reached its bound, which without a delimiter is
4828    // exactly when it has taken `max` words. A delimiter breaks that: one word can carry
4829    // several values, so the run can end up *past* the bound rather than on it, and stopping
4830    // is no longer the same as staying within it. `--include a,b,c` under `var_max=2` is the
4831    // case — three values out of the one word the loop was entitled to take.
4832    //
4833    // Counted against `carried` like the loop itself, so this stays a statement about the
4834    // occurrence rather than about the list the occurrences build up.
4835    let taken = flags
4836        .get(flag)
4837        .map(value_count)
4838        .unwrap_or(0)
4839        .saturating_sub(carried);
4840    if let Some(min) = flag.arg.as_ref().and_then(|arg| arg.var_min) {
4841        if taken < min {
4842            errors.push(UsageErr::VarFlagTooFew {
4843                name: flag.name.clone(),
4844                min,
4845                got: taken,
4846            });
4847        }
4848    }
4849    if taken > max {
4850        errors.push(UsageErr::VarFlagTooMany {
4851            name: flag.name.clone(),
4852            max,
4853            got: taken,
4854        });
4855    }
4856    Ok(false)
4857}
4858
4859/// How many values a flag is holding, for a bound that counts them.
4860fn value_count(value: &ParseValue) -> usize {
4861    match value {
4862        ParseValue::MultiString(values) => values.len(),
4863        ParseValue::MultiBool(values) => values.len(),
4864        _ => 1,
4865    }
4866}
4867
4868/// Finish a value-optional flag that was given with no value.
4869///
4870/// Returns whether anything was bound. Completions keep the flag waiting — a
4871/// half-typed `--color ` is a question about the value — so this is asked only
4872/// once a full parse has decided the value is not coming, or once the next token
4873/// has made that decision.
4874///
4875/// The missing string is a real value: if the flag names `choices`, it has to
4876/// be one of them, the same way an env var or a `default` is checked. Binding
4877/// first and failing later would leave the flag set to a value the spec forbids.
4878fn try_bind_default_missing(
4879    flags: &mut IndexMap<Arc<SpecFlag>, ParseValue>,
4880    flag_awaiting_value: &mut Vec<Arc<SpecFlag>>,
4881    custom_env: Option<&HashMap<String, String>>,
4882    origins: &mut IndexMap<Arc<SpecFlag>, Vec<ValueOrigin>>,
4883) -> miette::Result<bool> {
4884    let Some(flag) = flag_awaiting_value.last() else {
4885        return Ok(false);
4886    };
4887    let value = match flag.default_missing.clone() {
4888        Some(value) => value,
4889        None if flag.value_optional => {
4890            let flag = flag_awaiting_value.pop().unwrap();
4891            // Presence in the map distinguishes this from an absent flag; an
4892            // empty collection distinguishes it from an explicitly empty
4893            // `--flag=` string without inventing a sentinel value.
4894            let variadic_value = flag.arg.as_ref().is_some_and(|arg| arg.var);
4895            origins
4896                .entry(Arc::clone(&flag))
4897                .or_default()
4898                .push(ValueOrigin::DefaultMissing);
4899            if flag.var {
4900                // A repeated bare occurrence is still an occurrence. The string collection
4901                // uses an empty value for it, just as the concrete `default_missing` path
4902                // pushes one value per occurrence; otherwise bounds and consumers silently
4903                // lose every bare repeat after the first.
4904                flags
4905                    .entry(flag)
4906                    .or_insert_with(|| ParseValue::MultiString(Vec::new()))
4907                    .try_as_multi_string_mut()
4908                    .unwrap()
4909                    .push(String::new());
4910            } else if variadic_value {
4911                // A variadic occurrence stays pending after each value. Reaching the next
4912                // flag (or EOF) closes that same occurrence; it must not erase what it took.
4913                flags
4914                    .entry(flag)
4915                    .or_insert_with(|| ParseValue::MultiString(Vec::new()));
4916            } else {
4917                // A scalar pending here is a new bare occurrence. The normal permissive
4918                // repeat policy makes the later occurrence a correction, including a
4919                // correction from an explicit value back to the bare tri-state.
4920                flags.insert(flag, ParseValue::MultiString(Vec::new()));
4921            }
4922            return Ok(true);
4923        }
4924        None => return Ok(false),
4925    };
4926    if let Some(arg) = flag.arg.as_ref() {
4927        validate_choice_value(
4928            ChoiceTarget::option(flag),
4929            &value,
4930            arg.choices.as_ref(),
4931            custom_env,
4932        )?;
4933    }
4934    let flag = flag_awaiting_value.pop().unwrap();
4935    origins
4936        .entry(Arc::clone(&flag))
4937        .or_default()
4938        .push(ValueOrigin::DefaultMissing);
4939    let collecting = flag.var || flag.arg.as_ref().is_some_and(|arg| arg.var);
4940    if collecting {
4941        let arr = flags
4942            .entry(flag)
4943            .or_insert_with(|| ParseValue::MultiString(vec![]))
4944            .try_as_multi_string_mut()
4945            .unwrap();
4946        arr.push(value);
4947    } else {
4948        flags.insert(flag, ParseValue::String(value));
4949    }
4950    Ok(true)
4951}
4952
4953/// `bound` collects what each drained flag took, in the order it took it. The values are
4954/// the word after any `delimiter` split, which is the only place that split is known: by the
4955/// time they are in `flags` a scalar and a one-element list are indistinguishable, and a
4956/// second occurrence has appended to the same list.
4957#[allow(clippy::too_many_arguments)]
4958fn drain_pending_flag_values(
4959    spec: &Spec,
4960    cmd: &SpecCommand,
4961    errors: &mut Vec<UsageErr>,
4962    flags: &mut IndexMap<Arc<SpecFlag>, ParseValue>,
4963    flag_awaiting_value: &mut Vec<Arc<SpecFlag>>,
4964    word: &mut String,
4965    custom_env: Option<&HashMap<String, String>>,
4966    bound: &mut Vec<(Arc<SpecFlag>, Vec<String>)>,
4967) -> miette::Result<bool> {
4968    while let Some(flag) = flag_awaiting_value.pop() {
4969        let arg = flag.arg.as_ref().unwrap();
4970        // Split before anything judges the word, because after the split it is no longer
4971        // one value: `--env dev,prod` is two, and `choices` has to be asked about each.
4972        // Judging first would reject the whole word against a list neither half is on.
4973        let parts: Vec<String> = match arg.delimiter {
4974            Some(delimiter) => word.split(delimiter).map(str::to_string).collect(),
4975            None => vec![std::mem::take(word)],
4976        };
4977        for part in &parts {
4978            if validate_choices(
4979                spec,
4980                cmd,
4981                errors,
4982                ChoiceTarget::option(&flag),
4983                part,
4984                arg.choices.as_ref(),
4985                custom_env,
4986            )? {
4987                return Ok(true);
4988            }
4989        }
4990        word.clear();
4991        bound.push((Arc::clone(&flag), parts.clone()));
4992        // Two ways to hold several values, and both record a list: a `var` flag
4993        // collects one per occurrence, a variadic argument collects several from one.
4994        if flag.var || arg.var {
4995            let arr = flags
4996                .entry(flag)
4997                .or_insert_with(|| ParseValue::MultiString(vec![]))
4998                .try_as_multi_string_mut()
4999                .unwrap();
5000            arr.extend(parts);
5001        } else {
5002            // Nowhere for a second value to go, so the word stands as it was typed. A
5003            // delimiter on a flag that takes one value is refused where it is written.
5004            flags.insert(
5005                flag,
5006                ParseValue::String(parts.into_iter().next().unwrap_or_default()),
5007            );
5008        }
5009    }
5010    Ok(false)
5011}
5012
5013fn choice_error(
5014    target: ChoiceTarget<'_>,
5015    value: &str,
5016    choices: Option<&SpecChoices>,
5017    custom_env: Option<&HashMap<String, String>>,
5018) -> Option<String> {
5019    let choices = choices?;
5020    if !choices.strict {
5021        return None;
5022    }
5023    let values = choices.values_with_env(custom_env);
5024    if choices.matches_with_env(value, custom_env) {
5025        return None;
5026    }
5027    if let Some(env) = choices.env() {
5028        if values.is_empty() {
5029            return Some(format!(
5030                "Invalid choice for {} {}: {value}, no choices resolved from env {env}",
5031                target.kind, target.name,
5032            ));
5033        }
5034    }
5035    Some(format!(
5036        "Invalid choice for {} {}: {value}, expected one of {}",
5037        target.kind,
5038        target.name,
5039        values.join(", ")
5040    ))
5041}
5042
5043fn validate_choices(
5044    spec: &Spec,
5045    cmd: &SpecCommand,
5046    errors: &mut Vec<UsageErr>,
5047    target: ChoiceTarget<'_>,
5048    value: &str,
5049    choices: Option<&SpecChoices>,
5050    custom_env: Option<&HashMap<String, String>>,
5051) -> miette::Result<bool> {
5052    if is_help_arg(spec, cmd, value)
5053        && choices
5054            .is_some_and(|choices| choices.strict && !choices.matches_with_env(value, custom_env))
5055    {
5056        errors.push(render_help_err(spec, cmd, value.len() > 2));
5057        return Ok(true);
5058    }
5059
5060    if let Some(err) = choice_error(target, value, choices, custom_env) {
5061        bail!("{err}");
5062    }
5063    Ok(false)
5064}
5065
5066fn validate_choice_value(
5067    target: ChoiceTarget<'_>,
5068    value: &str,
5069    choices: Option<&SpecChoices>,
5070    custom_env: Option<&HashMap<String, String>>,
5071) -> miette::Result<()> {
5072    if let Some(err) = choice_error(target, value, choices, custom_env) {
5073        bail!("{err}");
5074    }
5075    Ok(())
5076}
5077
5078fn validate_choice_values(
5079    target: ChoiceTarget<'_>,
5080    values: &[String],
5081    choices: Option<&SpecChoices>,
5082    custom_env: Option<&HashMap<String, String>>,
5083) -> miette::Result<()> {
5084    for value in values {
5085        validate_choice_value(target, value, choices, custom_env)?;
5086    }
5087    Ok(())
5088}
5089
5090/// Everything a parse records about where it stopped: the positional cursor, so callers that
5091/// do not re-run the parse — completions, above all — agree with it, and the token trace.
5092///
5093/// Every exit from the binding phase comes through here, which is what makes it the right
5094/// place to close the trace: whatever is still queued was never read, and saying so is more
5095/// useful than leaving those words out of the report entirely.
5096fn record_stop(
5097    out: &mut ParseOutput,
5098    next_arg_idx: usize,
5099    seen_double_dash: bool,
5100    trace: &mut Trace,
5101    unread: &VecDeque<Token>,
5102) {
5103    out.next_arg = active_args(&out.cmd)
5104        .get(cursor_skip_sigils(&out.cmd, next_arg_idx))
5105        .cloned()
5106        .map(Arc::new);
5107    out.double_dash_seen = seen_double_dash;
5108    finalize_current_clause(out);
5109    trace.close(unread);
5110    out.tokens = std::mem::take(&mut trace.tokens);
5111}
5112
5113fn finalize_current_clause(out: &mut ParseOutput) {
5114    let Some(clause) = out.cmd.clause.as_ref() else {
5115        return;
5116    };
5117    let has_scoped_flags = clause
5118        .flags
5119        .iter()
5120        .any(|flag| out.flags.contains_key(&Arc::new(flag.clone())));
5121    if clause.separator.is_none() && out.args.is_empty() && !has_scoped_flags {
5122        return;
5123    }
5124    out.clauses
5125        .entry(clause.name.clone())
5126        .or_default()
5127        .push(std::mem::take(&mut out.args));
5128    let mut flags = IndexMap::new();
5129    for clause_flag in &clause.flags {
5130        let key = Arc::new(clause_flag.clone());
5131        if let Some((flag, value)) = out.flags.shift_remove_entry(&key) {
5132            flags.insert(flag, value);
5133        }
5134    }
5135    out.clause_flags
5136        .entry(clause.name.clone())
5137        .or_default()
5138        .push(flags);
5139}
5140
5141fn restore_current_clause(out: &mut ParseOutput) {
5142    let Some(clause) = out.cmd.clause.as_ref() else {
5143        return;
5144    };
5145    if let Some(current) = out.clauses.get_mut(&clause.name).and_then(Vec::pop) {
5146        out.args = current;
5147    }
5148    if let Some(current) = out.clause_flags.get_mut(&clause.name).and_then(Vec::pop) {
5149        out.flags.extend(current);
5150    }
5151}
5152
5153fn reset_clause_scalar_occurrences(
5154    out: &ParseOutput,
5155    occurrences: &mut HashMap<(usize, usize), u8>,
5156) {
5157    let Some(clause) = out.cmd.clause.as_ref() else {
5158        return;
5159    };
5160    let names = clause
5161        .flags
5162        .iter()
5163        .map(|flag| flag.name.as_str())
5164        .collect::<HashSet<_>>();
5165    let pointers = unique_flags(out.available_flags.values())
5166        .filter(|flag| names.contains(flag.name.as_str()))
5167        .map(|flag| Arc::as_ptr(flag) as usize)
5168        .collect::<HashSet<_>>();
5169    occurrences.retain(|(flag, _), _| !pointers.contains(flag));
5170}
5171
5172fn cursor_skip_sigils(cmd: &SpecCommand, mut idx: usize) -> usize {
5173    while active_args(cmd)
5174        .get(idx)
5175        .is_some_and(|arg| arg.sigil.is_some())
5176    {
5177        idx += 1;
5178    }
5179    idx
5180}
5181
5182fn active_args(cmd: &SpecCommand) -> &[SpecArg] {
5183    cmd.clause
5184        .as_ref()
5185        .map(|clause| clause.args.as_slice())
5186        .unwrap_or(cmd.args.as_slice())
5187}
5188
5189fn match_sigil_arg<'a>(
5190    cmd: &'a SpecCommand,
5191    word: &'a str,
5192) -> Option<(&'a SpecArg, &'a str, &'a str)> {
5193    cmd.args
5194        .iter()
5195        .filter_map(|arg| {
5196            let sigil = arg.sigil.as_deref()?;
5197            let value = word.strip_prefix(sigil)?;
5198            Some((arg, sigil, value))
5199        })
5200        .max_by_key(|(_, sigil, _)| sigil.len())
5201}
5202
5203fn match_sigil_arg_chain<'a>(
5204    cmds: &'a [SpecCommand],
5205    word: &'a str,
5206) -> Option<(&'a SpecArg, &'a str, &'a str)> {
5207    cmds.iter()
5208        .filter_map(|cmd| match_sigil_arg(cmd, word))
5209        .max_by_key(|(_, sigil, _)| sigil.len())
5210}
5211
5212/// Record that `arg` was handed a word before the `--` it requires.
5213///
5214/// A variadic arg would otherwise report the same mistake once per word it was offered, so the
5215/// message is emitted only the first time each arg is seen. The set is also what suppresses the
5216/// `MissingArg` that a `required` + `double_dash="required"` arg would otherwise collect at the
5217/// end of the parse.
5218fn report_double_dash_violation(
5219    arg: &SpecArg,
5220    errors: &mut Vec<UsageErr>,
5221    violations: &mut HashSet<String>,
5222) {
5223    if violations.insert(arg.name.clone()) {
5224        errors.push(UsageErr::ArgRequiresDoubleDash(arg.name.clone()));
5225    }
5226}
5227
5228/// `--version` and `-V`, which the parser supplies where the spec declares a version.
5229///
5230/// The twin of [`is_help_arg`], and of the `version` bit in usage-argv's and usage-go's
5231/// command tables — both of which accepted these spellings while this parser called them
5232/// unknown words. The help page has always listed `-V, --version` under the same
5233/// condition, and said in as many words that it did so "only where a version is
5234/// declared, which is where a parser accepts one", so a spec with a `version` rendered a
5235/// page advertising a flag the parse refused.
5236///
5237/// The root only, because that is where the page lists it: `version` is a property of
5238/// the program, and a subcommand answering with the program's version is a claim no spec
5239/// made. A declared flag wins by arriving first — every scan consults this only after
5240/// nothing declared matched — so a CLI that spends `-V` on something else keeps it, and
5241/// keeps `--version` supplied beside it.
5242fn is_version_arg(spec: &Spec, cmds: &[SpecCommand], w: &str) -> bool {
5243    (spec.version.is_some() || spec.long_version.is_some())
5244        && cmds.len() == 1
5245        && !spec.cmd.disable_version_flag
5246        && (w == "--version" || w == "-V")
5247}
5248
5249fn is_help_arg(spec: &Spec, cmd: &SpecCommand, w: &str) -> bool {
5250    spec.disable_help != Some(true)
5251        && (((w == "--help" || w == "-h" || w == "-?") && !cmd.disable_help_flag)
5252            || (w == "help" && !cmd.disable_help_subcommand && cmd.subcommands.is_empty()))
5253}
5254
5255impl ParseOutput {
5256    pub fn as_env(&self) -> BTreeMap<String, String> {
5257        let mut env = BTreeMap::new();
5258        for (flag, val) in &self.flags {
5259            let key = format!("usage_{}", crate::case::snake(&flag.name));
5260            let val = match val {
5261                ParseValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
5262                ParseValue::String(s) => s.clone(),
5263                ParseValue::MultiBool(b) => b.iter().filter(|b| **b).count().to_string(),
5264                ParseValue::MultiString(s) => crate::shell_words::join(s),
5265            };
5266            env.insert(key, val);
5267        }
5268        for (arg, val) in &self.args {
5269            let key = format!("usage_{}", crate::case::snake(&arg.name));
5270            env.insert(key, val.to_string());
5271        }
5272        env
5273    }
5274}
5275
5276impl Display for ParseValue {
5277    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5278        match self {
5279            ParseValue::Bool(b) => write!(f, "{b}"),
5280            ParseValue::String(s) => write!(f, "{s}"),
5281            ParseValue::MultiBool(b) => write!(f, "{}", b.iter().join(" ")),
5282            ParseValue::MultiString(s) => write!(f, "{}", crate::shell_words::join(s)),
5283        }
5284    }
5285}
5286
5287/// One `tokens` line for [`Debug`]: the position, the word, and what it became.
5288fn render_token(token: &TokenBinding) -> String {
5289    let roles = token.roles.iter().map(render_role).join(", ");
5290    let synthesized = if token.synthesized { " (read as)" } else { "" };
5291    format!("[{}] {}{synthesized}: {roles}", token.index, token.word)
5292}
5293
5294fn render_role(role: &TokenRole) -> String {
5295    match role {
5296        TokenRole::Program => "program".to_string(),
5297        TokenRole::Command { name } => format!("subcommand {name}"),
5298        TokenRole::Flag {
5299            flag,
5300            spelling,
5301            negated,
5302        } => {
5303            let negated = if *negated { ", negated" } else { "" };
5304            format!("flag {} as {spelling}{negated}", flag.name)
5305        }
5306        TokenRole::Value {
5307            flag,
5308            values,
5309            attached,
5310        } => {
5311            let attached = if *attached { ", attached" } else { "" };
5312            format!("value of {} = {values:?}{attached}", flag.name)
5313        }
5314        TokenRole::Arg { arg, values } => format!("arg {} = {values:?}", arg.name),
5315        TokenRole::Sigil { arg, sigil, values } => {
5316            format!("sigil arg {} ({sigil}) = {values:?}", arg.name)
5317        }
5318        TokenRole::Separator => "separator".to_string(),
5319        TokenRole::Builtin { spelling } => format!("built-in {spelling}"),
5320        TokenRole::ValueTerminator { ends } => format!("value terminator, ends {ends}"),
5321        TokenRole::Restart => "restart".to_string(),
5322        TokenRole::ClauseSeparator { name } => format!("clause separator for {name}"),
5323        TokenRole::UnknownFlag { bound_as } => match bound_as {
5324            Some(arg) => format!("unknown flag, bound as {}", arg.name),
5325            None => "unknown flag".to_string(),
5326        },
5327        TokenRole::Refused { reason } => format!("refused: {reason}"),
5328        TokenRole::External => "external".to_string(),
5329        TokenRole::Unread => "unread".to_string(),
5330    }
5331}
5332
5333impl Debug for ParseOutput {
5334    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
5335        f.debug_struct("ParseOutput")
5336            .field("cmds", &self.cmds.iter().map(|c| &c.name).join(" ").trim())
5337            .field(
5338                "args",
5339                &self
5340                    .args
5341                    .iter()
5342                    .map(|(a, w)| format!("{}: {w}", a.name))
5343                    .collect_vec(),
5344            )
5345            .field("clauses", &self.clauses)
5346            .field(
5347                "available_flags",
5348                &self
5349                    .available_flags
5350                    .iter()
5351                    .map(|(f, w)| format!("{f}: {w}"))
5352                    .collect_vec(),
5353            )
5354            .field(
5355                "flags",
5356                &self
5357                    .flags
5358                    .iter()
5359                    .map(|(f, w)| format!("{}: {w}", f.name))
5360                    .collect_vec(),
5361            )
5362            .field("flag_awaiting_value", &self.flag_awaiting_value)
5363            .field("errors", &self.errors)
5364            .field("external", &self.external)
5365            // Provenance, one line per token and one per fallback. This is the parser's
5366            // debug channel under `USAGE_LOG=trace`, so it is where a spec author looks
5367            // first — `usage explain` renders the same facts for a reader.
5368            .field(
5369                "tokens",
5370                &self.tokens.iter().map(render_token).collect_vec(),
5371            )
5372            .field(
5373                "origins",
5374                &self
5375                    .flag_origins
5376                    .iter()
5377                    .map(|(f, o)| format!("{}: {o:?}", f.name))
5378                    .chain(
5379                        self.arg_origins
5380                            .iter()
5381                            .map(|(a, o)| format!("{}: {o:?}", a.name)),
5382                    )
5383                    .collect_vec(),
5384            )
5385            .field("overridden_flags", &self.overridden_flags)
5386            .finish()
5387    }
5388}
5389
5390#[cfg(test)]
5391mod tests {
5392    use super::*;
5393    use crate::SpecFlagAction;
5394
5395    fn input(words: &[&str]) -> Vec<String> {
5396        words.iter().map(|word| (*word).to_string()).collect()
5397    }
5398
5399    #[test]
5400    fn a_declared_version_supplies_the_flag_the_help_page_lists() {
5401        // The page has always listed `-V, --version` wherever a `version` is declared,
5402        // and usage-argv and usage-go have always accepted both. This parser called them
5403        // unknown words, so the one implementation the corpus measures the others against
5404        // was the one that disagreed.
5405        let spec: Spec = "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\ncmd \"run\"\n"
5406            .parse()
5407            .unwrap();
5408
5409        for spelling in ["--version", "-V"] {
5410            let err = parse(&spec, &input(&["ex", spelling]))
5411                .expect_err("answering with a version ends the parse");
5412            assert_eq!(err.to_string(), "1.2.3", "{spelling}");
5413        }
5414    }
5415
5416    #[test]
5417    fn the_supplied_version_flag_is_the_roots_alone() {
5418        // `version` describes the program, and the page lists the entry on the program's
5419        // own page only. A subcommand answering with it would be a claim no spec made.
5420        let spec: Spec = "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\ncmd \"run\"\n"
5421            .parse()
5422            .unwrap();
5423
5424        let err = parse(&spec, &input(&["ex", "run", "--version"])).unwrap_err();
5425        assert_eq!(err.to_string(), "unexpected word: --version");
5426    }
5427
5428    #[test]
5429    fn no_declared_version_supplies_nothing() {
5430        // A `--version` answering with nothing is worse than one that is not there, which
5431        // is why the entry is conditional on the page and the spelling on the parse.
5432        let spec: Spec = "name \"ex\"\nbin \"ex\"\n".parse().unwrap();
5433
5434        for spelling in ["--version", "-V"] {
5435            let err = parse(&spec, &input(&["ex", spelling])).unwrap_err();
5436            assert_eq!(err.to_string(), format!("unexpected word: {spelling}"));
5437        }
5438    }
5439
5440    #[test]
5441    fn disable_version_flag_removes_the_supplied_spellings() {
5442        let spec: Spec = "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\ndisable_version_flag #true\n"
5443            .parse()
5444            .unwrap();
5445
5446        for spelling in ["--version", "-V"] {
5447            let err = parse(&spec, &input(&["ex", spelling])).unwrap_err();
5448            assert_eq!(err.to_string(), format!("unexpected word: {spelling}"));
5449        }
5450    }
5451
5452    #[test]
5453    fn a_spelling_the_spec_spends_elsewhere_keeps_its_meaning() {
5454        // The page drops each supplied spelling the CLI claimed and keeps the other; the
5455        // parse agrees without being told, because a declared flag is matched first.
5456        let spec: Spec = "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\nflag \"-V --verbose\"\n"
5457            .parse()
5458            .unwrap();
5459
5460        let out = parse(&spec, &input(&["ex", "-V"])).expect("-V is the CLI's own flag");
5461        assert_eq!(out.flags.len(), 1);
5462
5463        let err = parse(&spec, &input(&["ex", "--version"])).unwrap_err();
5464        assert_eq!(err.to_string(), "1.2.3");
5465    }
5466
5467    #[test]
5468    fn the_supplied_spellings_split_the_two_version_texts() {
5469        // The same split `render_action_err` gives a declared version flag: the long
5470        // spelling prefers `long_version`, the short prefers the concise one.
5471        let spec: Spec =
5472            "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\nlong_version \"1.2.3 (abcdef)\"\n"
5473                .parse()
5474                .unwrap();
5475
5476        assert_eq!(
5477            parse(&spec, &input(&["ex", "--version"]))
5478                .unwrap_err()
5479                .to_string(),
5480            "1.2.3 (abcdef)"
5481        );
5482        assert_eq!(
5483            parse(&spec, &input(&["ex", "-V"])).unwrap_err().to_string(),
5484            "1.2.3"
5485        );
5486    }
5487
5488    #[test]
5489    fn a_supplied_short_is_a_letter_a_bundle_may_contain() {
5490        // `-h` and `-V` are recognized letters that no spec declares, so a token holding
5491        // one beside a declared letter is a bundle. usage-lib alone read `-vh` as a word
5492        // naming nothing: usage-argv and usage-go both resolve the letter through the
5493        // same lookup that finds a declared short, and clap prints help for it too.
5494        let spec: Spec =
5495            "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\nflag \"-v --verbose\"\ncmd \"run\"\n"
5496                .parse()
5497                .unwrap();
5498
5499        for token in ["-vh", "-hv"] {
5500            let err = parse(&spec, &input(&["ex", token])).expect_err("help ends the parse");
5501            assert!(err.to_string().starts_with("ex 1.2.3"), "{token}: {err}");
5502        }
5503        for token in ["-vV", "-Vv"] {
5504            let err = parse(&spec, &input(&["ex", token])).expect_err("a version ends it too");
5505            assert_eq!(err.to_string(), "1.2.3", "{token}");
5506        }
5507    }
5508
5509    #[test]
5510    fn a_bundled_help_letter_asks_for_the_short_page() {
5511        // Whatever else shares the token: `-h` is the short spelling, and the letters
5512        // beside it say nothing about which page was asked for.
5513        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-v --verbose\" help=\"Be loud\" {\n    long_help \"Be loud, and say so at length.\"\n}\n"
5514            .parse()
5515            .unwrap();
5516
5517        let short = parse(&spec, &input(&["ex", "-vh"]))
5518            .unwrap_err()
5519            .to_string();
5520        let long = parse(&spec, &input(&["ex", "--help"]))
5521            .unwrap_err()
5522            .to_string();
5523        assert!(short.contains("Be loud"), "{short}");
5524        assert!(!short.contains("at length"), "{short}");
5525        assert!(long.contains("at length"), "{long}");
5526    }
5527
5528    #[test]
5529    fn the_bundled_version_letter_is_the_roots_alone() {
5530        // The same rule the whole-token spelling follows, asked one letter at a time.
5531        let spec: Spec =
5532            "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\nflag \"-v --verbose\" global=#true\ncmd \"run\"\n"
5533                .parse()
5534                .unwrap();
5535
5536        let err = parse(&spec, &input(&["ex", "run", "-vV"])).unwrap_err();
5537        assert_eq!(err.to_string(), "unexpected word: -vV");
5538    }
5539
5540    #[test]
5541    fn a_declared_letter_keeps_its_meaning_inside_a_bundle() {
5542        // Nothing is supplied where the CLI spent the letter itself, so `-vh local` is
5543        // this spec's own `-h`, taking its value from the rest of the token.
5544        let spec: Spec =
5545            "name \"ex\"\nbin \"ex\"\nversion \"1.2.3\"\nflag \"-v --verbose\"\nflag \"-h --host <host>\"\n"
5546                .parse()
5547                .unwrap();
5548
5549        let out = parse(&spec, &input(&["ex", "-vhlocal"])).expect("a bundle and its value");
5550        assert_eq!(out.flags.len(), 2);
5551        assert!(out
5552            .flags
5553            .iter()
5554            .any(|(flag, value)| flag.name == "host" && value.to_string() == "local"));
5555    }
5556
5557    #[test]
5558    fn disabling_help_takes_the_letter_back_out_of_the_bundle() {
5559        let spec: Spec =
5560            "name \"ex\"\nbin \"ex\"\ndisable_help_flag #true\nflag \"-v --verbose\"\n"
5561                .parse()
5562                .unwrap();
5563
5564        let err = parse(&spec, &input(&["ex", "-vh"])).unwrap_err();
5565        assert_eq!(err.to_string(), "unexpected word: -vh");
5566    }
5567
5568    #[test]
5569    fn a_letter_nothing_supplies_still_refuses_the_whole_bundle() {
5570        // The rule this must not weaken: `-az` is not a bundle at all, so `-a` is not set
5571        // on the way to discovering that `z` names nothing.
5572        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-a --all\"\narg \"[file]\"\n"
5573            .parse()
5574            .unwrap();
5575
5576        let out = parse(&spec, &input(&["ex", "-az"])).expect("it falls through to the argument");
5577        assert!(out.flags.is_empty(), "{:?}", out.flags);
5578        assert_eq!(out.args.len(), 1);
5579    }
5580
5581    fn spec_with_arg(arg: SpecArg) -> Spec {
5582        let cmd = SpecCommand::builder().name("test").arg(arg).build();
5583        Spec {
5584            name: "test".to_string(),
5585            bin: "test".to_string(),
5586            cmd,
5587            ..Default::default()
5588        }
5589    }
5590
5591    fn spec_with_flag(flag: SpecFlag) -> Spec {
5592        let cmd = SpecCommand::builder().name("test").flag(flag).build();
5593        Spec {
5594            name: "test".to_string(),
5595            bin: "test".to_string(),
5596            cmd,
5597            ..Default::default()
5598        }
5599    }
5600
5601    fn parse_with_env(
5602        spec: &Spec,
5603        words: &[&str],
5604        env: &[(&str, &str)],
5605    ) -> Result<ParseOutput, miette::Error> {
5606        let env = env
5607            .iter()
5608            .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
5609            .collect();
5610        Parser::new(spec).with_env(env).parse(&input(words))
5611    }
5612
5613    fn first_string_value(parsed: &ParseOutput) -> &str {
5614        if let Some(ParseValue::String(value)) = parsed.args.values().next() {
5615            return value;
5616        }
5617        if let Some(ParseValue::String(value)) = parsed.flags.values().next() {
5618            return value;
5619        }
5620        panic!("expected first parsed value to be ParseValue::String");
5621    }
5622
5623    #[test]
5624    fn custom_environment_parser_dispatches_executable_views() {
5625        let spec: Spec = r#"
5626bin "ex"
5627view "runner" root="run"
5628cmd "run" {
5629    flag "--token <token>" env="TOKEN"
5630}
5631        "#
5632        .parse()
5633        .unwrap();
5634        let parsed = Parser::new(&spec)
5635            .with_env([("TOKEN".to_string(), "secret".to_string())].into())
5636            .parse(&input(&["runner"]))
5637            .unwrap();
5638
5639        assert_eq!(parsed.cmd.name, "runner");
5640        assert!(parsed.flags.iter().any(|(flag, value)| flag.name == "token"
5641            && matches!(value, ParseValue::String(value) if value == "secret")));
5642    }
5643
5644    #[test]
5645    fn an_executable_view_keeps_the_hosts_version_action() {
5646        let spec: Spec = r#"
5647bin "ex"
5648version "1.2.3"
5649flag "-V --version" action="version"
5650flag "--verbose" global=#true
5651view "runner" root="run" globals=#true
5652cmd "run"
5653        "#
5654        .parse()
5655        .unwrap();
5656
5657        let error = Parser::new(&spec)
5658            .parse(&input(&["runner", "--version"]))
5659            .expect_err("the host version action should answer before view projection");
5660        assert_eq!(error.to_string(), "1.2.3");
5661
5662        let error = Parser::new(&spec)
5663            .parse(&input(&["runner", "--verbose", "--version"]))
5664            .expect_err("the host version action should remain after a carried global");
5665        assert_eq!(error.to_string(), "1.2.3");
5666    }
5667
5668    fn flag_string_value<'a>(parsed: &'a ParseOutput, name: &str) -> &'a str {
5669        let flag = parsed
5670            .flags
5671            .keys()
5672            .find(|flag| flag.name == name)
5673            .unwrap_or_else(|| panic!("expected flag {name}"));
5674        let value = parsed
5675            .flags
5676            .get(flag)
5677            .unwrap_or_else(|| panic!("expected value for flag {name}"));
5678        match value {
5679            ParseValue::String(value) => value,
5680            _ => panic!("expected flag {name} to be ParseValue::String"),
5681        }
5682    }
5683
5684    fn assert_parse_err(result: Result<ParseOutput, miette::Error>, expected: &str) {
5685        let err = result.expect_err("expected parser error");
5686        assert_eq!(format!("{err}"), expected);
5687    }
5688
5689    #[test]
5690    fn a_short_version_action_falls_back_to_the_long_version() {
5691        let flag = SpecFlag::builder()
5692            .short('R')
5693            .action(SpecFlagAction::Version)
5694            .build();
5695        let spec = Spec {
5696            name: "test".to_string(),
5697            bin: "test".to_string(),
5698            long_version: Some("1.2.3\ncommit abc123".to_string()),
5699            ..Default::default()
5700        };
5701        let UsageErr::Version(version) = render_action_err(&spec, &spec.cmd, &flag, "-R") else {
5702            panic!("expected version action")
5703        };
5704        assert_eq!(version, "1.2.3\ncommit abc123");
5705    }
5706
5707    #[cfg(feature = "unstable_choices_env")]
5708    fn spec_arg_choices_env(key: &str) -> Spec {
5709        spec_with_arg(
5710            SpecArg::builder()
5711                .name("env")
5712                .choices_env(key)
5713                .required(false)
5714                .build(),
5715        )
5716    }
5717
5718    #[cfg(feature = "unstable_choices_env")]
5719    fn spec_flag_choices_env(key: &str) -> Spec {
5720        spec_with_flag(
5721            SpecFlag::builder()
5722                .long("env")
5723                .arg(SpecArg::builder().name("env").choices_env(key).build())
5724                .build(),
5725        )
5726    }
5727
5728    #[test]
5729    fn test_parse() {
5730        let cmd = SpecCommand::builder()
5731            .name("test")
5732            .arg(SpecArg::builder().name("arg").build())
5733            .flag(SpecFlag::builder().long("flag").build())
5734            .build();
5735        let spec = Spec {
5736            name: "test".to_string(),
5737            bin: "test".to_string(),
5738            cmd,
5739            ..Default::default()
5740        };
5741        let input = vec!["test".to_string(), "arg1".to_string(), "--flag".to_string()];
5742        let parsed = parse(&spec, &input).unwrap();
5743        assert_eq!(parsed.cmds.len(), 1);
5744        assert_eq!(parsed.cmds[0].name, "test");
5745        assert_eq!(parsed.args.len(), 1);
5746        assert_eq!(parsed.flags.len(), 1);
5747        assert_eq!(parsed.available_flags.len(), 1);
5748    }
5749
5750    #[test]
5751    fn test_flag_overrides_last_occurrence_wins() {
5752        let spec: Spec = r#"
5753flag "--stdin" default=#true
5754flag "--file <file>" overrides="--stdin"
5755        "#
5756        .parse()
5757        .unwrap();
5758
5759        let file_wins = parse(&spec, &input(&["test", "--stdin", "--file", "input.txt"])).unwrap();
5760        assert_eq!(file_wins.flags.len(), 1);
5761        assert_eq!(flag_string_value(&file_wins, "file"), "input.txt");
5762        assert!(!file_wins.flags.keys().any(|flag| flag.name == "stdin"));
5763
5764        let stdin_wins = parse(&spec, &input(&["test", "--file", "input.txt", "--stdin"])).unwrap();
5765        assert_eq!(stdin_wins.flags.len(), 1);
5766        assert!(stdin_wins.flags.keys().any(|flag| flag.name == "stdin"));
5767        assert!(!stdin_wins.flags.keys().any(|flag| flag.name == "file"));
5768    }
5769
5770    #[test]
5771    fn test_flag_override_clears_pending_value() {
5772        let spec: Spec = r#"
5773flag "--file <file>" overrides="--stdin"
5774flag "--stdin"
5775arg "[input]"
5776        "#
5777        .parse()
5778        .unwrap();
5779
5780        let parsed = parse(&spec, &input(&["test", "--file", "--stdin", "input.txt"])).unwrap();
5781        assert_eq!(parsed.flags.len(), 1);
5782        assert!(parsed.flags.keys().any(|flag| flag.name == "stdin"));
5783        assert_eq!(first_string_value(&parsed), "input.txt");
5784    }
5785
5786    #[cfg(unix)]
5787    #[test]
5788    fn a_mount_on_the_root_discovers_subcommands() {
5789        // The root is a command like any other, so it can find its own subcommands
5790        // by running something. Uses `echo` rather than a fixture because resolving
5791        // a mount is what is being tested.
5792        let spec: Spec = r#"
5793name "ex"
5794bin "ex"
5795cmd "declared"
5796mount run="echo 'cmd \"discovered\"'"
5797"#
5798        .parse()
5799        .unwrap();
5800
5801        let out = parse(&spec, &["ex".to_string(), "discovered".to_string()]).unwrap();
5802        assert_eq!(out.cmd.name, "discovered");
5803    }
5804
5805    #[test]
5806    fn injected_mount_outputs_are_complete_and_never_fall_back_to_processes() {
5807        let spec: Spec = r#"
5808name "ex"
5809bin "ex"
5810mount run="this command must never run"
5811cmd "declared"
5812"#
5813        .parse()
5814        .unwrap();
5815
5816        Parser::new(&spec)
5817            .with_mount_outputs(HashMap::new())
5818            .parse(&input(&["ex", "declared"]))
5819            .expect("a declared command does not resolve the mount");
5820
5821        let error = Parser::new(&spec)
5822            .with_mount_outputs(HashMap::new())
5823            .parse(&input(&["ex", "discovered"]))
5824            .unwrap_err();
5825        assert!(
5826            error
5827                .to_string()
5828                .contains("No injected output was provided for mount command"),
5829            "{error}"
5830        );
5831    }
5832
5833    #[cfg(unix)]
5834    #[test]
5835    fn completion_sees_root_mounted_commands_with_nothing_typed() {
5836        // The case a root mount exists for. `mycli <tab>` has no word to trigger
5837        // discovery with, so a completion has to resolve up front or the mounted
5838        // commands are never offered.
5839        let spec: Spec = r#"
5840name "ex"
5841bin "ex"
5842cmd "declared"
5843mount run="echo 'cmd \"discovered\"'"
5844"#
5845        .parse()
5846        .unwrap();
5847
5848        let out = parse_partial(&spec, &["ex".to_string()]).unwrap();
5849        assert!(
5850            out.cmd.subcommands.contains_key("discovered"),
5851            "a completion should see mounted commands; got {:?}",
5852            out.cmd.subcommands.keys().collect::<Vec<_>>()
5853        );
5854    }
5855
5856    #[cfg(unix)]
5857    #[test]
5858    fn completion_and_execution_agree_about_discovery() {
5859        // Offering a command that a real parse would hand to the default instead is
5860        // worse than not offering it, so the gate applies to both paths. The mount
5861        // fails if it runs, which is how both halves are checked at once.
5862        let spec: Spec = r#"
5863name "ex"
5864bin "ex"
5865default_subcommand "run"
5866cmd "run" {
5867  arg "<task>"
5868}
5869mount run="exit 1"
5870"#
5871        .parse()
5872        .unwrap();
5873
5874        let out = parse_partial(&spec, &["ex".to_string()]).unwrap();
5875        assert!(
5876            !out.cmd.subcommands.contains_key("discovered"),
5877            "a completion must not offer what execution will not route"
5878        );
5879
5880        let out = parse(&spec, &["ex".to_string(), "mytask".to_string()]).unwrap();
5881        assert_eq!(out.cmd.name, "run");
5882    }
5883
5884    #[cfg(unix)]
5885    #[test]
5886    fn a_default_subcommand_outranks_discovery() {
5887        // The default already says what an unmatched word means, and says it for
5888        // free. The mount fails if it runs, so parsing proves discovery was skipped.
5889        let spec: Spec = r#"
5890name "ex"
5891bin "ex"
5892default_subcommand "run"
5893cmd "run" {
5894  arg "<task>"
5895}
5896mount run="exit 1"
5897"#
5898        .parse()
5899        .unwrap();
5900
5901        let out = parse(&spec, &["ex".to_string(), "mytask".to_string()]).unwrap();
5902        assert_eq!(out.cmd.name, "run");
5903    }
5904
5905    #[cfg(unix)]
5906    #[test]
5907    fn a_mount_may_ask_to_outrank_the_default() {
5908        // Opting in, and paying for it: discovery runs first, so a discovered
5909        // command wins over the fallback.
5910        let spec: Spec = r#"
5911name "ex"
5912bin "ex"
5913default_subcommand "run"
5914cmd "run" {
5915  arg "<task>"
5916}
5917mount run="echo 'cmd \"discovered\"'" overrides_default=#true
5918"#
5919        .parse()
5920        .unwrap();
5921
5922        let out = parse(&spec, &["ex".to_string(), "discovered".to_string()]).unwrap();
5923        assert_eq!(out.cmd.name, "discovered");
5924
5925        // A word it does not know still reaches the default.
5926        let out = parse(&spec, &["ex".to_string(), "mytask".to_string()]).unwrap();
5927        assert_eq!(out.cmd.name, "run");
5928    }
5929
5930    #[cfg(unix)]
5931    #[test]
5932    fn a_flag_does_not_run_the_mount() {
5933        // A flag matches no subcommand, which would have been enough to trigger
5934        // discovery — so `ex --help` spawned a process. The mount fails if it runs,
5935        // so parsing at all is the proof that it did not.
5936        let spec: Spec = r#"
5937name "ex"
5938bin "ex"
5939flag "--verbose"
5940cmd "declared"
5941mount run="exit 1"
5942"#
5943        .parse()
5944        .unwrap();
5945
5946        let out = parse(&spec, &["ex".to_string(), "--verbose".to_string()]).unwrap();
5947        assert_eq!(out.cmd.name, "ex");
5948    }
5949
5950    #[cfg(unix)]
5951    #[test]
5952    fn a_declared_subcommand_does_not_run_the_mount() {
5953        // The mount would fail if it ran, so this parsing at all is the proof that
5954        // discovery is skipped when the word is already known. Worth pinning: a root
5955        // mount that resolved eagerly would spawn a process on every invocation.
5956        let spec: Spec = r#"
5957name "ex"
5958bin "ex"
5959cmd "declared"
5960mount run="exit 1"
5961"#
5962        .parse()
5963        .unwrap();
5964
5965        let out = parse(&spec, &["ex".to_string(), "declared".to_string()]).unwrap();
5966        assert_eq!(out.cmd.name, "declared");
5967    }
5968
5969    #[test]
5970    fn a_root_mount_survives_being_written_out() {
5971        let spec: Spec = "name \"ex\"\nbin \"ex\"\nmount run=\"ex plugins --usage\"\n"
5972            .parse()
5973            .unwrap();
5974        assert_eq!(spec.cmd.mounts.len(), 1);
5975
5976        let reparsed: Spec = spec.to_string().parse().unwrap();
5977        assert_eq!(reparsed.cmd.mounts.len(), 1, "written:\n{spec}");
5978        assert_eq!(reparsed.cmd.mounts[0].run, "ex plugins --usage");
5979    }
5980
5981    #[test]
5982    fn test_mount_prefix_applies_flag_overrides() {
5983        let stdin = Arc::new(
5984            SpecFlag::builder()
5985                .name("stdin")
5986                .long("stdin")
5987                .global(true)
5988                .build(),
5989        );
5990        let file = Arc::new(
5991            SpecFlag::builder()
5992                .name("file")
5993                .long("file")
5994                .arg(SpecArg::builder().name("file").build())
5995                .global(true)
5996                .overrides_with(vec!["--stdin".to_string()])
5997                .build(),
5998        );
5999        let mut prefix_flags = vec![(stdin, vec!["--stdin".to_string()])];
6000
6001        apply_prefix_flag_overrides(&mut prefix_flags, Arc::clone(&file));
6002        prefix_flags.push((file, vec!["--file".to_string(), "input.txt".to_string()]));
6003
6004        assert_eq!(mount_prefix_words(&prefix_flags), ["--file", "input.txt"]);
6005    }
6006
6007    #[test]
6008    fn test_flag_override_suppresses_env_value() {
6009        let spec: Spec = r#"
6010flag "--stdin" env="USE_STDIN"
6011flag "--file <file>" overrides="--stdin"
6012        "#
6013        .parse()
6014        .unwrap();
6015
6016        let parsed = parse_with_env(
6017            &spec,
6018            &["test", "--file", "input.txt"],
6019            &[("USE_STDIN", "true")],
6020        )
6021        .unwrap();
6022        assert_eq!(parsed.flags.len(), 1);
6023        assert_eq!(flag_string_value(&parsed, "file"), "input.txt");
6024    }
6025
6026    #[test]
6027    fn test_flag_override_suppresses_required_check() {
6028        let spec: Spec = r#"
6029flag "--stdin" required=#true
6030flag "--file <file>" overrides="--stdin"
6031        "#
6032        .parse()
6033        .unwrap();
6034
6035        let parsed = parse(&spec, &input(&["test", "--file", "input.txt"])).unwrap();
6036        assert_eq!(parsed.flags.len(), 1);
6037        assert_eq!(flag_string_value(&parsed, "file"), "input.txt");
6038    }
6039
6040    #[test]
6041    fn test_flag_required_if() {
6042        let spec: Spec = r#"
6043flag "--dir <dir>"
6044flag "--file <file>" required_if="--dir"
6045        "#
6046        .parse()
6047        .unwrap();
6048
6049        parse(&spec, &input(&["test"])).unwrap();
6050        assert_parse_err(
6051            parse(&spec, &input(&["test", "--dir", "src"])),
6052            "Missing required flag: --file <file>",
6053        );
6054        parse(
6055            &spec,
6056            &input(&["test", "--dir", "src", "--file", "input.txt"]),
6057        )
6058        .unwrap();
6059    }
6060
6061    #[test]
6062    fn test_flag_required_unless() {
6063        let spec: Spec = r#"
6064flag "--stdin"
6065flag "--file <file>" required_unless="--stdin"
6066        "#
6067        .parse()
6068        .unwrap();
6069
6070        assert_parse_err(
6071            parse(&spec, &input(&["test"])),
6072            "Missing required flag: --file <file>",
6073        );
6074        parse(&spec, &input(&["test", "--stdin"])).unwrap();
6075        parse(&spec, &input(&["test", "--file", "input.txt"])).unwrap();
6076    }
6077
6078    #[test]
6079    fn complete_required_relationship_truth_tables() {
6080        let spec: Spec = r#"
6081name "ex"
6082bin "ex"
6083flag "--mode <mode>"
6084flag "--scope <scope>"
6085flag "--token <token>" {
6086    required_if_eq "--mode" "remote"
6087}
6088flag "--approval <approval>" {
6089    required_if_eq_all "--mode" "remote" "--scope" "global"
6090}
6091flag "--input <input>" {
6092    required_unless "--stdin" "--file"
6093}
6094flag "--checksum <checksum>" {
6095    required_unless_all "--stdin" "--file"
6096}
6097flag "--stdin"
6098flag "--file <file>"
6099arg "[request]" {
6100    requires "--mode" "--scope"
6101}
6102"#
6103        .parse()
6104        .unwrap();
6105        let parse_args = |args: &[&str]| {
6106            parse(
6107                &spec,
6108                &args
6109                    .iter()
6110                    .map(|arg| (*arg).to_string())
6111                    .collect::<Vec<_>>(),
6112            )
6113        };
6114
6115        assert!(parse_args(&["ex", "--mode", "remote", "--stdin"]).is_err());
6116        assert!(parse_args(&[
6117            "ex", "--mode", "remote", "--token", "secret", "--scope", "global", "--stdin",
6118        ])
6119        .is_err());
6120        parse_args(&[
6121            "ex",
6122            "--mode",
6123            "remote",
6124            "--token",
6125            "secret",
6126            "--scope",
6127            "global",
6128            "--approval",
6129            "yes",
6130            "--stdin",
6131            "--file",
6132            "in",
6133        ])
6134        .unwrap();
6135        parse_args(&[
6136            "ex",
6137            "--mode",
6138            "local",
6139            "--scope",
6140            "project",
6141            "--stdin",
6142            "--checksum",
6143            "sum",
6144            "request.json",
6145        ])
6146        .unwrap();
6147
6148        let reparsed: Spec = spec.to_string().parse().unwrap();
6149        assert_eq!(reparsed.cmd.flags[2].required_if_eq.len(), 1);
6150        assert_eq!(reparsed.cmd.flags[3].required_if_eq_all.len(), 2);
6151        assert_eq!(reparsed.cmd.flags[5].required_unless_all.len(), 2);
6152        assert_eq!(reparsed.cmd.args[0].requires.len(), 2);
6153    }
6154
6155    #[test]
6156    fn test_conditional_requirements_treat_env_as_explicit() {
6157        let spec: Spec = r#"
6158flag "--dir <dir>" env="INPUT_DIR"
6159flag "--stdin" env="USE_STDIN"
6160flag "--file <file>" required_if="--dir" required_unless="--stdin"
6161        "#
6162        .parse()
6163        .unwrap();
6164
6165        assert_parse_err(
6166            parse_with_env(&spec, &["test"], &[("INPUT_DIR", "src")]),
6167            "Missing required flag: --file <file>",
6168        );
6169        parse_with_env(&spec, &["test"], &[("USE_STDIN", "true")]).unwrap();
6170    }
6171
6172    #[test]
6173    fn test_custom_env_does_not_fall_back_to_process_env() {
6174        assert!(std::env::var("PATH").is_ok());
6175        let spec: Spec = r#"flag "--file <file>" env="PATH" required=#true"#.parse().unwrap();
6176
6177        assert_parse_err(
6178            parse_with_env(&spec, &["test"], &[]),
6179            "Missing required flag: --file <file>",
6180        );
6181    }
6182
6183    #[test]
6184    fn test_conditional_requirements_ignore_defaults_on_condition_flags() {
6185        let spec: Spec = r#"
6186flag "--dir <dir>" default="src"
6187flag "--file <file>" required_if="--dir"
6188        "#
6189        .parse()
6190        .unwrap();
6191
6192        parse(&spec, &input(&["test"])).unwrap();
6193    }
6194
6195    #[test]
6196    fn test_conditional_requirements_see_overridden_flags_as_absent() {
6197        let spec: Spec = r#"
6198flag "--stdin"
6199flag "--dir <dir>" overrides="--stdin"
6200flag "--file <file>" required_unless="--stdin"
6201        "#
6202        .parse()
6203        .unwrap();
6204
6205        assert_parse_err(
6206            parse(&spec, &input(&["test", "--stdin", "--dir", "src"])),
6207            "Missing required flag: --file <file>",
6208        );
6209    }
6210
6211    #[test]
6212    fn short_flag_is_one_character_not_one_byte() {
6213        // A short is declared and read by character. Counting bytes instead either
6214        // refuses the declaration or slices the token inside the character, and clap
6215        // — which many specs are generated from — accepts shorts like this one.
6216        let spec = spec_with_flag(
6217            SpecFlag::builder()
6218                .short('磨')
6219                .long("polish")
6220                .arg(SpecArg::builder().name("opt").build())
6221                .build(),
6222        );
6223        let attached = Parser::new(&spec)
6224            .parse(&input(&["test", "-磨VALUE"]))
6225            .unwrap();
6226        assert_eq!(flag_string_value(&attached, "polish"), "VALUE");
6227        let detached = Parser::new(&spec)
6228            .parse(&input(&["test", "-磨", "V"]))
6229            .unwrap();
6230        assert_eq!(flag_string_value(&detached, "polish"), "V");
6231    }
6232
6233    #[test]
6234    fn test_as_env() {
6235        let cmd = SpecCommand::builder()
6236            .name("test")
6237            .arg(SpecArg::builder().name("arg").build())
6238            .flag(SpecFlag::builder().long("flag").build())
6239            .flag(
6240                SpecFlag::builder()
6241                    .long("force")
6242                    .negate("--no-force")
6243                    .build(),
6244            )
6245            .build();
6246        let spec = Spec {
6247            name: "test".to_string(),
6248            bin: "test".to_string(),
6249            cmd,
6250            ..Default::default()
6251        };
6252        let input = vec![
6253            "test".to_string(),
6254            "--flag".to_string(),
6255            "--no-force".to_string(),
6256        ];
6257        let parsed = parse(&spec, &input).unwrap();
6258        let env = parsed.as_env();
6259        assert_eq!(env.len(), 2);
6260        assert_eq!(env.get("usage_flag"), Some(&"true".to_string()));
6261        assert_eq!(env.get("usage_force"), Some(&"false".to_string()));
6262    }
6263
6264    #[test]
6265    fn test_arg_env_var() {
6266        let cmd = SpecCommand::builder()
6267            .name("test")
6268            .arg(
6269                SpecArg::builder()
6270                    .name("input")
6271                    .env("TEST_ARG_INPUT")
6272                    .required(true)
6273                    .build(),
6274            )
6275            .build();
6276        let spec = Spec {
6277            name: "test".to_string(),
6278            bin: "test".to_string(),
6279            cmd,
6280            ..Default::default()
6281        };
6282
6283        // Set env var
6284        std::env::set_var("TEST_ARG_INPUT", "test_file.txt");
6285
6286        let input = vec!["test".to_string()];
6287        let parsed = parse(&spec, &input).unwrap();
6288
6289        assert_eq!(parsed.args.len(), 1);
6290        let arg = parsed.args.keys().next().unwrap();
6291        assert_eq!(arg.name, "input");
6292        let value = parsed.args.values().next().unwrap();
6293        assert_eq!(value.to_string(), "test_file.txt");
6294
6295        // Clean up
6296        std::env::remove_var("TEST_ARG_INPUT");
6297    }
6298
6299    #[test]
6300    fn test_flag_env_var_with_arg() {
6301        let cmd = SpecCommand::builder()
6302            .name("test")
6303            .flag(
6304                SpecFlag::builder()
6305                    .long("output")
6306                    .env("TEST_FLAG_OUTPUT")
6307                    .arg(SpecArg::builder().name("file").build())
6308                    .build(),
6309            )
6310            .build();
6311        let spec = Spec {
6312            name: "test".to_string(),
6313            bin: "test".to_string(),
6314            cmd,
6315            ..Default::default()
6316        };
6317
6318        // Set env var
6319        std::env::set_var("TEST_FLAG_OUTPUT", "output.txt");
6320
6321        let input = vec!["test".to_string()];
6322        let parsed = parse(&spec, &input).unwrap();
6323
6324        assert_eq!(parsed.flags.len(), 1);
6325        let flag = parsed.flags.keys().next().unwrap();
6326        assert_eq!(flag.name, "output");
6327        let value = parsed.flags.values().next().unwrap();
6328        assert_eq!(value.to_string(), "output.txt");
6329
6330        // Clean up
6331        std::env::remove_var("TEST_FLAG_OUTPUT");
6332    }
6333
6334    #[test]
6335    fn test_flag_env_var_boolean() {
6336        let cmd = SpecCommand::builder()
6337            .name("test")
6338            .flag(
6339                SpecFlag::builder()
6340                    .long("verbose")
6341                    .env("TEST_FLAG_VERBOSE")
6342                    .build(),
6343            )
6344            .build();
6345        let spec = Spec {
6346            name: "test".to_string(),
6347            bin: "test".to_string(),
6348            cmd,
6349            ..Default::default()
6350        };
6351
6352        // Set env var to true
6353        std::env::set_var("TEST_FLAG_VERBOSE", "true");
6354
6355        let input = vec!["test".to_string()];
6356        let parsed = parse(&spec, &input).unwrap();
6357
6358        assert_eq!(parsed.flags.len(), 1);
6359        let flag = parsed.flags.keys().next().unwrap();
6360        assert_eq!(flag.name, "verbose");
6361        let value = parsed.flags.values().next().unwrap();
6362        assert_eq!(value.to_string(), "true");
6363
6364        // Clean up
6365        std::env::remove_var("TEST_FLAG_VERBOSE");
6366    }
6367
6368    #[test]
6369    fn test_env_var_precedence() {
6370        // CLI args should take precedence over env vars
6371        let cmd = SpecCommand::builder()
6372            .name("test")
6373            .arg(
6374                SpecArg::builder()
6375                    .name("input")
6376                    .env("TEST_PRECEDENCE_INPUT")
6377                    .required(true)
6378                    .build(),
6379            )
6380            .build();
6381        let spec = Spec {
6382            name: "test".to_string(),
6383            bin: "test".to_string(),
6384            cmd,
6385            ..Default::default()
6386        };
6387
6388        // Set env var
6389        std::env::set_var("TEST_PRECEDENCE_INPUT", "env_file.txt");
6390
6391        let input = vec!["test".to_string(), "cli_file.txt".to_string()];
6392        let parsed = parse(&spec, &input).unwrap();
6393
6394        assert_eq!(parsed.args.len(), 1);
6395        let value = parsed.args.values().next().unwrap();
6396        // CLI arg should take precedence
6397        assert_eq!(value.to_string(), "cli_file.txt");
6398
6399        // Clean up
6400        std::env::remove_var("TEST_PRECEDENCE_INPUT");
6401    }
6402
6403    #[test]
6404    fn test_flag_var_true_with_single_default() {
6405        // When var=true and default="bar", the default should be MultiString(["bar"])
6406        let cmd = SpecCommand::builder()
6407            .name("test")
6408            .flag(
6409                SpecFlag::builder()
6410                    .long("foo")
6411                    .var(true)
6412                    .arg(SpecArg::builder().name("foo").build())
6413                    .default_value("bar")
6414                    .build(),
6415            )
6416            .build();
6417        let spec = Spec {
6418            name: "test".to_string(),
6419            bin: "test".to_string(),
6420            cmd,
6421            ..Default::default()
6422        };
6423
6424        // User doesn't provide the flag
6425        let input = vec!["test".to_string()];
6426        let parsed = parse(&spec, &input).unwrap();
6427
6428        assert_eq!(parsed.flags.len(), 1);
6429        let flag = parsed.flags.keys().next().unwrap();
6430        assert_eq!(flag.name, "foo");
6431        let value = parsed.flags.values().next().unwrap();
6432        // Should be MultiString, not String
6433        match value {
6434            ParseValue::MultiString(v) => {
6435                assert_eq!(v.len(), 1);
6436                assert_eq!(v[0], "bar");
6437            }
6438            _ => panic!("Expected MultiString, got {:?}", value),
6439        }
6440    }
6441
6442    #[test]
6443    fn test_flag_var_true_with_multiple_defaults() {
6444        // When var=true and multiple defaults, should return MultiString(["xyz", "bar"])
6445        let cmd = SpecCommand::builder()
6446            .name("test")
6447            .flag(
6448                SpecFlag::builder()
6449                    .long("foo")
6450                    .var(true)
6451                    .arg(SpecArg::builder().name("foo").build())
6452                    .default_values(["xyz", "bar"])
6453                    .build(),
6454            )
6455            .build();
6456        let spec = Spec {
6457            name: "test".to_string(),
6458            bin: "test".to_string(),
6459            cmd,
6460            ..Default::default()
6461        };
6462
6463        // User doesn't provide the flag
6464        let input = vec!["test".to_string()];
6465        let parsed = parse(&spec, &input).unwrap();
6466
6467        assert_eq!(parsed.flags.len(), 1);
6468        let value = parsed.flags.values().next().unwrap();
6469        // Should be MultiString with both values
6470        match value {
6471            ParseValue::MultiString(v) => {
6472                assert_eq!(v.len(), 2);
6473                assert_eq!(v[0], "xyz");
6474                assert_eq!(v[1], "bar");
6475            }
6476            _ => panic!("Expected MultiString, got {:?}", value),
6477        }
6478    }
6479
6480    #[test]
6481    fn test_flag_var_false_with_default_remains_string() {
6482        // When var=false (default), the default should still be String("bar")
6483        let cmd = SpecCommand::builder()
6484            .name("test")
6485            .flag(
6486                SpecFlag::builder()
6487                    .long("foo")
6488                    .var(false) // Default behavior
6489                    .arg(SpecArg::builder().name("foo").build())
6490                    .default_value("bar")
6491                    .build(),
6492            )
6493            .build();
6494        let spec = Spec {
6495            name: "test".to_string(),
6496            bin: "test".to_string(),
6497            cmd,
6498            ..Default::default()
6499        };
6500
6501        // User doesn't provide the flag
6502        let input = vec!["test".to_string()];
6503        let parsed = parse(&spec, &input).unwrap();
6504
6505        assert_eq!(parsed.flags.len(), 1);
6506        let value = parsed.flags.values().next().unwrap();
6507        // Should be String, not MultiString
6508        match value {
6509            ParseValue::String(s) => {
6510                assert_eq!(s, "bar");
6511            }
6512            _ => panic!("Expected String, got {:?}", value),
6513        }
6514    }
6515
6516    #[test]
6517    fn test_arg_var_true_with_single_default() {
6518        // When arg has var=true and default="bar", the default should be MultiString(["bar"])
6519        let cmd = SpecCommand::builder()
6520            .name("test")
6521            .arg(
6522                SpecArg::builder()
6523                    .name("files")
6524                    .var(true)
6525                    .default_value("default.txt")
6526                    .required(false)
6527                    .build(),
6528            )
6529            .build();
6530        let spec = Spec {
6531            name: "test".to_string(),
6532            bin: "test".to_string(),
6533            cmd,
6534            ..Default::default()
6535        };
6536
6537        // User doesn't provide the arg
6538        let input = vec!["test".to_string()];
6539        let parsed = parse(&spec, &input).unwrap();
6540
6541        assert_eq!(parsed.args.len(), 1);
6542        let value = parsed.args.values().next().unwrap();
6543        // Should be MultiString, not String
6544        match value {
6545            ParseValue::MultiString(v) => {
6546                assert_eq!(v.len(), 1);
6547                assert_eq!(v[0], "default.txt");
6548            }
6549            _ => panic!("Expected MultiString, got {:?}", value),
6550        }
6551    }
6552
6553    #[test]
6554    fn test_arg_var_true_with_multiple_defaults() {
6555        // When arg has var=true and multiple defaults
6556        let cmd = SpecCommand::builder()
6557            .name("test")
6558            .arg(
6559                SpecArg::builder()
6560                    .name("files")
6561                    .var(true)
6562                    .default_values(["file1.txt", "file2.txt"])
6563                    .required(false)
6564                    .build(),
6565            )
6566            .build();
6567        let spec = Spec {
6568            name: "test".to_string(),
6569            bin: "test".to_string(),
6570            cmd,
6571            ..Default::default()
6572        };
6573
6574        // User doesn't provide the arg
6575        let input = vec!["test".to_string()];
6576        let parsed = parse(&spec, &input).unwrap();
6577
6578        assert_eq!(parsed.args.len(), 1);
6579        let value = parsed.args.values().next().unwrap();
6580        // Should be MultiString with both values
6581        match value {
6582            ParseValue::MultiString(v) => {
6583                assert_eq!(v.len(), 2);
6584                assert_eq!(v[0], "file1.txt");
6585                assert_eq!(v[1], "file2.txt");
6586            }
6587            _ => panic!("Expected MultiString, got {:?}", value),
6588        }
6589    }
6590
6591    #[test]
6592    fn test_arg_var_false_with_default_remains_string() {
6593        // When arg has var=false (default), the default should still be String
6594        let cmd = SpecCommand::builder()
6595            .name("test")
6596            .arg(
6597                SpecArg::builder()
6598                    .name("file")
6599                    .var(false)
6600                    .default_value("default.txt")
6601                    .required(false)
6602                    .build(),
6603            )
6604            .build();
6605        let spec = Spec {
6606            name: "test".to_string(),
6607            bin: "test".to_string(),
6608            cmd,
6609            ..Default::default()
6610        };
6611
6612        // User doesn't provide the arg
6613        let input = vec!["test".to_string()];
6614        let parsed = parse(&spec, &input).unwrap();
6615
6616        assert_eq!(parsed.args.len(), 1);
6617        let value = parsed.args.values().next().unwrap();
6618        // Should be String, not MultiString
6619        match value {
6620            ParseValue::String(s) => {
6621                assert_eq!(s, "default.txt");
6622            }
6623            _ => panic!("Expected String, got {:?}", value),
6624        }
6625    }
6626
6627    #[test]
6628    fn test_scalar_defaults_validate_only_first_default_choice() {
6629        let specs = [
6630            spec_with_arg(
6631                SpecArg::builder()
6632                    .name("env")
6633                    .var(false)
6634                    .default_values(["dev", "prod"])
6635                    .choices(["dev"])
6636                    .required(false)
6637                    .build(),
6638            ),
6639            spec_with_flag(
6640                SpecFlag::builder()
6641                    .long("env")
6642                    .arg(
6643                        SpecArg::builder()
6644                            .name("env")
6645                            .default_values(["dev", "prod"])
6646                            .choices(["dev"])
6647                            .build(),
6648                    )
6649                    .build(),
6650            ),
6651        ];
6652
6653        for spec in specs {
6654            let parsed = parse(&spec, &input(&["test"])).unwrap();
6655            assert_eq!(first_string_value(&parsed), "dev");
6656        }
6657    }
6658
6659    #[test]
6660    fn a_delimiter_turns_one_word_into_several_values() {
6661        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--tags <tag>\" var=#true delimiter=\",\"\narg \"[files]...\" var=#true delimiter=\":\"\n"
6662            .parse()
6663            .unwrap();
6664
6665        let parsed = parse(&spec, &input(&["ex", "--tags", "a,b,c", "x:y"])).unwrap();
6666        let multi = |value: &ParseValue| match value {
6667            ParseValue::MultiString(values) => values.clone(),
6668            other => panic!("expected several values, got {other:?}"),
6669        };
6670        let tags = parsed
6671            .flags
6672            .iter()
6673            .find(|(f, _)| f.name == "tags")
6674            .map(|(_, v)| v)
6675            .unwrap();
6676        assert_eq!(multi(tags), vec!["a", "b", "c"]);
6677        assert_eq!(multi(parsed.args.values().next().unwrap()), vec!["x", "y"]);
6678    }
6679
6680    #[test]
6681    fn a_positional_splits_before_its_choices_are_asked() {
6682        // The flag path did this and the positional path did not, so a word whose parts
6683        // were all choices was rejected as one value, and a bad half was reported as the
6684        // whole word.
6685        let spec: Spec = "name \"ex\"\nbin \"ex\"\narg \"[paths]...\" var=#true delimiter=\":\" {\n  choices \"src\" \"docs\"\n}\n"
6686            .parse()
6687            .unwrap();
6688
6689        parse(&spec, &input(&["ex", "src:docs"])).expect("both halves are choices");
6690
6691        let err = parse(&spec, &input(&["ex", "src:nowhere"])).unwrap_err();
6692        let message = err.to_string();
6693        assert!(message.contains("nowhere"), "{message}");
6694        assert!(
6695            !message.contains("src:nowhere"),
6696            "the bad half should be named, not the whole word: {message}"
6697        );
6698    }
6699
6700    #[test]
6701    fn a_split_value_is_counted_and_judged_as_values() {
6702        // Split during the parse rather than after it, so everything downstream sees the
6703        // values the user meant rather than the words they typed: `choices` judges each
6704        // one, and the bounds count them.
6705        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--env <e>\" var=#true delimiter=\",\" var_max=2 {\n  choices \"dev\" \"prod\"\n}\n"
6706            .parse()
6707            .unwrap();
6708
6709        parse(&spec, &input(&["ex", "--env", "dev,prod"])).expect("two values, both allowed");
6710        let err = parse(&spec, &input(&["ex", "--env", "dev,staging"])).unwrap_err();
6711        assert!(err.to_string().contains("staging"), "{err}");
6712        assert!(
6713            parse(&spec, &input(&["ex", "--env", "dev,prod,dev"])).is_err(),
6714            "three values should breach var_max=2"
6715        );
6716    }
6717
6718    #[test]
6719    fn a_split_bound_counts_one_occurrence_at_a_time() {
6720        // The bound on a variadic flag *argument* is what one occurrence may take. Without a
6721        // delimiter the collection simply stops at it, so it could never be exceeded; a word
6722        // carrying several values can carry an occurrence past it in one step, and that is
6723        // the only way this bound is ever breached.
6724        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--include <pattern>...\" delimiter=\",\" {\n  arg \"<pattern>...\" var=#true var_max=2\n}\n"
6725            .parse()
6726            .unwrap();
6727
6728        parse(&spec, &input(&["ex", "--include", "a,b"])).expect("exactly the bound is fine");
6729        assert!(
6730            parse(&spec, &input(&["ex", "--include", "a,b,c"])).is_err(),
6731            "three values out of one word is still three values"
6732        );
6733        // The rule the corpus documents for plain words, on split ones: a second occurrence
6734        // starts counting again rather than adding to the first.
6735        parse(
6736            &spec,
6737            &input(&["ex", "--include", "a,b", "--include", "c,d"]),
6738        )
6739        .expect("two per occurrence, twice, is within the bound");
6740    }
6741
6742    #[test]
6743    fn a_nested_minimum_is_checked_once_per_flag_occurrence() {
6744        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--pair <value>...\" {\n  arg \"<value>...\" var=#true var_min=2 var_max=2\n}\n"
6745            .parse()
6746            .unwrap();
6747
6748        parse(
6749            &spec,
6750            &input(&["ex", "--pair", "a", "b", "--pair", "c", "d"]),
6751        )
6752        .expect("each occurrence satisfies the bound independently");
6753
6754        let error = parse(&spec, &input(&["ex", "--pair", "a", "--pair", "b", "c"])).unwrap_err();
6755        assert!(
6756            error
6757                .to_string()
6758                .contains("requires at least 2 value(s), got 1"),
6759            "{error:?}"
6760        );
6761    }
6762
6763    #[test]
6764    fn an_exclusive_flag_has_to_be_alone() {
6765        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--dump\" exclusive=#true\nflag \"--verbose\"\narg \"[target]\"\n"
6766            .parse()
6767            .unwrap();
6768
6769        parse(&spec, &input(&["ex", "--dump"])).expect("alone is the point");
6770
6771        // Any other flag.
6772        let err = parse(&spec, &input(&["ex", "--dump", "--verbose"])).unwrap_err();
6773        assert!(err.to_string().contains("on its own"), "{err}");
6774
6775        // And a positional, which is what makes this more than a conflict with every
6776        // other flag.
6777        let err = parse(&spec, &input(&["ex", "--dump", "t"])).unwrap_err();
6778        assert!(err.to_string().contains("on its own"), "{err}");
6779
6780        // Not given, so it imposes nothing.
6781        parse(&spec, &input(&["ex", "--verbose", "t"])).expect("without it, nothing changes");
6782    }
6783
6784    #[test]
6785    fn an_exclusive_flag_conflicts_with_clause_arguments() {
6786        let spec: Spec = r#"name "ex"
6787bin "ex"
6788flag "--dump" exclusive=#true
6789clause "tasks" separator=":::" {
6790  arg "<task>"
6791}
6792"#
6793        .parse()
6794        .unwrap();
6795
6796        let err = parse(&spec, &input(&["ex", "--dump", "lint"])).unwrap_err();
6797        assert!(err.to_string().contains("on its own"), "{err}");
6798    }
6799
6800    #[test]
6801    fn a_partial_parse_reports_the_next_implicit_clause_argument() {
6802        let spec: Spec = r#"name "ex"
6803bin "ex"
6804clause "tools" {
6805  arg "<tool@version>"
6806}
6807"#
6808        .parse()
6809        .unwrap();
6810
6811        let parsed = parse_partial(&spec, &input(&["ex"])).unwrap();
6812
6813        assert_eq!(
6814            parsed.next_arg.as_ref().map(|arg| arg.name.as_str()),
6815            Some("tool@version")
6816        );
6817    }
6818
6819    #[test]
6820    fn an_exclusive_flag_is_not_disturbed_by_a_default() {
6821        // Only what was supplied counts, as `conflicts` reads it. A default counting as
6822        // company would make an exclusive flag unusable on any command that has one.
6823        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--dump\" exclusive=#true\nflag \"--jobs <n>\" default=\"4\"\n"
6824            .parse()
6825            .unwrap();
6826
6827        parse(&spec, &input(&["ex", "--dump"])).expect("a default is nobody saying anything");
6828        assert!(parse(&spec, &input(&["ex", "--dump", "--jobs", "8"])).is_err());
6829    }
6830
6831    #[test]
6832    fn an_exclusive_flag_bypasses_required_siblings() {
6833        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--dump\" exclusive=#true\nflag \"--out <path>\" required=#true\narg \"<target>\"\n"
6834            .parse()
6835            .unwrap();
6836
6837        parse(&spec, &input(&["ex", "--dump"]))
6838            .expect("exclusive is the command's requiredness escape");
6839        assert!(parse(
6840            &spec,
6841            &input(&["ex", "--dump", "--out", "somewhere", "target"])
6842        )
6843        .is_err());
6844    }
6845
6846    #[test]
6847    fn an_environment_value_counts_for_an_exclusive_flag() {
6848        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--dump\" exclusive=#true\nflag \"--out <path>\" env=\"EX_OUT\"\n"
6849            .parse()
6850            .unwrap();
6851
6852        assert!(parse_with_env(&spec, &["ex", "--dump"], &[("EX_OUT", "somewhere")]).is_err());
6853        parse_with_env(&spec, &["ex", "--dump"], &[]).expect("without the value it is alone");
6854    }
6855
6856    #[test]
6857    fn a_selected_subcommand_counts_for_an_ancestor_exclusive_flag() {
6858        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--version\" global=#true exclusive=#true\ncmd \"run\"\n"
6859            .parse()
6860            .unwrap();
6861
6862        parse(&spec, &input(&["ex", "--version"])).expect("alone is allowed");
6863        assert!(parse(&spec, &input(&["ex", "--version", "run"])).is_err());
6864    }
6865
6866    #[test]
6867    fn a_child_exclusive_flag_is_not_mistaken_for_a_same_named_parent_flag() {
6868        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" exclusive=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n}\n"
6869            .parse()
6870            .unwrap();
6871
6872        parse(&spec, &input(&["ex", "run", "--clean"]))
6873            .expect("the child flag is alone within the child command");
6874        assert!(
6875            parse(&spec, &input(&["ex", "--clean", "run"])).is_err(),
6876            "the parent flag still conflicts with selecting the child"
6877        );
6878    }
6879
6880    #[test]
6881    fn a_child_local_exclusive_redeclaration_belongs_to_the_child() {
6882        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" global=#true exclusive=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n}\n"
6883            .parse()
6884            .unwrap();
6885
6886        parse(&spec, &input(&["ex", "run", "--clean"]))
6887            .expect("the child-local exclusive flag is alone inside the child command");
6888        assert!(
6889            parse(&spec, &input(&["ex", "--clean", "run"])).is_err(),
6890            "the ancestor spelling still conflicts with selecting the child"
6891        );
6892    }
6893
6894    #[test]
6895    fn a_same_named_parent_flag_is_company_for_a_child_exclusive_flag() {
6896        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" global=#true\ncmd \"run\" {\n  flag \"--clean\" global=#true exclusive=#true\n}\n"
6897            .parse()
6898            .unwrap();
6899
6900        parse(&spec, &input(&["ex", "run", "--clean"])).expect("the child exclusive flag is alone");
6901        assert!(
6902            parse(&spec, &input(&["ex", "--clean", "run", "--clean"])).is_err(),
6903            "the distinct parent declaration is still company despite sharing a name"
6904        );
6905    }
6906
6907    #[test]
6908    fn a_local_child_redeclaration_keeps_its_exclusivity_when_merged() {
6909        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" global=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n  flag \"--verbose\"\n}\n"
6910            .parse()
6911            .unwrap();
6912
6913        parse(&spec, &input(&["ex", "run", "--clean"]))
6914            .expect("the child exclusive flag is valid alone");
6915        assert!(
6916            parse(&spec, &input(&["ex", "run", "--clean", "--verbose"])).is_err(),
6917            "merging with the inherited global must not discard child exclusivity"
6918        );
6919    }
6920
6921    #[test]
6922    fn an_orphan_parent_alias_does_not_disown_a_child_local_exclusive_flag() {
6923        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-c --clean\" global=#true exclusive=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n}\n"
6924            .parse()
6925            .unwrap();
6926
6927        parse(&spec, &input(&["ex", "run", "--clean"]))
6928            .expect("the typed long form belongs to the child declaration");
6929        assert!(
6930            parse(&spec, &input(&["ex", "run", "-c"])).is_err(),
6931            "the inherited short form still belongs to the ancestor"
6932        );
6933        assert!(
6934            parse(&spec, &input(&["ex", "run", "-c", "--clean"])).is_err(),
6935            "a child spelling cannot mask the ancestor-exclusive occurrence on the same merged flag"
6936        );
6937    }
6938
6939    #[test]
6940    fn an_inherited_alias_keeps_its_ancestor_exclusivity() {
6941        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-c --clean\" global=#true exclusive=#true\ncmd \"run\" {\n  flag \"--clean\" global=#true\n}\n"
6942            .parse()
6943            .unwrap();
6944
6945        parse(&spec, &input(&["ex", "run", "--clean"]))
6946            .expect("the child's spelling does not activate the orphan ancestor alias");
6947        assert!(
6948            parse(&spec, &input(&["ex", "run", "-c"])).is_err(),
6949            "the inherited short alias still belongs to the ancestor exclusive flag"
6950        );
6951    }
6952
6953    #[test]
6954    fn an_inherited_negated_alias_keeps_its_ancestor_exclusivity() {
6955        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-c --clean\" negate=\"--no-clean\" global=#true exclusive=#true\ncmd \"run\" {\n  flag \"-c --clean\" global=#true\n}\n"
6956            .parse()
6957            .unwrap();
6958
6959        assert!(
6960            parse(&spec, &input(&["ex", "run", "--no-clean"])).is_err(),
6961            "the inherited negated alias still belongs to the ancestor exclusive flag"
6962        );
6963    }
6964
6965    #[test]
6966    fn a_colliding_alias_does_not_disown_the_child_from_the_rest() {
6967        // The child re-declares the inherited `--clean` as exclusive and gives it a `-c` that
6968        // an unrelated inherited global already owns. That collision is resolved in the other
6969        // global's favor, so the child's `-c` resolves elsewhere — but the child plainly owns
6970        // the `--clean` it declared, and its exclusivity holds.
6971        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" global=#true\nflag \"-c --config <f>\" global=#true\ncmd \"run\" {\n  flag \"-c --clean\" exclusive=#true\n  flag \"--verbose\"\n}\n"
6972            .parse()
6973            .unwrap();
6974
6975        parse(&spec, &input(&["ex", "run", "--clean"])).expect("alone is allowed");
6976        assert!(
6977            parse(&spec, &input(&["ex", "run", "--clean", "--verbose"])).is_err(),
6978            "one unrelated alias collision cannot disown the child from its own flag"
6979        );
6980    }
6981
6982    #[test]
6983    fn a_local_child_declaration_is_not_in_scope_before_the_subcommand() {
6984        // A child's *local* re-declaration describes the flag at the child. Typed ahead of the
6985        // subcommand word the flag can only be the ancestor's, because that is the only one in
6986        // scope there — so the ancestor's exclusivity is the one that answers, whichever way it
6987        // is set. The pair below differ in nothing else, which is what makes this one rule
6988        // rather than two behaviors.
6989        let quiet: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" global=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n  flag \"--verbose\"\n}\n"
6990            .parse()
6991            .unwrap();
6992        parse(&quiet, &input(&["ex", "--clean", "run", "--verbose"]))
6993            .expect("the ancestor owns this occurrence, and it is not exclusive");
6994        assert!(
6995            parse(&quiet, &input(&["ex", "run", "--clean", "--verbose"])).is_err(),
6996            "after the subcommand word the child's declaration is in scope, and it is exclusive"
6997        );
6998
6999        let loud: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" global=#true exclusive=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n}\n"
7000            .parse()
7001            .unwrap();
7002        assert!(
7003            parse(&loud, &input(&["ex", "--clean", "run"])).is_err(),
7004            "the same rule, with an exclusive ancestor: selecting the child is company for it"
7005        );
7006    }
7007
7008    #[test]
7009    fn an_orphan_ancestor_alias_keeps_its_exclusivity_past_a_plain_child_redeclaration() {
7010        // The mirror of `a_local_child_redeclaration_keeps_its_exclusivity_when_merged`: the
7011        // child owns `--clean` and says nothing about exclusivity, but `-c` is a spelling only
7012        // the ancestor ever declared, so the ancestor's answer still governs it.
7013        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-c --clean\" global=#true exclusive=#true\ncmd \"run\" {\n  flag \"--clean\"\n  flag \"--verbose\"\n}\n"
7014            .parse()
7015            .unwrap();
7016
7017        assert!(
7018            parse(&spec, &input(&["ex", "run", "-c"])).is_err(),
7019            "the orphan ancestor alias is still the ancestor's exclusive flag"
7020        );
7021        parse(&spec, &input(&["ex", "run", "--clean", "--verbose"]))
7022            .expect("the child's own spelling drops the exclusivity the child did not restate");
7023    }
7024
7025    #[test]
7026    fn a_child_spelling_carries_its_exclusivity_even_beside_an_ancestor_spelling() {
7027        // Both spellings of one merged flag, typed together. The child's `--clean` is exclusive
7028        // whatever else was typed alongside it, so `--verbose` is company; attributing the whole
7029        // occurrence to the ancestor because `-c` appeared in it lost that.
7030        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-c --clean\" global=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n  flag \"--verbose\"\n}\n"
7031            .parse()
7032            .unwrap();
7033
7034        assert!(
7035            parse(&spec, &input(&["ex", "run", "-c", "--clean", "--verbose"])).is_err(),
7036            "the child spelling is exclusive whatever it was typed beside"
7037        );
7038        parse(&spec, &input(&["ex", "run", "-c", "--verbose"]))
7039            .expect("the ancestor's own spelling was never exclusive");
7040    }
7041
7042    #[test]
7043    fn an_environment_value_takes_the_exclusivity_of_the_declaration_in_scope() {
7044        // An environment value has no spelling to attribute, so the declaration the selected
7045        // command has in scope answers — in both directions. Comparing whole alias sets asked
7046        // the ancestor instead, because the merged flag also carries its orphan `-c`.
7047        let added: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-c --clean\" global=#true env=\"EX_CLEAN\"\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n  flag \"--verbose\"\n}\n"
7048            .parse()
7049            .unwrap();
7050
7051        assert!(
7052            parse_with_env(&added, &["ex", "run", "--verbose"], &[("EX_CLEAN", "1")]).is_err(),
7053            "the child added exclusivity the environment value has to honor"
7054        );
7055
7056        let dropped: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-c --clean\" global=#true exclusive=#true env=\"EX_CLEAN\"\ncmd \"run\" {\n  flag \"--clean\"\n  flag \"--verbose\"\n}\n"
7057            .parse()
7058            .unwrap();
7059
7060        parse_with_env(&dropped, &["ex", "run", "--verbose"], &[("EX_CLEAN", "1")])
7061            .expect("the child dropped the exclusivity, and the environment value follows it");
7062    }
7063
7064    #[test]
7065    fn a_merged_child_exclusive_flag_still_escapes_requiredness() {
7066        // Exclusivity suppresses missing-value checks, and that has to survive the merge for
7067        // the same reason the companion check does.
7068        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--clean\" global=#true\ncmd \"run\" {\n  flag \"--clean\" exclusive=#true\n  flag \"--out <path>\" required=#true\n}\n"
7069            .parse()
7070            .unwrap();
7071
7072        parse(&spec, &input(&["ex", "run", "--clean"]))
7073            .expect("a merged child exclusive flag is still the command's requiredness escape");
7074    }
7075
7076    #[test]
7077    fn a_group_allows_one_member_and_refuses_two() {
7078        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--file <f>\"\nflag \"--url <u>\"\nflag \"--stdin\"\ngroup \"input\" \"--file\" \"--url\" \"--stdin\"\n"
7079            .parse()
7080            .unwrap();
7081
7082        // One is fine, and so is none: a plain group says "at most one".
7083        parse(&spec, &input(&["ex", "--file", "a.txt"])).expect("one member is fine");
7084        parse(&spec, &input(&["ex"])).expect("a group that is not required asks for nothing");
7085
7086        let err = parse(&spec, &input(&["ex", "--file", "a.txt", "--stdin"])).unwrap_err();
7087        assert!(err.to_string().contains("group input"), "{err}");
7088    }
7089
7090    #[test]
7091    fn positional_selectors_work_in_conflicts_and_groups() {
7092        let conflicts: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--from-file <path>\" conflicts=\"value\"\narg \"[value]\"\n"
7093            .parse()
7094            .unwrap();
7095        parse(&conflicts, &input(&["ex", "--from-file", "vars.env"]))
7096            .expect("the flag alone is valid");
7097        parse(&conflicts, &input(&["ex", "literal"])).expect("the positional alone is valid");
7098        assert!(parse(
7099            &conflicts,
7100            &input(&["ex", "--from-file", "vars.env", "literal"])
7101        )
7102        .is_err());
7103
7104        let positional_source: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--from-file <path>\"\narg \"[value]\" conflicts=\"--from-file\"\n"
7105            .parse()
7106            .unwrap();
7107        assert!(parse(
7108            &positional_source,
7109            &input(&["ex", "--from-file", "vars.env", "literal"])
7110        )
7111        .is_err());
7112
7113        let group: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--file <path>\"\narg \"[target]\"\ngroup \"input\" \"--file\" \"target\" required=#true\n"
7114            .parse()
7115            .unwrap();
7116        assert!(parse(&group, &input(&["ex"])).is_err());
7117        parse(&group, &input(&["ex", "target-name"]))
7118            .expect("a positional satisfies a required group");
7119        assert!(parse(
7120            &group,
7121            &input(&["ex", "--file", "input.txt", "target-name"])
7122        )
7123        .is_err());
7124    }
7125
7126    #[test]
7127    fn a_required_group_needs_one_of_its_members() {
7128        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--file <f>\"\nflag \"--url <u>\"\ngroup \"input\" \"--file\" \"--url\" required=#true\n"
7129            .parse()
7130            .unwrap();
7131
7132        let err = parse(&spec, &input(&["ex"])).unwrap_err();
7133        // The members, because that is what a user has to type; the name, because a
7134        // command with several groups would otherwise report the same sentence twice.
7135        assert!(err.to_string().contains("--file, --url"), "{err}");
7136        assert!(err.to_string().contains("input"), "{err}");
7137
7138        parse(&spec, &input(&["ex", "--url", "u"])).expect("one member satisfies it");
7139    }
7140
7141    #[test]
7142    fn a_multiple_group_only_polices_requiredness() {
7143        // `multiple` with `required` is "at least one of these", so two is fine and
7144        // none is not.
7145        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--a\"\nflag \"--b\"\ngroup \"any\" \"--a\" \"--b\" required=#true multiple=#true\n"
7146            .parse()
7147            .unwrap();
7148
7149        parse(&spec, &input(&["ex", "--a", "--b"])).expect("multiple allows both");
7150        assert!(parse(&spec, &input(&["ex"])).is_err());
7151    }
7152
7153    #[test]
7154    fn a_group_reads_a_default_for_requiredness_and_not_for_exclusivity() {
7155        // The two halves of a group are two kinds of rule, and they read a default
7156        // differently on purpose. Requiredness asks whether a member has a value, and a
7157        // default is a value — the rule `requires` follows. Exclusivity asks what the
7158        // user supplied, because a defaulted member counted as supplied would collide
7159        // with the sibling they actually typed and refuse a correct command line.
7160        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--file <f>\" default=\"a.txt\"\nflag \"--url <u>\"\ngroup \"input\" \"--file\" \"--url\" required=#true\n"
7161            .parse()
7162            .unwrap();
7163
7164        parse(&spec, &input(&["ex"])).expect("the default fills the group");
7165        parse(&spec, &input(&["ex", "--url", "u"]))
7166            .expect("the default must not conflict with the flag the user typed");
7167    }
7168
7169    #[test]
7170    fn a_group_naming_two_spellings_of_one_flag_is_not_a_conflict() {
7171        // `-f` and `--file` are one flag. Counted by selector, giving it once would
7172        // report it as conflicting with itself.
7173        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-f --file <f>\"\nflag \"--url <u>\"\ngroup \"input\" \"-f\" \"--file\" \"--url\"\n"
7174            .parse()
7175            .unwrap();
7176
7177        parse(&spec, &input(&["ex", "--file", "a.txt"])).expect("one flag is one member");
7178        parse(&spec, &input(&["ex", "-f", "a.txt"])).expect("either spelling, still one member");
7179
7180        // A genuine collision is still one.
7181        let err = parse(&spec, &input(&["ex", "--file", "a.txt", "--url", "u"])).unwrap_err();
7182        assert!(err.to_string().contains("group input"), "{err}");
7183    }
7184
7185    #[test]
7186    fn a_group_reads_the_environment_as_given() {
7187        // The environment does count, which is the same asymmetry `conflicts` has: an
7188        // env var is somebody saying something, a default is nobody saying anything.
7189        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--file <f>\" env=\"EX_FILE\"\nflag \"--url <u>\"\ngroup \"input\" \"--file\" \"--url\" required=#true\n"
7190            .parse()
7191            .unwrap();
7192
7193        parse_with_env(&spec, &["ex"], &[("EX_FILE", "a.txt")]).expect("the environment fills it");
7194    }
7195
7196    #[test]
7197    fn a_requirement_names_the_flag_that_is_missing() {
7198        // Reported as the missing flag rather than as something wrong with `--out`,
7199        // which is what clap says for an unmet `requires` and what a user can act on.
7200        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--out <p>\" requires=\"--format\"\nflag \"--format <f>\"\n"
7201            .parse()
7202            .unwrap();
7203
7204        let err = parse(&spec, &input(&["ex", "--out", "a.txt"])).unwrap_err();
7205        assert!(
7206            err.to_string().contains("format"),
7207            "the missing flag should be named: {err}"
7208        );
7209
7210        // Satisfied, in either order.
7211        for words in [
7212            &["ex", "--out", "a.txt", "--format", "json"][..],
7213            &["ex", "--format", "json", "--out", "a.txt"][..],
7214        ] {
7215            parse(&spec, &input(words)).unwrap_or_else(|e| panic!("{words:?}: {e}"));
7216        }
7217
7218        // Nothing happens when the flag that imposes the rule is absent: a requirement
7219        // is a consequence of using the flag, not a rule about the command line.
7220        parse(&spec, &input(&["ex"])).expect("a bare invocation requires nothing");
7221    }
7222
7223    #[test]
7224    fn a_value_activates_only_its_conditional_requirement() {
7225        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--config <file>\" {\n  requires_if \"special.toml\" \"--key\"\n  requires_if \"remote.toml\" \"--token\"\n}\nflag \"--key <key>\"\nflag \"--token <token>\"\n"
7226            .parse()
7227            .unwrap();
7228
7229        parse(&spec, &input(&["ex", "--config", "ordinary.toml"]))
7230            .expect("an unrelated value requires nothing");
7231
7232        let key = parse(&spec, &input(&["ex", "--config", "special.toml"])).unwrap_err();
7233        assert!(key.to_string().contains("key"), "{key}");
7234        parse(
7235            &spec,
7236            &input(&["ex", "--config", "special.toml", "--key", "secret"]),
7237        )
7238        .expect("the matching requirement is satisfied");
7239
7240        let token = parse(&spec, &input(&["ex", "--config", "remote.toml"])).unwrap_err();
7241        assert!(token.to_string().contains("token"), "{token}");
7242    }
7243
7244    #[test]
7245    fn conditional_requirements_read_explicit_env_but_not_defaults() {
7246        let from_env: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--config <file>\" env=\"EX_CONFIG\" {\n  requires_if \"special.toml\" \"--key\"\n}\nflag \"--key <key>\"\n"
7247            .parse()
7248            .unwrap();
7249        let err = parse_with_env(&from_env, &["ex"], &[("EX_CONFIG", "special.toml")]).unwrap_err();
7250        assert!(err.to_string().contains("key"), "{err}");
7251
7252        let from_default: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--config <file>\" default=\"special.toml\" {\n  requires_if \"special.toml\" \"--key\"\n}\nflag \"--key <key>\"\n"
7253            .parse()
7254            .unwrap();
7255        parse(&from_default, &input(&["ex"]))
7256            .expect("a default is not an explicit conditional value");
7257    }
7258
7259    #[test]
7260    fn command_line_values_override_env_for_conditional_requirements() {
7261        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--config <file>\" env=\"EX_CONFIG\" {\n  requires_if \"special.toml\" \"--key\"\n}\nflag \"--key <key>\"\n"
7262            .parse()
7263            .unwrap();
7264
7265        parse_with_env(
7266            &spec,
7267            &["ex", "--config", "ordinary.toml"],
7268            &[("EX_CONFIG", "special.toml")],
7269        )
7270        .expect("the command-line value takes precedence over the environment");
7271    }
7272
7273    #[test]
7274    fn conditional_requirements_normalize_boolean_env_values() {
7275        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--feature\" env=\"EX_FEATURE\" {\n  requires_if \"true\" \"--key\"\n}\nflag \"--key <key>\"\n"
7276            .parse()
7277            .unwrap();
7278
7279        for value in ["1", "true", "True", "TRUE"] {
7280            let err = parse_with_env(&spec, &["ex"], &[("EX_FEATURE", value)]).unwrap_err();
7281            assert!(err.to_string().contains("key"), "{value}: {err}");
7282        }
7283        parse_with_env(&spec, &["ex"], &[("EX_FEATURE", "false")])
7284            .expect("a false environment value does not activate a true condition");
7285    }
7286
7287    #[test]
7288    fn a_default_satisfies_a_requirement() {
7289        // The flag it names has a value, which is the question a requirement asks. Read
7290        // any other way, `--format` would be missing here and present ten lines further
7291        // down, where plain required-ness reads the same default as filling it.
7292        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--out <p>\" requires=\"--format\"\nflag \"--format <f>\" default=\"json\"\n"
7293            .parse()
7294            .unwrap();
7295
7296        parse(&spec, &input(&["ex", "--out", "a.txt"]))
7297            .expect("a defaulted flag is not a missing one");
7298    }
7299
7300    #[test]
7301    fn a_present_flag_binds_a_conditional_default() {
7302        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--bin-names\" {\n  default_if \"--json\" \"true\"\n}\nflag \"--json\"\n"
7303            .parse()
7304            .unwrap();
7305
7306        let with = parse(&spec, &input(&["ex", "--json"])).unwrap();
7307        assert_eq!(
7308            with.as_env().get("usage_bin_names").map(String::as_str),
7309            Some("true")
7310        );
7311
7312        let without = parse(&spec, &input(&["ex"])).unwrap();
7313        assert!(
7314            !without.as_env().contains_key("usage_bin_names"),
7315            "IsPresent does nothing when the selector is absent"
7316        );
7317    }
7318
7319    #[test]
7320    fn an_equals_condition_binds_a_conditional_default() {
7321        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--style <s>\" {\n  default_if \"--output\" \"json\" \"pretty\"\n}\nflag \"--output <fmt>\"\n"
7322            .parse()
7323            .unwrap();
7324
7325        let json = parse(&spec, &input(&["ex", "--output", "json"])).unwrap();
7326        assert_eq!(
7327            json.as_env().get("usage_style").map(String::as_str),
7328            Some("pretty")
7329        );
7330        let yaml = parse(&spec, &input(&["ex", "--output", "yaml"])).unwrap();
7331        assert!(!yaml.as_env().contains_key("usage_style"));
7332    }
7333
7334    #[test]
7335    fn an_equals_condition_reads_a_negated_flag() {
7336        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--pretty\" {\n  default_if \"--json\" \"false\" \"true\"\n}\nflag \"--json\" negate=\"--no-json\"\n"
7337            .parse()
7338            .unwrap();
7339
7340        let off = parse(&spec, &input(&["ex", "--no-json"])).unwrap();
7341        assert_eq!(
7342            off.as_env().get("usage_pretty").map(String::as_str),
7343            Some("true")
7344        );
7345        let on = parse(&spec, &input(&["ex", "--json"])).unwrap();
7346        assert!(
7347            !on.as_env().contains_key("usage_pretty"),
7348            "--json is true, so when=false should miss"
7349        );
7350    }
7351
7352    #[test]
7353    fn the_first_matching_conditional_default_wins() {
7354        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--style <s>\" {\n  default_if \"--json\" \"compact\"\n  default_if \"--pretty\" \"pretty\"\n}\nflag \"--json\"\nflag \"--pretty\"\n"
7355            .parse()
7356            .unwrap();
7357
7358        let out = parse(&spec, &input(&["ex", "--json", "--pretty"])).unwrap();
7359        assert_eq!(
7360            out.as_env().get("usage_style").map(String::as_str),
7361            Some("compact")
7362        );
7363    }
7364
7365    #[test]
7366    fn argv_and_env_suppress_a_conditional_default() {
7367        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--bin-names\" env=\"EX_BIN\" {\n  default_if \"--json\" \"true\"\n}\nflag \"--json\"\n"
7368            .parse()
7369            .unwrap();
7370
7371        let from_env = parse_with_env(&spec, &["ex", "--json"], &[("EX_BIN", "false")]).unwrap();
7372        assert_eq!(
7373            from_env.as_env().get("usage_bin_names").map(String::as_str),
7374            Some("false"),
7375            "the target's environment wins over default_if"
7376        );
7377    }
7378
7379    #[test]
7380    fn a_sibling_env_activates_a_conditional_default() {
7381        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--bin-names\" {\n  default_if \"--json\" \"true\"\n}\nflag \"--json\" env=\"EX_JSON\"\n"
7382            .parse()
7383            .unwrap();
7384
7385        let out = parse_with_env(&spec, &["ex"], &[("EX_JSON", "1")]).unwrap();
7386        assert_eq!(
7387            out.as_env().get("usage_bin_names").map(String::as_str),
7388            Some("true")
7389        );
7390    }
7391
7392    #[test]
7393    fn a_default_does_not_activate_a_conditional_default() {
7394        // `--json` sorts before `--pretty` in the available-flag map, so a one-pass
7395        // bind would put json's default into `out.flags` and then treat it as
7396        // explicit for pretty's `default_if`. Go and the derive ignore defaults.
7397        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--pretty\" {\n  default_if \"--json\" \"true\"\n}\nflag \"--json\" default=#true\n"
7398            .parse()
7399            .unwrap();
7400
7401        let out = parse(&spec, &input(&["ex"])).unwrap();
7402        assert_eq!(
7403            out.as_env().get("usage_json").map(String::as_str),
7404            Some("true")
7405        );
7406        assert!(
7407            !out.as_env().contains_key("usage_pretty"),
7408            "a default is not an explicit value for default_if"
7409        );
7410    }
7411
7412    #[test]
7413    fn a_conditional_default_does_not_activate_requires_if() {
7414        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--format <f>\" {\n  default_if \"--json\" \"json\"\n  requires_if \"json\" \"--schema\"\n}\nflag \"--schema <s>\"\nflag \"--json\"\n"
7415            .parse()
7416            .unwrap();
7417
7418        parse(&spec, &input(&["ex", "--json"]))
7419            .expect("a default_if value is not explicit for requires_if");
7420        assert!(parse(&spec, &input(&["ex", "--format", "json"])).is_err());
7421    }
7422
7423    #[test]
7424    fn a_conditional_default_satisfies_a_requirement() {
7425        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--out <p>\" requires=\"--format\"\nflag \"--format <f>\" {\n  default_if \"--json\" \"json\"\n}\nflag \"--json\"\n"
7426            .parse()
7427            .unwrap();
7428
7429        parse(&spec, &input(&["ex", "--out", "a.txt", "--json"]))
7430            .expect("default_if fills the required flag");
7431        assert!(parse(&spec, &input(&["ex", "--out", "a.txt"])).is_err());
7432    }
7433
7434    #[test]
7435    fn an_environment_value_satisfies_a_requirement() {
7436        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--out <p>\" requires=\"--format\"\nflag \"--format <f>\" env=\"EX_FORMAT\"\n"
7437            .parse()
7438            .unwrap();
7439
7440        assert!(parse(&spec, &input(&["ex", "--out", "a.txt"])).is_err());
7441        parse_with_env(&spec, &["ex", "--out", "a.txt"], &[("EX_FORMAT", "json")])
7442            .expect("the environment supplies it");
7443    }
7444
7445    #[test]
7446    fn a_requirement_is_satisfied_by_a_short_form() {
7447        // The selector may spell the other flag any way it answers to, so the check
7448        // resolves it the way every other selector is resolved rather than matching
7449        // text. The error names the flag, not the selector.
7450        let spec: Spec =
7451            "name \"ex\"\nbin \"ex\"\nflag \"--sign\" requires=\"-k\"\nflag \"-k --key <k>\"\n"
7452                .parse()
7453                .unwrap();
7454
7455        parse(&spec, &input(&["ex", "--sign", "--key", "x"])).expect("--key satisfies -k");
7456
7457        let err = parse(&spec, &input(&["ex", "--sign"])).unwrap_err();
7458        assert!(err.to_string().contains("key"), "{err}");
7459    }
7460
7461    #[test]
7462    fn conflicting_flags_are_rejected_in_either_order() {
7463        // Declared once, on `--file`, which is all clap exposes — so the check has to
7464        // be order-independent by looking at every flag that was given rather than at
7465        // the one that declared the conflict.
7466        let spec: Spec =
7467            "name \"ex\"\nbin \"ex\"\nflag \"--file <f>\" conflicts=\"--stdin\"\nflag \"--stdin\"\n"
7468                .parse()
7469                .unwrap();
7470
7471        for words in [
7472            &["ex", "--file", "a.txt", "--stdin"][..],
7473            &["ex", "--stdin", "--file", "a.txt"][..],
7474        ] {
7475            let err = parse(&spec, &input(words)).unwrap_err();
7476            assert!(
7477                err.to_string().contains("conflicts with --stdin"),
7478                "{words:?} should be refused: {err}"
7479            );
7480        }
7481
7482        // Either one alone is fine.
7483        parse(&spec, &input(&["ex", "--stdin"])).unwrap();
7484        parse(&spec, &input(&["ex", "--file", "a.txt"])).unwrap();
7485    }
7486
7487    #[test]
7488    fn unknown_flags_are_values_by_default() {
7489        // The default, and the reason it is the default: a spec often parses a
7490        // command line whose flags belong to something else.
7491        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--force\"\narg \"[rest]...\"\n"
7492            .parse()
7493            .unwrap();
7494        let out = parse(
7495            &spec,
7496            &["ex".to_string(), "--wat".to_string(), "x".to_string()],
7497        )
7498        .unwrap();
7499        let rest = out.args.keys().find(|a| a.name == "rest").unwrap();
7500        assert_eq!(out.args[rest].to_string(), "--wat x");
7501    }
7502
7503    #[test]
7504    fn repeated_scalar_flags_override_by_default_and_can_be_strict() {
7505        let permissive: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--jobs <n>\"\nflag \"--verbose\"\n"
7506            .parse()
7507            .unwrap();
7508        let out = parse(&permissive, &input(&["ex", "--jobs", "1", "--jobs", "2"]))
7509            .expect("a repeat is a correction by default");
7510        let jobs = out.flags.keys().find(|f| f.name == "jobs").unwrap();
7511        assert_eq!(out.flags[jobs].to_string(), "2");
7512        parse(&permissive, &input(&["ex", "--verbose", "--verbose"]))
7513            .expect("switches use the same default");
7514
7515        let strict: Spec = "name \"ex\"\nbin \"ex\"\nargs_override_self #false\nflag \"--jobs <n>\"\nflag \"--verbose\"\n"
7516            .parse()
7517            .unwrap();
7518        for words in [
7519            &["ex", "--jobs", "1", "--jobs", "2"][..],
7520            &["ex", "--verbose", "--verbose"][..],
7521        ] {
7522            let err = parse(&strict, &input(words)).unwrap_err();
7523            assert!(
7524                err.to_string().contains("cannot be used multiple times"),
7525                "{err}"
7526            );
7527        }
7528
7529        let reparsed: Spec = strict.to_string().parse().unwrap();
7530        assert!(!reparsed.cmd.args_override_self);
7531    }
7532
7533    #[test]
7534    fn strict_negated_flags_allow_opposite_forms_but_reject_the_same_form() {
7535        let spec: Spec = "name \"ex\"\nbin \"ex\"\nargs_override_self #false\nflag \"--color\" negate=\"--no-color\"\n"
7536            .parse()
7537            .unwrap();
7538
7539        let out = parse(&spec, &input(&["ex", "--color", "--no-color"]))
7540            .expect("opposite forms override each other");
7541        let color = out.flags.keys().find(|f| f.name == "color").unwrap();
7542        assert!(matches!(out.flags[color], ParseValue::Bool(false)));
7543
7544        for words in [
7545            &["ex", "--color", "--color"][..],
7546            &["ex", "--no-color", "--no-color"][..],
7547        ] {
7548            let err = parse(&spec, &input(words)).unwrap_err();
7549            assert!(err.to_string().contains("cannot be used multiple times"));
7550        }
7551    }
7552
7553    #[test]
7554    fn strict_global_flags_may_repeat_across_command_levels() {
7555        let spec: Spec = "name \"ex\"\nbin \"ex\"\nargs_override_self #false\nflag \"--color\" negate=\"--no-color\" global=#true\nflag \"--jobs <n>\" global=#true\ncmd \"run\" {\n  args_override_self #false\n}\n"
7556            .parse()
7557            .unwrap();
7558
7559        let out = parse(
7560            &spec,
7561            &input(&[
7562                "ex", "--color", "--jobs", "1", "run", "--color", "--jobs", "2",
7563            ]),
7564        )
7565        .expect("an inherited global is allowed once at each command level");
7566        let jobs = out.flags.keys().find(|f| f.name == "jobs").unwrap();
7567        assert_eq!(out.flags[jobs].to_string(), "2");
7568
7569        for words in [
7570            &["ex", "--color", "--color", "run", "--no-color"][..],
7571            &["ex", "--jobs", "1", "run", "--jobs", "2", "--jobs", "3"][..],
7572        ] {
7573            let err = parse(&spec, &input(words)).unwrap_err();
7574            assert!(err.to_string().contains("cannot be used multiple times"));
7575        }
7576    }
7577
7578    #[test]
7579    fn a_subcommand_can_negate_only_its_parents_requirements() {
7580        let base = r#"name "ex"
7581bin "ex"
7582flag "--config" required=#true
7583flag "--mode" requires="--config"
7584flag "--other"
7585arg "<input>"
7586group "source" "--config" "--other" required=#true
7587cmd "run" { flag "--child" required=#true }
7588"#;
7589        let strict: Spec = base.parse().unwrap();
7590        let err = parse(&strict, &input(&["ex", "run"])).unwrap_err();
7591        let message = err.to_string();
7592        assert!(
7593            message.contains("input") || message.contains("config"),
7594            "{message}"
7595        );
7596
7597        let negated: Spec = base
7598            .replacen("bin \"ex\"", "bin \"ex\"\nsubcommand_negates_reqs #true", 1)
7599            .parse()
7600            .unwrap();
7601        let err = parse(&negated, &input(&["ex", "run"])).unwrap_err();
7602        assert!(
7603            err.to_string().contains("child"),
7604            "the selected command keeps its own requirements: {err}"
7605        );
7606
7607        let mut child_optional = negated.clone();
7608        child_optional.cmd.subcommands["run"].flags[0].required = false;
7609        parse(&child_optional, &input(&["ex", "run"]))
7610            .expect("the child selection satisfies all parent requirements");
7611        parse(&child_optional, &input(&["ex", "--mode", "run"]))
7612            .expect("parent requires relationships are negated too");
7613    }
7614
7615    #[test]
7616    fn a_parent_argument_can_conflict_with_a_later_subcommand() {
7617        let spec: Spec = r#"name "ex"
7618bin "ex"
7619args_conflicts_with_subcommands #true
7620flag "--verbose"
7621cmd "run"
7622"#
7623        .parse()
7624        .unwrap();
7625
7626        parse(&spec, &input(&["ex", "run"]))
7627            .expect("the subcommand is valid without a parent argument");
7628        let err = parse(&spec, &input(&["ex", "--verbose", "run"])).unwrap_err();
7629        assert!(
7630            err.to_string().contains("cannot be used with arguments"),
7631            "{err}"
7632        );
7633    }
7634
7635    #[test]
7636    fn a_subcommand_can_take_precedence_over_a_variadic_flag() {
7637        let base = r#"name "ex"
7638bin "ex"
7639flag "--values <value>..."
7640cmd "run"
7641"#;
7642        let plain: Spec = base.parse().unwrap();
7643        let out = parse(&plain, &input(&["ex", "--values", "a", "run"])).unwrap();
7644        assert_eq!(out.cmd.name, "ex");
7645
7646        let precedence: Spec = base
7647            .replacen(
7648                "bin \"ex\"",
7649                "bin \"ex\"\nsubcommand_precedence_over_arg #true",
7650                1,
7651            )
7652            .parse()
7653            .unwrap();
7654        let out = parse(&precedence, &input(&["ex", "--values", "a", "run"])).unwrap();
7655        assert_eq!(out.cmd.name, "run");
7656    }
7657
7658    #[test]
7659    fn a_required_positional_can_follow_an_unfilled_optional_one() {
7660        let base = r#"name "ex"
7661bin "ex"
7662arg "[optional]"
7663arg "<required>"
7664"#;
7665        let plain: Spec = base.parse().unwrap();
7666        let err = parse(&plain, &input(&["ex", "value"])).unwrap_err();
7667        assert!(err.to_string().contains("required"), "{err}");
7668
7669        let enabled: Spec = base
7670            .replacen(
7671                "bin \"ex\"",
7672                "bin \"ex\"\nallow_missing_positional #true",
7673                1,
7674            )
7675            .parse()
7676            .unwrap();
7677        let out = parse(&enabled, &input(&["ex", "value"])).unwrap();
7678        assert!(!out.args.keys().any(|arg| arg.name == "optional"));
7679        let value = &out
7680            .args
7681            .iter()
7682            .find(|(arg, _)| arg.name == "required")
7683            .unwrap()
7684            .1;
7685        assert!(matches!(value, ParseValue::String(value) if value == "value"));
7686    }
7687
7688    #[test]
7689    fn sigil_args_do_not_block_optional_positional_skipping() {
7690        let spec: Spec = r#"name "ex"
7691bin "ex"
7692allow_missing_positional #true
7693arg "[optional]"
7694arg "[tool]" sigil="@"
7695arg "<required>"
7696"#
7697        .parse()
7698        .unwrap();
7699
7700        let out = parse(&spec, &input(&["ex", "@node", "value"])).unwrap();
7701        assert!(!out.args.keys().any(|arg| arg.name == "optional"));
7702        let tool = out.args.keys().find(|arg| arg.name == "tool").unwrap();
7703        let required = out.args.keys().find(|arg| arg.name == "required").unwrap();
7704        assert_eq!(out.args[tool].to_string(), "node");
7705        assert_eq!(out.args[required].to_string(), "value");
7706    }
7707
7708    #[test]
7709    fn unknown_flags_can_be_rejected_for_the_whole_cli() {
7710        let spec: Spec =
7711            "name \"ex\"\nbin \"ex\"\nunknown_flags \"error\"\nflag \"--force\"\nflag \"-0 --print0\"\narg \"[rest]...\" allow_negative_numbers=#true\n"
7712                .parse()
7713                .unwrap();
7714        let err = parse(&spec, &["ex".to_string(), "--wat".to_string()]).unwrap_err();
7715        assert!(
7716            err.to_string().contains("--wat"),
7717            "the message should name the token: {err}"
7718        );
7719
7720        // The positional opts into the narrower negative-number carve-out without
7721        // accepting arbitrary unknown flags.
7722        let out = parse(&spec, &["ex".to_string(), "-1".to_string()]).unwrap();
7723        let rest = out.args.keys().find(|a| a.name == "rest").unwrap();
7724        assert_eq!(out.args[rest].to_string(), "-1");
7725
7726        let out = parse(&spec, &["ex".to_string(), "-0".to_string()]).unwrap();
7727        let print0 = out.flags.keys().find(|flag| flag.name == "print0").unwrap();
7728        assert!(matches!(out.flags[print0], ParseValue::Bool(true)));
7729    }
7730
7731    #[test]
7732    fn a_declared_digit_short_does_not_stop_the_subcommand_scan() {
7733        let spec: Spec = r#"
7734name "ex"
7735bin "ex"
7736unknown_flags "error"
7737flag "-0 --print0" global=#true
7738cmd "run" {
7739  flag "--force"
7740}
7741"#
7742        .parse()
7743        .unwrap();
7744        let out = parse(&spec, &input(&["ex", "-0", "run", "--force"])).unwrap();
7745        assert_eq!(out.cmd.name, "run");
7746        let print0 = out.flags.keys().find(|flag| flag.name == "print0").unwrap();
7747        let force = out.flags.keys().find(|flag| flag.name == "force").unwrap();
7748        assert!(matches!(out.flags[print0], ParseValue::Bool(true)));
7749        assert!(matches!(out.flags[force], ParseValue::Bool(true)));
7750    }
7751
7752    #[test]
7753    fn a_command_may_override_the_cli_wide_setting() {
7754        // Strict overall, lenient for the one command that forwards options.
7755        let spec: Spec = r#"
7756name "ex"
7757bin "ex"
7758unknown_flags "error"
7759cmd "exec" unknown_flags="value" {
7760  arg "[rest]..."
7761}
7762cmd "build" {
7763  arg "[rest]..."
7764}
7765"#
7766        .parse()
7767        .unwrap();
7768
7769        let out = parse(
7770            &spec,
7771            &["ex".to_string(), "exec".to_string(), "--wat".to_string()],
7772        )
7773        .unwrap();
7774        let rest = out.args.keys().find(|a| a.name == "rest").unwrap();
7775        assert_eq!(out.args[rest].to_string(), "--wat");
7776
7777        assert!(
7778            parse(
7779                &spec,
7780                &["ex".to_string(), "build".to_string(), "--wat".to_string()]
7781            )
7782            .is_err(),
7783            "a command that says nothing inherits the CLI's choice"
7784        );
7785    }
7786
7787    #[test]
7788    fn the_setting_survives_a_round_trip() {
7789        let spec: Spec =
7790            "name \"ex\"\nbin \"ex\"\nunknown_flags \"error\"\ncmd \"x\" unknown_flags=\"value\"\n"
7791                .parse()
7792                .unwrap();
7793        let reparsed: Spec = spec.to_string().parse().unwrap();
7794        assert_eq!(reparsed.unknown_flags, Some(UnknownFlags::Error));
7795        assert_eq!(
7796            reparsed.cmd.subcommands["x"].unknown_flags,
7797            Some(UnknownFlags::Value)
7798        );
7799    }
7800
7801    #[test]
7802    fn test_default_subcommand() {
7803        // Test that default_subcommand routes to the specified subcommand
7804        let run_cmd = SpecCommand::builder()
7805            .name("run")
7806            .arg(SpecArg::builder().name("task").build())
7807            .build();
7808        let mut cmd = SpecCommand::builder().name("test").build();
7809        cmd.subcommands.insert("run".to_string(), run_cmd);
7810
7811        let spec = Spec {
7812            name: "test".to_string(),
7813            bin: "test".to_string(),
7814            cmd,
7815            default_subcommand: Some("run".to_string()),
7816            ..Default::default()
7817        };
7818
7819        // "test mytask" should be parsed as if it were "test run mytask"
7820        let input = vec!["test".to_string(), "mytask".to_string()];
7821        let parsed = parse(&spec, &input).unwrap();
7822
7823        // Should have two commands: root and "run"
7824        assert_eq!(parsed.cmds.len(), 2);
7825        assert_eq!(parsed.cmds[1].name, "run");
7826
7827        // Should have parsed the task argument
7828        assert_eq!(parsed.args.len(), 1);
7829        let arg = parsed.args.keys().next().unwrap();
7830        assert_eq!(arg.name, "task");
7831        let value = parsed.args.values().next().unwrap();
7832        assert_eq!(value.to_string(), "mytask");
7833    }
7834
7835    #[test]
7836    fn default_subcommand_outranks_root_sigil_arg() {
7837        let spec: Spec = r#"
7838name "test"
7839bin "test"
7840default_subcommand "run"
7841arg "[tools]..." sigil="+"
7842cmd "run" { arg "<task>" }
7843"#
7844        .parse()
7845        .unwrap();
7846
7847        let parsed = parse(&spec, &input(&["test", "+node", "node"])).unwrap();
7848        assert_eq!(parsed.cmd.name, "run");
7849        let value = |name| {
7850            parsed
7851                .args
7852                .iter()
7853                .find(|(arg, _)| arg.name == name)
7854                .map(|(_, value)| value.to_string())
7855                .unwrap()
7856        };
7857        assert_eq!(value("tools"), "node");
7858        assert_eq!(value("task"), "node");
7859    }
7860
7861    #[test]
7862    fn test_default_subcommand_explicit_still_works() {
7863        // Test that explicit subcommand takes precedence
7864        let run_cmd = SpecCommand::builder()
7865            .name("run")
7866            .arg(SpecArg::builder().name("task").build())
7867            .build();
7868        let other_cmd = SpecCommand::builder()
7869            .name("other")
7870            .arg(SpecArg::builder().name("other_arg").build())
7871            .build();
7872        let mut cmd = SpecCommand::builder().name("test").build();
7873        cmd.subcommands.insert("run".to_string(), run_cmd);
7874        cmd.subcommands.insert("other".to_string(), other_cmd);
7875
7876        let spec = Spec {
7877            name: "test".to_string(),
7878            bin: "test".to_string(),
7879            cmd,
7880            default_subcommand: Some("run".to_string()),
7881            ..Default::default()
7882        };
7883
7884        // "test other foo" should use "other" subcommand, not default
7885        let input = vec!["test".to_string(), "other".to_string(), "foo".to_string()];
7886        let parsed = parse(&spec, &input).unwrap();
7887
7888        // Should have used "other" subcommand
7889        assert_eq!(parsed.cmds.len(), 2);
7890        assert_eq!(parsed.cmds[1].name, "other");
7891    }
7892
7893    #[test]
7894    fn test_default_subcommand_applies_only_at_the_root() {
7895        // `default_subcommand` is declared once, for the whole spec, and only at the top. It
7896        // was being looked up wherever the parser happened to be standing, so a command with
7897        // an unrelated subcommand of the same name acquired a default of its own: with
7898        // `default_subcommand "ls"`, `ex config zzz` descended into `config ls` and bound
7899        // `zzz` there. Nothing declared that, and nothing could have.
7900        let mut config_ls = SpecCommand::builder().name("ls").build();
7901        config_ls.args.push(SpecArg::builder().name("what").build());
7902        let mut config_cmd = SpecCommand::builder().name("config").build();
7903        config_cmd.subcommands.insert("ls".to_string(), config_ls);
7904
7905        // The root's own `ls`, which is what its default points at. It takes an argument so
7906        // that a routed word has somewhere to land.
7907        let mut root_ls = SpecCommand::builder().name("ls").build();
7908        root_ls.args.push(SpecArg::builder().name("what").build());
7909        let mut cmd = SpecCommand::builder().name("ex").build();
7910        cmd.subcommands.insert("ls".to_string(), root_ls);
7911        cmd.subcommands.insert("config".to_string(), config_cmd);
7912
7913        let spec = Spec {
7914            name: "ex".to_string(),
7915            bin: "ex".to_string(),
7916            cmd,
7917            default_subcommand: Some("ls".to_string()),
7918            ..Default::default()
7919        };
7920
7921        // `config` has an `ls`, but `config` did not declare a default, so `zzz` is `config`'s
7922        // own business — and `config` takes no argument, so this is an error rather than a
7923        // silent descent.
7924        let input = vec!["ex".to_string(), "config".to_string(), "zzz".to_string()];
7925        assert!(
7926            parse(&spec, &input).is_err(),
7927            "`config` has no default subcommand and no argument, so `zzz` cannot bind"
7928        );
7929
7930        // At the root, where it is declared, it still applies.
7931        let input = vec!["ex".to_string(), "zzz".to_string()];
7932        let parsed = parse(&spec, &input).expect("the root's default applies");
7933        assert_eq!(
7934            parsed
7935                .cmds
7936                .iter()
7937                .map(|c| c.name.as_str())
7938                .collect::<Vec<_>>(),
7939            ["ex", "ls"]
7940        );
7941        assert_eq!(
7942            parsed.args.values().next().map(|v| v.to_string()),
7943            Some("zzz".to_string()),
7944            "and the word binds inside the command it reached"
7945        );
7946    }
7947
7948    #[test]
7949    fn test_default_subcommand_with_nested_subcommands() {
7950        // Test that default_subcommand works when the default subcommand has nested subcommands.
7951        // This is the mise use case: "mise say" should be parsed as "mise run say"
7952        // where "say" is a subcommand of "run" (a task).
7953        let say_cmd = SpecCommand::builder()
7954            .name("say")
7955            .arg(SpecArg::builder().name("name").build())
7956            .build();
7957        let mut run_cmd = SpecCommand::builder().name("run").build();
7958        run_cmd.subcommands.insert("say".to_string(), say_cmd);
7959
7960        let mut cmd = SpecCommand::builder().name("test").build();
7961        cmd.subcommands.insert("run".to_string(), run_cmd);
7962
7963        let spec = Spec {
7964            name: "test".to_string(),
7965            bin: "test".to_string(),
7966            cmd,
7967            default_subcommand: Some("run".to_string()),
7968            ..Default::default()
7969        };
7970
7971        // "test say hello" should be parsed as "test run say hello"
7972        let input = vec!["test".to_string(), "say".to_string(), "hello".to_string()];
7973        let parsed = parse(&spec, &input).unwrap();
7974
7975        // Should have three commands: root, "run", and "say"
7976        assert_eq!(parsed.cmds.len(), 3);
7977        assert_eq!(parsed.cmds[0].name, "test");
7978        assert_eq!(parsed.cmds[1].name, "run");
7979        assert_eq!(parsed.cmds[2].name, "say");
7980
7981        // Should have parsed the "name" argument
7982        assert_eq!(parsed.args.len(), 1);
7983        let arg = parsed.args.keys().next().unwrap();
7984        assert_eq!(arg.name, "name");
7985        let value = parsed.args.values().next().unwrap();
7986        assert_eq!(value.to_string(), "hello");
7987    }
7988
7989    /// Build a spec equivalent to the post-mount structure produced by mise's
7990    /// `mise usage` output: a root with a value-taking global flag (`-C/--cd`), a `run`
7991    /// subcommand that re-declares the same flag as NON-global, and a mounted task
7992    /// (`sample:run`) carrying a positional arg with `choices`.
7993    ///
7994    /// We construct the merged structure directly instead of executing a real mount so the
7995    /// test stays hermetic and cross-platform while still exercising the parser defect.
7996    fn mounted_global_flag_spec() -> Spec {
7997        let task_cmd = SpecCommand::builder()
7998            .name("sample:run")
7999            .arg(
8000                SpecArg::builder()
8001                    .name("profile")
8002                    .choices(["alpha", "beta", "gamma"])
8003                    .build(),
8004            )
8005            .build();
8006        // `run` re-declares `-C/--cd` but as a NON-global flag, mirroring the mise spec.
8007        let mut run_cmd = SpecCommand::builder()
8008            .name("run")
8009            .flag(
8010                SpecFlag::builder()
8011                    .name("cd")
8012                    .short('C')
8013                    .long("cd")
8014                    .arg(SpecArg::builder().name("dir").build())
8015                    .global(false)
8016                    .build(),
8017            )
8018            .build();
8019        run_cmd
8020            .subcommands
8021            .insert("sample:run".to_string(), task_cmd);
8022
8023        let mut cmd = SpecCommand::builder()
8024            .name("test")
8025            .flag(
8026                SpecFlag::builder()
8027                    .name("cd")
8028                    .short('C')
8029                    .long("cd")
8030                    .arg(SpecArg::builder().name("dir").build())
8031                    .global(true)
8032                    .build(),
8033            )
8034            .build();
8035        cmd.subcommands.insert("run".to_string(), run_cmd);
8036
8037        Spec {
8038            name: "test".to_string(),
8039            bin: "test".to_string(),
8040            cmd,
8041            ..Default::default()
8042        }
8043    }
8044
8045    #[test]
8046    fn test_prefix_global_flag_does_not_pollute_choices() {
8047        // Regression for the parser-side root cause referenced by jdx/mise#10069.
8048        //
8049        // When `run` re-declares the global `-C/--cd` as non-global, descending into it (and
8050        // then into the mounted `sample:run`) used to drop the inherited global flag from
8051        // `available_flags`. Phase 2 then no longer recognized the prefix `-C`, so it was
8052        // mis-validated against the task's `choices` positional arg.
8053        let spec = mounted_global_flag_spec();
8054
8055        // The prefix global flag must stay recognized so it is consumed as a flag (not as the
8056        // positional). Before the fix this bailed with "Invalid choice for arg profile: -C".
8057        for words in [
8058            &["test", "-C", "/tmp", "run", "sample:run"][..],
8059            // Embedded-value form must behave identically.
8060            &["test", "--cd=/tmp", "run", "sample:run"][..],
8061        ] {
8062            let parsed = parse_partial(&spec, &input(words)).unwrap();
8063            assert_eq!(
8064                parsed
8065                    .cmds
8066                    .iter()
8067                    .map(|c| c.name.as_str())
8068                    .collect::<Vec<_>>(),
8069                vec!["test", "run", "sample:run"],
8070            );
8071            // No positional arg should have been consumed by the leftover global-flag tokens.
8072            assert!(
8073                parsed.args.is_empty(),
8074                "args should be empty, got {:?}",
8075                parsed.args
8076            );
8077
8078            // Fix (B): the inherited global flag survives the descent even though `run`
8079            // re-declares `-C/--cd` as non-global.
8080            let cd = parsed
8081                .available_flags
8082                .get("--cd")
8083                .expect("--cd should remain available after descending into the subcommand");
8084            assert!(cd.global, "--cd must stay global after descent");
8085            assert!(
8086                parsed.available_flags.get("-C").is_some_and(|f| f.global),
8087                "-C must stay global after descent",
8088            );
8089
8090            // The global flag must still be recorded in `out.flags` so it reaches `as_env()`
8091            // for normal execution and for the env passed to mount scripts. (Removing the
8092            // token in Phase 1 instead of re-parsing it would silently drop `usage_cd`.)
8093            assert_eq!(
8094                parsed.as_env().get("usage_cd").map(String::as_str),
8095                Some("/tmp"),
8096                "global flag value must survive in as_env(), got {:?}",
8097                parsed.as_env(),
8098            );
8099        }
8100
8101        // A real, valid choice still parses through the global flag prefix.
8102        let parsed = parse_partial(
8103            &spec,
8104            &input(&["test", "-C", "/tmp", "run", "sample:run", "alpha"]),
8105        )
8106        .unwrap();
8107        assert_eq!(parsed.args.len(), 1);
8108        assert_eq!(parsed.args.values().next().unwrap().to_string(), "alpha");
8109
8110        // And genuinely invalid choices are still rejected (we didn't disable validation).
8111        assert_parse_err(
8112            parse_partial(&spec, &input(&["test", "run", "sample:run", "wrong"])),
8113            "Invalid choice for arg profile: wrong, expected one of alpha, beta, gamma",
8114        );
8115    }
8116
8117    /// Build a spec mirroring mise's orphan-short re-declarations: a root with a LONG-ONLY
8118    /// global boolean flag (`--raw`, no short), a `run` subcommand that re-declares it as a
8119    /// NON-global flag while ADDING a short (`-r --raw`) plus a purely-local `-f/--force`
8120    /// flag, and a mounted task (`sample:run`) with a `choices` positional arg.
8121    fn mounted_orphan_short_spec() -> Spec {
8122        let task_cmd = SpecCommand::builder()
8123            .name("sample:run")
8124            .arg(
8125                SpecArg::builder()
8126                    .name("profile")
8127                    .choices(["alpha", "beta", "gamma"])
8128                    .build(),
8129            )
8130            .build();
8131        // `run` re-declares `--raw` as NON-global but adds a `-r` short that exists only here,
8132        // and also carries a purely-local `-f/--force` flag (shares nothing with a global).
8133        let mut run_cmd = SpecCommand::builder()
8134            .name("run")
8135            .flag(
8136                SpecFlag::builder()
8137                    .name("raw")
8138                    .short('r')
8139                    .long("raw")
8140                    .global(false)
8141                    .build(),
8142            )
8143            .flag(
8144                SpecFlag::builder()
8145                    .name("force")
8146                    .short('f')
8147                    .long("force")
8148                    .global(false)
8149                    .build(),
8150            )
8151            .build();
8152        run_cmd
8153            .subcommands
8154            .insert("sample:run".to_string(), task_cmd);
8155
8156        // Root global is LONG-ONLY: `--raw` with no short.
8157        let mut cmd = SpecCommand::builder()
8158            .name("test")
8159            .flag(
8160                SpecFlag::builder()
8161                    .name("raw")
8162                    .long("raw")
8163                    .global(true)
8164                    .build(),
8165            )
8166            .build();
8167        cmd.subcommands.insert("run".to_string(), run_cmd);
8168
8169        Spec {
8170            name: "test".to_string(),
8171            bin: "test".to_string(),
8172            cmd,
8173            ..Default::default()
8174        }
8175    }
8176
8177    #[test]
8178    fn test_orphan_short_alias_survives_merge() {
8179        // Follow-up to test_prefix_global_flag_does_not_pollute_choices (jdx/mise#10069):
8180        // when `run` re-declares the long-only global `--raw` as a non-global `-r --raw`, the
8181        // added short `-r` must be unioned onto the surviving inherited global flag instead of
8182        // being discarded with the wholesale re-declaration. Otherwise `mycli run -r <task>`
8183        // would not recognize `-r` and would mis-validate it against the task's `choices` arg.
8184        let spec = mounted_orphan_short_spec();
8185
8186        let parsed = parse_partial(&spec, &input(&["test", "run", "-r", "sample:run"])).unwrap();
8187        assert_eq!(
8188            parsed
8189                .cmds
8190                .iter()
8191                .map(|c| c.name.as_str())
8192                .collect::<Vec<_>>(),
8193            vec!["test", "run", "sample:run"],
8194        );
8195
8196        // (a) The orphan short `-r` survives the descent, merged onto the inherited global flag,
8197        // and the original long `--raw` is still global too.
8198        assert!(
8199            parsed.available_flags.get("-r").is_some_and(|f| f.global),
8200            "-r must be merged onto the inherited global flag and stay global after descent",
8201        );
8202        assert!(
8203            parsed
8204                .available_flags
8205                .get("--raw")
8206                .is_some_and(|f| f.global),
8207            "--raw must stay global after descent",
8208        );
8209
8210        // (b) The token is consumed as a flag, not mistaken for the `choices` positional.
8211        assert!(
8212            parsed.args.is_empty(),
8213            "args should be empty, got {:?}",
8214            parsed.args
8215        );
8216
8217        // (c) The value still reaches as_env() so `usage_raw` is produced for execution/mounts.
8218        assert_eq!(
8219            parsed.as_env().get("usage_raw").map(String::as_str),
8220            Some("true"),
8221            "merged short's value must survive in as_env(), got {:?}",
8222            parsed.as_env(),
8223        );
8224
8225        // (d) Negative case: a purely-local flag that shares nothing with a global is NOT
8226        // promoted/merged — it is correctly dropped when descending into the mount.
8227        assert!(
8228            !parsed.available_flags.contains_key("-f"),
8229            "purely-local -f must not be promoted onto a global",
8230        );
8231        assert!(
8232            !parsed.available_flags.contains_key("--force"),
8233            "purely-local --force must not be promoted onto a global",
8234        );
8235
8236        // A real, valid choice still parses through the merged short prefix.
8237        let parsed =
8238            parse_partial(&spec, &input(&["test", "run", "-r", "sample:run", "alpha"])).unwrap();
8239        assert_eq!(parsed.args.len(), 1);
8240        assert_eq!(parsed.args.values().next().unwrap().to_string(), "alpha");
8241
8242        // And genuinely invalid choices are still rejected.
8243        assert_parse_err(
8244            parse_partial(&spec, &input(&["test", "run", "-r", "sample:run", "wrong"])),
8245            "Invalid choice for arg profile: wrong, expected one of alpha, beta, gamma",
8246        );
8247    }
8248
8249    #[test]
8250    fn test_orphan_short_does_not_clobber_unrelated_global() {
8251        // When a re-declaration's orphan short collides with a DIFFERENT inherited global's
8252        // short, the merge must not steal it. Here the root has both a long-only `--raw` global
8253        // and a `-r --restrict` global; `run` re-declares `-r --raw` as non-global. `-r` is a
8254        // genuine collision with `--restrict`, so global precedence must keep `-r -> restrict`.
8255        let run_cmd = SpecCommand::builder()
8256            .name("run")
8257            .flag(
8258                SpecFlag::builder()
8259                    .name("raw")
8260                    .short('r')
8261                    .long("raw")
8262                    .global(false)
8263                    .build(),
8264            )
8265            .build();
8266        let mut cmd = SpecCommand::builder()
8267            .name("test")
8268            .flag(
8269                SpecFlag::builder()
8270                    .name("raw")
8271                    .long("raw")
8272                    .global(true)
8273                    .build(),
8274            )
8275            .flag(
8276                SpecFlag::builder()
8277                    .name("restrict")
8278                    .short('r')
8279                    .long("restrict")
8280                    .global(true)
8281                    .build(),
8282            )
8283            .build();
8284        cmd.subcommands.insert("run".to_string(), run_cmd);
8285        let spec = Spec {
8286            name: "test".to_string(),
8287            bin: "test".to_string(),
8288            cmd,
8289            ..Default::default()
8290        };
8291
8292        let parsed = parse_partial(&spec, &input(&["test", "run"])).unwrap();
8293        // `-r` stays owned by the unrelated `--restrict` global, not stolen by the merged raw.
8294        assert_eq!(
8295            parsed.available_flags.get("-r").map(|f| f.name.as_str()),
8296            Some("restrict"),
8297            "-r must remain owned by the unrelated global it already belonged to",
8298        );
8299        // Both globals are still recognized and global after the descent.
8300        assert!(parsed
8301            .available_flags
8302            .get("--raw")
8303            .is_some_and(|f| f.global));
8304        assert!(parsed
8305            .available_flags
8306            .get("--restrict")
8307            .is_some_and(|f| f.global));
8308    }
8309
8310    #[test]
8311    fn test_redeclared_global_aliases_share_one_flag() {
8312        // A global declared with BOTH a short and a long, re-declared non-globally by a
8313        // subcommand that adds a third alias. Every alias key must resolve to the SAME merged
8314        // flag: the child's keys iterate in BTreeMap order (`--assume-yes`, `--yes`, `-y`), so by
8315        // the time `-y` is reached the long already points at the merged flag. That merged flag is
8316        // not a *different* inherited global, so the collision guard must not skip `-y` and leave
8317        // it pointing at the pre-merge global (which lacks the added `assume-yes` alias).
8318        let spec = r#"
8319flag "-y --yes" global=#true effect="write"
8320cmd "run" {
8321    flag "-y --yes --assume-yes"
8322}
8323"#
8324        .parse::<Spec>()
8325        .unwrap();
8326
8327        let parsed = parse_partial(&spec, &input(&["test", "run"])).unwrap();
8328
8329        for key in ["-y", "--yes", "--assume-yes"] {
8330            let flag = parsed
8331                .available_flags
8332                .get(key)
8333                .unwrap_or_else(|| panic!("{key} must be recognized after the descent"));
8334            assert!(flag.global, "{key} must stay global after the descent");
8335            assert_eq!(
8336                flag.long,
8337                vec!["yes".to_string(), "assume-yes".to_string()],
8338                "{key} must resolve to the flag carrying every alias",
8339            );
8340            assert_eq!(flag.short, vec!['y'], "{key} must keep the global's short");
8341        }
8342
8343        // One logical flag means one object: all three keys share a single `Arc`.
8344        assert_eq!(
8345            unique_flags(parsed.available_flags.values()).count(),
8346            1,
8347            "all aliases must point at one flag object, got {:?}",
8348            parsed.available_flags,
8349        );
8350
8351        // The global's effect survives the merge, so `-y` still marks the command as writing.
8352        assert_eq!(
8353            parsed.available_flags["-y"].effect,
8354            Some(crate::SpecCommandEffect::Write),
8355        );
8356    }
8357
8358    #[test]
8359    fn test_redeclared_global_keeps_hidden_alias_metadata() {
8360        let spec = r#"
8361flag "--yes" global=#true {
8362    alias "-q" "--quietly" hide=#true
8363}
8364cmd "run" {
8365    flag "--yes --assume-yes" {
8366        alias "-s" "--secret" hide=#true
8367    }
8368}
8369"#
8370        .parse::<Spec>()
8371        .unwrap();
8372
8373        let parsed = parse_partial(&spec, &input(&["test", "run"])).unwrap();
8374        let merged = &parsed.available_flags["--yes"];
8375        assert_eq!(merged.hidden_short_aliases, ['q', 's']);
8376        assert_eq!(merged.hidden_aliases, ["quietly", "secret"]);
8377        for key in ["-q", "-s", "--quietly", "--secret"] {
8378            assert!(Arc::ptr_eq(&parsed.available_flags[key], merged), "{key}");
8379        }
8380    }
8381
8382    #[test]
8383    fn test_redeclared_global_can_promote_hidden_aliases() {
8384        let spec = r#"
8385flag "--yes" global=#true {
8386    alias "-q" "--quietly" hide=#true
8387}
8388cmd "run" {
8389    flag "-q --yes --quietly"
8390}
8391"#
8392        .parse::<Spec>()
8393        .unwrap();
8394
8395        let parsed = parse_partial(&spec, &input(&["test", "run"])).unwrap();
8396        let merged = &parsed.available_flags["--yes"];
8397        assert!(merged.hidden_short_aliases.is_empty());
8398        assert!(merged.hidden_aliases.is_empty());
8399        for key in ["-q", "--quietly"] {
8400            assert!(Arc::ptr_eq(&parsed.available_flags[key], merged), "{key}");
8401        }
8402    }
8403
8404    #[test]
8405    fn test_partially_redeclared_global_keeps_all_aliases_on_one_flag() {
8406        // Same one-flag-one-object requirement as above, but the child re-declares only ONE of
8407        // the global's three aliases (`--yes`, not `-y`/`--confirm`) while adding a new one. The
8408        // aliases the child omits are never visited by the merge loop, so they must be rebound to
8409        // the merged flag explicitly — otherwise `-y` and `--confirm` keep pointing at the
8410        // pre-merge global and miss the added `assume-yes`.
8411        let spec = r#"
8412flag "-y --yes --confirm" global=#true
8413cmd "run" {
8414    flag "--yes --assume-yes"
8415}
8416"#
8417        .parse::<Spec>()
8418        .unwrap();
8419
8420        let parsed = parse_partial(&spec, &input(&["test", "run"])).unwrap();
8421
8422        for key in ["-y", "--yes", "--confirm", "--assume-yes"] {
8423            let flag = parsed
8424                .available_flags
8425                .get(key)
8426                .unwrap_or_else(|| panic!("{key} must be recognized after the descent"));
8427            assert!(flag.global, "{key} must stay global after the descent");
8428            assert_eq!(
8429                flag.long,
8430                vec![
8431                    "yes".to_string(),
8432                    "confirm".to_string(),
8433                    "assume-yes".to_string()
8434                ],
8435                "{key} must resolve to the flag carrying every alias",
8436            );
8437        }
8438
8439        assert_eq!(
8440            unique_flags(parsed.available_flags.values()).count(),
8441            1,
8442            "all aliases must point at one flag object, got {:?}",
8443            parsed.available_flags,
8444        );
8445    }
8446
8447    /// Build a spec shaped like mise's post-mount structure for jdx/mise#11282: a root with
8448    /// globals (`-E/--env <ENV>`, `--silent`), a `run` subcommand with a non-global flag, and a
8449    /// MOUNTED task command that declares its own `--env` (with choices) plus `--bump`.
8450    ///
8451    /// The task command is marked `mounted` the same way `SpecCommand::mount()` marks the
8452    /// commands it merges in, so the test stays hermetic (no mount subprocess).
8453    fn mounted_task_flag_spec() -> Spec {
8454        let mut task_cmd = SpecCommand::builder()
8455            .name("mytask")
8456            .flag(
8457                SpecFlag::builder()
8458                    .name("env")
8459                    .long("env")
8460                    .arg(
8461                        SpecArg::builder()
8462                            .name("name")
8463                            .choices(["dev", "stage", "prod"])
8464                            .build(),
8465                    )
8466                    .global(false)
8467                    .build(),
8468            )
8469            .flag(
8470                SpecFlag::builder()
8471                    .name("bump")
8472                    .long("bump")
8473                    .arg(
8474                        SpecArg::builder()
8475                            .name("type")
8476                            .choices(["auto", "major"])
8477                            .build(),
8478                    )
8479                    .global(false)
8480                    .build(),
8481            )
8482            .build();
8483        task_cmd.mounted = true;
8484
8485        let mut run_cmd = SpecCommand::builder()
8486            .name("run")
8487            .flag(
8488                SpecFlag::builder()
8489                    .name("force")
8490                    .short('f')
8491                    .long("force")
8492                    .global(false)
8493                    .build(),
8494            )
8495            .build();
8496        run_cmd.subcommands.insert("mytask".to_string(), task_cmd);
8497
8498        let mut cmd = SpecCommand::builder()
8499            .name("test")
8500            .flag(
8501                SpecFlag::builder()
8502                    .name("env")
8503                    .short('E')
8504                    .long("env")
8505                    .arg(SpecArg::builder().name("ENV").build())
8506                    .global(true)
8507                    .build(),
8508            )
8509            .flag(
8510                SpecFlag::builder()
8511                    .name("silent")
8512                    .long("silent")
8513                    .global(true)
8514                    .build(),
8515            )
8516            .build();
8517        cmd.subcommands.insert("run".to_string(), run_cmd);
8518
8519        Spec {
8520            name: "test".to_string(),
8521            bin: "test".to_string(),
8522            cmd,
8523            ..Default::default()
8524        }
8525    }
8526
8527    #[test]
8528    fn test_mount_boundary_does_not_apply_inside_the_mounted_tree() {
8529        // The mounted program's own commands are ordinary commands relative to each other, so
8530        // descending *within* the mounted tree must follow the normal rules — including keeping
8531        // an inherited global that a nested command re-declares as non-global (jdx/usage#649).
8532        // Treating every level of the tree as a mount boundary let the re-declaration shadow the
8533        // global, which the next descent's `retain(global)` then dropped entirely.
8534        let deep = SpecCommand::builder().name("deep").build();
8535        let mut sub = SpecCommand::builder()
8536            .name("sub")
8537            // Re-declares the mounted program's own global as non-global.
8538            .flag(
8539                SpecFlag::builder()
8540                    .name("cd")
8541                    .short('C')
8542                    .long("cd")
8543                    .arg(SpecArg::builder().name("dir").build())
8544                    .global(false)
8545                    .build(),
8546            )
8547            .build();
8548        sub.subcommands.insert("deep".to_string(), deep);
8549        let mut task = SpecCommand::builder()
8550            .name("task")
8551            .flag(
8552                SpecFlag::builder()
8553                    .name("cd")
8554                    .short('C')
8555                    .long("cd")
8556                    .arg(SpecArg::builder().name("dir").build())
8557                    .global(true)
8558                    .build(),
8559            )
8560            .build();
8561        task.subcommands.insert("sub".to_string(), sub);
8562        task.mark_mounted();
8563
8564        let mut run_cmd = SpecCommand::builder().name("run").build();
8565        run_cmd.subcommands.insert("task".to_string(), task);
8566        let mut cmd = SpecCommand::builder().name("test").build();
8567        cmd.subcommands.insert("run".to_string(), run_cmd);
8568        let spec = Spec {
8569            name: "test".to_string(),
8570            bin: "test".to_string(),
8571            cmd,
8572            ..Default::default()
8573        };
8574
8575        let parsed = parse_partial(&spec, &input(&["test", "run", "task", "sub", "deep"])).unwrap();
8576        assert!(
8577            parsed.available_flags.get("--cd").is_some_and(|f| f.global),
8578            "the mounted program's own global must survive descents inside the mounted tree",
8579        );
8580        assert!(
8581            parsed.completion_flags().contains_key("--cd"),
8582            "and must still be offered there: it belongs to the mounted program",
8583        );
8584        assert!(
8585            parsed.completion_flags().contains_key("-C"),
8586            "including the short the nested command re-declared",
8587        );
8588    }
8589
8590    #[test]
8591    fn test_mount_flags_merged_into_the_mounting_cmd_are_offered() {
8592        // A mounted spec may declare flags on its own root, which `SpecCommand::merge` folds
8593        // into the command the mount sits on. They belong to the mounted program, so they must
8594        // be offered inside the mounted commands rather than filtered out with the mounting
8595        // CLI's own flags.
8596        let mut task = SpecCommand::builder()
8597            .name("task")
8598            .flag(
8599                SpecFlag::builder()
8600                    .name("bump")
8601                    .long("bump")
8602                    .global(false)
8603                    .build(),
8604            )
8605            .build();
8606        task.mark_mounted();
8607
8608        let mut run_cmd = SpecCommand::builder().name("run").build();
8609        run_cmd.subcommands.insert("task".to_string(), task);
8610        // What `mount()` leaves behind when the mounted spec's root declares flags.
8611        run_cmd.flags = vec![
8612            SpecFlag::builder()
8613                .name("tglobal")
8614                .long("tglobal")
8615                .global(true)
8616                .build(),
8617            SpecFlag::builder()
8618                .name("tlocal")
8619                .long("tlocal")
8620                .global(false)
8621                .build(),
8622        ];
8623        run_cmd.flags_from_mount = true;
8624
8625        let mut cmd = SpecCommand::builder()
8626            .name("test")
8627            .flag(
8628                SpecFlag::builder()
8629                    .name("silent")
8630                    .long("silent")
8631                    .global(true)
8632                    .build(),
8633            )
8634            .build();
8635        cmd.subcommands.insert("run".to_string(), run_cmd);
8636        let spec = Spec {
8637            name: "test".to_string(),
8638            bin: "test".to_string(),
8639            cmd,
8640            ..Default::default()
8641        };
8642
8643        let parsed = parse_partial(&spec, &input(&["test", "run", "task"])).unwrap();
8644        assert_eq!(
8645            parsed.completion_flags().keys().collect::<Vec<_>>(),
8646            vec!["--bump", "--tglobal"],
8647            "the mounted spec's root global belongs to the mounted program; the mounting CLI's \
8648             `--silent` does not, and the mount's non-global root flag is not inherited",
8649        );
8650    }
8651
8652    #[test]
8653    fn test_mounted_cmd_does_not_offer_mounting_cli_globals() {
8654        // Regression for jdx/mise#11282. A mounted command describes another program, which
8655        // does not accept the mounting CLI's globals (mise forwards everything after a task
8656        // name to the task). They must stay recognized — they may appear before the mounted
8657        // command — but must not be offered in completions there.
8658        let spec = mounted_task_flag_spec();
8659        let parsed = parse_partial(&spec, &input(&["test", "run", "mytask"])).unwrap();
8660
8661        // Still recognized for parsing...
8662        assert!(parsed.available_flags.contains_key("--silent"));
8663        assert!(parsed.available_flags.contains_key("-E"));
8664        // ...but belonging to a command above the mount, so not offered.
8665        assert_eq!(
8666            parsed.completion_flags().keys().collect::<Vec<_>>(),
8667            vec!["--bump", "--env"],
8668            "only the mounted command's own flags may be offered",
8669        );
8670
8671        // `run`'s own non-global flag is dropped on descent, as it always was.
8672        assert!(!parsed.available_flags.contains_key("--force"));
8673    }
8674
8675    #[test]
8676    fn test_mounted_cmd_flag_wins_over_inherited_global() {
8677        // Second half of jdx/mise#11282: the mounted `--env` (with choices) used to be shadowed
8678        // by the root's `--env` global, so completing its value fell back to file completion.
8679        let spec = mounted_task_flag_spec();
8680        let parsed = parse_partial(&spec, &input(&["test", "run", "mytask", "--env"])).unwrap();
8681
8682        let awaiting = parsed
8683            .flag_awaiting_value
8684            .first()
8685            .expect("--env should await a value");
8686        assert_eq!(
8687            awaiting
8688                .arg
8689                .as_ref()
8690                .and_then(|a| a.choices.as_ref())
8691                .map(|c| c.choices.clone()),
8692            Some(vec![
8693                "dev".to_string(),
8694                "stage".to_string(),
8695                "prod".to_string()
8696            ]),
8697            "the mounted command's own --env must win over the inherited global",
8698        );
8699
8700        // The global's short is not declared by the mounted command, so it keeps pointing at
8701        // the global and a value passed before the mounted command still parses.
8702        let parsed =
8703            parse_partial(&spec, &input(&["test", "-E", "anything", "run", "mytask"])).unwrap();
8704        assert!(
8705            parsed.args.is_empty(),
8706            "prefix global tokens must not be consumed as positionals, got {:?}",
8707            parsed.args
8708        );
8709        assert_eq!(
8710            parsed.as_env().get("usage_env").map(String::as_str),
8711            Some("anything"),
8712        );
8713    }
8714
8715    #[test]
8716    fn test_prefix_flag_keeps_the_flag_it_was_read_as() {
8717        // A word before the mounted command is re-parsed by Phase 2, when the mounted command
8718        // already owns the name. It has to stay bound to the flag Phase 1 read it as, or the
8719        // global's value would be validated against the mounted flag's choices and a legitimate
8720        // value would be rejected.
8721        let spec = mounted_task_flag_spec();
8722        let parsed = parse_partial(
8723            &spec,
8724            &input(&["test", "--env", "not-a-task-choice", "run", "mytask"]),
8725        )
8726        .unwrap();
8727        assert!(
8728            parsed.errors.is_empty(),
8729            "prefix global value must not be validated against the mounted flag: {:?}",
8730            parsed
8731                .errors
8732                .iter()
8733                .map(|e| e.to_string())
8734                .collect::<Vec<_>>(),
8735        );
8736        assert_eq!(
8737            parsed.as_env().get("usage_env").map(String::as_str),
8738            Some("not-a-task-choice"),
8739        );
8740
8741        // The embedded-value form binds the same way.
8742        let parsed = parse_partial(
8743            &spec,
8744            &input(&["test", "--env=not-a-task-choice", "run", "mytask"]),
8745        )
8746        .unwrap();
8747        assert!(parsed.errors.is_empty());
8748        assert_eq!(
8749            parsed.as_env().get("usage_env").map(String::as_str),
8750            Some("not-a-task-choice"),
8751        );
8752
8753        // Meanwhile a word *after* the mounted command belongs to the mounted flag, even when
8754        // the same name was already used before it.
8755        let parsed = parse_partial(
8756            &spec,
8757            &input(&["test", "--env", "prod", "run", "mytask", "--env"]),
8758        )
8759        .unwrap();
8760        let awaiting = parsed
8761            .flag_awaiting_value
8762            .first()
8763            .expect("--env should await a value");
8764        assert_eq!(
8765            awaiting
8766                .arg
8767                .as_ref()
8768                .and_then(|a| a.choices.as_ref())
8769                .map(|c| c.choices.clone()),
8770            Some(vec![
8771                "dev".to_string(),
8772                "stage".to_string(),
8773                "prod".to_string()
8774            ]),
8775            "the mounted command's --env must own the name after the mounted command",
8776        );
8777    }
8778
8779    #[test]
8780    fn test_non_global_flag_does_not_hide_subcommand() {
8781        // A non-global flag may precede a subcommand (`mycli run --force task`). Phase 1 used to
8782        // stop scanning at one, so the subcommand — and any mount on it — was never reached and
8783        // its name was left to Phase 2 to mis-read as a positional: `unexpected word: mytask`.
8784        let spec = mounted_task_flag_spec();
8785
8786        for words in [
8787            // `run` declares `-f/--force` as non-global.
8788            &["test", "run", "--force", "mytask"][..],
8789            &["test", "run", "-f", "mytask"][..],
8790            // Mixed with a global before the subcommand.
8791            &["test", "-E", "prod", "run", "--force", "mytask"][..],
8792        ] {
8793            let parsed = parse_partial(&spec, &input(words)).unwrap();
8794            assert_eq!(
8795                parsed
8796                    .cmds
8797                    .iter()
8798                    .map(|c| c.name.as_str())
8799                    .collect::<Vec<_>>(),
8800                vec!["test", "run", "mytask"],
8801                "{words:?} should descend into the mounted command",
8802            );
8803            assert!(
8804                parsed.args.is_empty(),
8805                "{words:?} should not consume a positional, got {:?}",
8806                parsed.args,
8807            );
8808            assert_eq!(
8809                parsed.as_env().get("usage_force").map(String::as_str),
8810                Some("true"),
8811                "the non-global flag must still be recorded for {words:?}",
8812            );
8813        }
8814
8815        // A non-global flag that takes a value consumes it, rather than reading the value as the
8816        // subcommand.
8817        let mut run_cmd = SpecCommand::builder()
8818            .name("run")
8819            .flag(
8820                SpecFlag::builder()
8821                    .name("output")
8822                    .short('o')
8823                    .long("output")
8824                    .arg(SpecArg::builder().name("mode").build())
8825                    .global(false)
8826                    .build(),
8827            )
8828            .build();
8829        run_cmd.subcommands.insert(
8830            "task".to_string(),
8831            SpecCommand::builder().name("task").build(),
8832        );
8833        let mut cmd = SpecCommand::builder().name("test").build();
8834        cmd.subcommands.insert("run".to_string(), run_cmd);
8835        let spec = Spec {
8836            name: "test".to_string(),
8837            bin: "test".to_string(),
8838            cmd,
8839            ..Default::default()
8840        };
8841
8842        let parsed =
8843            parse_partial(&spec, &input(&["test", "run", "--output", "quiet", "task"])).unwrap();
8844        assert_eq!(
8845            parsed
8846                .cmds
8847                .iter()
8848                .map(|c| c.name.as_str())
8849                .collect::<Vec<_>>(),
8850            vec!["test", "run", "task"],
8851        );
8852        assert_eq!(
8853            parsed.as_env().get("usage_output").map(String::as_str),
8854            Some("quiet"),
8855        );
8856
8857        // An unknown flag still stops the scan: it may take a value, so the next word cannot be
8858        // assumed to be a subcommand. `run` takes no positional, so this stays an error.
8859        assert_parse_err(
8860            parse_partial(&spec, &input(&["test", "run", "--nope", "task"])),
8861            "unexpected word: --nope",
8862        );
8863    }
8864
8865    #[test]
8866    fn test_non_mounted_subcommand_offers_inherited_globals() {
8867        // Nothing changes for ordinary (non-mounted) subcommands: a global declared above is
8868        // still both recognized and offered.
8869        let mut run_cmd = SpecCommand::builder().name("run").build();
8870        run_cmd.subcommands.insert(
8871            "nested".to_string(),
8872            SpecCommand::builder().name("nested").build(),
8873        );
8874        let mut cmd = SpecCommand::builder()
8875            .name("test")
8876            .flag(
8877                SpecFlag::builder()
8878                    .name("silent")
8879                    .long("silent")
8880                    .global(true)
8881                    .build(),
8882            )
8883            .build();
8884        cmd.subcommands.insert("run".to_string(), run_cmd);
8885        let spec = Spec {
8886            name: "test".to_string(),
8887            bin: "test".to_string(),
8888            cmd,
8889            ..Default::default()
8890        };
8891
8892        let parsed = parse_partial(&spec, &input(&["test", "run", "nested"])).unwrap();
8893        assert_eq!(
8894            parsed.completion_flags().keys().collect::<Vec<_>>(),
8895            parsed.available_flags.keys().collect::<Vec<_>>(),
8896        );
8897        assert!(parsed.completion_flags().contains_key("--silent"));
8898    }
8899
8900    #[test]
8901    fn test_subcommand_alias_collision_keeps_last_owner() {
8902        // The orphan-alias merge must not disturb how two flags in the SAME subcommand that
8903        // share an alias are resolved. Historically the flattened flag map gave the shared
8904        // alias to the LAST-declared flag (last-writer-wins); that must be preserved.
8905        let run_cmd = SpecCommand::builder()
8906            .name("run")
8907            .flag(
8908                SpecFlag::builder()
8909                    .name("alpha")
8910                    .short('x')
8911                    .long("alpha")
8912                    .global(false)
8913                    .build(),
8914            )
8915            .flag(
8916                SpecFlag::builder()
8917                    .name("beta")
8918                    .short('x')
8919                    .long("beta")
8920                    .global(false)
8921                    .build(),
8922            )
8923            .build();
8924        let mut cmd = SpecCommand::builder().name("test").build();
8925        cmd.subcommands.insert("run".to_string(), run_cmd);
8926        let spec = Spec {
8927            name: "test".to_string(),
8928            bin: "test".to_string(),
8929            cmd,
8930            ..Default::default()
8931        };
8932
8933        let parsed = parse_partial(&spec, &input(&["test", "run"])).unwrap();
8934        // `-x` is declared by both flags; the last one (`beta`) keeps it, as before the fix.
8935        assert_eq!(
8936            parsed.available_flags.get("-x").map(|f| f.name.as_str()),
8937            Some("beta"),
8938            "the last-declared flag must keep a shared short alias",
8939        );
8940        // Both distinct long aliases remain recognized and point to their own flag.
8941        assert_eq!(
8942            parsed
8943                .available_flags
8944                .get("--alpha")
8945                .map(|f| f.name.as_str()),
8946            Some("alpha"),
8947        );
8948        assert_eq!(
8949            parsed
8950                .available_flags
8951                .get("--beta")
8952                .map(|f| f.name.as_str()),
8953            Some("beta"),
8954        );
8955    }
8956
8957    #[test]
8958    fn test_default_subcommand_same_name_child() {
8959        // Test that default_subcommand doesn't cause issues when the default subcommand
8960        // has a child with the same name (e.g., "run" has a task named "run").
8961        // This verifies we don't switch multiple times or get stuck in a loop.
8962        let run_task = SpecCommand::builder()
8963            .name("run")
8964            .arg(SpecArg::builder().name("args").build())
8965            .build();
8966        let mut run_cmd = SpecCommand::builder().name("run").build();
8967        run_cmd.subcommands.insert("run".to_string(), run_task);
8968
8969        let mut cmd = SpecCommand::builder().name("test").build();
8970        cmd.subcommands.insert("run".to_string(), run_cmd);
8971
8972        let spec = Spec {
8973            name: "test".to_string(),
8974            bin: "test".to_string(),
8975            cmd,
8976            default_subcommand: Some("run".to_string()),
8977            ..Default::default()
8978        };
8979
8980        // "test run" explicitly matches the "run" subcommand (not via default_subcommand)
8981        let input = vec!["test".to_string(), "run".to_string()];
8982        let parsed = parse(&spec, &input).unwrap();
8983
8984        // Should have two commands: root and "run"
8985        assert_eq!(parsed.cmds.len(), 2);
8986        assert_eq!(parsed.cmds[0].name, "test");
8987        assert_eq!(parsed.cmds[1].name, "run");
8988
8989        // "test run run" should descend into the "run" task (child of "run" subcommand)
8990        let input = vec![
8991            "test".to_string(),
8992            "run".to_string(),
8993            "run".to_string(),
8994            "hello".to_string(),
8995        ];
8996        let parsed = parse(&spec, &input).unwrap();
8997
8998        assert_eq!(parsed.cmds.len(), 3);
8999        assert_eq!(parsed.cmds[0].name, "test");
9000        assert_eq!(parsed.cmds[1].name, "run");
9001        assert_eq!(parsed.cmds[2].name, "run");
9002        assert_eq!(parsed.args.len(), 1);
9003        let value = parsed.args.values().next().unwrap();
9004        assert_eq!(value.to_string(), "hello");
9005
9006        // Key test case: "test other" should switch to default subcommand "run"
9007        // and treat "other" as a positional arg (not try to switch again because
9008        // "run" also has a "run" child).
9009        let mut run_cmd = SpecCommand::builder()
9010            .name("run")
9011            .arg(SpecArg::builder().name("task").build())
9012            .build();
9013        let run_task = SpecCommand::builder().name("run").build();
9014        run_cmd.subcommands.insert("run".to_string(), run_task);
9015
9016        let mut cmd = SpecCommand::builder().name("test").build();
9017        cmd.subcommands.insert("run".to_string(), run_cmd);
9018
9019        let spec = Spec {
9020            name: "test".to_string(),
9021            bin: "test".to_string(),
9022            cmd,
9023            default_subcommand: Some("run".to_string()),
9024            ..Default::default()
9025        };
9026
9027        let input = vec!["test".to_string(), "other".to_string()];
9028        let parsed = parse(&spec, &input).unwrap();
9029
9030        // Should have two commands: root and "run" (the default)
9031        // We should NOT have switched again to the "run" task child
9032        assert_eq!(parsed.cmds.len(), 2);
9033        assert_eq!(parsed.cmds[0].name, "test");
9034        assert_eq!(parsed.cmds[1].name, "run");
9035
9036        // "other" should be parsed as a positional arg
9037        assert_eq!(parsed.args.len(), 1);
9038        let value = parsed.args.values().next().unwrap();
9039        assert_eq!(value.to_string(), "other");
9040    }
9041
9042    #[test]
9043    fn test_restart_token() {
9044        // Test that restart_token resets argument parsing
9045        let run_cmd = SpecCommand::builder()
9046            .name("run")
9047            .arg(SpecArg::builder().name("task").build())
9048            .restart_token(":::".to_string())
9049            .build();
9050        let mut cmd = SpecCommand::builder().name("test").build();
9051        cmd.subcommands.insert("run".to_string(), run_cmd);
9052
9053        let spec = Spec {
9054            name: "test".to_string(),
9055            bin: "test".to_string(),
9056            cmd,
9057            ..Default::default()
9058        };
9059
9060        // "test run task1 ::: task2" - should end up with task2 as the arg
9061        let input = vec![
9062            "test".to_string(),
9063            "run".to_string(),
9064            "task1".to_string(),
9065            ":::".to_string(),
9066            "task2".to_string(),
9067        ];
9068        let parsed = parse(&spec, &input).unwrap();
9069
9070        // After restart, args were cleared and task2 was parsed
9071        assert_eq!(parsed.args.len(), 1);
9072        let value = parsed.args.values().next().unwrap();
9073        assert_eq!(value.to_string(), "task2");
9074    }
9075
9076    #[test]
9077    fn test_restart_token_multiple() {
9078        // Test multiple restart tokens
9079        let run_cmd = SpecCommand::builder()
9080            .name("run")
9081            .arg(SpecArg::builder().name("task").build())
9082            .restart_token(":::".to_string())
9083            .build();
9084        let mut cmd = SpecCommand::builder().name("test").build();
9085        cmd.subcommands.insert("run".to_string(), run_cmd);
9086
9087        let spec = Spec {
9088            name: "test".to_string(),
9089            bin: "test".to_string(),
9090            cmd,
9091            ..Default::default()
9092        };
9093
9094        // "test run task1 ::: task2 ::: task3" - should end up with task3 as the arg
9095        let input = vec![
9096            "test".to_string(),
9097            "run".to_string(),
9098            "task1".to_string(),
9099            ":::".to_string(),
9100            "task2".to_string(),
9101            ":::".to_string(),
9102            "task3".to_string(),
9103        ];
9104        let parsed = parse(&spec, &input).unwrap();
9105
9106        // After multiple restarts, args were cleared and task3 was parsed
9107        assert_eq!(parsed.args.len(), 1);
9108        let value = parsed.args.values().next().unwrap();
9109        assert_eq!(value.to_string(), "task3");
9110    }
9111
9112    #[test]
9113    fn test_restart_token_clears_flag_awaiting_value() {
9114        // Test that restart_token clears pending flag values
9115        let run_cmd = SpecCommand::builder()
9116            .name("run")
9117            .arg(SpecArg::builder().name("task").build())
9118            .flag(
9119                SpecFlag::builder()
9120                    .name("jobs")
9121                    .long("jobs")
9122                    .arg(SpecArg::builder().name("count").build())
9123                    .build(),
9124            )
9125            .restart_token(":::".to_string())
9126            .build();
9127        let mut cmd = SpecCommand::builder().name("test").build();
9128        cmd.subcommands.insert("run".to_string(), run_cmd);
9129
9130        let spec = Spec {
9131            name: "test".to_string(),
9132            bin: "test".to_string(),
9133            cmd,
9134            ..Default::default()
9135        };
9136
9137        // "test run task1 --jobs ::: task2" - task2 should be an arg, not a flag value
9138        let input = vec![
9139            "test".to_string(),
9140            "run".to_string(),
9141            "task1".to_string(),
9142            "--jobs".to_string(),
9143            ":::".to_string(),
9144            "task2".to_string(),
9145        ];
9146        let parsed = parse(&spec, &input).unwrap();
9147
9148        // task2 should be parsed as the task arg, not as --jobs value
9149        assert_eq!(parsed.args.len(), 1);
9150        let value = parsed.args.values().next().unwrap();
9151        assert_eq!(value.to_string(), "task2");
9152        // --jobs should not have a value
9153        assert!(parsed.flag_awaiting_value.is_empty());
9154    }
9155
9156    #[test]
9157    fn test_restart_token_resets_double_dash() {
9158        // Test that restart_token resets the -- separator effect
9159        let run_cmd = SpecCommand::builder()
9160            .name("run")
9161            .arg(SpecArg::builder().name("task").build())
9162            .arg(SpecArg::builder().name("extra_args").var(true).build())
9163            .flag(SpecFlag::builder().name("verbose").long("verbose").build())
9164            .restart_token(":::".to_string())
9165            .build();
9166        let mut cmd = SpecCommand::builder().name("test").build();
9167        cmd.subcommands.insert("run".to_string(), run_cmd);
9168
9169        let spec = Spec {
9170            name: "test".to_string(),
9171            bin: "test".to_string(),
9172            cmd,
9173            ..Default::default()
9174        };
9175
9176        // "test run task1 -- extra ::: --verbose task2" - --verbose should be a flag after :::
9177        let input = vec![
9178            "test".to_string(),
9179            "run".to_string(),
9180            "task1".to_string(),
9181            "--".to_string(),
9182            "extra".to_string(),
9183            ":::".to_string(),
9184            "--verbose".to_string(),
9185            "task2".to_string(),
9186        ];
9187        let parsed = parse(&spec, &input).unwrap();
9188
9189        // --verbose should be parsed as a flag (not an arg) after the restart
9190        assert!(parsed.flags.keys().any(|f| f.name == "verbose"));
9191        // task2 should be the arg after restart
9192        let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
9193        let value = parsed.args.get(task_arg).unwrap();
9194        assert_eq!(value.to_string(), "task2");
9195    }
9196
9197    #[test]
9198    fn test_double_dashes_without_preserve() {
9199        // Only the first `--` is a separator; a later one is a value, because flag
9200        // parsing has already stopped and there is nothing left for it to do.
9201        // `preserve` is about the *first* one — see the test below, where none is
9202        // consumed at all.
9203        let run_cmd = SpecCommand::builder()
9204            .name("run")
9205            .arg(SpecArg::builder().name("args").var(true).build())
9206            .build();
9207        let mut cmd = SpecCommand::builder().name("test").build();
9208        cmd.subcommands.insert("run".to_string(), run_cmd);
9209
9210        let spec = Spec {
9211            name: "test".to_string(),
9212            bin: "test".to_string(),
9213            cmd,
9214            ..Default::default()
9215        };
9216
9217        // "test run arg1 -- arg2 -- arg3": the first separates, the second is a value
9218        let input = vec![
9219            "test".to_string(),
9220            "run".to_string(),
9221            "arg1".to_string(),
9222            "--".to_string(),
9223            "arg2".to_string(),
9224            "--".to_string(),
9225            "arg3".to_string(),
9226        ];
9227        let parsed = parse(&spec, &input).unwrap();
9228
9229        let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
9230        let value = parsed.args.get(args_arg).unwrap();
9231        assert_eq!(value.to_string(), "arg1 arg2 -- arg3");
9232    }
9233
9234    #[test]
9235    fn test_double_dashes_with_preserve() {
9236        // Test that variadic args WITH `preserve` keep all double dashes
9237        let run_cmd = SpecCommand::builder()
9238            .name("run")
9239            .arg(
9240                SpecArg::builder()
9241                    .name("args")
9242                    .var(true)
9243                    .double_dash(SpecDoubleDashChoices::Preserve)
9244                    .build(),
9245            )
9246            .build();
9247        let mut cmd = SpecCommand::builder().name("test").build();
9248        cmd.subcommands.insert("run".to_string(), run_cmd);
9249
9250        let spec = Spec {
9251            name: "test".to_string(),
9252            bin: "test".to_string(),
9253            cmd,
9254            ..Default::default()
9255        };
9256
9257        // "test run arg1 -- arg2 -- arg3" - all double dashes should be preserved
9258        let input = vec![
9259            "test".to_string(),
9260            "run".to_string(),
9261            "arg1".to_string(),
9262            "--".to_string(),
9263            "arg2".to_string(),
9264            "--".to_string(),
9265            "arg3".to_string(),
9266        ];
9267        let parsed = parse(&spec, &input).unwrap();
9268
9269        let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
9270        let value = parsed.args.get(args_arg).unwrap();
9271        assert_eq!(value.to_string(), "arg1 -- arg2 -- arg3");
9272    }
9273
9274    #[test]
9275    fn test_double_dashes_with_preserve_only_dashes() {
9276        // Test that variadic args WITH `preserve` keep all double dashes even
9277        // if the values are just double dashes
9278        let run_cmd = SpecCommand::builder()
9279            .name("run")
9280            .arg(
9281                SpecArg::builder()
9282                    .name("args")
9283                    .var(true)
9284                    .double_dash(SpecDoubleDashChoices::Preserve)
9285                    .build(),
9286            )
9287            .build();
9288        let mut cmd = SpecCommand::builder().name("test").build();
9289        cmd.subcommands.insert("run".to_string(), run_cmd);
9290
9291        let spec = Spec {
9292            name: "test".to_string(),
9293            bin: "test".to_string(),
9294            cmd,
9295            ..Default::default()
9296        };
9297
9298        // "test run -- --" - all double dashes should be preserved
9299        let input = vec![
9300            "test".to_string(),
9301            "run".to_string(),
9302            "--".to_string(),
9303            "--".to_string(),
9304        ];
9305        let parsed = parse(&spec, &input).unwrap();
9306
9307        let args_arg = parsed.args.keys().find(|a| a.name == "args").unwrap();
9308        let value = parsed.args.get(args_arg).unwrap();
9309        assert_eq!(value.to_string(), "-- --");
9310    }
9311
9312    #[test]
9313    fn test_double_dashes_with_preserve_multiple_args() {
9314        // Test with multiple args where only the second has has `preserve`
9315        let run_cmd = SpecCommand::builder()
9316            .name("run")
9317            .arg(SpecArg::builder().name("task").build())
9318            .arg(
9319                SpecArg::builder()
9320                    .name("extra_args")
9321                    .var(true)
9322                    .double_dash(SpecDoubleDashChoices::Preserve)
9323                    .build(),
9324            )
9325            .build();
9326        let mut cmd = SpecCommand::builder().name("test").build();
9327        cmd.subcommands.insert("run".to_string(), run_cmd);
9328
9329        let spec = Spec {
9330            name: "test".to_string(),
9331            bin: "test".to_string(),
9332            cmd,
9333            ..Default::default()
9334        };
9335
9336        // The first arg "task1" is captured normally
9337        // Then extra_args with `preserve` captures everything, including the "--" tokens
9338        let input = vec![
9339            "test".to_string(),
9340            "run".to_string(),
9341            "task1".to_string(),
9342            "--".to_string(),
9343            "arg1".to_string(),
9344            "--".to_string(),
9345            "--foo".to_string(),
9346        ];
9347        let parsed = parse(&spec, &input).unwrap();
9348
9349        let task_arg = parsed.args.keys().find(|a| a.name == "task").unwrap();
9350        let task_value = parsed.args.get(task_arg).unwrap();
9351        assert_eq!(task_value.to_string(), "task1");
9352
9353        let extra_arg = parsed.args.keys().find(|a| a.name == "extra_args").unwrap();
9354        let extra_value = parsed.args.get(extra_arg).unwrap();
9355        assert_eq!(extra_value.to_string(), "-- arg1 -- --foo");
9356    }
9357
9358    fn spec_with_args(args: impl IntoIterator<Item = SpecArg>) -> Spec {
9359        let cmd = SpecCommand::builder().name("test").args(args).build();
9360        Spec {
9361            name: "test".to_string(),
9362            bin: "test".to_string(),
9363            cmd,
9364            ..Default::default()
9365        }
9366    }
9367
9368    fn arg_value(parsed: &ParseOutput, name: &str) -> String {
9369        let arg = parsed
9370            .args
9371            .keys()
9372            .find(|a| a.name == name)
9373            .unwrap_or_else(|| panic!("expected arg {name} to be parsed"));
9374        parsed.args.get(arg).unwrap().to_string()
9375    }
9376
9377    fn required_arg(name: &str) -> SpecArg {
9378        SpecArg::builder()
9379            .name(name)
9380            .var(true)
9381            .required(false)
9382            .double_dash(SpecDoubleDashChoices::Required)
9383            .build()
9384    }
9385
9386    #[test]
9387    fn test_double_dash_required_reports_error_once_for_variadic() {
9388        // A variadic arg is offered every remaining word, but the mistake is one mistake.
9389        let spec = spec_with_args([required_arg("files")]);
9390
9391        let parsed = parse_partial(&spec, &input(&["test", "a", "b", "c"])).unwrap();
9392
9393        assert!(parsed.args.is_empty());
9394        assert_eq!(parsed.errors.len(), 1);
9395        assert!(
9396            matches!(&parsed.errors[0], UsageErr::ArgRequiresDoubleDash(name) if name == "files")
9397        );
9398    }
9399
9400    #[test]
9401    fn test_double_dash_required_suppresses_missing_arg() {
9402        // The arg is never filled, so the end-of-parse check would also call it missing.
9403        let spec = spec_with_args([SpecArg::builder()
9404            .name("file")
9405            .required(true)
9406            .double_dash(SpecDoubleDashChoices::Required)
9407            .build()]);
9408
9409        let parsed = parse_partial(&spec, &input(&["test", "x"])).unwrap();
9410
9411        assert_eq!(parsed.errors.len(), 1);
9412        assert!(matches!(
9413            &parsed.errors[0],
9414            UsageErr::ArgRequiresDoubleDash(_)
9415        ));
9416        // The cursor stays put, so a completion keeps offering the same arg.
9417        assert_eq!(
9418            parsed.next_arg.as_ref().map(|a| a.name.as_str()),
9419            Some("file")
9420        );
9421        assert!(!parsed.double_dash_seen);
9422    }
9423
9424    #[test]
9425    fn test_double_dash_routes_to_required_arg() {
9426        // Everything after `--` belongs to the arg that requires it, even though the greedy
9427        // variadic before it would otherwise swallow the rest (clap's `Arg::last(true)`).
9428        let spec = spec_with_args([
9429            SpecArg::builder()
9430                .name("tool")
9431                .var(true)
9432                .required(false)
9433                .build(),
9434            required_arg("command"),
9435        ]);
9436
9437        let parsed = parse(&spec, &input(&["test", "node@20", "--", "node", "app.js"])).unwrap();
9438
9439        assert_eq!(arg_value(&parsed, "tool"), "node@20");
9440        assert_eq!(arg_value(&parsed, "command"), "node app.js");
9441        assert!(parsed.double_dash_seen);
9442    }
9443
9444    #[test]
9445    fn test_double_dash_routes_with_gap_reports_missing_arg() {
9446        // Jumping the cursor leaves `tool` empty even though `command` is filled, so the
9447        // "is it filled?" check cannot be a count of how many args were filled.
9448        let spec = spec_with_args([
9449            SpecArg::builder()
9450                .name("tool")
9451                .var(true)
9452                .required(true)
9453                .build(),
9454            required_arg("command"),
9455        ]);
9456
9457        let parsed = parse_partial(&spec, &input(&["test", "--", "ls"])).unwrap();
9458
9459        assert_eq!(arg_value(&parsed, "command"), "ls");
9460        assert!(parsed.args.keys().all(|a| a.name != "tool"));
9461        assert!(parsed
9462            .errors
9463            .iter()
9464            .any(|e| matches!(e, UsageErr::MissingArg(name) if name == "tool")));
9465    }
9466
9467    #[test]
9468    fn test_double_dash_gap_applies_defaults() {
9469        // Same gap, seen from `Parser::parse`: the skipped arg still gets its default.
9470        let spec = spec_with_args([
9471            SpecArg::builder()
9472                .name("tool")
9473                .var(true)
9474                .required(false)
9475                .default_value("node@20")
9476                .build(),
9477            required_arg("command"),
9478        ]);
9479
9480        let parsed = parse(&spec, &input(&["test", "--", "ls"])).unwrap();
9481
9482        assert_eq!(arg_value(&parsed, "command"), "ls");
9483        assert_eq!(arg_value(&parsed, "tool"), "node@20");
9484    }
9485
9486    fn spec_with_restart_token_and_required_arg() -> Spec {
9487        let run_cmd = SpecCommand::builder()
9488            .name("run")
9489            .arg(SpecArg::builder().name("task").build())
9490            .arg(required_arg("run_args"))
9491            .restart_token(":::".to_string())
9492            .build();
9493        let mut cmd = SpecCommand::builder().name("test").build();
9494        cmd.subcommands.insert("run".to_string(), run_cmd);
9495        Spec {
9496            name: "test".to_string(),
9497            bin: "test".to_string(),
9498            cmd,
9499            ..Default::default()
9500        }
9501    }
9502
9503    #[test]
9504    fn test_double_dash_required_restart_token_resets_separator() {
9505        // The `--` before `:::` belongs to the previous invocation only.
9506        let spec = spec_with_restart_token_and_required_arg();
9507
9508        let parsed = parse_partial(
9509            &spec,
9510            &input(&["test", "run", "task1", "--", "a", ":::", "task2", "b"]),
9511        )
9512        .unwrap();
9513
9514        assert_eq!(arg_value(&parsed, "task"), "task2");
9515        assert!(parsed.args.keys().all(|a| a.name != "run_args"));
9516        // Reported once even though the arg was violated after already succeeding once.
9517        assert_eq!(
9518            parsed
9519                .errors
9520                .iter()
9521                .filter(|e| matches!(e, UsageErr::ArgRequiresDoubleDash(_)))
9522                .count(),
9523            1
9524        );
9525    }
9526
9527    #[test]
9528    fn test_double_dash_required_restart_token_accepts_new_separator() {
9529        let spec = spec_with_restart_token_and_required_arg();
9530
9531        let parsed = parse(
9532            &spec,
9533            &input(&["test", "run", "task1", "--", "a", ":::", "task2", "--", "c"]),
9534        )
9535        .unwrap();
9536
9537        assert_eq!(arg_value(&parsed, "task"), "task2");
9538        assert_eq!(arg_value(&parsed, "run_args"), "c");
9539    }
9540
9541    #[test]
9542    fn test_double_dash_preserve_is_not_a_separator() {
9543        // A `--` that `preserve` keeps is a *value* of that arg, so it must not unlock the
9544        // arg that requires a separator. Deliberate: one token cannot be both.
9545        let spec = spec_with_args([
9546            SpecArg::builder()
9547                .name("kept")
9548                .var(true)
9549                .var_max(1)
9550                .required(false)
9551                .double_dash(SpecDoubleDashChoices::Preserve)
9552                .build(),
9553            required_arg("rest"),
9554        ]);
9555
9556        let parsed = parse_partial(&spec, &input(&["test", "--", "x"])).unwrap();
9557
9558        assert_eq!(arg_value(&parsed, "kept"), "--");
9559        assert!(parsed.args.keys().all(|a| a.name != "rest"));
9560        assert!(!parsed.double_dash_seen);
9561        assert_eq!(parsed.errors.len(), 1);
9562    }
9563
9564    #[test]
9565    fn test_double_dash_required_does_not_bail_in_parse_partial() {
9566        // Completions parse half-typed command lines; they must still get a result.
9567        let spec = spec_with_args([required_arg("file")]);
9568
9569        assert!(parse_partial(&spec, &input(&["test", "x"])).is_ok());
9570        assert!(parse(&spec, &input(&["test", "x"])).is_err());
9571    }
9572
9573    #[test]
9574    fn test_double_dash_without_required_arg_does_not_move_cursor() {
9575        // Specs with no `double_dash="required"` arg are untouched by the jump.
9576        let spec = spec_with_args([
9577            SpecArg::builder().name("first").required(false).build(),
9578            SpecArg::builder().name("second").required(false).build(),
9579        ]);
9580
9581        let parsed = parse(&spec, &input(&["test", "--", "a", "b"])).unwrap();
9582
9583        assert_eq!(arg_value(&parsed, "first"), "a");
9584        assert_eq!(arg_value(&parsed, "second"), "b");
9585        assert!(parsed.next_arg.is_none());
9586    }
9587
9588    #[test]
9589    fn test_parser_with_custom_env_for_required_arg() {
9590        let spec = spec_with_arg(
9591            SpecArg::builder()
9592                .name("name")
9593                .env("NAME")
9594                .required(true)
9595                .build(),
9596        );
9597        std::env::remove_var("NAME");
9598
9599        let parsed = parse_with_env(&spec, &["test"], &[("NAME", "john")])
9600            .expect("parse should succeed with custom env");
9601        assert_eq!(parsed.args.len(), 1);
9602        assert_eq!(first_string_value(&parsed), "john");
9603    }
9604
9605    #[test]
9606    fn test_parser_with_custom_env_for_required_flag() {
9607        let spec = spec_with_flag(
9608            SpecFlag::builder()
9609                .long("name")
9610                .env("NAME")
9611                .required(true)
9612                .arg(SpecArg::builder().name("name").build())
9613                .build(),
9614        );
9615        std::env::remove_var("NAME");
9616
9617        let parsed = parse_with_env(&spec, &["test"], &[("NAME", "jane")])
9618            .expect("parse should succeed with custom env");
9619        assert_eq!(parsed.flags.len(), 1);
9620        assert_eq!(first_string_value(&parsed), "jane");
9621    }
9622
9623    #[test]
9624    fn test_flag_environment_fallbacks_preserve_declaration_order() {
9625        let spec = spec_with_flag(
9626            SpecFlag::builder()
9627                .long("name")
9628                .env("NAME")
9629                .env_fallback("OLD_NAME")
9630                .env_fallback("OLDER_NAME")
9631                .deprecated_env("DEPRECATED_NAME")
9632                .arg(SpecArg::builder().name("name").build())
9633                .build(),
9634        );
9635
9636        let parsed = parse_with_env(
9637            &spec,
9638            &["test"],
9639            &[
9640                ("NAME", "canonical"),
9641                ("OLD_NAME", "fallback"),
9642                ("DEPRECATED_NAME", "deprecated"),
9643            ],
9644        )
9645        .unwrap();
9646        assert_eq!(first_string_value(&parsed), "canonical");
9647
9648        let parsed = parse_with_env(
9649            &spec,
9650            &["test"],
9651            &[("OLDER_NAME", "older"), ("OLD_NAME", "old")],
9652        )
9653        .unwrap();
9654        assert_eq!(first_string_value(&parsed), "old");
9655
9656        let parsed =
9657            parse_with_env(&spec, &["test"], &[("DEPRECATED_NAME", "deprecated")]).unwrap();
9658        assert_eq!(first_string_value(&parsed), "deprecated");
9659    }
9660
9661    #[test]
9662    fn a_value_from_a_deprecated_alias_says_which_name_to_use() {
9663        let spec = spec_with_flag(
9664            SpecFlag::builder()
9665                .long("name")
9666                .env("NAME")
9667                .deprecated_env("DEPRECATED_NAME")
9668                .arg(SpecArg::builder().name("name").build())
9669                .build(),
9670        );
9671
9672        // The current name is not a deprecated one, and says nothing.
9673        let parsed = parse_with_env(&spec, &["test"], &[("NAME", "canonical")]).unwrap();
9674        assert!(parsed.warnings.is_empty(), "{:?}", parsed.warnings);
9675
9676        let parsed =
9677            parse_with_env(&spec, &["test"], &[("DEPRECATED_NAME", "deprecated")]).unwrap();
9678        assert_eq!(parsed.warnings.len(), 1, "{:?}", parsed.warnings);
9679        assert_eq!(
9680            parsed.warnings[0].kind,
9681            crate::warn::WarningKind::DeprecatedEnv
9682        );
9683        assert_eq!(parsed.warnings[0].name, "DEPRECATED_NAME");
9684        assert_eq!(parsed.warnings[0].replacement.as_deref(), Some("NAME"));
9685        // Reported, not printed, and the value still arrives.
9686        assert_eq!(first_string_value(&parsed), "deprecated");
9687    }
9688
9689    #[test]
9690    fn a_deprecated_flag_reports_only_when_it_was_used() {
9691        let spec = spec_with_flag(
9692            SpecFlag::builder()
9693                .long("output")
9694                .deprecated("use --out")
9695                .deprecated_remove_at("3.0.0")
9696                .arg(SpecArg::builder().name("output").build())
9697                .build(),
9698        );
9699
9700        let parsed = parse_with_env(&spec, &["test"], &[]).unwrap();
9701        assert!(parsed.warnings.is_empty(), "{:?}", parsed.warnings);
9702
9703        let parsed = parse_with_env(&spec, &["test", "--output", "a.txt"], &[]).unwrap();
9704        assert_eq!(parsed.warnings.len(), 1, "{:?}", parsed.warnings);
9705        assert_eq!(
9706            parsed.warnings[0].kind,
9707            crate::warn::WarningKind::DeprecatedFlag
9708        );
9709        // Named the way it was typed, dashes and all.
9710        assert_eq!(parsed.warnings[0].name, "--output");
9711        assert_eq!(parsed.warnings[0].remove_at.as_deref(), Some("3.0.0"));
9712        assert_eq!(
9713            parsed.warnings[0].render(),
9714            "warning: --output is deprecated, removed at 3.0.0: use --out\n",
9715        );
9716    }
9717
9718    #[test]
9719    fn a_milestone_the_spec_has_not_reached_stays_quiet() {
9720        let flag = SpecFlag::builder()
9721            .long("output")
9722            .deprecated("use --out")
9723            .deprecated_warn_at("9.0.0")
9724            .arg(SpecArg::builder().name("output").build())
9725            .build();
9726        let mut spec = spec_with_flag(flag);
9727        spec.version = Some("2.0.0".to_string());
9728
9729        let parsed = parse_with_env(&spec, &["test", "--output", "a.txt"], &[]).unwrap();
9730        assert!(parsed.warnings.is_empty(), "{:?}", parsed.warnings);
9731
9732        // And once the CLI is the release that was named, it speaks up.
9733        spec.version = Some("9.0.0".to_string());
9734        let parsed = parse_with_env(&spec, &["test", "--output", "a.txt"], &[]).unwrap();
9735        assert_eq!(parsed.warnings.len(), 1, "{:?}", parsed.warnings);
9736    }
9737
9738    #[test]
9739    fn test_parser_with_custom_env_still_fails_when_missing() {
9740        let spec = spec_with_arg(
9741            SpecArg::builder()
9742                .name("name")
9743                .env("NAME")
9744                .required(true)
9745                .build(),
9746        );
9747        std::env::remove_var("NAME");
9748        assert!(parse_with_env(&spec, &["test"], &[]).is_err());
9749    }
9750
9751    #[test]
9752    fn test_parser_does_not_treat_env_choice_value_as_help() {
9753        let spec = spec_with_arg(
9754            SpecArg::builder()
9755                .name("env")
9756                .env("CURRENT_ENV")
9757                .choices(["dev", "staging"])
9758                .required(false)
9759                .build(),
9760        );
9761
9762        assert_parse_err(
9763            parse_with_env(&spec, &["test"], &[("CURRENT_ENV", "--help")]),
9764            "Invalid choice for arg env: --help, expected one of dev, staging",
9765        );
9766    }
9767
9768    #[test]
9769    fn test_parser_does_not_treat_default_choice_value_as_help() {
9770        let spec = spec_with_flag(
9771            SpecFlag::builder()
9772                .long("env")
9773                .arg(
9774                    SpecArg::builder()
9775                        .name("env")
9776                        .choices(["dev", "staging"])
9777                        .build(),
9778                )
9779                .default_value("--help")
9780                .build(),
9781        );
9782
9783        assert_parse_err(
9784            parse_with_env(&spec, &["test"], &[]),
9785            "Invalid choice for option env: --help, expected one of dev, staging",
9786        );
9787    }
9788
9789    /// argv as `parse` wants it, program name included.
9790    fn words(of: &[&str]) -> Vec<String> {
9791        of.iter().map(|s| s.to_string()).collect()
9792    }
9793
9794    #[test]
9795    fn a_command_that_needs_a_subcommand_says_so() {
9796        // The spec has carried `subcommand_required` since the derive needed it, and this parser
9797        // never read it — so `mise generate`, which declares it, parsed as a complete
9798        // invocation while usage-argv and clap both refused. Found by the differential fuzzer.
9799        let spec: Spec = r#"
9800name "ex"
9801bin "ex"
9802cmd "gen" subcommand_required=#true {
9803    cmd "two" {}
9804    cmd "one" {}
9805    cmd "secret" hide=#true {}
9806    alias "g"
9807}
9808cmd "open" {
9809    cmd "sub" {}
9810}
9811"#
9812        .parse()
9813        .unwrap();
9814
9815        let err = parse(&spec, &words(&["ex", "gen"])).unwrap_err();
9816        // Sorted, so the message does not depend on map order; hidden commands left out,
9817        // because a message telling someone to type a hidden name is worse than a vague one;
9818        // and the alias not listed beside the name it points at.
9819        assert_eq!(err.to_string(), "`gen` needs a subcommand: one of one, two");
9820
9821        // Reached through its alias, and still about the command rather than the spelling.
9822        let err = parse(&spec, &words(&["ex", "g"])).unwrap_err();
9823        assert!(err.to_string().starts_with("`gen` needs a subcommand"));
9824
9825        // Given one: fine.
9826        parse(&spec, &words(&["ex", "gen", "one"])).unwrap();
9827
9828        // And a command that has subcommands without declaring them required is untouched —
9829        // this is the half that keeps the check from being "any command with children".
9830        parse(&spec, &words(&["ex", "open"])).unwrap();
9831        parse(&spec, &words(&["ex", "open", "sub"])).unwrap();
9832    }
9833
9834    #[test]
9835    fn arg_required_else_help_observes_the_selected_commands_argv() {
9836        let spec: Spec = r#"
9837name "ex"
9838bin "ex"
9839flag "--verbose" global=#true
9840cmd "run" arg_required_else_help=#true {
9841    flag "--all"
9842}
9843"#
9844        .parse()
9845        .unwrap();
9846        let words = |items: &[&str]| items.iter().map(|s| (*s).to_string()).collect::<Vec<_>>();
9847
9848        let err = parse(&spec, &words(&["ex", "run"])).unwrap_err();
9849        assert!(err.to_string().contains("Usage: ex run"), "{err}");
9850
9851        // A global before the command belongs to the ancestor. It selected `run`, but did not
9852        // give `run` an argument of its own.
9853        let err = parse(&spec, &words(&["ex", "--verbose", "run"])).unwrap_err();
9854        assert!(err.to_string().contains("Usage: ex run"), "{err}");
9855
9856        parse(&spec, &words(&["ex", "run", "--all"])).expect("run received an argv token");
9857    }
9858
9859    #[test]
9860    fn an_unmatched_word_is_forwarded_when_external_subcommand_is_set() {
9861        let spec: Spec = r#"
9862name "ex"
9863bin "ex"
9864unknown_flags "error"
9865external_subcommand #true
9866cmd "install"
9867flag "-v --verbose" global=#true
9868"#
9869        .parse()
9870        .unwrap();
9871
9872        let parsed = parse(&spec, &input(&["ex", "foo", "--help", "bar"])).unwrap();
9873        assert_eq!(
9874            parsed.external,
9875            Some(vec!["foo".into(), "--help".into(), "bar".into()])
9876        );
9877        assert!(parsed.flags.is_empty());
9878
9879        // Known subcommands still win.
9880        let parsed = parse(&spec, &input(&["ex", "install"])).unwrap();
9881        assert_eq!(parsed.cmd.name, "install");
9882        assert!(parsed.external.is_none());
9883
9884        // A global flag before the unmatched word still binds on the parent.
9885        let parsed = parse(&spec, &input(&["ex", "-v", "foo", "--verbose"])).unwrap();
9886        assert_eq!(
9887            parsed.external,
9888            Some(vec!["foo".into(), "--verbose".into()])
9889        );
9890        assert!(parsed.flags.keys().any(|flag| flag.name == "verbose"));
9891
9892        // An unknown flag on the parent is still an error, which is what clap does.
9893        assert!(parse(&spec, &input(&["ex", "--wat"])).is_err());
9894
9895        // A negative number is a value, not a flag, so it can be the unmatched word.
9896        // usage-argv already forwarded `-1`; Phase 1 used to treat every `starts_with('-')`
9897        // token as a flag and never reach the catch-all.
9898        let parsed = parse(&spec, &input(&["ex", "-1", "rest"])).unwrap();
9899        assert_eq!(parsed.external, Some(vec!["-1".into(), "rest".into()]));
9900    }
9901
9902    #[test]
9903    fn an_external_subcommand_satisfies_subcommand_required() {
9904        let mut spec: Spec = r#"
9905name "ex"
9906bin "ex"
9907external_subcommand #true
9908cmd "install"
9909"#
9910        .parse()
9911        .unwrap();
9912        spec.cmd.subcommand_required = true;
9913
9914        parse(&spec, &input(&["ex", "foo", "--help"])).unwrap();
9915        assert!(parse(&spec, &input(&["ex"])).is_err());
9916    }
9917
9918    #[test]
9919    fn a_default_subcommand_outranks_an_external_one() {
9920        let spec: Spec = r#"
9921name "ex"
9922bin "ex"
9923default_subcommand "run"
9924external_subcommand #true
9925cmd "run" {
9926    arg "[task]"
9927}
9928"#
9929        .parse()
9930        .unwrap();
9931
9932        let parsed = parse(&spec, &input(&["ex", "build"])).unwrap();
9933        assert_eq!(parsed.cmd.name, "run");
9934        assert!(parsed.external.is_none());
9935        assert_eq!(first_string_value(&parsed), "build");
9936    }
9937
9938    #[test]
9939    fn multicall_basename_strips_a_path_and_exe() {
9940        assert_eq!(multicall_basename("/usr/bin/ls"), "ls");
9941        assert_eq!(multicall_basename(r"C:\busybox\ls.exe"), "ls");
9942        assert_eq!(multicall_basename("LS.EXE"), "LS");
9943        assert_eq!(multicall_basename("busybox"), "busybox");
9944    }
9945
9946    #[test]
9947    fn a_multicall_applet_is_the_first_word() {
9948        let spec: Spec = r#"
9949name "busybox"
9950bin "busybox"
9951multicall #true
9952cmd "ls" {
9953    arg "[ARGS]" var=#true
9954}
9955cmd "cat"
9956"#
9957        .parse()
9958        .unwrap();
9959
9960        // A symlink: argv[0] is the applet.
9961        let parsed = parse(&spec, &input(&["/usr/bin/ls", "-l"])).unwrap();
9962        assert_eq!(parsed.cmd.name, "ls");
9963        match parsed.args.values().next() {
9964            Some(ParseValue::MultiString(values)) => assert_eq!(values, &["-l".to_string()]),
9965            other => panic!("expected ARGS to collect -l, got {other:?}"),
9966        }
9967
9968        // A dispatcher invocation still skips argv[0].
9969        let parsed = parse(&spec, &input(&["/usr/bin/busybox", "ls", "-l"])).unwrap();
9970        assert_eq!(parsed.cmd.name, "ls");
9971
9972        // Configured dispatcher values receive the same path and extension normalization.
9973        let mut configured = spec.clone();
9974        configured.name = "BusyBox".to_string();
9975        configured.bin = "/opt/bin/busybox.exe".to_string();
9976        let parsed = parse(&configured, &input(&["/usr/bin/busybox.exe", "ls", "-l"])).unwrap();
9977        assert_eq!(parsed.cmd.name, "ls");
9978
9979        // `.exe` is stripped so Windows and Unix agree.
9980        let parsed = parse(&spec, &input(&["ls.exe"])).unwrap();
9981        assert_eq!(parsed.cmd.name, "ls");
9982
9983        // Without the property, argv[0] is discarded as usual.
9984        let mut plain = spec.clone();
9985        plain.multicall = false;
9986        let parsed = parse(&plain, &input(&["/usr/bin/ls", "ls"])).unwrap();
9987        assert_eq!(parsed.cmd.name, "ls");
9988    }
9989
9990    #[test]
9991    fn a_multicall_unknown_applet_can_be_external() {
9992        let spec: Spec = r#"
9993name "busybox"
9994bin "busybox"
9995multicall #true
9996unknown_flags "error"
9997external_subcommand #true
9998cmd "ls"
9999"#
10000        .parse()
10001        .unwrap();
10002
10003        let parsed = parse(&spec, &input(&["/usr/bin/git", "--help"])).unwrap();
10004        assert_eq!(parsed.external, Some(vec!["git".into(), "--help".into()]));
10005
10006        let mut closed = spec.clone();
10007        closed.cmd.external_subcommand = false;
10008        assert!(parse(&closed, &input(&["wat"])).is_err());
10009    }
10010
10011    #[cfg(feature = "unstable_choices_env")]
10012    #[test]
10013    fn test_parser_arg_choices_from_custom_env() {
10014        let spec = spec_arg_choices_env("DEPLOY_ENVS");
10015
10016        let parsed =
10017            parse_with_env(&spec, &["test", "bar"], &[("DEPLOY_ENVS", "foo,bar baz")]).unwrap();
10018        assert_eq!(first_string_value(&parsed), "bar");
10019
10020        assert_parse_err(
10021            parse_with_env(&spec, &["test", "prod"], &[("DEPLOY_ENVS", "foo,bar baz")]),
10022            "Invalid choice for arg env: prod, expected one of foo, bar, baz",
10023        );
10024        assert_parse_err(
10025            parse_with_env(&spec, &["test", "prod"], &[]),
10026            "Invalid choice for arg env: prod, no choices resolved from env DEPLOY_ENVS",
10027        );
10028    }
10029
10030    #[cfg(feature = "unstable_choices_env")]
10031    #[test]
10032    fn test_parser_validates_flag_choices_from_custom_env() {
10033        let spec = spec_flag_choices_env("DEPLOY_ENVS");
10034        let parsed = parse_with_env(
10035            &spec,
10036            &["test", "--env", "baz"],
10037            &[("DEPLOY_ENVS", "foo,bar baz")],
10038        )
10039        .unwrap();
10040        assert_eq!(first_string_value(&parsed), "baz");
10041    }
10042
10043    #[cfg(feature = "unstable_choices_env")]
10044    #[test]
10045    fn test_parser_revalidates_env_and_default_values_against_choices_env() {
10046        let arg_env_spec = spec_with_arg(
10047            SpecArg::builder()
10048                .name("env")
10049                .env("CURRENT_ENV")
10050                .choices_env("DEPLOY_ENVS")
10051                .build(),
10052        );
10053        assert_parse_err(
10054            parse_with_env(
10055                &arg_env_spec,
10056                &["test"],
10057                &[("CURRENT_ENV", "prod"), ("DEPLOY_ENVS", "dev,staging")],
10058            ),
10059            "Invalid choice for arg env: prod, expected one of dev, staging",
10060        );
10061
10062        let flag_default_spec = spec_with_flag(
10063            SpecFlag::builder()
10064                .long("env")
10065                .arg(
10066                    SpecArg::builder()
10067                        .name("env")
10068                        .choices_env("DEPLOY_ENVS")
10069                        .build(),
10070                )
10071                .default_value("prod")
10072                .build(),
10073        );
10074        assert_parse_err(
10075            parse_with_env(
10076                &flag_default_spec,
10077                &["test"],
10078                &[("DEPLOY_ENVS", "dev,staging")],
10079            ),
10080            "Invalid choice for option env: prod, expected one of dev, staging",
10081        );
10082    }
10083
10084    #[test]
10085    fn test_variadic_arg_captures_unknown_flags_from_spec_string() {
10086        let spec: Spec = r#"
10087            flag "-v --verbose" var=#true
10088            arg "[database]" default="myapp_dev"
10089            arg "[args...]"
10090        "#
10091        .parse()
10092        .unwrap();
10093        let input: Vec<String> = vec!["test", "mydb", "--host", "localhost"]
10094            .into_iter()
10095            .map(String::from)
10096            .collect();
10097        let parsed = parse(&spec, &input).unwrap();
10098        let env = parsed.as_env();
10099        assert_eq!(env.get("usage_database").unwrap(), "mydb");
10100        assert_eq!(env.get("usage_args").unwrap(), "--host localhost");
10101    }
10102
10103    #[test]
10104    fn test_variadic_arg_captures_unknown_flags() {
10105        let cmd = SpecCommand::builder()
10106            .name("test")
10107            .flag(SpecFlag::builder().short('v').long("verbose").build())
10108            .arg(SpecArg::builder().name("database").required(false).build())
10109            .arg(
10110                SpecArg::builder()
10111                    .name("args")
10112                    .required(false)
10113                    .var(true)
10114                    .build(),
10115            )
10116            .build();
10117        let spec = Spec {
10118            name: "test".to_string(),
10119            bin: "test".to_string(),
10120            cmd,
10121            ..Default::default()
10122        };
10123
10124        // Unknown --host flag and its value should be captured by [args...]
10125        let input: Vec<String> = vec!["test", "mydb", "--host", "localhost"]
10126            .into_iter()
10127            .map(String::from)
10128            .collect();
10129        let parsed = parse(&spec, &input).unwrap();
10130        assert_eq!(parsed.args.len(), 2);
10131        let args_val = parsed
10132            .args
10133            .iter()
10134            .find(|(a, _)| a.name == "args")
10135            .unwrap()
10136            .1;
10137        match args_val {
10138            ParseValue::MultiString(v) => {
10139                assert_eq!(v, &vec!["--host".to_string(), "localhost".to_string()]);
10140            }
10141            _ => panic!("Expected MultiString, got {:?}", args_val),
10142        }
10143    }
10144
10145    #[test]
10146    fn test_variadic_arg_captures_unknown_flags_with_double_dash() {
10147        let cmd = SpecCommand::builder()
10148            .name("test")
10149            .flag(SpecFlag::builder().short('v').long("verbose").build())
10150            .arg(SpecArg::builder().name("database").required(false).build())
10151            .arg(
10152                SpecArg::builder()
10153                    .name("args")
10154                    .required(false)
10155                    .var(true)
10156                    .build(),
10157            )
10158            .build();
10159        let spec = Spec {
10160            name: "test".to_string(),
10161            bin: "test".to_string(),
10162            cmd,
10163            ..Default::default()
10164        };
10165
10166        // With explicit -- separator
10167        let input: Vec<String> = vec!["test", "--", "mydb", "--host", "localhost"]
10168            .into_iter()
10169            .map(String::from)
10170            .collect();
10171        let parsed = parse(&spec, &input).unwrap();
10172        assert_eq!(parsed.args.len(), 2);
10173        let args_val = parsed
10174            .args
10175            .iter()
10176            .find(|(a, _)| a.name == "args")
10177            .unwrap()
10178            .1;
10179        match args_val {
10180            ParseValue::MultiString(v) => {
10181                assert_eq!(v, &vec!["--host".to_string(), "localhost".to_string()]);
10182            }
10183            _ => panic!("Expected MultiString, got {:?}", args_val),
10184        }
10185    }
10186
10187    #[test]
10188    fn test_variadic_arg_unknown_flag_equals_value_not_split() {
10189        // Regression: --flag=value should be treated as a single positional token when
10190        // --flag is not a known spec flag, not split into "--flag=value" AND "value".
10191        let spec: Spec = r#"arg "[other_args]" var=#true"#.parse().unwrap();
10192
10193        // Single unknown --flag=value: must not produce a stray "3" positional.
10194        // as_env() shell-joins values, so "=" gets quoted.
10195        let input: Vec<String> = vec!["test", "--option=3"]
10196            .into_iter()
10197            .map(String::from)
10198            .collect();
10199        let parsed = parse(&spec, &input).unwrap();
10200        let env = parsed.as_env();
10201        assert_eq!(
10202            env.get("usage_other_args").map(String::as_str),
10203            Some("'--option=3'"),
10204            "expected a single --option=3 token, got {:?}",
10205            env.get("usage_other_args"),
10206        );
10207
10208        // Multiple unknown --flag=value args should each be kept intact
10209        let input2: Vec<String> = vec!["test", "--foo=bar", "--baz=qux"]
10210            .into_iter()
10211            .map(String::from)
10212            .collect();
10213        let parsed2 = parse(&spec, &input2).unwrap();
10214        let env2 = parsed2.as_env();
10215        assert_eq!(
10216            env2.get("usage_other_args").map(String::as_str),
10217            Some("'--foo=bar' '--baz=qux'"),
10218            "expected two intact tokens, got {:?}",
10219            env2.get("usage_other_args"),
10220        );
10221
10222        // Mix of plain positional args and unknown --flag=value tokens
10223        let input3: Vec<String> = vec!["test", "positional1", "--option=3", "positional2"]
10224            .into_iter()
10225            .map(String::from)
10226            .collect();
10227        let parsed3 = parse(&spec, &input3).unwrap();
10228        let env3 = parsed3.as_env();
10229        assert_eq!(
10230            env3.get("usage_other_args").map(String::as_str),
10231            Some("positional1 '--option=3' positional2"),
10232            "expected positional args and intact flag token, got {:?}",
10233            env3.get("usage_other_args"),
10234        );
10235    }
10236
10237    #[test]
10238    fn test_allow_hyphen_values_consumes_short_flag_collision() {
10239        let spec = r#"
10240flag "-d --working-dir <DIR>"
10241flag "-a --args <ARGS>" allow_hyphen_values=#true
10242"#
10243        .parse::<Spec>()
10244        .unwrap();
10245
10246        let parsed = parse(&spec, &input(&["test", "-a", "-destroy"])).unwrap();
10247
10248        assert_eq!(parsed.flags.len(), 1);
10249        assert_eq!(flag_string_value(&parsed, "args"), "-destroy");
10250    }
10251
10252    #[test]
10253    fn test_allow_hyphen_values_consumes_embedded_long_value() {
10254        let spec = r#"
10255flag "-d --working-dir <DIR>"
10256flag "-a --args <ARGS>" allow_hyphen_values=#true
10257"#
10258        .parse::<Spec>()
10259        .unwrap();
10260
10261        let parsed = parse(&spec, &input(&["test", "--args=-destroy"])).unwrap();
10262
10263        assert_eq!(parsed.flags.len(), 1);
10264        assert_eq!(flag_string_value(&parsed, "args"), "-destroy");
10265    }
10266
10267    #[test]
10268    fn test_allow_hyphen_values_takes_the_separator_as_its_value() {
10269        // The flag is declared to accept a token that looks like a flag, and `--` looks
10270        // like one, so it binds — which is what clap does with the same declaration.
10271        // Letting the separator arm run first consumed it and left the flag hungry, and
10272        // the flag then ate the word past it: `-a -- -x` bound `-x` with the `--` gone.
10273        let spec = r#"
10274flag "-a --args <ARGS>" allow_hyphen_values=#true
10275arg "[rest]..."
10276"#
10277        .parse::<Spec>()
10278        .unwrap();
10279
10280        let parsed = parse(&spec, &input(&["test", "-a", "--", "-x"])).unwrap();
10281
10282        assert_eq!(flag_string_value(&parsed, "args"), "--");
10283        let rest = parsed
10284            .args
10285            .values()
10286            .next()
10287            .expect("expected the word after the separator to reach the argument");
10288        assert_eq!(rest.to_string(), "-x");
10289    }
10290
10291    #[test]
10292    fn test_variadic_allow_hyphen_values_collects_after_a_hyphenated_first_value() {
10293        // Which token supplied the first value says nothing about how many the argument
10294        // takes, so collection carries on from a hyphenated one exactly as from a plain
10295        // one. It still stops at the next flag-like token, which is what keeps a second
10296        // occurrence of the flag from being eaten as a value.
10297        let spec = r#"
10298flag "-a --args <ARGS>..." allow_hyphen_values=#true
10299"#
10300        .parse::<Spec>()
10301        .unwrap();
10302
10303        let parsed = parse(&spec, &input(&["test", "-a", "-x", "b", "c"])).unwrap();
10304
10305        let flag = parsed
10306            .flags
10307            .keys()
10308            .find(|flag| flag.name == "args")
10309            .expect("expected args flag");
10310        match parsed.flags.get(flag).expect("expected args value") {
10311            ParseValue::MultiString(values) => assert_eq!(values, &["-x", "b", "c"]),
10312            other => panic!("expected a list of values, got {other:?}"),
10313        }
10314    }
10315
10316    #[test]
10317    fn test_variadic_allow_hyphen_values_consumes_repeated_flag_values() {
10318        let spec = r#"
10319flag "-a --args <ARGS>" var=#true allow_hyphen_values=#true
10320"#
10321        .parse::<Spec>()
10322        .unwrap();
10323
10324        let parsed = parse(&spec, &input(&["test", "-a", "-val1", "-a", "-val2"])).unwrap();
10325
10326        let flag = parsed
10327            .flags
10328            .keys()
10329            .find(|flag| flag.name == "args")
10330            .expect("expected args flag");
10331        let value = parsed.flags.get(flag).expect("expected args value");
10332        match value {
10333            ParseValue::MultiString(values) => {
10334                assert_eq!(values, &vec!["-val1".to_string(), "-val2".to_string()]);
10335            }
10336            _ => panic!("expected MultiString, got {value:?}"),
10337        }
10338    }
10339
10340    #[test]
10341    fn test_require_equals_accepts_attached_and_refuses_detached() {
10342        let spec = r#"
10343flag "--inspect <PORT>" require_equals=#true
10344"#
10345        .parse::<Spec>()
10346        .unwrap();
10347
10348        let parsed = parse(&spec, &input(&["test", "--inspect=9229"])).unwrap();
10349        assert_eq!(flag_string_value(&parsed, "inspect"), "9229");
10350
10351        let err = parse(&spec, &input(&["test", "--inspect", "9229"])).unwrap_err();
10352        let msg = format!("{err}");
10353        assert!(
10354            msg.contains("requires an argument") || msg.contains("inspect"),
10355            "detached value must be refused: {msg}"
10356        );
10357    }
10358
10359    #[test]
10360    fn boolean_flags_can_accept_attached_values_when_enabled() {
10361        let spec: Spec = r#"
10362name "ex"
10363bin "ex"
10364flag "--color" negate="--no-color" bool_value=#true
10365arg "[rest]"
10366"#
10367        .parse()
10368        .unwrap();
10369
10370        for (token, expected) in [
10371            ("--color", true),
10372            ("--color=true", true),
10373            ("--color=false", false),
10374            ("--no-color", false),
10375            ("--no-color=false", true),
10376        ] {
10377            let parsed = parse(&spec, &input(&["ex", token])).unwrap();
10378            assert!(
10379                matches!(
10380                    parsed.flags.get(&spec.cmd.flags[0]),
10381                    Some(ParseValue::Bool(value)) if *value == expected
10382                ),
10383                "{token}"
10384            );
10385        }
10386
10387        let parsed = parse(&spec, &input(&["ex", "--color=false", "word"])).unwrap();
10388        assert!(matches!(
10389            parsed.args.get(&spec.cmd.args[0]),
10390            Some(ParseValue::String(value)) if value == "word"
10391        ));
10392        let err = parse(&spec, &input(&["ex", "--color=maybe"])).unwrap_err();
10393        assert!(err.to_string().contains("expected `true` or `false`"));
10394
10395        let strict: Spec = r#"
10396name "ex"
10397bin "ex"
10398args_override_self #false
10399flag "--color" negate="--no-color" bool_value=#true
10400"#
10401        .parse()
10402        .unwrap();
10403        assert!(parse(&strict, &input(&["ex", "--color=false", "--color=true"])).is_err());
10404        let parsed = parse(
10405            &strict,
10406            &input(&["ex", "--color=false", "--no-color=false"]),
10407        )
10408        .unwrap();
10409        assert!(matches!(
10410            parsed.flags.get(&strict.cmd.flags[0]),
10411            Some(ParseValue::Bool(true))
10412        ));
10413    }
10414
10415    #[test]
10416    fn test_require_equals_refuses_a_detached_value_after_a_short_bundle() {
10417        let spec = r#"
10418flag "-a --all"
10419flag "-i --inspect <PORT>" require_equals=#true
10420"#
10421        .parse::<Spec>()
10422        .unwrap();
10423
10424        let err = parse(&spec, &input(&["test", "-ai", "9229"])).unwrap_err();
10425        let msg = format!("{err}");
10426        assert!(
10427            msg.contains("requires an argument") || msg.contains("inspect"),
10428            "bundled short must refuse the following word: {msg}"
10429        );
10430    }
10431
10432    #[test]
10433    fn test_default_missing_binds_when_the_value_is_left_off() {
10434        let spec = r#"
10435flag "-c --color <WHEN>" default_missing="always"
10436flag "-v --verbose"
10437"#
10438        .parse::<Spec>()
10439        .unwrap();
10440
10441        let parsed = parse(&spec, &input(&["test", "--color"])).unwrap();
10442        assert_eq!(flag_string_value(&parsed, "color"), "always");
10443
10444        let parsed = parse(&spec, &input(&["test", "--color=never"])).unwrap();
10445        assert_eq!(flag_string_value(&parsed, "color"), "never");
10446
10447        let parsed = parse(&spec, &input(&["test", "--color", "never"])).unwrap();
10448        assert_eq!(flag_string_value(&parsed, "color"), "never");
10449
10450        let parsed = parse(&spec, &input(&["test", "--color", "--verbose"])).unwrap();
10451        assert_eq!(flag_string_value(&parsed, "color"), "always");
10452        assert!(parsed.flags.keys().any(|f| f.name == "verbose"));
10453
10454        let parsed = parse(&spec, &input(&["test", "--color="])).unwrap();
10455        assert_eq!(flag_string_value(&parsed, "color"), "");
10456
10457        let parsed = parse(&spec, &input(&["test", "-cnever"])).unwrap();
10458        assert_eq!(flag_string_value(&parsed, "color"), "never");
10459
10460        let parsed = parse(&spec, &input(&["test", "-c", "-v"])).unwrap();
10461        assert_eq!(flag_string_value(&parsed, "color"), "always");
10462        assert!(parsed.flags.keys().any(|f| f.name == "verbose"));
10463    }
10464
10465    #[test]
10466    fn test_default_missing_requires_opt_in_for_detached_negative_flag_values() {
10467        let spec = r#"
10468flag "--apps <N>"
10469flag "--jobs <N>" default_missing="default missing"
10470flag "--kids <N>" default_missing="default missing" allow_negative_numbers=#true
10471"#
10472        .parse::<Spec>()
10473        .unwrap();
10474
10475        let parsed = parse(&spec, &input(&["test", "--apps", "-1"])).unwrap();
10476        assert_eq!(flag_string_value(&parsed, "apps"), "-1");
10477
10478        let err = parse(&spec, &input(&["test", "--jobs", "-1"])).unwrap_err();
10479        assert!(
10480            err.to_string().contains("unexpected word: -1"),
10481            "default_missing must keep an unopted negative value separate: {err}"
10482        );
10483
10484        let parsed = parse(&spec, &input(&["test", "--kids", "-1"])).unwrap();
10485        assert_eq!(flag_string_value(&parsed, "kids"), "-1");
10486
10487        let external_spec = r#"
10488external_subcommand #true
10489flag "--apps <N>"
10490"#
10491        .parse::<Spec>()
10492        .unwrap();
10493        let parsed = parse(&external_spec, &input(&["test", "--apps", "-1"])).unwrap();
10494        assert_eq!(flag_string_value(&parsed, "apps"), "-1");
10495        assert!(parsed.external.is_none());
10496
10497        for words in [
10498            &["test", "--apps", "--jobs", "1"][..],
10499            &["test", "--apps", "--kids", "-1"][..],
10500        ] {
10501            let err = parse(&spec, &input(words)).unwrap_err();
10502            let message = err.to_string();
10503            assert!(
10504                message.contains("--apps") && message.contains("requires an argument"),
10505                "the earlier flag must report its missing value for {words:?}: {message}"
10506            );
10507        }
10508    }
10509
10510    #[test]
10511    fn test_optional_flag_value_preserves_bare_and_explicit_empty_forms() {
10512        let spec = r#"
10513flag "--bump [LEVEL]" value_optional=#true
10514flag "--verbose"
10515arg "[FILE]"
10516"#
10517        .parse::<Spec>()
10518        .unwrap();
10519
10520        let absent = parse(&spec, &input(&["test"])).unwrap();
10521        assert!(!absent.flags.keys().any(|flag| flag.name == "bump"));
10522
10523        let bare = parse(&spec, &input(&["test", "--bump", "--verbose", "file.txt"])).unwrap();
10524        let bump = bare
10525            .flags
10526            .iter()
10527            .find(|(flag, _)| flag.name == "bump")
10528            .map(|(_, value)| value)
10529            .unwrap();
10530        assert!(matches!(bump, ParseValue::MultiString(values) if values.is_empty()));
10531        assert!(bare.flags.keys().any(|flag| flag.name == "verbose"));
10532        assert_eq!(arg_value(&bare, "FILE"), "file.txt");
10533
10534        let explicit = parse(&spec, &input(&["test", "--bump=", "file.txt"])).unwrap();
10535        assert_eq!(flag_string_value(&explicit, "bump"), "");
10536
10537        let corrected = parse(
10538            &spec,
10539            &input(&["test", "--bump=2", "--bump", "--verbose", "file.txt"]),
10540        )
10541        .unwrap();
10542        let bump = corrected
10543            .flags
10544            .iter()
10545            .find(|(flag, _)| flag.name == "bump")
10546            .map(|(_, value)| value)
10547            .unwrap();
10548        assert!(matches!(bump, ParseValue::MultiString(values) if values.is_empty()));
10549
10550        let collecting = r#"
10551flag "--tag [TAG]..." value_optional=#true
10552flag "--verbose"
10553"#
10554        .parse::<Spec>()
10555        .unwrap();
10556        let valued = parse(
10557            &collecting,
10558            &input(&["test", "--tag", "one", "two", "--verbose"]),
10559        )
10560        .unwrap();
10561        let tag = valued
10562            .flags
10563            .iter()
10564            .find(|(flag, _)| flag.name == "tag")
10565            .map(|(_, value)| value)
10566            .unwrap();
10567        assert!(matches!(tag, ParseValue::MultiString(values) if values == &["one", "two"]));
10568    }
10569
10570    #[test]
10571    fn test_repeatable_bare_optional_values_count_each_occurrence() {
10572        let spec = r#"
10573flag "--tag [TAG]" var=#true var_min=2 var_max=2 value_optional=#true
10574"#
10575        .parse::<Spec>()
10576        .unwrap();
10577
10578        let parsed = parse(&spec, &input(&["test", "--tag", "--tag"])).unwrap();
10579        let tag = parsed
10580            .flags
10581            .iter()
10582            .find(|(flag, _)| flag.name == "tag")
10583            .map(|(_, value)| value)
10584            .unwrap();
10585        assert!(matches!(tag, ParseValue::MultiString(values) if values == &["", ""]));
10586
10587        assert!(parse(&spec, &input(&["test", "--tag"])).is_err());
10588        assert!(parse(&spec, &input(&["test", "--tag", "--tag", "--tag"])).is_err());
10589    }
10590
10591    #[test]
10592    fn test_repeatable_variadic_optional_values_do_not_gain_bare_occurrences() {
10593        let spec = r#"
10594flag "--tag [TAG]..." var=#true value_optional=#true
10595flag "--verbose"
10596"#
10597        .parse::<Spec>()
10598        .unwrap();
10599
10600        for argv in [
10601            &["test", "--tag", "one", "two"][..],
10602            &["test", "--tag", "one", "two", "--verbose"][..],
10603            &["test", "--tag", "one", "--tag", "two"][..],
10604        ] {
10605            let parsed = parse(&spec, &input(argv)).unwrap();
10606            let tag = parsed
10607                .flags
10608                .iter()
10609                .find(|(flag, _)| flag.name == "tag")
10610                .map(|(_, value)| value)
10611                .unwrap();
10612            assert!(
10613                matches!(tag, ParseValue::MultiString(values) if values == &["one", "two"]),
10614                "argv={argv:?}: {tag:?}"
10615            );
10616        }
10617
10618        let bare = parse(&spec, &input(&["test", "--tag", "--verbose"])).unwrap();
10619        let tag = bare
10620            .flags
10621            .iter()
10622            .find(|(flag, _)| flag.name == "tag")
10623            .map(|(_, value)| value)
10624            .unwrap();
10625        assert!(matches!(tag, ParseValue::MultiString(values) if values == &[""]));
10626    }
10627
10628    #[test]
10629    fn test_default_missing_with_require_equals_refuses_the_following_word() {
10630        let spec = r#"
10631flag "--inspect <PORT>" require_equals=#true default_missing="9229"
10632arg "[rest]"
10633"#
10634        .parse::<Spec>()
10635        .unwrap();
10636
10637        let parsed = parse(&spec, &input(&["test", "--inspect"])).unwrap();
10638        assert_eq!(flag_string_value(&parsed, "inspect"), "9229");
10639
10640        let parsed = parse(&spec, &input(&["test", "--inspect=1234"])).unwrap();
10641        assert_eq!(flag_string_value(&parsed, "inspect"), "1234");
10642
10643        // The following word is not the value; the missing value is, and 80 is a positional.
10644        let parsed = parse(&spec, &input(&["test", "--inspect", "80"])).unwrap();
10645        assert_eq!(flag_string_value(&parsed, "inspect"), "9229");
10646        assert_eq!(
10647            parsed
10648                .args
10649                .values()
10650                .next()
10651                .map(|v| v.to_string())
10652                .as_deref(),
10653            Some("80")
10654        );
10655
10656        let parsed = parse(&spec, &input(&["test", "--inspect="])).unwrap();
10657        assert_eq!(flag_string_value(&parsed, "inspect"), "");
10658    }
10659
10660    #[test]
10661    fn test_default_missing_must_be_a_choice() {
10662        let spec = r#"
10663flag "--color <WHEN>" default_missing="always" {
10664    choices "auto" "always" "never"
10665}
10666"#
10667        .parse::<Spec>()
10668        .unwrap();
10669
10670        let parsed = parse(&spec, &input(&["test", "--color"])).unwrap();
10671        assert_eq!(flag_string_value(&parsed, "color"), "always");
10672
10673        let parsed = parse(&spec, &input(&["test", "--color=never"])).unwrap();
10674        assert_eq!(flag_string_value(&parsed, "color"), "never");
10675
10676        let spec = r#"
10677flag "--color <WHEN>" default_missing="wat" {
10678    choices "auto" "always" "never"
10679}
10680"#
10681        .parse::<Spec>()
10682        .unwrap();
10683
10684        let err = parse(&spec, &input(&["test", "--color"])).unwrap_err();
10685        let msg = format!("{err}");
10686        assert!(
10687            msg.contains("Invalid choice for option color: wat"),
10688            "missing default has to pass choices the same way a typed value does: {msg}"
10689        );
10690
10691        let err = parse(&spec, &input(&["test", "--color=wat"])).unwrap_err();
10692        let msg = format!("{err}");
10693        assert!(
10694            msg.contains("Invalid choice for option color: wat"),
10695            "an attached value that is not a choice is still refused: {msg}"
10696        );
10697
10698        let spec = r#"
10699flag "--inspect <PORT>" require_equals=#true default_missing="wat" {
10700    choices "9229" "80"
10701}
10702arg "[rest]"
10703"#
10704        .parse::<Spec>()
10705        .unwrap();
10706
10707        let err = parse(&spec, &input(&["test", "--inspect", "80"])).unwrap_err();
10708        let msg = format!("{err}");
10709        assert!(
10710            msg.contains("Invalid choice for option inspect: wat"),
10711            "require_equals still binds the missing string, so the error is the choice: {msg}"
10712        );
10713    }
10714
10715    #[test]
10716    fn test_hyphen_values_still_start_short_flag_parsing() {
10717        let spec = r#"
10718flag "-d --working-dir <DIR>"
10719flag "-a --args <ARGS>"
10720"#
10721        .parse::<Spec>()
10722        .unwrap();
10723
10724        let err = parse(&spec, &input(&["test", "-a", "-destroy"])).unwrap_err();
10725        let message = err.to_string();
10726        assert!(
10727            message.contains("--args") && message.contains("requires an argument"),
10728            "the recognized -d must leave the earlier -a missing: {message}"
10729        );
10730    }
10731
10732    /// `available_flags` has to agree with what an actual parse accepts, since
10733    /// its whole reason to exist is answering that question without one.
10734    mod available_flags {
10735        use super::*;
10736
10737        fn spec() -> Spec {
10738            r#"
10739bin "test"
10740flag "-v --verbose" global=#true
10741flag "--raw" global=#true effect="write"
10742flag "--local-only"
10743cmd "run" {
10744    flag "-r --raw"
10745    flag "-w --watch"
10746    cmd "once"
10747}
10748"#
10749            .parse::<Spec>()
10750            .unwrap()
10751        }
10752
10753        fn chain<'a>(spec: &'a Spec, path: &[&str]) -> Vec<&'a SpecCommand> {
10754            let mut chain = vec![&spec.cmd];
10755            for segment in path {
10756                chain.push(chain.last().unwrap().find_subcommand(segment).unwrap());
10757            }
10758            chain
10759        }
10760
10761        fn names(spec: &Spec, path: &[&str]) -> Vec<String> {
10762            let mut names: Vec<_> = available_flags(&chain(spec, path))
10763                .iter()
10764                .map(|f| f.name.clone())
10765                .collect();
10766            names.sort();
10767            names
10768        }
10769
10770        #[test]
10771        fn an_empty_chain_yields_nothing() {
10772            assert!(available_flags(&[]).is_empty());
10773        }
10774
10775        #[test]
10776        fn the_root_gets_its_own_flags() {
10777            let spec = spec();
10778            assert_eq!(names(&spec, &[]), ["local-only", "raw", "verbose"]);
10779        }
10780
10781        #[test]
10782        fn a_subcommand_keeps_globals_and_drops_local_only_ancestors() {
10783            let spec = spec();
10784            assert_eq!(names(&spec, &["run"]), ["raw", "verbose", "watch"]);
10785        }
10786
10787        #[test]
10788        fn a_re_declared_global_is_listed_once() {
10789            // The merge can leave the long key on the merged flag and the short
10790            // key on the pre-merge one. Same flag; it must not be listed twice.
10791            let spec = r#"
10792bin "test"
10793flag "-y --yes" global=#true effect="write"
10794cmd "rm" {
10795    flag "-y --yes"
10796}
10797"#
10798            .parse::<Spec>()
10799            .unwrap();
10800            let flags = available_flags(&chain(&spec, &["rm"]));
10801            assert_eq!(flags.len(), 1, "{flags:?}");
10802            assert_eq!(flags[0].effect.map(|e| e.as_str()), Some("write"));
10803        }
10804
10805        #[test]
10806        fn a_re_declared_global_keeps_the_globals_declaration() {
10807            // `run` re-declares the long-only global `--raw` as `-r --raw`
10808            // without `global`. That is the same flag: the global's `effect`
10809            // survives, the orphan short is unioned in, and it stays global.
10810            let spec = spec();
10811            let flags = available_flags(&chain(&spec, &["run"]));
10812            let raw = flags.iter().find(|f| f.name == "raw").unwrap();
10813            assert!(raw.global);
10814            assert_eq!(raw.effect.map(|e| e.as_str()), Some("write"));
10815            assert_eq!(raw.short, ['r']);
10816        }
10817
10818        #[test]
10819        fn it_matches_what_a_parse_accepts() {
10820            // The invariant. If these ever disagree, one of them is lying to a
10821            // caller about which flags a command takes.
10822            let spec = spec();
10823            for path in [vec![], vec!["run"], vec!["run", "once"]] {
10824                let argv = std::iter::once("test".to_string())
10825                    .chain(path.iter().map(|s| s.to_string()))
10826                    .collect::<Vec<_>>();
10827                let parsed = parse_partial(&spec, &argv).unwrap();
10828
10829                let mut from_parse: Vec<_> = unique_flags(parsed.available_flags.values())
10830                    .map(|f| f.name.clone())
10831                    .collect();
10832                from_parse.sort();
10833                assert_eq!(names(&spec, &path), from_parse, "path {path:?}");
10834            }
10835        }
10836    }
10837
10838    // Provenance: which token bound what, and where a value came from when no token did.
10839
10840    /// Every role a token was given, rendered the way `Debug` renders it, so a test can
10841    /// assert on the whole picture rather than on one field at a time.
10842    fn roles(parsed: &ParseOutput, index: usize) -> Vec<String> {
10843        parsed
10844            .tokens
10845            .iter()
10846            .find(|token| token.index == index)
10847            .unwrap_or_else(|| panic!("no token at {index}"))
10848            .roles
10849            .iter()
10850            .map(render_role)
10851            .collect()
10852    }
10853
10854    fn origins(parsed: &ParseOutput, flag: &str) -> Vec<ValueOrigin> {
10855        parsed
10856            .flag_origins
10857            .iter()
10858            .find(|(f, _)| f.name == flag)
10859            .map(|(_, origins)| origins.clone())
10860            .unwrap_or_default()
10861    }
10862
10863    fn explain_with_env(spec: &Spec, words: &[&str], env: &[(&str, &str)]) -> ParseOutput {
10864        let env = env
10865            .iter()
10866            .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
10867            .collect();
10868        Parser::new(spec)
10869            .with_env(env)
10870            .explain(&input(words))
10871            .unwrap()
10872    }
10873
10874    fn explain(spec: &Spec, words: &[&str]) -> ParseOutput {
10875        explain_with_env(spec, words, &[])
10876    }
10877
10878    #[test]
10879    fn an_attached_long_value_is_recorded_on_the_flag_token() {
10880        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--env <env>\"\n"
10881            .parse()
10882            .unwrap();
10883
10884        let parsed = explain(&spec, &["ex", "--env=prod"]);
10885
10886        assert_eq!(roles(&parsed, 0), ["program"]);
10887        assert_eq!(
10888            roles(&parsed, 1),
10889            ["flag env as --env", "value of env = [\"prod\"], attached"]
10890        );
10891        // This is jdx/mise discussion #8883: a hand-written scanner dropped the attached
10892        // form while the detached one worked, and nothing could show the difference.
10893        assert!(origins(&parsed, "env").is_empty(), "typed, so no fallback");
10894    }
10895
10896    #[test]
10897    fn a_detached_long_value_is_recorded_on_its_own_token() {
10898        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--env <env>\"\n"
10899            .parse()
10900            .unwrap();
10901
10902        let parsed = explain(&spec, &["ex", "--env", "prod"]);
10903
10904        assert_eq!(roles(&parsed, 1), ["flag env as --env"]);
10905        assert_eq!(roles(&parsed, 2), ["value of env = [\"prod\"]"]);
10906    }
10907
10908    #[test]
10909    fn a_short_bundle_is_attributed_to_the_bundle_token() {
10910        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"-a\"\nflag \"-b\"\nflag \"-j <n>\"\n"
10911            .parse()
10912            .unwrap();
10913
10914        let parsed = explain(&spec, &["ex", "-abj8"]);
10915
10916        // One word the caller wrote, four things it did — and the re-queued tails are
10917        // folded back onto it rather than appearing as tokens nobody typed.
10918        assert_eq!(
10919            roles(&parsed, 1),
10920            [
10921                "flag a as -a",
10922                "flag b as -b",
10923                "flag j as -j",
10924                "value of j = [\"8\"], attached",
10925            ]
10926        );
10927        assert_eq!(parsed.tokens.len(), 2);
10928    }
10929
10930    #[test]
10931    fn a_delimiter_splits_one_token_into_several_values() {
10932        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--tags <tags>...\" delimiter=\",\"\n"
10933            .parse()
10934            .unwrap();
10935
10936        let parsed = explain(&spec, &["ex", "--tags", "a,b,c"]);
10937
10938        assert_eq!(
10939            roles(&parsed, 2),
10940            ["value of tags = [\"a\", \"b\", \"c\"]"],
10941            "the values meant, not the word typed"
10942        );
10943    }
10944
10945    #[test]
10946    fn a_separator_and_the_words_after_it_are_distinguished() {
10947        let spec: Spec = "name \"ex\"\nbin \"ex\"\narg \"<src>\"\narg \"[raw]...\"\n"
10948            .parse()
10949            .unwrap();
10950
10951        let parsed = explain(&spec, &["ex", "a", "--", "-x"]);
10952
10953        assert_eq!(roles(&parsed, 1), ["arg src = [\"a\"]"]);
10954        assert_eq!(roles(&parsed, 2), ["separator"]);
10955        // Past the separator `-x` is data, not an unknown flag.
10956        assert_eq!(roles(&parsed, 3), ["arg raw = [\"-x\"]"]);
10957    }
10958
10959    #[test]
10960    fn a_second_separator_is_data() {
10961        let spec: Spec = "name \"ex\"\nbin \"ex\"\narg \"[raw]...\"\n"
10962            .parse()
10963            .unwrap();
10964
10965        let parsed = explain(&spec, &["ex", "--", "a", "--", "b"]);
10966
10967        assert_eq!(roles(&parsed, 1), ["separator"]);
10968        assert_eq!(roles(&parsed, 3), ["arg raw = [\"--\"]"]);
10969    }
10970
10971    #[test]
10972    fn an_unknown_flag_says_what_took_it() {
10973        let spec: Spec = "name \"ex\"\nbin \"ex\"\narg \"[rest]...\"\n"
10974            .parse()
10975            .unwrap();
10976
10977        let parsed = explain(&spec, &["ex", "--wat"]);
10978
10979        // The default is lax, so the word became data. Which is the useful thing to be
10980        // told: the alternative reading is "you have a typo".
10981        assert_eq!(roles(&parsed, 1), ["unknown flag, bound as rest"]);
10982    }
10983
10984    #[test]
10985    fn a_subcommand_word_is_not_a_positional() {
10986        let spec: Spec = "name \"ex\"\nbin \"ex\"\ncmd \"build\" {\n    arg \"<target>\"\n}\n"
10987            .parse()
10988            .unwrap();
10989
10990        let parsed = explain(&spec, &["ex", "build", "a"]);
10991
10992        assert_eq!(roles(&parsed, 1), ["subcommand build"]);
10993        assert_eq!(roles(&parsed, 2), ["arg target = [\"a\"]"]);
10994    }
10995
10996    #[test]
10997    fn a_multicall_applet_is_read_at_argv0() {
10998        let spec: Spec =
10999            "name \"box\"\nbin \"box\"\nmulticall #true\ncmd \"ls\" {\n    flag \"-l\"\n}\n"
11000                .parse()
11001                .unwrap();
11002
11003        let parsed = explain(&spec, &["/usr/bin/ls", "-l"]);
11004
11005        // argv[0] is both the program and the word that selected the applet, and the word
11006        // read there is not the word the caller wrote.
11007        assert_eq!(roles(&parsed, 0), ["program", "subcommand ls"]);
11008        assert!(parsed.tokens[0].synthesized);
11009        assert_eq!(parsed.tokens[0].word, "/usr/bin/ls");
11010    }
11011
11012    #[test]
11013    fn words_the_parse_never_reached_say_so() {
11014        let spec: Spec = "name \"ex\"\nbin \"ex\"\narg \"[rest]...\"\n"
11015            .parse()
11016            .unwrap();
11017
11018        let parsed = Parser::new(&spec)
11019            .explain(&input(&["ex", "--help", "a"]))
11020            .unwrap();
11021
11022        assert_eq!(roles(&parsed, 2), ["unread"]);
11023    }
11024
11025    #[test]
11026    fn an_env_origin_names_the_variable_that_fired() {
11027        let spec: Spec =
11028            "name \"ex\"\nbin \"ex\"\nflag \"--token <t>\" env=\"EX_TOKEN\" env_fallback=\"EX_TOKEN_OLD\"\n"
11029                .parse()
11030                .unwrap();
11031
11032        let primary = explain_with_env(&spec, &["ex"], &[("EX_TOKEN", "a")]);
11033        assert_eq!(
11034            origins(&primary, "token"),
11035            [ValueOrigin::Env("EX_TOKEN".to_string())]
11036        );
11037
11038        // The fallback firing is a different fact from the primary firing, and which one it
11039        // was is what says which declaration to delete.
11040        let fallback = explain_with_env(&spec, &["ex"], &[("EX_TOKEN_OLD", "b")]);
11041        assert_eq!(
11042            origins(&fallback, "token"),
11043            [ValueOrigin::Env("EX_TOKEN_OLD".to_string())]
11044        );
11045    }
11046
11047    #[test]
11048    fn a_default_origin_is_recorded_for_flags_and_args() {
11049        let spec: Spec =
11050            "name \"ex\"\nbin \"ex\"\nflag \"--color <when>\" default=\"auto\"\narg \"[src]\" default=\".\"\n"
11051                .parse()
11052                .unwrap();
11053
11054        let parsed = explain(&spec, &["ex"]);
11055
11056        assert_eq!(origins(&parsed, "color"), [ValueOrigin::Default]);
11057        let (arg, origins) = parsed.arg_origins.iter().next().unwrap();
11058        assert_eq!(arg.name, "src");
11059        assert_eq!(origins, &[ValueOrigin::Default]);
11060    }
11061
11062    #[test]
11063    fn a_default_if_origin_carries_the_condition_that_fired() {
11064        let spec: Spec = r#"
11065name "ex"
11066bin "ex"
11067flag "--profile <p>"
11068flag "--strict" {
11069    default_if "--profile" "prod" "true"
11070}
11071        "#
11072        .parse()
11073        .unwrap();
11074
11075        let parsed = explain(&spec, &["ex", "--profile", "prod"]);
11076
11077        // The selector alone is ambiguous: several conditions may name it with different
11078        // `when` values, so the report has to say which one matched.
11079        assert_eq!(
11080            origins(&parsed, "strict"),
11081            [ValueOrigin::DefaultIf {
11082                selector: "--profile".to_string(),
11083                when: Some("prod".to_string()),
11084            }]
11085        );
11086    }
11087
11088    #[test]
11089    fn a_bare_optional_value_flag_records_default_missing() {
11090        let spec: Spec =
11091            "name \"ex\"\nbin \"ex\"\nflag \"--color <when>\" default_missing=\"always\"\nflag \"-v\"\n"
11092                .parse()
11093                .unwrap();
11094
11095        let parsed = explain(&spec, &["ex", "--color", "-v"]);
11096
11097        // The flag was typed and the value was not, which is the distinction a spec author
11098        // is asking about when they ask why `--color` came out `always`.
11099        assert_eq!(roles(&parsed, 1), ["flag color as --color"]);
11100        assert_eq!(origins(&parsed, "color"), [ValueOrigin::DefaultMissing]);
11101        assert_eq!(roles(&parsed, 2), ["flag v as -v"]);
11102    }
11103
11104    #[test]
11105    fn a_var_flag_can_take_one_value_from_argv_and_one_from_default_missing() {
11106        let spec: Spec =
11107            "name \"ex\"\nbin \"ex\"\nflag \"--color <when>\" var=#true default_missing=\"always\"\n"
11108                .parse()
11109                .unwrap();
11110
11111        let parsed = explain(&spec, &["ex", "--color=red", "--color"]);
11112
11113        // Why origins are a list: one declaration, two occurrences, two different answers.
11114        assert_eq!(
11115            roles(&parsed, 1),
11116            [
11117                "flag color as --color",
11118                "value of color = [\"red\"], attached"
11119            ]
11120        );
11121        assert_eq!(origins(&parsed, "color"), [ValueOrigin::DefaultMissing]);
11122    }
11123
11124    #[test]
11125    fn an_override_names_the_flag_that_did_it() {
11126        let spec: Spec =
11127            "name \"ex\"\nbin \"ex\"\nflag \"--quiet\" default=\"true\"\nflag \"--loud\" overrides=\"--quiet\"\n"
11128                .parse()
11129                .unwrap();
11130
11131        let parsed = explain(&spec, &["ex", "--loud"]);
11132
11133        // Without the overriding name, "`--quiet` is unset despite its default" has no
11134        // answer: the fallback phase silently declines to fill an overridden flag.
11135        assert_eq!(parsed.overridden_flags.get("quiet").unwrap(), "loud");
11136        assert!(origins(&parsed, "quiet").is_empty());
11137    }
11138
11139    #[test]
11140    fn a_restart_token_leaves_the_tokens_and_clears_the_arg_origins() {
11141        let spec: Spec = r#"
11142name "ex"
11143bin "ex"
11144cmd "run" restart_token=":::" {
11145    arg "<task>" default="build"
11146}
11147        "#
11148        .parse()
11149        .unwrap();
11150
11151        let parsed = explain(&spec, &["ex", "run", "lint", ":::", "test"]);
11152
11153        // The values belong to the last invocation, so provenance must too — but the words
11154        // of the first were still read, and a report that dropped them would show a command
11155        // line with a hole in it.
11156        assert_eq!(roles(&parsed, 2), ["arg task = [\"lint\"]"]);
11157        // And the token that did the resetting says so: without a role of its own it reads
11158        // as a word that did nothing, next to a `lint` that filled an arg now empty.
11159        assert_eq!(roles(&parsed, 3), ["restart"]);
11160        assert_eq!(roles(&parsed, 4), ["arg task = [\"test\"]"]);
11161        assert!(parsed.arg_origins.is_empty());
11162    }
11163
11164    #[test]
11165    fn a_value_terminator_says_which_run_it_ended() {
11166        let spec: Spec = r#"
11167name "ex"
11168bin "ex"
11169flag "--exec <cmd>..." value_terminator=";"
11170arg "<src>"
11171        "#
11172        .parse()
11173        .unwrap();
11174
11175        let parsed = explain(&spec, &["ex", "--exec", "rm", "tmp", ";", "a"]);
11176
11177        assert_eq!(roles(&parsed, 3), ["value of exec = [\"tmp\"]"]);
11178        // The terminator is consumed and is not one of the values, which is the whole reason
11179        // it was declared — so it needs a row saying that rather than an empty one.
11180        assert_eq!(roles(&parsed, 4), ["value terminator, ends exec"]);
11181        assert_eq!(roles(&parsed, 5), ["arg src = [\"a\"]"]);
11182    }
11183
11184    #[test]
11185    fn an_args_value_terminator_says_which_run_it_ended() {
11186        let spec: Spec = r#"
11187name "ex"
11188bin "ex"
11189arg "<files>..." value_terminator=";"
11190arg "[dest]"
11191        "#
11192        .parse()
11193        .unwrap();
11194
11195        let parsed = explain(&spec, &["ex", "a", "b", ";", "out"]);
11196
11197        assert_eq!(roles(&parsed, 2), ["arg files = [\"b\"]"]);
11198        assert_eq!(roles(&parsed, 3), ["value terminator, ends files"]);
11199        assert_eq!(roles(&parsed, 4), ["arg dest = [\"out\"]"]);
11200    }
11201
11202    #[test]
11203    fn explain_keeps_the_bindings_of_a_command_line_that_fails() {
11204        let spec: Spec = "name \"ex\"\nbin \"ex\"\nflag \"--env <env>\"\narg \"<src>\"\n"
11205            .parse()
11206            .unwrap();
11207
11208        let parsed = Parser::new(&spec)
11209            .explain(&input(&["ex", "--env=prod"]))
11210            .unwrap();
11211
11212        // `parse` reports "missing required <src>" and nothing else, which is the report the
11213        // caller already had. This is the case the whole thing exists for.
11214        assert!(Parser::new(&spec)
11215            .parse(&input(&["ex", "--env=prod"]))
11216            .is_err());
11217        assert_eq!(
11218            roles(&parsed, 1),
11219            ["flag env as --env", "value of env = [\"prod\"], attached"]
11220        );
11221        assert!(
11222            parsed.errors.iter().any(|e| e.to_string().contains("src")),
11223            "{:?}",
11224            parsed.errors
11225        );
11226    }
11227
11228    #[test]
11229    fn an_external_subcommand_forwards_whole_tokens() {
11230        let spec: Spec = "name \"ex\"\nbin \"ex\"\nexternal_subcommand #true\ncmd \"build\"\n"
11231            .parse()
11232            .unwrap();
11233
11234        let parsed = explain(&spec, &["ex", "deploy", "--now"]);
11235
11236        assert_eq!(roles(&parsed, 1), ["external"]);
11237        assert_eq!(roles(&parsed, 2), ["external"]);
11238    }
11239
11240    #[test]
11241    fn a_view_keeps_the_callers_argv_positions() {
11242        let spec: Spec = r#"
11243bin "ex"
11244view "runner" root="run"
11245cmd "run" {
11246    flag "--token <token>"
11247}
11248        "#
11249        .parse()
11250        .unwrap();
11251
11252        let parsed = explain(&spec, &["runner", "--token", "secret"]);
11253
11254        // A view re-enters the parse with the same argv, so the positions still mean what
11255        // the caller wrote.
11256        assert_eq!(roles(&parsed, 0), ["program"]);
11257        assert_eq!(roles(&parsed, 2), ["value of token = [\"secret\"]"]);
11258    }
11259}