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