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