//! A zero-allocation argv parser for [usage](https://usage.jdx.dev) specs. //! //! This crate implements the binding rules of [the argv grammar]: which token //! becomes which flag or argument, when a word selects a subcommand, and what //! is an error. It does so without building a command tree, without allocating, //! and in one pass. //! //! It is the runtime half of a compiled parser. The tables it reads are meant to //! be emitted by a derive macro as `static` data, so that starting a parse costs //! nothing at all: there is no construction step to pay for, only the walk over //! `argv`. //! //! # Shape of the API //! //! Parsing yields [`Event`]s rather than a map. A map would have to allocate, //! and would then have to be read back out again — whereas generated code can //! assign an event straight into a struct field. This is the same reason serde //! deserializes into your type instead of into a `Value`. //! //! ``` //! use usage_argv::{Arg, Command, Event, Flag, Parser}; //! //! static FORCE: Flag = Flag { key: 0, longs: &["force"], shorts: b"f", ..Flag::BOOL }; //! static FILE: Arg = Arg { key: 1, ..Arg::REQUIRED }; //! static ROOT: Command = Command { //! name: "ex", //! flags: &[&FORCE], //! args: &[&FILE], //! ..Command::EMPTY //! }; //! //! let argv = ["--force", "a.txt"].map(std::ffi::OsStr::new); //! let mut parser = Parser::new(&ROOT, &argv); //! //! let mut force = false; //! let mut file = None; //! while let Some(event) = parser.next_event() { //! match event.expect("valid command line") { //! Event::Flag { flag, .. } if flag.key == 0 => force = true, //! Event::Arg { value, .. } => file = Some(value), //! _ => {} //! } //! } //! assert!(force); //! assert_eq!(file, Some(&b"a.txt"[..])); //! ``` //! //! # Values are bytes //! //! An [`Event`] carries `&[u8]`, borrowed from `argv`. Converting to `&str` is //! the caller's step ([`as_str`]), and it is the right place for the only //! failure a value can have: a command line that is not valid UTF-8 still //! *parses* — flags match, subcommands route — and only the values that are //! actually looked at can fail to convert. //! //! Slicing an `OsStr` into `&str` pieces safely is not possible without //! allocating or `unsafe`. Bytes are what is left, and they turn out to be the //! honest interface anyway. //! //! The reverse conversion is [`os_string_from_bytes`], which lets a `PathBuf` //! field hold a filename that is not UTF-8 rather than a mangled copy of one. On //! Unix that is lossless and safe; on Windows, where WTF-8 makes it partial, a //! value that will not convert is reported. Either way this crate contains no //! `unsafe`, which a conversion that guessed would have cost. //! //! # What this crate does not do //! //! Only binding. Required-ness, `choices`, `env` fallback, defaults, `var_min` //! and `var_max` are all decided *after* the last token is read, and they need to //! know a value's type, so they belong to the layer that owns the target struct. //! Keeping them out is what makes this loop small. //! //! # Features //! //! - `spec` — a parallel tree of cold metadata (help text, choices, defaults, //! effects) and a writer that emits it as a usage spec. Off by default: a //! successful parse never reads any of it, so a CLI that only wants a parser //! should not compile it. //! - `complete` — answering a partial command line ([`complete`]), the shell //! scripts that ask ([`script`]), and putting one of those scripts where its //! shell will look for it ([`install`]). Installing ships with the scripts //! rather than behind a gate of its own: a script a CLI still has to tell its //! users to redirect by hand is the unfinished half of shipping one. //! //! [the argv grammar]: https://usage.jdx.dev/spec/argv // One module opts out: `tty`, which asks the kernel how wide the terminal is. `forbid` would // make that opt-out impossible, and the alternative to the opt-out is a dependency — this // crate has none, and a CLI that wraps its help at 80 columns on a wide terminal is worse // than two dozen lines of reviewed FFI. Everything else in the crate is still refused. #![deny(unsafe_code)] /// Terminate at the compiled CLI entry-point boundary. /// /// Kept in the runtime rather than expanded into an adopter crate so a project that /// forbids direct `std::process::exit` calls does not attribute the derive's process /// boundary to application code. `Cli::parse_from*` continues to return errors. #[doc(hidden)] #[allow(clippy::disallowed_methods)] pub fn __usage_process_exit(status: i32) -> ! { std::process::exit(status) } /// Print a generated entry point's output to stdout, ignoring a failed write. /// /// `print!` panics when the write fails, and the usual failure is a reader that has gone /// away: `cli --help | head -1`, a pager quit early, or a completion the shell cancelled while /// typing. Every caller is about to leave (or has finished a successful parse), so there is /// nobody left to tell, and a panic only turns that into a crash — an abort with a core dump /// in a `panic = "abort"` build. The exit status stays the one the caller chose. #[doc(hidden)] pub fn __usage_print(output: std::fmt::Arguments<'_>) { use std::io::Write; let mut stdout = std::io::stdout().lock(); let _ = stdout.write_fmt(output).and_then(|()| stdout.flush()); } /// The process's arguments, argv[0] included. /// /// Out of line so each derived entry point calls it rather than carrying its own copy of /// the collecting loop. #[doc(hidden)] pub fn __usage_process_args() -> Vec { let args = std::env::args_os(); let mut all = Vec::with_capacity(args.len()); for arg in args { all.push(arg); } all } /// Borrow every word of `words`, for a parse that takes `&[&OsStr]`. #[doc(hidden)] pub fn __usage_borrow_words(words: &[OsString]) -> Vec<&OsStr> { let mut refs = Vec::with_capacity(words.len()); for word in words { refs.push(word.as_os_str()); } refs } /// Own every word of `words`, behind a `prefix` of whitespace-separated words, the shape a /// view or multicall rewrite hands the parser. #[doc(hidden)] pub fn __usage_own_words(prefix: &str, words: &[&OsStr]) -> Vec { let mut owned = Vec::with_capacity(words.len()); for word in prefix.split_ascii_whitespace() { owned.push(OsString::from(word)); } for word in words { owned.push((*word).to_os_string()); } owned } /// Build a command's future on the heap, for a generated async dispatch to await. /// /// The future is created inside this function, not by the caller, and that is the point: /// without optimizations every temporary in an `async fn` gets its own stack slot, held for /// as long as the future runs, so a dispatch that wrote `run_async(inner).await` in each arm /// kept a slot the size of every command's future on the stack while running any one of them. /// Created here, the future only passes through this function's frame, and the dispatch keeps /// a `Box` per arm instead. `Send` still follows the command's future, since a box of it is /// `Send` exactly when it is. #[doc(hidden)] pub fn __usage_box_future(make: F) -> std::pin::Pin> where F: FnOnce() -> Fut, Fut: std::future::Future, { Box::pin(make()) } /// [`__usage_print`] for stderr: help and failures that go there, and deprecation warnings. #[doc(hidden)] pub fn __usage_eprint(output: std::fmt::Arguments<'_>) { use std::io::Write; let _ = std::io::stderr().lock().write_fmt(output); } use std::ffi::{OsStr, OsString}; /// A value's shell-native completion class for `#[usage(value_hint = ...)]`. /// /// This lives in the runtime crate so a declaration never needs clap merely to describe what /// kind of path a shell should offer. It is metadata only and adds no work to a successful /// parse. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ValueHint { /// Let the shell use its normal fallback behavior. Unknown, /// No structured hint applies; suppress the shell's path fallback. Other, /// A path to a file. FilePath, /// A path to either a file or a directory. AnyPath, /// A path to a directory. DirPath, /// A path to an executable file. ExecutablePath, /// A command name, resolved through the shell's command table and `PATH`. CommandName, /// One string containing a command and any arguments. CommandString, /// A trailing argv vector: complete the first value as a command, then its arguments. CommandWithArguments, /// A local operating-system user name. Username, /// A host name known to the shell or operating system. Hostname, /// A web address. This suppresses path fallback but offers no finite candidate set. Url, /// An email address. This suppresses path fallback but offers no finite candidate set. EmailAddress, } #[cfg(feature = "complete")] pub mod complete; #[cfg(feature = "diagnostics")] pub mod diagnostic; #[cfg(feature = "spec")] pub mod embedded; #[cfg(feature = "spec")] pub mod env; #[cfg(feature = "complete")] pub mod install; #[cfg(feature = "complete")] pub mod script; /// Checks that the `complete` feature is on, with an explanation when it is not. /// /// `#[usage(completion)]` generates code that reaches into [`complete`], which is behind a /// feature the *depending* crate enables — a derive cannot turn on a feature of another crate. /// Without this, the failure was `unresolved module complete`, which says nothing about the /// attribute that caused it. #[cfg(feature = "complete")] #[macro_export] macro_rules! __usage_needs_complete_feature { () => {}; } /// See [`__usage_needs_complete_feature`]. #[cfg(not(feature = "complete"))] #[macro_export] macro_rules! __usage_needs_complete_feature { () => { ::core::compile_error!( "`#[usage(completion)]` needs usage-argv's `complete` feature. Add it where \ usage-argv is depended on: usage-argv = { version = \"…\", features = \ [\"spec\", \"complete\"] }" ); }; } #[cfg(feature = "spec")] pub mod help; #[cfg(feature = "spec")] mod order; mod slice; #[cfg(feature = "spec")] mod text; // Behind no feature: two traits and no code, so there is nothing here for a binary that // does not dispatch to pay for, and a hand-written CLI on the bare runtime can use them. pub mod run; // Behind no feature either, and public: an application that lays out its own output beside a // help page — a table, a progress line — should not have to work the width out a second way // and disagree with the page it sits next to. #[cfg(feature = "spec")] pub mod spec; pub mod tty; #[cfg(feature = "spec")] pub use spec::{parse_args_from, parse_args_from_argv}; #[cfg(feature = "spec")] pub mod warn; pub use run::{Run, RunAsync, RunAsyncWith, RunWith}; /// How deep a command tree this parser will descend. /// /// The ancestor chain is kept in a fixed-size array so that a parse allocates /// nothing; this is that array's size. mise, the largest usage CLI, is four /// levels deep. pub const MAX_DEPTH: usize = 16; /// A command: its flags, its positional arguments, and its subcommands. /// /// Every field is a borrowed slice so that a derive can emit the whole tree as /// `static` data. Use `..Command::EMPTY` to fill in the parts you do not need. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Command<'a> { /// The canonical name, used to select this command. pub name: &'a str, /// Alternative names that also select it. pub aliases: &'a [&'a str], pub flags: &'a [&'a Flag<'a>], /// Positional arguments, in the order they are filled. pub args: &'a [&'a Arg<'a>], /// A repeatable group of scoped flags and positional arguments, if this command has one. /// /// Behind a reference because hardly any command has one: held inline, the clause's five /// slices were two thirds of every command's table whether it declared one or not. pub clause: ::core::option::Option<&'a Clause<'a>>, pub subcommands: &'a [&'a Command<'a>], /// Where a word goes when it names no subcommand of this one. /// /// The spec's `default_subcommand`. `mise build` means `mise run build`: the word names /// no command, so the parser descends into `run` and lets *`run`* have it — even where /// this command declares an argument of its own, which is what makes the property worth /// having rather than a synonym for a positional. /// /// Applied at most once per parse, so a CLI cannot loop through it, and only where a /// subcommand could still be selected. /// /// Resolve it with [`find_subcommand`], which turns a name that no subcommand answers to /// into a compile error. pub default_subcommand: ::core::option::Option<&'a Command<'a>>, /// Look ahead past parent/default flags before implicitly selecting the default. /// A sibling name before any default-only flag stays on the parent; after one, /// later words are the default command's args. pub default_subcommand_flags: bool, /// Select the default subcommand for an otherwise empty successful invocation. pub default_subcommand_on_empty: bool, /// Whether an unmatched word is forwarded as an external command plus the rest of argv. /// /// clap's `allow_external_subcommands`. Known subcommands still win; a /// [`default_subcommand`](Self::default_subcommand) still catches first. Once the /// unmatched word is taken, remaining tokens — including `--help` — are not parsed /// as this command's flags. pub external_subcommand: bool, /// Show this command's help when no argv token follows its name. /// /// This is clap's `arg_required_else_help`. It deliberately observes argv rather than /// bound values: an environment variable or default may fill a field, but neither means /// the user supplied an argument to this invocation. pub arg_required_else_help: bool, /// Selecting a subcommand suppresses this command's required arguments. pub subcommand_negates_reqs: bool, /// Once this command binds a flag or positional, selecting one of its /// subcommands is an error. pub args_conflicts_with_subcommands: bool, /// Let a known subcommand interrupt a variadic argument that would otherwise consume it. pub subcommand_precedence_over_arg: bool, /// Let a later required positional take a word while an earlier optional positional /// remains empty. pub allow_missing_positional: bool, /// Disable delimiter splitting for positional values after `--` or on an /// automatic trailing argument. Inherited by subcommands. pub dont_delimit_trailing_values: bool, /// What an unrecognized flag-like token means here, or `None` to keep whatever the /// enclosing command said. See [`UnknownFlags`]. /// /// Inherited rather than resolved per command, which is what usage-lib does — its /// `effective_unknown_flags` walks outward from the command that ran and falls back to /// the spec's. Resolving it in the tables instead was possible only for a builder that /// can see the whole tree: a derive expands one struct at a time and cannot see its /// parent, so `#[usage(unknown_flags = "error")]` on the root reached the root alone and /// a subcommand had no way to say it at all. /// /// The parser carries the effective value down as it descends, so a command that states /// nothing costs nothing. pub unknown_flags: ::core::option::Option, /// Whether this command answers to `--version` and `-V`. /// /// Set on the root, and only when the CLI declares a version: clap adds the flag exactly /// then, and a `--version` that answers with nothing is worse than one that is not there. /// A field rather than a rule about depth, so a CLI that wants it on a subcommand — clap's /// `propagate_version` — has somewhere to say so. pub version: bool, /// Do not synthesize `--help` and `-h` for this command. pub disable_help_flag: bool, /// Do not synthesize the `help` subcommand route for this command. pub disable_help_subcommand: bool, /// Do not synthesize `--version` and `-V` for this command. pub disable_version_flag: bool, /// Caller-assigned identifier, echoed back in [`Event::Command`]. /// /// Wide enough for a derive to make these unique without coordination: two /// macro expansions cannot see each other, so the generated keys carry a hash /// of the type they came from in the high half and a per-type index in the low /// half. A parse dispatches on this, so a collision would bind the wrong field /// — [`Spec::to_kdl`](crate::spec::Spec::to_kdl) checks the tree for duplicates /// in debug builds. pub key: u64, } impl Command<'_> { /// A command with nothing declared, for use with struct update syntax. pub const EMPTY: Command<'static> = Command { name: "", aliases: &[], flags: &[], args: &[], clause: ::core::option::Option::None, subcommands: &[], default_subcommand: ::core::option::Option::None, default_subcommand_flags: false, default_subcommand_on_empty: false, external_subcommand: false, arg_required_else_help: false, subcommand_negates_reqs: false, args_conflicts_with_subcommands: false, subcommand_precedence_over_arg: false, allow_missing_positional: false, dont_delimit_trailing_values: false, unknown_flags: ::core::option::Option::None, version: false, disable_help_flag: false, disable_help_subcommand: false, disable_version_flag: false, key: 0, }; } /// A repeatable group of scoped flags and positional arguments. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Clause<'a> { pub key: u64, pub name: &'a str, /// An explicit token that starts the next instance. When absent, consuming the /// clause's sole required positional completes the current instance. pub separator: ::core::option::Option<&'a [u8]>, pub flags: &'a [&'a Flag<'a>], pub args: &'a [&'a Arg<'a>], } /// Basename of argv[0] for a multicall CLI: last path component, with a trailing /// `.exe` stripped so Windows and Unix agree. pub fn multicall_basename(argv0: &str) -> &str { let name = argv0.rsplit(['/', '\\']).next().unwrap_or(argv0); match name.get(name.len().saturating_sub(4)..) { Some(ext) if ext.eq_ignore_ascii_case(".exe") => &name[..name.len() - 4], _ => name, } } /// The applet name to parse as the first word, when argv[0] is not the dispatcher. /// /// `None` means a dispatcher invocation (`busybox ls`): skip argv[0] and parse the /// rest. `Some` is a symlink invocation (`ls -l`): inject the basename. pub fn multicall_applet<'a>(argv0: &'a str, name: &str, bin: Option<&str>) -> Option<&'a str> { let base = multicall_basename(argv0); if !name.is_empty() && base == multicall_basename(name) { return None; } if let Some(bin) = bin && !bin.is_empty() && base == multicall_basename(bin) { return None; } Some(base) } /// Resolved identity of a derive-generated binding type. #[derive(Clone, Copy)] pub struct BindingType(pub fn() -> &'static str); impl BindingType { pub fn name(self) -> &'static str { (self.0)() } } impl ::core::fmt::Debug for BindingType { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.debug_tuple("BindingType").field(&self.name()).finish() } } impl PartialEq for BindingType { fn eq(&self, other: &Self) -> bool { self.name() == other.name() } } impl Eq for BindingType {} /// A flag, addressed by any of its long or short forms. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Flag<'a> { /// Caller-assigned identifier, echoed back in [`Event::Flag`]. This is how /// generated code knows which field to assign without any string comparison. /// See [`Command::key`] on why it is this wide. pub key: u64, /// Compatibility key for mirroring a redeclared child global into an ancestor field. /// /// Zero means no typed binding contract is declared. Derive-generated tables hash the /// binding shape and portable metadata so only equivalent bindings receive the same event. pub binding_key: u64, /// Resolved Rust value type for a derive-generated binding. /// /// This is separate from [`Self::binding_key`] because token spellings are not type /// identities: an imported alias and a fully qualified path can name the same type. pub binding_type: Option, /// Unused by binding, kept so a table entry can carry its own name for /// diagnostics. pub name: &'a str, /// Long forms, written without the leading `--`. pub longs: &'a [&'a str], /// Short forms, as single bytes. /// /// **Should be ASCII.** A cluster like `-xyz` is walked one byte at a time, so a /// non-ASCII short can never be matched, and the remainder after a value-taking one — /// which becomes its value — would begin in the middle of a character. /// `#[derive(Cli)]` rejects a non-ASCII `short`; a table written by hand should keep to /// it. Nothing is unsound if it does not: the value would simply be cut in a place that /// makes no sense, and on Windows would then fail to convert. pub shorts: &'a [u8], /// A long form that sets the flag to false, written without the `--`. pub negate: Option<&'a str>, /// Whether the flag takes a value. pub takes_value: bool, /// Whether one occurrence of this flag keeps taking values, until a flag-like /// token or the end of the command line. /// /// This is the spec's variadic flag *argument* (`--include ...`). It /// is not the spec's flag-level `var=#true`, which means the flag may be /// repeated and takes one value each time — repetition needs nothing from the /// parser, since it already reports every occurrence separately. Conflating /// the two makes a merely repeatable flag greedy enough to eat a positional. pub variadic: bool, /// How many values one variadic occurrence may take, after which the next word /// belongs to whatever comes next. /// /// Only for [`variadic`](Self::variadic). A merely repeatable flag — the spec's /// `var=#true` — is bounded on how many times it was *given*, which no single token /// can decide, so that bound stays with the metadata and is checked after the parse. pub var_max: ::core::option::Option, /// The byte that makes one word several values, if the flag declares one. /// /// Here rather than with the metadata for the same reason [`var_max`](Self::var_max) /// is: it decides *where* a word lands. A bound counts values, and a delimiter is what /// makes a word stop being one of them — `--include a,b,c` is three, so a `var_max` of /// two is already past its bound on the single word it was entitled to take. Binding /// cannot count without it. pub delimiter: ::core::option::Option, /// Whether a detached value may itself look like a flag. /// /// The default is to refuse: `--jobs --force` is far more likely a forgotten /// value than a jobs of `"--force"`. Declared, the next token is taken /// whatever it looks like — including `--` — which is clap's /// `allow_hyphen_values` and the spec's property of the same name. A variadic /// occurrence still stops collecting at a later flag-like token, so a second /// occurrence of the flag is not eaten as a value. pub allow_hyphen_values: bool, /// Whether a detached value may be a negative number while other flag-like /// tokens still stop collection or report as flags. pub allow_negative_numbers: bool, /// A token that ends one variadic occurrence without becoming a value. pub value_terminator: ::core::option::Option<&'a [u8]>, /// Whether the value must be attached with `=`. /// /// `--flag=value` is accepted and `--flag value` is not, which is clap's /// `require_equals` and the spec's property of the same name. A short's /// attached form (`-i9229`, `-i=9229`) still binds: only the following word /// is refused. pub require_equals: bool, /// Whether this value-taking flag may be present without a value. /// /// A missing value emits the flag event with `value: None`; bindings such as /// `Option>` can therefore distinguish an absent flag from a bare /// flag and from a flag with an explicit value. pub value_optional: bool, /// Whether a boolean long flag accepts an attached `true` or `false` value. /// /// This does not make the flag value-taking in the ordinary sense: a detached /// word is never consumed, and help keeps rendering a switch. Only /// `--flag=true` and `--flag=false` opt into an explicit boolean value. pub bool_value: bool, /// Value used when the flag is present but no value is given. /// /// clap's `default_missing_value` and the spec's `default_missing`. `--color` /// binds this, `--color=never` binds `never`, and an absent flag is not bound. /// Combined with [`Self::require_equals`], a following word is still refused. pub default_missing: ::core::option::Option<&'a [u8]>, /// Whether the flag is recognized by every command beneath the one that /// declares it. pub global: bool, /// Whether this declared flag binds a field or requests a built-in response. pub action: ArgAction, } impl Flag<'_> { /// A value-less flag, for use with struct update syntax. pub const BOOL: Flag<'static> = Flag { key: 0, binding_key: 0, binding_type: None, name: "", longs: &[], shorts: &[], negate: None, takes_value: false, variadic: false, var_max: ::core::option::Option::None, delimiter: ::core::option::Option::None, allow_hyphen_values: false, allow_negative_numbers: false, value_terminator: ::core::option::Option::None, require_equals: false, value_optional: false, bool_value: false, default_missing: ::core::option::Option::None, global: false, action: ArgAction::Set, }; /// A flag that takes a value, for use with struct update syntax. pub const VALUE: Flag<'static> = Flag { takes_value: true, ..Flag::BOOL }; } /// What supplying a declared flag does. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum ArgAction { /// Bind the flag to its declared field. #[default] Set, /// Show help, choosing the long form for a long spelling and the short form otherwise. Help, /// Always show short help. HelpShort, /// Always show long help. HelpLong, /// Show long help for this command and every visible descendant. HelpAll, /// Show version information. Version, } /// A positional argument. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Arg<'a> { /// Caller-assigned identifier, echoed back in [`Event::Arg`]. See /// [`Command::key`] on why it is this wide. pub key: u64, /// Prefix that classifies this positional independently of declaration order. /// The prefix is removed from the value emitted in [`Event::Arg`]. pub sigil: ::core::option::Option<&'a [u8]>, /// Whether post-binding requires this positional to have a value. Kept in the hot /// table because `allow_missing_positional` must reserve words for later required args. pub required: bool, /// Whether this argument keeps taking values once it has one. pub var: bool, /// How many words a variadic may take before the next argument gets the rest. /// /// A bound belongs here, in the table binding reads, rather than with the metadata: /// it decides *where* a word lands, not whether what landed is acceptable. clap's /// `num_args` works the same way, and every spec in the wild is generated from a clap /// command. `u32` rather than `usize` because a CLI that bounds a variadic above four /// billion has other problems, and this table is read on the hot path. pub var_max: ::core::option::Option, /// The byte that makes one word several values, if the argument declares one. /// /// See [`Flag::delimiter`]: a bound counts values, and only this says how many values a /// word carries. pub delimiter: ::core::option::Option, /// Whether a negative-number token is accepted as this positional even in /// strict flag mode. pub allow_negative_numbers: bool, /// A token that ends this variadic positional without becoming a value. pub value_terminator: ::core::option::Option<&'a [u8]>, /// This argument's relationship to the `--` separator. pub double_dash: DoubleDash, /// Unused by binding, kept so a table entry can carry its own name for /// diagnostics. pub name: &'a str, } impl Arg<'_> { /// A single-value argument, for use with struct update syntax. pub const REQUIRED: Arg<'static> = Arg { key: 0, sigil: ::core::option::Option::None, required: true, var: false, var_max: ::core::option::Option::None, delimiter: ::core::option::Option::None, allow_negative_numbers: false, value_terminator: ::core::option::Option::None, double_dash: DoubleDash::Optional, name: "", }; /// A variadic argument, for use with struct update syntax. pub const VAR: Arg<'static> = Arg { var: true, ..Arg::REQUIRED }; } /// What to do with a flag-like token that names no flag in scope. /// /// The default is [`UnknownFlags::Value`]: the token carries on to the positional /// arguments, because a spec is often parsing a command line whose flags belong to /// something else — a wrapped tool, a task script. A CLI that owns all of its /// flags declares [`UnknownFlags::Error`] and gets typo detection instead. /// /// Stored per command and already resolved: inheritance is a question for whoever /// builds the tables, and answering it at compile time keeps it out of the parse. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum UnknownFlags { /// Offer the token to the positionals. If none can take it, it is an /// unexpected argument. #[default] Value, /// Reject the token. Error, } /// How an argument relates to the `--` separator. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum DoubleDash { /// Values may appear on either side of a `--`. #[default] Optional, /// Values are accepted only after a `--`. Required, /// A `--` is kept as a value rather than consumed as a separator. Preserve, /// Once the argument takes a value, behave as if a `--` had been given, so /// the rest of the command line is values. A wrapper can then forward flags /// without its caller typing the separator. Automatic, } /// Something the parser bound. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Event<'t, 'a, 'v> { /// A subcommand was selected; parsing continues inside it. Command(&'t Command<'t>), /// A flag was given. `value` is `Some` for a flag that takes one, and /// `negated` is true when the flag was set through its `negate` form. Flag { flag: &'t Flag<'t>, value: Option<&'v [u8]>, negated: bool, }, /// A word was bound to a positional argument. A variadic argument produces /// one event per value. Arg { arg: &'t Arg<'t>, value: &'v [u8], /// Whether this value should be split by the argument's declared delimiter. delimit: bool, }, /// Ended one clause instance and began the next. ClauseSeparator { clause: Clause<'t> }, /// An unmatched word was forwarded as an external command: the name, then /// every remaining token, including flags. External { values: &'a [&'v OsStr] }, } /// A binding failure. /// /// Carries the offending token so a caller can render a good message, but no /// message of its own: rendering belongs to a cold path, and building a string /// here would allocate on the way to reporting that nothing was allocated. /// /// `non_exhaustive`, because an error enum grows: a caller matching on it needs a /// fallback arm so that recognizing a new failure is never a breaking change. // No `Copy`: one variant owns its message. `Clone` stays, and the enum is still 40 bytes // because that variant is boxed, so nothing on the hot path grew. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum Error<'t, 'v> { /// A flag-like token matched no flag in scope. `token` is the whole token as /// typed, so a bundle containing an unrecognized letter reports `-fz` rather /// than the letter alone — which is also the unit in which it is rejected. UnknownFlag { token: &'v [u8] }, /// A flag that needs a value did not get one, either because the command /// line ended or because the next token was flag-like. MissingFlagValue { flag: &'t Flag<'t> }, /// A word arrived with no argument left to hold it. UnexpectedArg { token: &'v [u8] }, /// A word was offered to a `double_dash = "required"` argument before any /// `--` had been seen. ArgRequiresDoubleDash { arg: &'t Arg<'t> }, /// A subcommand was selected after this command had already bound an argument. SubcommandConflict { subcommand: &'t Command<'t> }, /// The command tree is deeper than [`MAX_DEPTH`]. TooDeep, // The rest are raised *after* the parse, by whoever owns the target type: they // need to know a value's declared type, which the parser deliberately does not. // They share this enum so that a caller has one error to handle rather than two. /// Something the command requires was never given. MissingRequired { /// The flag or argument's name, as the spec calls it. name: &'t str, }, /// A flag that is not repeatable was given more than once. DuplicateFlag { /// The flag's name, as the spec calls it. name: &'t str, }, /// A value was given that is not among the declared choices. /// /// Carries the choices rather than the offending value: rendering the value means /// owning it, and an error that allocates on a path this crate promises not to /// allocate on would be a poor trade for a better message. Diagnostics are a /// separate layer. InvalidChoice { name: &'t str, choices: &'t [&'t str], }, /// Fewer values than `var_min`. VarTooFew { name: &'t str, min: usize, got: usize, }, /// More values than `var_max`. VarTooMany { name: &'t str, max: usize, got: usize, }, /// Two flags declared to conflict were both given. /// /// Carries both names because either one alone reads as a puzzle: which flag is /// unwelcome depends entirely on what else is on the command line. ConflictingFlags { /// The flag whose declaration names the conflict. name: &'t str, /// The flag it cannot be given with, as the declaration spells it. other: &'t str, }, /// A value was given that the field's type could not be built from. /// /// Boxed, and the only error here that owns anything. Everything else borrows the /// tables or argv, which is what keeps a *successful* parse allocation-free — and the /// box keeps `Error` the size it was, so the `Result` this rides in on the hot path /// does not grow. A value that will not convert has already failed, and a message /// worth reading is worth one allocation. InvalidValue(::std::boxed::Box>), /// A required group had none of its members given. /// /// Carries the members as members rather than as a rendered sentence: the caller /// decides how to say it, and a completion asking what would satisfy this needs the /// list rather than the prose. MissingGroup { /// The group's declared name, which appears in the message so a command with /// several groups does not report the same sentence twice. group: &'t str, /// The flags that would satisfy it, as the declaration spells them. members: &'t [&'t str], }, /// A subcommand was required, and none was given. MissingSubcommand, /// `--help` or `-h` was given, and `cmd` is what it was asked about. /// /// Not a failure, and returned as one anyway: a parse that stops to print help has not /// produced a value, and every caller already handles the "no value" shape. clap does the /// same thing for the same reason. /// /// `long` distinguishes the two: `-h` prints the short form and `--help` the long one, as /// clap has them. The caller renders — this crate does not print, because a library that /// writes to stdout on its own is one an adopter cannot embed. Help { cmd: &'t Command<'t>, long: bool }, /// `arg_required_else_help` found no command-line arguments for `cmd`. /// /// Unlike an explicit help request, this is a usage failure: clap prints the short help to /// stderr and exits with status 2. Keeping the shape separate lets embedders preserve that /// terminal contract without guessing why [`Error::Help`] was returned. MissingArgsHelp { cmd: &'t Command<'t> }, /// Recursive long help was requested for `cmd` and every visible descendant. HelpAll { cmd: &'t Command<'t> }, /// `--version` or `-V` was asked for. Not a failure either — the caller prints and leaves. /// /// The version string lives in the spec rather than the parse tables. `long` lets the /// caller choose `long_version` for `--version` while `-V` retains the concise value. Version { long: bool }, /// Variables under the CLI's `unknown_env` prefix that nothing in it declares. /// /// Boxed like [`Error::InvalidValue`], and for the same reason: it owns the names, and /// the box keeps `Error` the size it was. See [`env`](crate::env). UnknownEnv(::std::boxed::Box>), } /// Why [`Error::UnknownEnv`] refused a parse. #[derive(Debug, Clone, PartialEq, Eq)] pub struct UnknownEnv<'t> { /// The prefix the CLI declared as its own. pub prefix: &'t str, /// Every variable under it that nothing declares, sorted. pub names: ::std::vec::Vec<::std::string::String>, } /// The high half of every key one declaration's items get. /// /// A derive cannot see other expansions, so it cannot hand out keys from a shared /// counter: it hashes the declaration it was given instead. It cannot see a module path /// either, which is why the module is mixed in *here* — `module_path!()` is available to /// the generated code as a compile-time string, so two byte-identical declarations in /// different modules end up with different keys rather than colliding. /// /// `declaration` is a hash the derive computed over the item's own tokens. pub const fn key_base(module: &str, declaration: u32) -> u64 { // FNV-1a, continuing from the declaration's hash rather than starting over, so both // halves contribute. Spelled out rather than taken from a `Hasher`, which is not // guaranteed to be stable between compilations — and these are baked into a binary. let mut hash: u32 = declaration; let bytes = module.as_bytes(); let mut i = 0; while i < bytes.len() { hash ^= bytes[i] as u32; hash = hash.wrapping_mul(0x0100_0193); i += 1; } (hash as u64) << 32 } /// Why a value would not convert into the type its field holds. /// /// Separate from [`Error`] so that the enum stays small: this is reached through a `Box`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct InvalidValue<'t> { /// The flag or argument's name, as the spec calls it. pub name: &'t str, /// The text that would not convert. pub value: ::std::string::String, /// What the type's own conversion complained about. pub reason: ::std::string::String, } /// A command-wide validation or finalization failure. /// /// Return this from a `#[usage(validate_with = ...)]` hook or from the /// `TryFrom` implementation named by `#[usage(try_into = ...)]`. The derive /// turns it into the same [`Error::InvalidValue`] diagnostic used by field /// conversion, so callers keep one parse error type. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ValidationError { name: &'static str, value: String, reason: String, } impl ValidationError { /// Start an error attributed to a flag, positional, or command name. pub fn field(name: &'static str) -> Self { Self { name, value: String::new(), reason: String::new(), } } /// Record the value that failed the command-wide invariant. pub fn value(mut self, value: impl Into) -> Self { self.value = value.into(); self } /// Explain the invariant that the value did not satisfy. pub fn reason(mut self, reason: impl Into) -> Self { self.reason = reason.into(); self } /// Convert this application-level failure into the parser's diagnostic. pub fn into_parse_error<'v>(self) -> Error<'static, 'v> { Error::InvalidValue(Box::new(InvalidValue { name: self.name, value: self.value, reason: self.reason, })) } } /// Interpret a value as UTF-8. /// /// The parser hands back bytes borrowed from `argv`; this is the conversion most /// callers want, and the point at which a non-UTF-8 command line is rejected — /// but only for the values actually inspected. pub fn as_str(value: &[u8]) -> Result<&str, std::str::Utf8Error> { std::str::from_utf8(value) } /// How many entries a group of tables holds in total. /// /// The length for [`concat_flags`] and [`concat_args`], which need it as a const generic — so /// it has to be computable separately from the concatenation itself. /// /// ``` /// use usage_argv::{table_len, Flag}; /// /// static A: Flag = Flag { name: "a", ..Flag::BOOL }; /// static B: Flag = Flag { name: "b", ..Flag::BOOL }; /// const GROUPS: &[&[&Flag]] = &[&[&A], &[], &[&B]]; /// const N: usize = table_len(GROUPS); /// assert_eq!(N, 2); /// ``` pub const fn table_len(groups: &[&[T]]) -> usize { let mut total = 0; let mut i = 0; while i < groups.len() { total += groups[i].len(); i += 1; } total } /// `all[start..start + len]`, at compile time, for a table that points into a joined one. /// /// A flattened type's metadata names its own run of the parent's joined tables rather than /// keeping a second copy of every entry. #[doc(hidden)] pub const fn __usage_subslice(all: &'static [T], start: usize, len: usize) -> &'static [T] { let (_, tail) = all.split_at(start); let (run, _) = tail.split_at(len); run } /// Join groups of flag tables into one, at compile time. /// /// This is how `#[usage(flatten)]` stays free. A flattened struct's flags have to appear in /// the parent's own table, and the parent's macro expansion cannot see them — it has only a /// type. But it can name that type's [`CommandArgs::COMMAND`](crate::spec::CommandArgs::COMMAND), /// and a `const fn` can read through it, so the two lists become one `static` array before the /// program runs. The parser then walks a single flat slice, exactly as it does for a command /// that declared everything itself: flatten costs nothing at run time. /// /// Groups are laid out in the order given, which is what lets a flattened group sit *between* /// two of the parent's own declarations — necessary for positional arguments, where order is /// the meaning. /// /// `N` must be [`table_len`] of the same groups. It cannot be inferred, and a wrong one fails /// to compile rather than leaving the difference filled with padding. /// /// ``` /// use usage_argv::{concat_flags, table_len, Flag}; /// /// static FORCE: Flag = Flag { name: "force", longs: &["force"], ..Flag::BOOL }; /// static QUIET: Flag = Flag { name: "quiet", longs: &["quiet"], ..Flag::BOOL }; /// static SHARED: &[&Flag] = &[&QUIET]; /// /// const GROUPS: &[&[&Flag]] = &[&[&FORCE], SHARED]; /// static FLAGS: [&Flag; table_len(GROUPS)] = concat_flags(GROUPS); /// /// assert_eq!(FLAGS.iter().map(|f| f.name).collect::>(), ["force", "quiet"]); /// ``` pub const fn concat_flags( groups: &[&[&'static Flag<'static>]], ) -> [&'static Flag<'static>; N] { // Every slot is written below, but an array has to start somewhere and `MaybeUninit` // would mean `unsafe`. A `Flag` nobody can reach is cheaper than that. static PLACEHOLDER: Flag<'static> = Flag::BOOL; let mut out = [&PLACEHOLDER; N]; let mut at = 0; let mut g = 0; while g < groups.len() { let group = groups[g]; let mut i = 0; while i < group.len() { out[at] = group[i]; at += 1; i += 1; } g += 1; } assert!( at == N, "`N` must be `table_len` of the same groups, or the table would keep a placeholder \ that answers to nothing" ); out } /// Join groups of argument tables into one, at compile time. /// /// The positional counterpart of [`concat_flags`] — see there for why this exists. Order /// matters more here: an argument's position *is* its identity, so a flattened group has to /// land exactly where the field was written. /// /// Two functions rather than one generic: each needs a value to fill an array with before /// overwriting it, and there is no way to ask a type parameter for one in a `const fn`. pub const fn concat_args( groups: &[&[&'static Arg<'static>]], ) -> [&'static Arg<'static>; N] { static PLACEHOLDER: Arg<'static> = Arg::REQUIRED; let mut out = [&PLACEHOLDER; N]; let mut at = 0; let mut g = 0; while g < groups.len() { let group = groups[g]; let mut i = 0; while i < group.len() { out[at] = group[i]; at += 1; i += 1; } g += 1; } assert!( at == N, "`N` must be `table_len` of the same groups, or the table would keep a placeholder \ that answers to nothing" ); out } /// The key `--help` answers to, and the one `-h` does. /// /// Reserved rather than generated: a derive builds keys from a hash of the type they came from /// in the high half and an index in the low half, so the top of the range belongs to nobody. /// Generated code compares against these to tell a help request from a flag of its own. pub const HELP_LONG_KEY: u64 = u64::MAX; /// See [`HELP_LONG_KEY`]. pub const HELP_SHORT_KEY: u64 = u64::MAX - 1; /// `--help`, which every command answers to. /// /// In the parse table and *not* in the metadata, which is the whole trick: the parser has to /// recognise the flag, and help output must not list it — a spec does not declare `--help`, so /// showing one would make the rendered page disagree with the spec it came from. pub static HELP_LONG: Flag<'static> = Flag { key: HELP_LONG_KEY, name: "help", longs: &["help"], action: ArgAction::HelpLong, ..Flag::BOOL }; /// See [`HELP_LONG_KEY`]. pub const VERSION_LONG_KEY: u64 = u64::MAX - 2; /// See [`HELP_LONG_KEY`]. pub const VERSION_SHORT_KEY: u64 = u64::MAX - 3; /// `--version`, where the CLI declared one. /// /// In the parse table and not in the metadata, exactly as `--help` is: a spec does not declare /// `--version`, so listing one would make the rendered page disagree with the spec it came from. pub static VERSION_LONG: Flag<'static> = Flag { key: VERSION_LONG_KEY, name: "version", longs: &["version"], action: ArgAction::Version, ..Flag::BOOL }; /// `-V`, which clap also supplies. pub static VERSION_SHORT: Flag<'static> = Flag { key: VERSION_SHORT_KEY, name: "version", shorts: b"V", action: ArgAction::Version, ..Flag::BOOL }; /// `-h`, which prints the shorter form. pub static HELP_SHORT: Flag<'static> = Flag { key: HELP_SHORT_KEY, name: "help", shorts: b"h", action: ArgAction::HelpShort, ..Flag::BOOL }; /// A named subcommand of a given command, by name or alias. /// /// Free rather than a method because `help` resolves a path *without* descending: the words /// after it are a question about a command rather than a walk into one. /// /// Names across every subcommand before any alias, the precedence the grammar states — and /// the reason this is the only implementation of it on argv's side. `ex run` and `ex help run` /// selecting different commands would be exactly the divergence this rule was written to end. #[inline(never)] pub(crate) fn find_named<'t>(cmd: &'t Command<'t>, name: &[u8]) -> Option<&'t Command<'t>> { let subcommands = || cmd.subcommands.iter().copied(); subcommands() .find(|c| c.name.as_bytes() == name) .or_else(|| subcommands().find(|c| c.aliases.iter().any(|a| a.as_bytes() == name))) } /// What a caller should print for a parse failure, and what to exit with. /// /// The one entry point a generated `parse()` reaches for, and the reason it exists here rather /// than in the derive: whether the good rendering is available is a *feature of this crate* in /// the adopter's dependency graph, and a `#[cfg]` written into generated code is evaluated in /// the adopter's crate, where the feature is not theirs to see. That is how a metadata field /// once got silently dropped; the answer is that the cfg lives beside the thing it gates. /// /// With `diagnostics` on, this is the clap-shaped message. Without it, the error's `Debug` /// form — which is still better than nothing and is what a parser-only build asked for. /// /// [`Error::Help`] and [`Error::Version`] are not failures and must be handled before this. #[cfg(feature = "diagnostics")] pub fn render_failure(spec: &spec::Spec<'_>, argv: &[&OsStr], error: &Error<'_, '_>) -> String { diagnostic::render(spec, argv, error, diagnostic::Style::auto()) } /// A parse failure, never coloured. /// /// [`render_failure`] asks the environment whether to colour, which is right for a process and /// wrong for anything that keeps the string: a test that asserts on a message, or a snapshot of /// one, would pass or fail by whether stderr happened to be a terminal. The renderer is the /// same; only the answer to that question is fixed. #[cfg(feature = "diagnostics")] pub fn render_failure_plain( spec: &spec::Spec<'_>, argv: &[&OsStr], error: &Error<'_, '_>, ) -> String { diagnostic::render(spec, argv, error, diagnostic::Style::PLAIN) } /// Render a failure through a spec-declared executable view. /// /// `argv` is the original full argv, including the view executable as argv0. #[cfg(feature = "diagnostics")] pub fn render_failure_view<'a>( spec: &'a spec::Spec<'a>, argv: &[&OsStr], error: &Error<'_, '_>, view: &'a spec::ViewMeta<'a>, ) -> String { diagnostic::render_view(spec, argv, error, diagnostic::Style::auto(), view) } /// What a caller should print for a parse failure, without the renderer that makes it readable. /// /// See the other half. A caller that wants the clap-shaped message turns on `diagnostics`; /// this is what a parser-only build asked for, and it still says which error it was. #[cfg(all(feature = "spec", not(feature = "diagnostics")))] pub fn render_failure(spec: &spec::Spec<'_>, argv: &[&OsStr], error: &Error<'_, '_>) -> String { let _ = (spec, argv); ::std::format!("error: {error:?}\n") } /// A parse failure without the renderer, which is plain either way. #[cfg(all(feature = "spec", not(feature = "diagnostics")))] pub fn render_failure_plain( spec: &spec::Spec<'_>, argv: &[&OsStr], error: &Error<'_, '_>, ) -> String { render_failure(spec, argv, error) } /// Render a failure through a declared view without the optional diagnostics renderer. #[cfg(all(feature = "spec", not(feature = "diagnostics")))] pub fn render_failure_view( spec: &spec::Spec<'_>, argv: &[&OsStr], error: &Error<'_, '_>, view: &spec::ViewMeta<'_>, ) -> String { let _ = (spec, argv, view); ::std::format!("error: {error:?}\n") } /// Answer an error from a generated `parse()` the way a command-line program does, and leave. /// /// A help request goes to stdout and exits 0; a request for help because nothing was given goes /// to stderr and exits 2; any other failure is rendered to stderr and exits 2, which is clap's /// status, so a script that checks for it keeps working. [`Error::Version`] is the caller's to /// answer before this, since only the generated code knows the version it prints. /// /// Here rather than expanded into every CLI: the body is the same for all of them, and only the /// spec — which carries any computed identity the generated code has already evaluated — and /// the tables differ. `all_argv` includes argv0, which a view's route needs; `argv` does not. #[cfg(feature = "spec")] #[doc(hidden)] pub fn __usage_exit_on_error( error: Error<'_, '_>, spec: &spec::Spec<'_>, root: &Command<'_>, all_argv: &[&OsStr], argv: &[&OsStr], view: Option<&spec::ViewMeta<'_>>, ) -> ! { let (want, to_stderr, cmd) = match error { Error::Help { cmd, long } => { let want = if long { help::Page::Long } else { help::Page::Short }; (want, false, cmd) } Error::MissingArgsHelp { cmd } => (help::Page::Short, true, cmd), Error::HelpAll { cmd } => (help::Page::All, false, cmd), error => { let failure = match view { Some(view) => render_failure_view(spec, all_argv, &error, view), None => render_failure(spec, argv, &error), }; __usage_eprint(format_args!("{failure}")); __usage_process_exit(2); } }; let style = if to_stderr { help::Style::auto_stderr() } else { help::Style::auto() }; let page = match view { Some(view) => help::page_view(spec, root, all_argv, cmd, view, want, style), None => help::page(spec, root, argv, cmd, want, style), }; // `None` is only reachable if the command came from another CLI's tables. if let Some(page) = page { if to_stderr { __usage_eprint(format_args!("{page}")); } else { __usage_print(format_args!("{page}")); } } __usage_process_exit(if to_stderr { 2 } else { 0 }) } /// Print `bin version` for a generated `parse()`, and leave. /// /// The half of [`__usage_exit_on_error`] the generated code answers itself, because the /// version is a declaration of that CLI; the printing and the exit are the same everywhere. #[cfg(feature = "spec")] #[doc(hidden)] pub fn __usage_exit_version( bin: &str, view: Option<&spec::ViewMeta<'_>>, version: &dyn std::fmt::Display, ) -> ! { let bin = view.map_or(bin, |view| view.bin); __usage_print(format_args!("{bin} {version}\n")); __usage_process_exit(0) } /// What a caller should print for the deprecations a command line used. /// /// The same arrangement as [`render_failure`], and for the same reason: whether the coloured /// rendering is available is a feature of *this* crate in the adopter's dependency graph, so the /// `#[cfg]` lives beside the thing it gates rather than in generated code. /// /// Warnings are not failures. A caller prints these to stderr and carries on. #[cfg(feature = "diagnostics")] pub fn render_warnings(warnings: &[warn::Warning<'_>]) -> String { diagnostic::render_warnings(warnings, diagnostic::Style::auto()) } /// The same wording without the renderer that colours it. See the other half. #[cfg(all(feature = "spec", not(feature = "diagnostics")))] pub fn render_warnings(warnings: &[warn::Warning<'_>]) -> String { warn::render_warnings(warnings) } /// The word a tool sends to ask a binary for its own spec. /// /// Not a flag and not a command: a spec request is not something this CLI *does*, so it is /// answered before the parse and stays out of the tables — the same reason /// `__complete_word__` is a word rather than a subcommand. It also keeps the endpoint from /// perturbing the document it prints, which a declared flag would not. pub const SPEC_REQUEST: &str = "__usage_spec__"; /// Whether this argv asks for the spec rather than for the CLI to run. /// /// Only the first word counts: `mycli build __usage_spec__` passes the word through as an /// ordinary value, because a request is the whole invocation or it is nothing. /// /// A root that declares a command of that name keeps it, which is the precedence the `help` /// subcommand already has. The check is here rather than in the derive because a `Cli` derive /// expands one struct and cannot see the variant names of a separate `Subcommands` enum — the /// static tables can. pub fn is_spec_request(root: &Command<'_>, argv: &[&OsStr]) -> bool { let [first, ..] = argv else { return false }; first.as_encoded_bytes() == SPEC_REQUEST.as_bytes() && find_named(root, SPEC_REQUEST.as_bytes()).is_none() } /// Whether a flag is one of the two the parser supplies rather than the CLI declaring it. pub fn is_help_flag(flag: &Flag<'_>) -> bool { matches!( flag.action, ArgAction::Help | ArgAction::HelpShort | ArgAction::HelpLong | ArgAction::HelpAll ) } /// Whether a flag is one of the two the parser supplies for `--version`. pub fn is_version_flag(flag: &Flag<'_>) -> bool { flag.action == ArgAction::Version } /// Whether one exact root argument selects a declared or synthesized version action. /// /// Declared flags are checked first because they shadow the built-in `--version` and `-V` /// spellings. Executable views use this before projection so custom version spellings keep /// reporting the package that owns the view. pub fn is_version_arg(cmd: &Command<'_>, word: &OsStr) -> bool { let token = word.as_encoded_bytes(); if let Some(long) = token.strip_prefix(b"--") { if let Some(flag) = cmd.flags.iter().find(|flag| { flag.longs .iter() .any(|spelling| spelling.as_bytes() == long) }) { return is_version_flag(flag); } if cmd.flags.iter().any(|flag| { flag.negate .is_some_and(|spelling| spelling.as_bytes() == long) }) { return false; } return long == b"version" && cmd.version && !cmd.disable_version_flag; } if let [b'-', short] = token { if let Some(flag) = cmd.flags.iter().find(|flag| has_byte(flag.shorts, *short)) { return is_version_flag(flag); } return *short == b'V' && cmd.version && !cmd.disable_version_flag; } false } /// Resolve a subcommand by name or alias, at compile time. /// /// For [`Command::default_subcommand`], which names a command that a derive cannot see: the /// variants of a subcommand enum are a different macro expansion, so the name is all the /// parent has. Searching the list in a `const fn` closes that gap — the answer is the same /// `&'static` the table already holds, found before the program runs. /// /// A name no subcommand answers to is a **compile error**, since this panics during const /// evaluation. That is the whole point of doing it here rather than at startup. /// /// ``` /// use usage_argv::{find_subcommand, Command}; /// /// static RUN: Command = Command { name: "run", ..Command::EMPTY }; /// static SUBS: &[&Command] = &[&RUN]; /// static ROOT: Command = Command { /// name: "ex", /// subcommands: SUBS, /// default_subcommand: Some(find_subcommand(SUBS, "run")), /// ..Command::EMPTY /// }; /// assert_eq!(ROOT.default_subcommand.unwrap().name, "run"); /// ``` pub const fn find_subcommand<'a>( subcommands: &'a [&'a Command<'a>], name: &str, ) -> &'a Command<'a> { // Names first, then aliases: a command's own name outranks another command's alias, so // the answer does not depend on the order the table happens to list them in. Checking // each candidate's name *and* aliases in one pass instead let whichever command came // first win, and usage-lib resolved the same spec to the last one. let mut i = 0; while i < subcommands.len() { if str_eq(subcommands[i].name, name) { return subcommands[i]; } i += 1; } // Aliases answer too, because usage-lib resolves the name against names, aliases and // hidden aliases alike — so a spec may point `default_subcommand` at any of them. let mut i = 0; while i < subcommands.len() { let candidate = subcommands[i]; let mut a = 0; while a < candidate.aliases.len() { if str_eq(candidate.aliases[a], name) { return candidate; } a += 1; } i += 1; } panic!("`default_subcommand` names a command that this one does not have") } /// Refuse two subcommands that answer to the same name, aliases included. /// /// A derive expansion can validate aliases written on one enum, but aliases may also live on /// the independently expanded `Args` structs its variants wrap. This final, joined-table check /// is where both declarations are visible. pub const fn assert_unique_subcommand_names(subcommands: &[&Command<'_>]) { const fn form<'a>(cmd: &'a Command<'a>, at: usize) -> Option<&'a str> { if at == 0 { Some(cmd.name) } else if at <= cmd.aliases.len() { Some(cmd.aliases[at - 1]) } else { None } } let mut command = 0; while command < subcommands.len() { let mut at = 0; while let Some(name) = form(subcommands[command], at) { let mut other_command = command; while other_command < subcommands.len() { let mut other_at = if other_command == command { at + 1 } else { 0 }; while let Some(other) = form(subcommands[other_command], other_at) { assert!( !str_eq(name, other), "two subcommands answer to the same name, counting aliases" ); other_at += 1; } other_command += 1; } at += 1; } command += 1; } } /// Assert that flags scoped to a clause do not reuse a command-level spelling. /// /// Derive-generated command tables include scoped flags so the parser can find them. Entries /// that share a key are therefore the same declaration and are ignored here; every other pair /// must have disjoint long, short, alias, and negated spellings. pub const fn assert_clause_flag_spellings(command: &Command<'_>) { let Some(clause) = command.clause else { return; }; let mut scoped_at = 0; while scoped_at < clause.flags.len() { let scoped = clause.flags[scoped_at]; let mut command_at = 0; while command_at < command.flags.len() { let flag = command.flags[command_at]; if flag.key != scoped.key && const_flag_forms_overlap(flag, scoped) { panic!("a clause flag spelling conflicts with a command-level flag"); } command_at += 1; } scoped_at += 1; } } const fn const_flag_forms_overlap(a: &Flag<'_>, b: &Flag<'_>) -> bool { let mut i = 0; while i < a.longs.len() { let mut j = 0; while j < b.longs.len() { if const_bytes_eq(a.longs[i].as_bytes(), b.longs[j].as_bytes()) { return true; } j += 1; } if let Some(negate) = b.negate && const_bytes_eq(a.longs[i].as_bytes(), negate.as_bytes()) { return true; } i += 1; } i = 0; while i < a.shorts.len() { let mut j = 0; while j < b.shorts.len() { if a.shorts[i] == b.shorts[j] { return true; } j += 1; } i += 1; } if let Some(negate) = a.negate { let mut j = 0; while j < b.longs.len() { if const_bytes_eq(negate.as_bytes(), b.longs[j].as_bytes()) { return true; } j += 1; } if let Some(other) = b.negate && const_bytes_eq(negate.as_bytes(), other.as_bytes()) { return true; } } false } const fn const_bytes_eq(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { return false; } let mut i = 0; while i < a.len() { if a[i] != b[i] { return false; } i += 1; } true } /// `==` on strings, in a `const fn`. const fn str_eq(a: &str, b: &str) -> bool { let (a, b) = (a.as_bytes(), b.as_bytes()); if a.len() != b.len() { return false; } let mut i = 0; while i < a.len() { if a[i] != b[i] { return false; } i += 1; } true } /// Rebuild an [`OsString`] from bytes the parser handed back. /// /// This is the reverse of [`OsStr::as_encoded_bytes`], and it is how a `PathBuf` field /// receives a filename the operating system accepts but UTF-8 does not — `/tmp/\xff` stays /// `/tmp/\xff` rather than becoming a *different* filename with `U+FFFD` in it. /// /// Where the platform cannot hold those bytes, they are handed back in the `Err` — as /// `String::from_utf8` does — so the caller can name the value in its error without this /// having to copy it for a case that is nearly never taken. /// /// # Why this is not `unsafe`, and why it is not lossless everywhere /// /// On **Unix** an `OsString` is an arbitrary byte sequence, so the conversion is total and /// uses the safe [`OsStringExt::from_vec`]. Every byte survives, which is the case that /// matters: non-UTF-8 filenames are ordinary there. /// /// [`OsStringExt::from_vec`]: std::os::unix::ffi::OsStringExt::from_vec /// /// On **Windows** the encoding is WTF-8, where not every byte sequence is valid, and the only /// constructor that accepts one is `OsString::from_encoded_bytes_unchecked` — whose /// precondition this function cannot enforce. It takes a `Vec` from a safe caller, so /// there is no way to know the bytes came from `as_encoded_bytes` rather than from anywhere /// else, and a safe function with a precondition that can be violated is unsound however /// carefully its callers behave today. /// /// So on Windows the bytes go through UTF-8, and one that is not valid UTF-8 is refused /// rather than assumed. What that gives up is a Windows argument containing an unpaired /// surrogate, which is reported instead of accepted; what it buys is that this crate needs no /// `unsafe` at all. pub fn os_string_from_bytes(value: Vec) -> Result> { #[cfg(unix)] { Ok(std::os::unix::ffi::OsStringExt::from_vec(value)) } #[cfg(not(unix))] { match String::from_utf8(value) { Ok(text) => Ok(OsString::from(text)), Err(bad) => Err(bad.into_bytes()), } } } /// A sigil argument given its sigil and nothing after it. Out of line and cold: two places in the /// parser report it, and neither is on the path of a command line that parses. #[cold] #[inline(never)] fn sigil_without_value<'t, 'v>(arg: &'t Arg<'t>, token: &[u8], sigil: &[u8]) -> Error<'t, 'v> { invalid_value_error( arg.name, as_str(token).unwrap_or_default().to_string(), format!( "expected a value after sigil {:?}", as_str(sigil).unwrap_or_default() ), ) } /// One [`Error::InvalidValue`], built out of line. /// /// Cold and never inlined on purpose: this is the failure path of every value /// conversion in every generated `build`, and inlining it there is what made /// those functions large. #[cold] #[inline(never)] pub(crate) fn invalid_value_error<'t, 'v>( name: &'t str, value: String, reason: String, ) -> Error<'t, 'v> { Error::InvalidValue(Box::new(InvalidValue { name, value, reason, })) } /// One [`Error::InvalidValue`] for a word that was not UTF-8. /// /// The error half of what a generated `build` does per text field: the check stays /// inline at the field, and this — the lossy rendering and the allocations — lives /// here once instead of once per field. #[cold] #[inline(never)] pub fn invalid_utf8_value<'t, 'v>(name: &'t str, bad: std::string::FromUtf8Error) -> Error<'t, 'v> { invalid_value_error( name, String::from_utf8_lossy(bad.as_bytes()).into_owned(), bad.utf8_error().to_string(), ) } /// One [`Error::InvalidValue`] for a value whose type would not build from it. /// /// Takes the reason as `&dyn Display` so one copy serves every `FromStr` error type. #[cold] #[inline(never)] pub fn invalid_parsed_value<'t, 'v>( name: &'t str, value: String, reason: &dyn std::fmt::Display, ) -> Error<'t, 'v> { invalid_value_error(name, value, reason.to_string()) } /// One [`Error::InvalidValue`] for a word that is not one of a value enum's choices. #[cold] #[inline(never)] pub fn invalid_choice_value<'t, 'v>(name: &'t str, value: String) -> Error<'t, 'v> { invalid_value_error(name, value, String::from("not one of the declared values")) } /// One [`Error::InvalidValue`] for bytes the platform cannot hold in a path. #[cold] #[inline(never)] pub fn invalid_os_value<'t, 'v>(name: &'t str, bytes: Vec) -> Error<'t, 'v> { invalid_value_error( name, String::from_utf8_lossy(&bytes).into_owned(), "this platform cannot hold these bytes in a path".to_string(), ) } /// Convert every repeated value of one text field, reporting `name` for the /// first that is not UTF-8. /// /// The shared body of what a generated `build` does per collecting text field: /// one loop in the binary rather than one per field. Converts element by /// element rather than with `collect` so the error can carry the value that /// failed rather than only that one did. /// /// The empty case is answered here rather than in the shared loop, and this is /// what keeps sharing the loop free: a command at mise's scale declares dozens /// of collecting fields and a command line names one or two of them, so most of /// these calls have nothing to convert. Testing that at the field costs a branch; /// reaching the loop to learn it costs the call. #[inline] pub fn utf8_values<'t, 'v>( values: Vec>, name: &'t str, ) -> Result, Error<'t, 'v>> { if values.is_empty() { return Ok(Vec::new()); } utf8_values_given(values, name) } #[inline(never)] fn utf8_values_given<'t, 'v>( values: Vec>, name: &'t str, ) -> Result, Error<'t, 'v>> { let mut out = Vec::with_capacity(values.len()); for value in values { match String::from_utf8(value) { Ok(text) => out.push(text), Err(bad) => return Err(invalid_utf8_value(name, bad)), } } Ok(out) } /// Convert every repeated value of one field through /// [`FromStr`](std::str::FromStr), reporting `name` for the first that fails. /// /// Monomorphized once per target type rather than expanded once per field, and /// the empty case is answered at the field for the reason [`utf8_values`] gives. #[inline] pub fn parsed_values<'t, 'v, T>( values: Vec>, name: &'t str, ) -> Result, Error<'t, 'v>> where T: std::str::FromStr, T::Err: std::fmt::Display, { if values.is_empty() { return Ok(Vec::new()); } parsed_values_given(values, name) } #[inline(never)] fn parsed_values_given<'t, 'v, T>( values: Vec>, name: &'t str, ) -> Result, Error<'t, 'v>> where T: std::str::FromStr, T::Err: std::fmt::Display, { let mut out = Vec::with_capacity(values.len()); for value in values { let text = match String::from_utf8(value) { Ok(text) => text, Err(bad) => return Err(invalid_utf8_value(name, bad)), }; match text.parse() { Ok(parsed) => out.push(parsed), Err(reason) => return Err(invalid_parsed_value(name, text, &reason)), } } Ok(out) } /// Convert every repeated value of one path-like field, reporting `name` for /// the first the platform cannot hold. /// /// `T` is what the field collects — [`PathBuf`](std::path::PathBuf) or /// [`OsString`] — so one body serves both. The same platform note as /// [`os_string_from_bytes`] applies: lossless on Unix, partial on Windows. The /// empty case is answered at the field for the reason [`utf8_values`] gives. #[inline] pub fn os_values<'t, 'v, T: From>( values: Vec>, name: &'t str, ) -> Result, Error<'t, 'v>> { if values.is_empty() { return Ok(Vec::new()); } os_values_given(values, name) } #[inline(never)] fn os_values_given<'t, 'v, T: From>( values: Vec>, name: &'t str, ) -> Result, Error<'t, 'v>> { let mut out = Vec::with_capacity(values.len()); for value in values { match os_string_from_bytes(value) { Ok(os) => out.push(T::from(os)), Err(bytes) => return Err(invalid_os_value(name, bytes)), } } Ok(out) } /// What a generated `apply` stores for a flag that takes one value: the word, or nothing /// when the flag arrived without one. /// /// Out of line because the copy is an allocation, a `memcpy`, and the drop of whatever the /// field held before — a few dozen instructions that would otherwise be expanded at every /// value-taking field of every command. #[doc(hidden)] #[inline(never)] pub fn bind_text(slot: &mut Vec, value: Option<&[u8]>) { *slot = value.map(<[u8]>::to_vec).unwrap_or_default(); } /// [`bind_text`] for an `Option` field, which a flag without a value still fills. #[doc(hidden)] #[inline(never)] pub fn bind_some_text(slot: &mut Option>, value: Option<&[u8]>) { *slot = Some(value.map(<[u8]>::to_vec).unwrap_or_default()); } /// [`bind_text`] for an `Option>` field, where a flag given without a value is /// the inner `None`. #[doc(hidden)] #[inline(never)] pub fn bind_optional_text(slot: &mut Option>, value: Option<&[u8]>) { *slot = value.map(<[u8]>::to_vec); } /// [`bind_text`] for a collecting field: one more occurrence, appended. #[doc(hidden)] #[inline(never)] pub fn bind_push_text(slot: &mut Vec>, value: Option<&[u8]>) { slot.push(value.map(<[u8]>::to_vec).unwrap_or_default()); } /// [`bind_push_text`] for a field with a value delimiter: every part, appended in order. #[doc(hidden)] #[inline(never)] pub fn bind_push_split(slot: &mut Vec>, value: Option<&[u8]>, delimiter: u8) { for part in value.unwrap_or_default().split(|b| *b == delimiter) { slot.push(part.to_vec()); } } /// A single binding pass over `argv`. /// /// [`Command::default_subcommand_flags`] adds a read-only lookahead before binding /// to choose an implicit command boundary. /// /// Created with [`Parser::new`] and driven with [`Parser::next_event`]. pub struct Parser<'t, 'a, 'v> { argv: &'a [&'v OsStr], /// Index of the next token to read. pos: usize, /// The command currently in scope. cmd: &'t Command<'t>, /// The canonical root, used to hide root globals omitted by an executable view. #[cfg(feature = "spec")] root: &'t Command<'t>, /// The executable projection being parsed, if argv0 selected one. #[cfg(feature = "spec")] view: Option<&'t spec::ViewMeta<'t>>, /// What an unrecognized flag-like token means in the command currently in scope. /// /// Carried rather than looked up, because it is inherited: a command that states /// nothing keeps what the enclosing one said, and walking back up the ancestors on /// every unrecognized token would pay for the inheritance at the wrong moment. unknown_flags: UnknownFlags, /// Effective inherited trailing-delimiter policy. dont_delimit_trailing_values: bool, /// The chain above `cmd`, used to find inherited global flags. Fixed size so /// that nothing is allocated. ancestors: [Option<&'t Command<'t>>; MAX_DEPTH], depth: usize, /// Bytes left in a short-flag bundle, if one is partly read. bundle: &'v [u8], /// The whole token the current bundle came from, so an error raised part way /// through it can still name what the user typed. bundle_token: &'v [u8], /// A variadic flag that is still collecting values. collecting: Option<&'t Flag<'t>>, /// Where the command in scope began, as an index into `argv`. cmd_start: usize, /// Where each ancestor's own words began, in step with `ancestors`. starts: [usize; MAX_DEPTH], /// How many values it has taken, so a bound can stop it. collected: u32, /// Which of `cmd.args` is next to fill. arg_pos: usize, /// How many words the variadic at `arg_pos` has taken, for the same reason. arg_taken: u32, /// Whether any word has been bound to a positional of `cmd`. Once one has, /// no further word can select a subcommand. arg_filled: bool, /// Whether this command has bound any flag or positional. Unlike /// `arg_filled`, flags count because clap's command policy treats both as /// arguments that exclude a later subcommand. command_arg_found: bool, positional_arg_found: bool, /// Whether flag interpretation has stopped. A `--` does this, and so does an /// `automatic` argument taking a value. flags_stopped: bool, /// Whether a `--` was actually consumed as a separator. /// /// Tracked apart from `flags_stopped` because the two can differ: an /// `automatic` argument stops flag interpretation without any separator being /// typed, and a `preserve` argument keeps one as a value rather than /// consuming it. Callers asking this question want to know what the user /// wrote, not what state the parser reached. separator_seen: bool, /// Whether a root clause separator was consumed, so empty-default routing cannot fire. clause_separator_seen: bool, /// Whether the default subcommand has already been taken. /// /// Once, per parse: a default subcommand that itself declares one would otherwise /// descend on every word until the tree ran out. default_taken: bool, /// Whether this parse reads the words before a cursor. The word under the cursor is still /// to be typed, so the line is not an empty invocation and `default_subcommand_on_empty` /// must not route away from the root's own subcommands and flags. completing: bool, /// First default-only flag, when lookahead found no explicit sibling. default_flag_at: Option, /// Cursor just after the implicit boundary bundle, whose shorts keep parent ownership. default_bundle_end: usize, /// Set once a fatal error has been reported, so iteration stops. done: bool, /// Whether declared built-in actions stop parsing with their action error. /// /// Invocation parsing does; completion walking only needs the grammar position after the /// flag, and must not execute an action while inspecting a partial command line. action_errors: bool, /// The `argv` range the `help` *word* resolved as a command path, if one was typed. /// /// Empty for `--help`, which asks about wherever the parse had got to. For the word, the /// question is about a command deeper than the parse reached, and only this walk knows /// which tokens named it: a caller re-scanning `argv` would count a flag's detached value /// that happens to spell a sibling's name. Two indices rather than the commands /// themselves, so the parser keeps allocating nothing. help_span: (usize, usize), /// An implicit clause boundary follows the positional event that completed it. pending_clause_boundary: ::core::option::Option<&'t Clause<'t>>, } impl<'t: 'v, 'a, 'v> Parser<'t, 'a, 'v> { /// Begin parsing `argv` against `root`. /// /// `argv` excludes the program name. pub fn new(root: &'t Command<'t>, argv: &'a [&'v OsStr]) -> Self { Self::with_action_errors(root, argv, true) } /// Begin a non-executing parse for completion walking. #[cfg(feature = "complete")] pub(crate) fn for_completion(root: &'t Command<'t>, argv: &'a [&'v OsStr]) -> Self { let mut parser = Self::with_action_errors(root, argv, false); parser.completing = true; parser } fn with_action_errors( root: &'t Command<'t>, argv: &'a [&'v OsStr], action_errors: bool, ) -> Self { let mut parser = Parser { argv, pos: 0, cmd: root, #[cfg(feature = "spec")] root, #[cfg(feature = "spec")] view: None, unknown_flags: match root.unknown_flags { ::core::option::Option::Some(mode) => mode, // Nothing above the root to inherit from, so the default stands. ::core::option::Option::None => UnknownFlags::Value, }, dont_delimit_trailing_values: root.dont_delimit_trailing_values, ancestors: [None; MAX_DEPTH], depth: 0, bundle: &[], bundle_token: &[], collecting: None, cmd_start: 0, starts: [0; MAX_DEPTH], collected: 0, arg_pos: 0, arg_taken: 0, arg_filled: false, command_arg_found: false, positional_arg_found: false, flags_stopped: false, separator_seen: false, clause_separator_seen: false, default_taken: false, completing: false, default_flag_at: None, default_bundle_end: 0, done: false, action_errors, help_span: (0, 0), pending_clause_boundary: ::core::option::Option::None, }; if root.default_subcommand_flags { parser.default_flag_at = parser.default_flag_route(); } parser } /// Find the implicit boundary without binding anything. Parent spellings win while /// scanning the prefix; after the boundary the ordinary child/global scope applies. /// Unknown flags stop lookahead because their value arity cannot be guessed. fn default_flag_route(&self) -> Option { let default = self.cmd.default_subcommand?; let mut at = None; let mut i = 0; while let Some(token) = self.argv.get(i).map(bytes) { let scope = if at.is_some() { default } else { self.cmd }; if token == b"--" || token == b"-" || scope.clause.is_some_and(|c| c.separator == Some(token)) { return at; } if !is_flag_like(token) { // A sibling name before any default-only flag stays on the parent. // After one, later words are the default command's args (`-u pkg` // when `pkg` is also a command). if at.is_none() && (self.find_subcommand(token).is_some() || (token == b"help" && !self.cmd.disable_help_subcommand)) { return None; } return at; } let mut value_flag = None; let mut attached = None; if let Some(body) = token.strip_prefix(b"--") { let end = body.iter().position(|b| *b == b'=').unwrap_or(body.len()); let name = slice::before(body, end); let parent = self.find_long(name).or_else(|| self.find_negation(name)); if parent.is_none() && ((name == b"help" && !self.cmd.disable_help_flag) || (name == b"version" && self.cmd.version && !self.cmd.disable_version_flag)) { i += 1; continue; } let flag = parent.or_else(|| { default.flags.iter().copied().find(|f| { f.longs.iter().any(|l| l.as_bytes() == name) || f.negate.is_some_and(|n| n.as_bytes() == name) }) }); let Some(flag) = flag else { return at; }; if parent.is_none() { at.get_or_insert(i); } if flag.takes_value && flag.negate.is_none_or(|n| n.as_bytes() != name) { value_flag = Some(flag); attached = (end < body.len()).then(|| slice::after(body, end + 1)); } } else { for (offset, byte) in slice::after(token, 1).iter().enumerate() { let parent = self.find_short(*byte); let Some(flag) = parent.or_else(|| { default .flags .iter() .copied() .find(|f| has_byte(f.shorts, *byte)) }) else { return at; }; if parent.is_none() { at.get_or_insert(i); } if flag.takes_value { value_flag = Some(flag); let rest = slice::after(token, offset + 2); attached = (!rest.is_empty()).then_some(rest.strip_prefix(b"=").unwrap_or(rest)); break; } } } i += 1; if let Some(flag) = value_flag { let scope = if at.is_some() { default } else { self.cmd }; let is_separator = |next: &[u8]| scope.clause.is_some_and(|c| c.separator == Some(next)); let first = if let Some(value) = attached { Some(value) } else if !flag.require_equals { self.argv .get(i) .map(bytes) .filter(|next| { !is_separator(next) && (flag.allow_hyphen_values || !is_flag_like(next) || (flag.allow_negative_numbers && is_negative_number(next))) }) .inspect(|_| i += 1) } else { None }; if first.is_none() && !flag.value_optional && flag.default_missing.is_none() { return at; } if flag.variadic { let mut count = first.map_or(0, |v| values_in(v, flag.delimiter)); while flag.var_max.is_none_or(|max| count < max) { let Some(next) = self.argv.get(i).map(bytes) else { break; }; if is_separator(next) { break; } if next == b"--" || flag.value_terminator.is_some_and(|end| end == next) { if next != b"--" { i += 1; } break; } if is_flag_like(next) && !(flag.allow_negative_numbers && is_negative_number(next)) { break; } if self.cmd.subcommand_precedence_over_arg && self.find_subcommand(next).is_some() { break; } count += values_in(next, flag.delimiter); i += 1; } } } } at } /// Restrict inherited root globals to those carried by an executable view. #[cfg(feature = "spec")] pub fn with_view(mut self, view: &'t spec::ViewMeta<'t>) -> Self { self.view = Some(view); self } /// The command in scope: the root, or the deepest subcommand selected so far. pub fn command(&self) -> &'t Command<'t> { self.cmd } /// Whether a `--` was consumed as a separator. /// /// False when flag interpretation stopped for another reason, such as an /// `automatic` argument taking a value, and false for a `--` that a /// `preserve` argument kept as a value. pub fn double_dash_seen(&self) -> bool { self.separator_seen } /// Every command entered so far, and where each one's own words begin. /// /// The ancestors are already kept for flag scoping; this is the same chain with the offsets, /// which is what lets a completion hand a callback the words of *its* command rather than of /// the deepest one — a global flag is declared on an ancestor. pub fn command_path(&self) -> Vec<(&'t Command<'t>, usize)> { let mut out = Vec::with_capacity(self.depth + 1); for (i, ancestor) in slice::before(&self.ancestors, self.depth) .iter() .enumerate() { if let Some(cmd) = ancestor { // An ancestor's own words start where the one before it descended, and the // root's start at the beginning. out.push((*cmd, self.starts[i])); } } out.push((self.cmd, self.cmd_start)); out } /// The `argv` range the `help` word resolved as a command path. /// /// Empty unless the word was typed. Every token in it named a subcommand of the one before /// it — the parser resolved them itself, so nothing here is a flag or a flag's value. pub fn help_span(&self) -> (usize, usize) { self.help_span } /// Where the command in scope began: the index in `argv` just after its name, or at the /// unmatched word routed into a default subcommand. /// /// `argv[command_start()..]` is what that command was given, which is what a completion /// callback needs to be handed its own command's half-parsed struct rather than the root's. pub fn command_start(&self) -> usize { self.cmd_start } /// Whether flag interpretation has stopped, for any reason. /// /// Wider than [`double_dash_seen`](Self::double_dash_seen), and the question completion /// asks: past a separator *or* past the first value of an `automatic` argument, a /// dash-prefixed word is a value, so there is no flag there to offer. pub fn flags_stopped(&self) -> bool { self.flags_stopped } /// A variadic flag that is still claiming words. /// /// Asked *between* events, because the answer is gone by the end: the call that finds argv /// exhausted is the one that clears it. A completion needs it — the next word after /// `--tools a ⌶` is another tool, not the positional that follows. pub fn collecting(&self) -> Option<&'t Flag<'t>> { self.collecting } /// The positional the next word would fill, if there is one left. /// /// A variadic stays here until it reaches its bound, which is what makes it the answer to /// "what could go where the cursor is" as many times as it can be filled. pub fn pending_arg(&self) -> Option<&'t Arg<'t>> { self.next_arg() } /// Flags a word here could name: this command's own, then any ancestor's globals. /// /// The same set the parser itself would look in, so what is offered and what is accepted /// cannot disagree — including the shadowing rule, where a subcommand redeclaring an /// inherited name hides it. pub fn flags_in_scope(&self) -> impl Iterator> + '_ { let mut all = Vec::new(); self.find_in_scope(Want::All, &mut all); all.into_iter() } /// Read the next event. /// /// Returns `None` when `argv` is exhausted. An `Err` is terminal: the parse /// stops there, since continuing past a token that could not be understood /// would only produce bindings derived from a guess. Events already yielded /// before an error are therefore not a partial result to be used — a caller /// that assigned them into fields should discard the whole attempt. /// /// One case is stronger than that, because the grammar demands it: a short /// bundle containing an unrecognized letter yields the error *instead of*, not /// after, the letters that did match. #[allow(clippy::should_implement_trait)] // not an Iterator: items borrow from self's tables pub fn next_event(&mut self) -> Option, Error<'t, 'v>>> { if self.done { return None; } let event = self.step(); if matches!(event, Some(Ok(Event::Flag { .. } | Event::Arg { .. }))) { self.command_arg_found = true; } if matches!(event, Some(Ok(Event::Arg { .. }))) { self.positional_arg_found = true; } if event.is_none() && self.depth == 0 && self.cmd.default_subcommand_on_empty && !self.default_taken && !self.completing && !self.positional_arg_found && !self.separator_seen && !self.clause_separator_seen && let Some(default) = self.cmd.default_subcommand { if self.cmd.args_conflicts_with_subcommands && self.command_arg_found { self.done = true; return Some(Err(Error::SubcommandConflict { subcommand: default, })); } self.default_taken = true; match self.descend(default) { Ok(()) => return Some(Ok(Event::Command(default))), Err(error) => { self.done = true; return Some(Err(error)); } } } if let Some(Err(_)) = event { self.done = true; } event } /// Count parent flags in the shared boundary token before entering the child. fn default_bundle_has_parent_flag(&self, default: &Command<'_>) -> bool { let Some(token) = self.argv.get(self.pos).map(bytes) else { return false; }; if !token.starts_with(b"-") || token.starts_with(b"--") { return false; } for byte in slice::after(token, 1) { if self.find_short(*byte).is_some() { return true; } match default.flags.iter().find(|f| has_byte(f.shorts, *byte)) { Some(flag) if !flag.takes_value => {} _ => break, } } false } fn step(&mut self) -> Option, Error<'t, 'v>>> { if self.default_flag_at == Some(self.pos) && self.bundle.is_empty() { self.default_flag_at = None; if let Some(default) = self.cmd.default_subcommand { if self.cmd.args_conflicts_with_subcommands && (self.command_arg_found || self.default_bundle_has_parent_flag(default)) { return Some(Err(Error::SubcommandConflict { subcommand: default, })); } if self.argv.get(self.pos).is_some_and(|word| { let token = bytes(word); token.starts_with(b"-") && !token.starts_with(b"--") }) { self.default_bundle_end = self.pos + 1; } self.default_taken = true; return Some(self.descend(default).map(|()| Event::Command(default))); } } if let Some(clause) = self.pending_clause_boundary.take() { self.arg_pos = 0; self.arg_taken = 0; self.arg_filled = false; self.collecting = None; self.flags_stopped = false; return Some(Ok(Event::ClauseSeparator { clause: *clause })); } // A partly-read short bundle takes priority: its remaining bytes are // still part of the token being processed. if !self.bundle.is_empty() { return Some(self.short_flag()); } if self.cmd.subcommand_precedence_over_arg && !self.flags_stopped && let Some(token) = self.argv.get(self.pos).map(bytes) && let Some(sub) = self.find_subcommand(token) { if self.cmd.args_conflicts_with_subcommands && self.command_arg_found { return Some(Err(Error::SubcommandConflict { subcommand: sub })); } self.pos += 1; return Some(self.descend(sub).map(|()| Event::Command(sub))); } // A variadic flag keeps claiming tokens until one of them could be // something else. if let Some(flag) = self.collecting { match self.argv.get(self.pos) { Some(next) if flag .value_terminator .is_some_and(|terminator| bytes(next) == terminator) => { self.pos += 1; self.collecting = None; return self.step(); } Some(next) if (!is_flag_like(bytes(next)) || (flag.allow_negative_numbers && is_negative_number(bytes(next)))) && self.clause_separator(bytes(next)).is_none() && bytes(next) != b"--" => { self.pos += 1; self.collected += values_in(bytes(next), flag.delimiter); // Same rule as a positional: a bounded occurrence takes that many and // leaves the rest to whatever follows. if flag.var_max.is_some_and(|max| self.collected >= max) { self.collecting = None; } // Stopping is only the same as staying within the bound while one word // is one value. A delimited word can carry the occurrence past it in a // single step, and that word cannot be split between two owners, so the // overshoot is an error rather than a place to stop. if let Some(max) = flag.var_max.filter(|max| self.collected > *max) { return Some(Err(Error::VarTooMany { name: flag.name, max: max as usize, got: self.collected as usize, })); } return Some(Ok(Event::Flag { flag, value: Some(bytes(next)), negated: false, })); } // A token that could be something else ends the run — but the *end of argv* // decides nothing. Clearing there threw away the answer to "would the next // word be claimed?", which is the question a completion asks and no parse // ever does: once argv is exhausted there are no more events either way. Some(_) => self.collecting = None, None => {} } } let token = bytes(self.argv.get(self.pos)?); self.pos += 1; // A clause separator remains syntax after an automatic positional stopped flags. // Only an explicit `--` protects a literal separator. if let Some(clause) = self.clause_separator(token) { self.arg_pos = 0; self.arg_taken = 0; self.arg_filled = false; self.collecting = None; self.flags_stopped = false; if self.depth == 0 { self.clause_separator_seen = true; } return Some(Ok(Event::ClauseSeparator { clause: *clause })); } // An automatic trailing argument stops flag interpretation without consuming an // explicit separator. A later `--` must still unlock a required trailing argument // (clap's `last`), while a separator already consumed makes every later `--` data. if self.flags_stopped && (token != b"--" || self.separator_seen) { return Some(self.word(token)); } if token == b"--" { // `preserve` wants the separator itself as a value, so ask the // argument that would receive it before treating it as syntax. if self .next_arg() .is_some_and(|a| a.double_dash == DoubleDash::Preserve) { return Some(self.word(token)); } self.flags_stopped = true; self.separator_seen = true; // An explicit separator unlocks any argument that required one, even // if earlier arguments are still unfilled. if let Some(idx) = slice::after(self.current_args(), self.arg_pos) .iter() .position(|a| a.double_dash == DoubleDash::Required) { // The count belongs to the argument at `arg_pos`, so jumping past it has // to leave the count behind: a bounded variadic before the separator would // otherwise lend its total to the argument after it, which then stops // early or at once. self.arg_pos += idx; self.arg_taken = 0; } return self.step(); } if self.arg_taken > 0 && self.next_arg().is_some_and(|arg| { arg.value_terminator .is_some_and(|terminator| token == terminator) }) { self.advance_arg(); return self.step(); } // An exact declared short outranks the numeric shape. This keeps ordinary negative // numbers available as values while allowing clap-compatible spellings such as fd's // `-0` / `--print0` switch. let declared_numeric_short = matches!(token, [b'-', short] if short.is_ascii_digit() && self.find_short(*short).is_some()); if !declared_numeric_short && is_negative_number(token) && self .next_arg() .is_some_and(|arg| arg.allow_negative_numbers) { return Some(self.word(token)); } if !declared_numeric_short && is_negative_number(token) && self.cmd.external_subcommand && !self.arg_filled { return Some(self.word(token)); } if is_flag_like(token) { if token.starts_with(b"--") { return Some(self.long_flag(token)); } // Check the whole bundle before emitting anything from it. Events go // out one at a time, so discovering an unknown letter half way // through would mean the earlier letters had already been applied — // and the grammar rejects the entire token, not the tail of it. match self.check_bundle(token) { Ok(()) => {} // Unrecognized, so it is a word unless this command wants it refused. Err(e) if self.unknown_flags == UnknownFlags::Error => { return Some(Err(e)); } Err(_) => return Some(self.word(token)), } self.bundle = slice::after(token, 1); self.bundle_token = token; return Some(self.short_flag()); } Some(self.word(token)) } fn long_flag(&mut self, token: &'v [u8]) -> Result, Error<'t, 'v>> { let body = slice::after(token, 2); let (name, attached) = match body.iter().position(|&b| b == b'=') { Some(i) => (slice::before(body, i), Some(slice::after(body, i + 1))), None => (body, None), }; if let Some(flag) = self.find_long(name) { let value = if flag.takes_value { match attached { Some(v) => Some(v), None => self.take_detached_value(flag)?, } } else if flag.bool_value { validate_bool_value(flag, attached)? } else { None }; if flag.variadic && let Some(value) = value { self.start_collecting(flag, value)?; } if let Some(error) = self.flag_action(flag, true) { return Err(error); } return Ok(Event::Flag { flag, value, negated: false, }); } if let Some(flag) = self.find_negation(name) { return Ok(Event::Flag { flag, value: if flag.bool_value { validate_bool_value(flag, attached)? } else { None }, negated: true, }); } // Where the CLI declared a version, `--version` answers with it — asked after the // command's own flags, so a CLI declaring its own keeps it. let version_command = self.version_command(); if name == b"version" && version_command.version && !version_command.disable_version_flag { return Ok(Event::Flag { flag: &VERSION_LONG, value: None, negated: false, }); } // Every CLI answers to `--help`, and none of them declares it. Asked *after* the // command's own flags, so a CLI that declares its own `--help` keeps it. if name == b"help" && !self.cmd.disable_help_flag { return Ok(Event::Flag { flag: &HELP_LONG, value: None, negated: false, }); } if self.unknown_flags == UnknownFlags::Error { return Err(Error::UnknownFlag { token }); } // Not a flag here, so it is a word like any other. self.word(token) } /// Walk a short-flag token without binding anything, to find out whether all /// of it is recognized. /// /// Scanning stops at the first letter whose flag takes a value, because /// everything after it is that value rather than more letters. fn check_bundle(&self, token: &'v [u8]) -> Result<(), Error<'t, 'v>> { let mut rest = slice::after(token, 1); while let Some((&byte, tail)) = rest.split_first() { match self.find_short(byte) { None => return Err(Error::UnknownFlag { token }), Some(flag) if flag.takes_value => return Ok(()), Some(_) => rest = tail, } } Ok(()) } fn short_flag(&mut self) -> Result, Error<'t, 'v>> { let (byte, rest) = match self.bundle.split_first() { Some((&byte, rest)) => (byte, rest), None => (0, &[][..]), }; let Some(flag) = self.find_short(byte) else { // check_bundle already rejected any token containing an unrecognized // letter, so this is unreachable — but a parser should report rather // than panic if that ever stops being true. self.bundle = &[]; return Err(Error::UnknownFlag { token: self.bundle_token, }); }; if !flag.takes_value { self.bundle = rest; if let Some(error) = self.flag_action(flag, false) { self.bundle = &[]; return Err(error); } return Ok(Event::Flag { flag, value: None, negated: false, }); } // A value-taking short ends the token: everything after it is the value, // less one separating `=`. self.bundle = &[]; let value = match rest.split_first() { None => self.take_detached_value(flag)?, Some((b'=', value)) => Some(value), Some(_) => Some(rest), }; if flag.variadic && let Some(value) = value { self.start_collecting(flag, value)?; } if let Some(error) = self.flag_action(flag, false) { return Err(error); } Ok(Event::Flag { flag, value, negated: false, }) } #[inline(never)] fn flag_action(&self, flag: &'t Flag<'t>, long_spelling: bool) -> Option> { if matches!( flag.key, HELP_LONG_KEY | HELP_SHORT_KEY | VERSION_LONG_KEY | VERSION_SHORT_KEY ) || !self.action_errors { return None; } match flag.action { ArgAction::Set => None, ArgAction::Help => Some(Error::Help { cmd: self.cmd, long: long_spelling, }), ArgAction::HelpShort => Some(Error::Help { cmd: self.cmd, long: false, }), ArgAction::HelpLong => Some(Error::Help { cmd: self.cmd, long: true, }), ArgAction::HelpAll => Some(Error::HelpAll { cmd: self.cmd }), ArgAction::Version => Some(Error::Version { long: long_spelling, }), } } /// Take the following token as a flag's value. /// /// Refuses a flag-like token unless [`Flag::allow_hyphen_values`] is set: /// `--jobs --force` is far more likely a forgotten value than a deliberate /// one, and the attached form is available for the deliberate case. Declared, /// the next token is taken whatever it looks like, including `--`. fn take_detached_value( &mut self, flag: &'t Flag<'t>, ) -> Result, Error<'t, 'v>> { if flag.require_equals { return self.missing_or_default(flag); } match self.argv.get(self.pos) { Some(next) if self.clause_separator(bytes(next)).is_none() && (flag.allow_hyphen_values || !is_flag_like(bytes(next)) || (flag.allow_negative_numbers && is_negative_number(bytes(next)))) => { self.pos += 1; Ok(Some(bytes(next))) } _ => self.missing_or_default(flag), } } fn missing_or_default(&self, flag: &'t Flag<'t>) -> Result, Error<'t, 'v>> { match flag.default_missing { Some(value) => Ok(Some(value)), None if flag.value_optional => Ok(None), None => Err(Error::MissingFlagValue { flag }), } } #[inline(never)] fn clause_separator(&self, token: &[u8]) -> Option<&'t Clause<'t>> { (!self.separator_seen) .then_some(self.cmd.clause) .flatten() .filter(|clause| clause.separator.is_some_and(|separator| token == separator)) } fn word(&mut self, token: &'v [u8]) -> Result, Error<'t, 'v>> { // Subcommands are only matched where descent is still possible: once a // positional of this command has taken a word, a later word that happens // to equal a subcommand name is just a value. if !self.arg_filled && !self.flags_stopped { if let Some(sub) = self.find_subcommand(token) { if self.cmd.args_conflicts_with_subcommands && self.command_arg_found { return Err(Error::SubcommandConflict { subcommand: sub }); } self.descend(sub)?; return Ok(Event::Command(sub)); } // `ex help config ls` — the line every page with a Commands section has printed // all along ("help Print this message or the help of the given subcommand(s)"), // and which until now did nothing. The page is what decides the condition here: // it prints that line where there are subcommands, so that is where the word is // answered, and to a leaf `help` is a word like any other. // // Asked *after* the subcommand lookup, so a CLI that declares a `help` of its own // keeps it — the same rule the two help flags follow. // // The words after it name a command, resolved here rather than descended into: // descending would bind them, and they are a question rather than an invocation. if token == b"help" && !self.cmd.disable_help_subcommand && !self.cmd.subcommands.is_empty() { let mut cmd = self.cmd; let from = self.pos; while let Some(next) = self.argv.get(self.pos) { let Some(sub) = find_named(cmd, bytes(next)) else { break; }; cmd = sub; self.pos += 1; } // Kept for `help::route_to`: which mount was asked about is not recoverable // from `cmd`, since two mounts of one `Subcommands` type are one address. self.help_span = (from, self.pos); // The long form, as `ex config --help` gives: someone who typed a whole word to // ask for help wants the fuller answer. return Err(Error::Help { cmd, long: true }); } // A word that names no subcommand goes to the default one, if there is one. // // Only a word, though. A dash-prefixed token that named no flag arrives here as a // value — that is what `unknown_flags = value` means — and it was never a // candidate to *select* anything, so it binds where it was typed. usage-lib stops // looking for subcommands at an unrecognised flag for the same reason. `--` is // excluded on the same grounds: it reaches this function only when a `preserve` // argument wants it as a value. // // The token is *not* consumed: the cursor steps back so the next event reads it // again, now against the command just descended into. That is what lets it be a // subcommand of the default (`mise build` where `build` is a task the mount // added) as easily as an argument of it, without this function having to decide // which — and without yielding two events for one word. if let Some(default) = self.cmd.default_subcommand { // `-` joins `--` in being excluded, and for the reason already written above: // a value was never a candidate to *select* anything. `is_flag_like` calls a // lone `-` a value — conventionally stdin — so it passed this guard and // descended, where mise's `run` has no positional and the parse failed. // usage-lib and clap both bind it to the root's own `[TASK]` instead. let default_accepts_negative = is_negative_number(token) && default .args .first() .is_some_and(|arg| arg.allow_negative_numbers); if !self.default_taken && (!is_flag_like(token) || default_accepts_negative) && token != b"--" && token != b"-" { self.default_taken = true; self.descend(default)?; self.pos -= 1; // Unlike an explicitly named command, the default command receives the // word that caused descent. Keep its argv boundary at that word so // command-level policies and completion callbacks see the same input the // command parser is about to re-read. self.cmd_start = self.pos; return Ok(Event::Command(default)); } } // Known subcommands and the default route keep precedence. A sigil positional // then claims its classified word before an external-subcommand catch-all can. if let Some((arg, sigil)) = self.match_sigil_arg(token) { if token.len() == sigil.len() { return Err(sigil_without_value(arg, token, sigil)); } return Ok(Event::Arg { arg, value: slice::after(token, sigil.len()), delimit: true, }); } // An unmatched word that names no subcommand is forwarded as an external // command: this word, then every token after it, including flags. Known // subcommands already won above, and a default_subcommand already caught. if self.cmd.external_subcommand && (!is_flag_like(token) || is_negative_number(token)) && token != b"--" && token != b"-" { let from = self.pos - 1; self.pos = self.argv.len(); return Ok(Event::External { values: slice::after(self.argv, from), }); } } if self.arg_filled && !self.flags_stopped && let Some((arg, sigil)) = self.match_sigil_arg(token) { if token.len() == sigil.len() { return Err(sigil_without_value(arg, token, sigil)); } return Ok(Event::Arg { arg, value: slice::after(token, sigil.len()), delimit: true, }); } self.skip_sigil_args(); self.reserve_for_required_positionals(); let Some(arg) = self.next_arg() else { return Err(Error::UnexpectedArg { token }); }; if arg.double_dash == DoubleDash::Required && !self.separator_seen { return Err(Error::ArgRequiresDoubleDash { arg }); } self.arg_filled = true; // An `automatic` argument stops flag interpretation from here on, as // though the caller had typed the separator themselves. let trailing_value = self.separator_seen || arg.double_dash == DoubleDash::Automatic; let delimit = !(self.dont_delimit_trailing_values && trailing_value); if arg.double_dash == DoubleDash::Automatic { self.flags_stopped = true; } // A variadic keeps taking values, so the cursor stays put — until it reaches its // bound, at which point the words after it belong to whatever comes next. That is // what makes `[a]… [b]` expressible at all. if arg.var { self.arg_taken += values_in(token, delimit.then_some(arg.delimiter).flatten()); // Before advancing, which resets the count: as with a variadic flag, reaching // the bound and passing it are the same event once a word can carry several // values, and only the second is a mistake. if let Some(max) = arg.var_max.filter(|max| self.arg_taken > *max) { return Err(Error::VarTooMany { name: arg.name, max: max as usize, got: self.arg_taken as usize, }); } if arg.var_max.is_some_and(|max| self.arg_taken >= max) { self.advance_arg(); } } else { self.advance_arg(); } if self .cmd .clause .is_some_and(|clause| clause.separator.is_none() && self.next_arg().is_none()) { self.pending_clause_boundary = self.cmd.clause; } Ok(Event::Arg { arg, value: token, delimit, }) } #[inline(never)] fn descend(&mut self, sub: &'t Command<'t>) -> Result<(), Error<'t, 'v>> { if self.depth >= MAX_DEPTH { return Err(Error::TooDeep); } self.ancestors[self.depth] = Some(self.cmd); self.starts[self.depth] = self.cmd_start; self.depth += 1; self.cmd = sub; // Only a command that says something changes it, which is what inheriting means. if let ::core::option::Option::Some(mode) = sub.unknown_flags { self.unknown_flags = mode; } self.dont_delimit_trailing_values |= sub.dont_delimit_trailing_values; // Where this command's own words start, which is what lets a completion hand a callback // the half-parsed struct of the command it was declared on rather than of the root. self.cmd_start = self.pos; self.arg_pos = 0; self.arg_taken = 0; self.arg_filled = false; self.command_arg_found = false; Ok(()) } /// Move to the next positional, forgetting what the last one took. #[inline(never)] fn advance_arg(&mut self) { self.skip_sigil_args(); self.arg_pos += 1; self.arg_taken = 0; self.skip_sigil_args(); } /// A variadic flag occurrence begins, counting from zero. /// /// The value it was given on the same token counts, which is why this starts at what /// that value holds: `--include a b` with `var_max=2` takes `a` and `b`, not three /// words — and `--include a,b` has already taken both on the one token. #[inline(never)] fn start_collecting(&mut self, flag: &'t Flag<'t>, first: &[u8]) -> Result<(), Error<'t, 'v>> { self.collected = values_in(first, flag.delimiter); if let Some(max) = flag.var_max.filter(|max| self.collected > *max) { return Err(Error::VarTooMany { name: flag.name, max: max as usize, got: self.collected as usize, }); } self.collecting = if flag.var_max.is_some_and(|max| self.collected >= max) { None } else { Some(flag) }; Ok(()) } #[inline(never)] fn next_arg(&self) -> Option<&'t Arg<'t>> { slice::after(self.current_args(), self.arg_pos) .iter() .find(|arg| arg.sigil.is_none()) .copied() } fn skip_sigil_args(&mut self) { while self .current_args() .get(self.arg_pos) .is_some_and(|arg| arg.sigil.is_some()) { self.arg_pos += 1; } } fn match_sigil_arg(&self, token: &[u8]) -> Option<(&'t Arg<'t>, &'t [u8])> { if self.flags_stopped { return None; } // The longest sigil that fits, the later declaration winning a tie, as `max_by_key` // would say — written as loops, which are smaller than the adapter chain and are // walked for every positional word. let mut best: Option<(&'t Arg<'t>, &'t [u8])> = None; let mut consider = |args: &'t [&'t Arg<'t>]| { for &arg in args { if let Some(sigil) = arg.sigil && token.starts_with(sigil) && best.is_none_or(|(_, longest)| sigil.len() >= longest.len()) { best = Some((arg, sigil)); } } }; consider(self.current_args()); for cmd in slice::before(&self.ancestors, self.depth) .iter() .rev() .flatten() { consider(cmd.args); } best } fn current_args(&self) -> &'t [&'t Arg<'t>] { self.cmd .clause .map(|clause| clause.args) .unwrap_or(self.cmd.args) } /// Skip empty optional positionals when every remaining value is needed by a later /// required positional. This is clap's opt-in `allow_missing_positional` policy. fn reserve_for_required_positionals(&mut self) { if !self.cmd.allow_missing_positional || self.arg_taken != 0 { return; } loop { let Some(current) = self.next_arg() else { return; }; if current.required { return; } let required_after = slice::after(self.current_args(), self.arg_pos + 1) .iter() .filter(|arg| arg.required && arg.sigil.is_none()) .count(); if required_after == 0 { return; } let remaining_values = 1 + slice::after(self.argv, self.pos) .iter() .filter(|word| { (self.flags_stopped || !is_flag_like(bytes(word))) && self.match_sigil_arg(bytes(word)).is_none() }) .count(); if remaining_values > required_after { return; } self.advance_arg(); } } #[cfg(feature = "spec")] fn view_allows_own_flag(&self, flag: &Flag<'_>) -> bool { match self.view { None => true, // The promoted command keeps its own surface. While the injected path is // still at the host root, however, only explicitly carried globals belong // to the view; root-local flags are not part of the projected executable. Some(view) => { !core::ptr::eq(self.cmd, self.root) || is_version_flag(flag) || view.carries(flag) } } } #[cfg(not(feature = "spec"))] fn view_allows_own_flag(&self, _flag: &Flag<'_>) -> bool { true } #[cfg(feature = "spec")] fn view_allows_inherited_flag(&self, flag: &Flag<'_>) -> bool { match self.view { None => true, // A portable view carries selected host globals, not globals declared // by intermediate commands on a multi-segment promoted path. Some(view) => { self.root .flags .iter() .any(|root| core::ptr::eq(*root, flag)) && (is_version_flag(flag) || view.carries(flag)) } } } #[cfg(not(feature = "spec"))] fn view_allows_inherited_flag(&self, _flag: &Flag<'_>) -> bool { true } #[cfg(feature = "spec")] fn inherited_flag_is_in_scope(&self, flag: &Flag<'_>) -> bool { flag.global || (self.view.is_some() && is_version_flag(flag)) } #[cfg(not(feature = "spec"))] fn inherited_flag_is_in_scope(&self, flag: &Flag<'_>) -> bool { flag.global } /// Walk the flags in scope: this command's own, then any ancestor's globals. /// /// Own flags come first so that a subcommand redeclaring an inherited name /// shadows it, which is what mise relies on when it redeclares root globals /// on `run` with different shorts. /// /// The first flag that answers to `want`, or none; with [`Want::All`] every flag in scope /// is appended to `all` instead. One loop for the three ways a flag is looked up and for /// listing them, rather than an iterator chain compiled once per use: a chain of filters /// and a flat map inlined at each lookup was the larger part of what a lookup cost in code. /// What is matched is a branch on a value the loop does not change. #[inline(never)] fn find_in_scope(&self, want: Want<'_>, all: &mut Vec<&'t Flag<'t>>) -> Option<&'t Flag<'t>> { for &flag in self.cmd.flags { if self.view_allows_own_flag(flag) && visit(want, all, flag) { return Some(flag); } } for ancestor in slice::before(&self.ancestors, self.depth) .iter() .rev() .flatten() { for &flag in ancestor.flags { if self.inherited_flag_is_in_scope(flag) && self.view_allows_inherited_flag(flag) && visit(want, all, flag) { return Some(flag); } } } None } fn find_long(&self, name: &[u8]) -> Option<&'t Flag<'t>> { self.find_in_scope(Want::Long(name), &mut Vec::new()) } fn find_negation(&self, name: &[u8]) -> Option<&'t Flag<'t>> { self.find_in_scope(Want::Negation(name), &mut Vec::new()) } fn find_short(&self, byte: u8) -> Option<&'t Flag<'t>> { // Only the boundary token is shared. Later tokens use ordinary child/global scope. if self.default_taken && self.pos == self.default_bundle_end && let Some(flag) = self.ancestors[0].and_then(|parent| { parent .flags .iter() .copied() .find(|f| has_byte(f.shorts, byte)) }) { return Some(flag); } self.find_in_scope(Want::Short(byte), &mut Vec::new()) // As for `--help`: supplied by the parser, and only where the command has not // declared a `-h` of its own. .or(if byte == b'h' && !self.cmd.disable_help_flag { Some(&HELP_SHORT) } else if byte == b'V' && self.version_command().version && !self.version_command().disable_version_flag { Some(&VERSION_SHORT) } else { None }) } #[cfg(feature = "spec")] fn version_command(&self) -> &'t Command<'t> { if self.view.is_some() { self.root } else { self.cmd } } #[cfg(not(feature = "spec"))] fn version_command(&self) -> &'t Command<'t> { self.cmd } fn find_subcommand(&self, name: &[u8]) -> Option<&'t Command<'t>> { // Shared with `help` rather than spelled out again, so descending into a command and // asking about one cannot drift apart. find_named(self.cmd, name) } } /// One flag met on a walk of the flags in scope: whether it answers to `want`, or, for /// [`Want::All`], noted in `all`. Always inlined, so that the walk's loop carries the test itself /// and not a call to it for every flag. #[inline(always)] fn visit<'t>(want: Want<'_>, all: &mut Vec<&'t Flag<'t>>, f: &'t Flag<'t>) -> bool { match want { Want::Long(name) => f.longs.iter().any(|l| l.as_bytes() == name), Want::Negation(name) => f.negate.is_some_and(|n| n.as_bytes() == name), Want::Short(byte) => has_byte(f.shorts, byte), Want::All => { all.push(f); false } } } /// What a walk of the flags in scope is for: a long spelling, a negation, a short letter, or all. #[derive(Clone, Copy)] enum Want<'a> { Long(&'a [u8]), Negation(&'a [u8]), Short(u8), All, } /// Whether `bytes` holds `wanted`. /// /// A loop of its own rather than `<[u8]>::contains`, which expands at each call to a /// word-at-a-time search tuned for long slices. A flag has one or two short letters. #[inline] pub(crate) fn has_byte(bytes: &[u8], wanted: u8) -> bool { for &byte in bytes { if byte == wanted { return true; } } false } /// Whether `list` holds the name `wanted`. One copy for every place a table of names is searched: /// `<[&str]>::contains` expands to its own comparison loop at each of them. #[cfg(any(feature = "complete", feature = "diagnostics"))] #[inline(never)] pub(crate) fn has_str(list: &[&str], wanted: &str) -> bool { for item in list { if *item == wanted { return true; } } false } /// View a token as bytes. /// /// `as_encoded_bytes` is a plain accessor with no conversion and no allocation. /// The reverse direction is the one with a cost — see [`os_string_from_bytes`] — /// which is why values come back as bytes. fn bytes<'v>(s: &&'v OsStr) -> &'v [u8] { s.as_encoded_bytes() } /// How many values one word carries. /// /// One, until a delimiter is declared — and then one per separator, counting the same way /// splitting on it does: `a,b` is two, `a,` is two with an empty second, and `` is one. /// Counted rather than split because binding only needs the number, and the split itself /// belongs to the layer that owns the values. #[inline] fn values_in(word: &[u8], delimiter: ::core::option::Option) -> u32 { match delimiter { Some(d) => values_delimited(word, d), None => 1, } } /// [`values_in`] for a word that has a delimiter to count. Out of line: counting bytes is a loop /// the optimizer widens into vector code, and inlined at each of the places a word is bound it /// was most of what they weighed. #[inline(never)] fn values_delimited(word: &[u8], delimiter: u8) -> u32 { 1 + word.iter().filter(|b| **b == delimiter).count() as u32 } /// Whether a token should be read as a flag. /// /// `-` alone is a value, conventionally stdin. Other dash-prefixed tokens are /// flag-like; a field may make the narrower negative-number exception. fn is_flag_like(token: &[u8]) -> bool { matches!(token, [b'-', rest @ ..] if !rest.is_empty()) } fn is_negative_number(token: &[u8]) -> bool { token.strip_prefix(b"-").is_some_and(is_number) } /// Whether the text after a `-` is a number, so `-1`, `-2.5`, and `-1e5` are values /// while `-1x` is a flag-shaped token that names nothing. /// /// Digits, at most one `.`, and an optional exponent. Deliberately narrower than /// `f64::from_str`, which also accepts `inf` and `NaN` — `-inf` is far likelier to be /// a misspelled flag than a number somebody meant to pass. /// /// usage-lib applies the same rule, and the corpus pins the edges so the two cannot /// drift apart: they disagreed about `-1e5` when this was a hand-rolled scanner on /// one side and a float parse on the other. /// /// Written out rather than deferred to `f64::from_str` because this runs on the hot /// path, and a parse would mean a UTF-8 check on a slice already decided by its /// bytes. #[inline(never)] fn is_number(rest: &[u8]) -> bool { // One pass: the mantissa's digits and at most one dot, then an optional exponent. let mut at = 0; let mut digits = 0; let mut seen_dot = false; while let Some(&b) = rest.get(at) { if b.is_ascii_digit() { digits += 1; } else if b == b'.' && !seen_dot { seen_dot = true; } else { break; } at += 1; } if digits == 0 { return false; } let Some(&marker) = rest.get(at) else { return true; }; if marker != b'e' && marker != b'E' { return false; } // An exponent needs digits of its own, and may carry a sign. let mut exponent = slice::after(rest, at + 1); if let [b'+' | b'-', tail @ ..] = exponent { exponent = tail; } if exponent.is_empty() { return false; } for b in exponent { if !b.is_ascii_digit() { return false; } } true } fn validate_bool_value<'t, 'v>( flag: &'t Flag<'t>, value: Option<&'v [u8]>, ) -> Result, Error<'t, 'v>> { match value { None | Some(b"true" | b"false") => Ok(value), Some(_) => Err(Error::InvalidChoice { name: flag.name, choices: &["true", "false"], }), } } #[cfg(test)] mod tests { use super::*; #[test] fn clause_separator_resets_inner_args_and_survives_automatic_mode() { static TASK: Arg = Arg { key: 91, name: "task", ..Arg::REQUIRED }; static REST: Arg = Arg { key: 92, name: "args", double_dash: DoubleDash::Automatic, ..Arg::VAR }; static ROOT: Command = Command { name: "ex", clause: Some(&Clause { key: 90, name: "tasks", separator: Some(b":::"), flags: &[], args: &[&TASK, &REST], }), ..Command::EMPTY }; let argv = [ OsStr::new("lint"), OsStr::new("--fix"), OsStr::new(":::"), OsStr::new("test"), ]; let mut parser = Parser::new(&ROOT, &argv); let mut seen = Vec::new(); while let Some(event) = parser.next_event() { match event.expect("valid clause") { Event::Arg { arg, value, .. } => { seen.push((arg.name, String::from_utf8_lossy(value).into_owned())) } Event::ClauseSeparator { clause } => seen.push((clause.name, ":::".into())), _ => {} } } assert_eq!( seen, [ ("task", "lint".into()), ("args", "--fix".into()), ("tasks", ":::".into()), ("task", "test".into()) ] ); } #[test] fn clause_separator_is_not_a_scoped_flag_value() { static POSTINSTALL: Flag = Flag { key: 93, name: "postinstall", longs: &["postinstall"], takes_value: true, variadic: true, ..Flag::BOOL }; static TOOL: Arg = Arg { key: 94, name: "tool", ..Arg::REQUIRED }; static ROOT: Command = Command { name: "ex", flags: &[&POSTINSTALL], clause: Some(&Clause { key: 95, name: "tools", separator: Some(b":::"), flags: &[&POSTINSTALL], args: &[&TOOL], }), ..Command::EMPTY }; let argv = [OsStr::new("--postinstall"), OsStr::new(":::")]; let mut parser = Parser::new(&ROOT, &argv); assert!(matches!( parser.next_event(), Some(Err(Error::MissingFlagValue { flag })) if flag.key == POSTINSTALL.key )); let argv = [ OsStr::new("--postinstall"), OsStr::new("setup"), OsStr::new(":::"), OsStr::new("tool"), ]; let mut parser = Parser::new(&ROOT, &argv); assert!(matches!( parser.next_event(), Some(Ok(Event::Flag { value: Some(b"setup"), .. })) )); assert!(matches!( parser.next_event(), Some(Ok(Event::ClauseSeparator { clause })) if clause.key == 95 )); assert!(matches!( parser.next_event(), Some(Ok(Event::Arg { value: b"tool", .. })) )); } static FORCE: Flag = Flag { key: 1, longs: &["force"], shorts: b"f", ..Flag::BOOL }; static EXPLICIT_BOOL: Flag = Flag { key: 20, name: "color", longs: &["color"], negate: Some("no-color"), bool_value: true, ..Flag::BOOL }; static EXPLICIT_BOOL_ROOT: Command = Command { name: "ex", flags: &[&EXPLICIT_BOOL], ..Command::EMPTY }; static JOBS: Flag = Flag { key: 2, longs: &["jobs"], shorts: b"j", allow_negative_numbers: true, ..Flag::VALUE }; static COLOR: Flag = Flag { key: 3, longs: &["color"], negate: Some("no-color"), ..Flag::BOOL }; static VERBOSE: Flag = Flag { key: 4, longs: &["verbose"], shorts: b"v", global: true, ..Flag::BOOL }; static FILE: Arg = Arg { key: 10, name: "file", allow_negative_numbers: true, ..Arg::REQUIRED }; static REST: Arg = Arg { key: 11, name: "rest", ..Arg::VAR }; static INSTALL: Command = Command { name: "install", aliases: &["i"], flags: &[&FORCE], key: 100, ..Command::EMPTY }; /// Same shape as ROOT, but a CLI that owns all of its flags. The subcommand says /// nothing and inherits it, which is the point: only the root declares the mode. static STRICT_INSTALL: Command = Command { name: "install", aliases: &["i"], flags: &[&FORCE], key: 100, ..Command::EMPTY }; static STRICT: Command = Command { name: "ex", flags: &[&FORCE, &JOBS, &COLOR, &VERBOSE], args: &[&FILE, &REST], subcommands: &[&STRICT_INSTALL], unknown_flags: Some(UnknownFlags::Error), ..Command::EMPTY }; static ROOT: Command = Command { name: "ex", flags: &[&FORCE, &JOBS, &COLOR, &VERBOSE], args: &[&FILE, &REST], subcommands: &[&INSTALL], ..Command::EMPTY }; static ARGUMENT_CONFLICT: Command = Command { name: "ex", flags: &[&FORCE], subcommands: &[&INSTALL], args_conflicts_with_subcommands: true, ..Command::EMPTY }; // A CLI shaped exactly like mise's root: a default subcommand, a positional of its own, // and a subcommand under the default — which is the arrangement that tells routing from // a plain positional. static TASK: Arg = Arg { key: 20, name: "task", ..Arg::REQUIRED }; static RUN_TASK: Arg = Arg { key: 21, name: "run_task", ..Arg::REQUIRED }; static DEEP: Command = Command { name: "deep", args: &[&RUN_TASK], key: 203, ..Command::EMPTY }; static LINT: Command = Command { name: "lint", subcommands: &[&DEEP], // A default of its own, so that a parse which forgot it had already taken one would // have somewhere to go. Nothing else in these fixtures can show the latch working. default_subcommand: Some(&DEEP), key: 202, ..Command::EMPTY }; static RUN: Command = Command { name: "run", args: &[&RUN_TASK], subcommands: &[&LINT], key: 200, ..Command::EMPTY }; static DEFAULTING: Command = Command { name: "mise", flags: &[&VERBOSE], args: &[&TASK], subcommands: &[&RUN, &INSTALL], default_subcommand: Some(find_subcommand(&[&RUN, &INSTALL], "run")), ..Command::EMPTY }; /// Collect every event, or the first error. fn parse<'t: 'v, 'v>( root: &'t Command<'t>, argv: &'v [&'v OsStr], ) -> Result>, Error<'t, 'v>> { let mut parser = Parser::new(root, argv); let mut events = Vec::new(); while let Some(event) = parser.next_event() { events.push(event?); } Ok(events) } fn argv(tokens: [&str; N]) -> [&OsStr; N] { tokens.map(OsStr::new) } #[test] fn long_boolean() { let a = argv(["--force"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Flag { flag: &FORCE, value: None, negated: false }] ); } #[test] fn long_boolean_accepts_only_opted_in_attached_values() { for (token, negated, value) in [ ("--color=false", false, b"false".as_slice()), ("--color=true", false, b"true".as_slice()), ("--no-color=false", true, b"false".as_slice()), ] { let a = argv([token]); assert_eq!( parse(&EXPLICIT_BOOL_ROOT, &a).unwrap(), vec![Event::Flag { flag: &EXPLICIT_BOOL, value: Some(value), negated, }] ); } let a = argv(["--color=maybe"]); assert!(matches!( parse(&EXPLICIT_BOOL_ROOT, &a), Err(Error::InvalidChoice { name: "color", .. }) )); let a = argv(["--force=false"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Flag { flag: &FORCE, value: None, negated: false, }] ); } #[test] fn long_value_forms() { for tokens in [vec!["--jobs=8"], vec!["--jobs", "8"]] { let a: Vec<&OsStr> = tokens.iter().map(|t| OsStr::new(*t)).collect(); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Flag { flag: &JOBS, value: Some(b"8"), negated: false }], "{tokens:?}" ); } } #[test] fn long_value_keeps_later_equals() { let a = argv(["--jobs=a=b"]); let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else { panic!("expected a flag"); }; assert_eq!(value, Some(&b"a=b"[..])); } #[test] fn long_value_attached_empty_is_empty_not_absent() { let a = argv(["--jobs="]); let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else { panic!("expected a flag"); }; assert_eq!(value, Some(&b""[..])); } #[test] fn long_value_refuses_flaglike_next_word() { let a = argv(["--jobs", "--force"]); assert_eq!( parse(&ROOT, &a), Err(Error::MissingFlagValue { flag: &JOBS }) ); } #[test] fn long_value_accepts_negative_number() { let a = argv(["--jobs", "-1"]); let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else { panic!("expected a flag"); }; assert_eq!(value, Some(&b"-1"[..])); } #[test] fn missing_optional_positional_reserves_the_last_word() { static OPTIONAL: Arg = Arg { key: 90, name: "optional", required: false, ..Arg::REQUIRED }; static REQUIRED: Arg = Arg { key: 91, name: "required", ..Arg::REQUIRED }; static CMD: Command = Command { name: "ex", args: &[&OPTIONAL, &REQUIRED], allow_missing_positional: true, ..Command::EMPTY }; let one = argv(["value"]); assert_eq!( parse(&CMD, &one).unwrap(), vec![Event::Arg { arg: &REQUIRED, value: b"value", delimit: true }] ); let two = argv(["optional", "required"]); assert_eq!( parse(&CMD, &two).unwrap(), vec![ Event::Arg { arg: &OPTIONAL, value: b"optional", delimit: true }, Event::Arg { arg: &REQUIRED, value: b"required", delimit: true }, ] ); } #[test] fn negative_numbers_are_narrowly_opted_in() { static PLAIN: Flag = Flag { key: 90, name: "plain", longs: &["plain"], ..Flag::VALUE }; static VALUE: Arg = Arg { key: 91, name: "value", ..Arg::REQUIRED }; static CMD: Command = Command { name: "ex", flags: &[&PLAIN], args: &[&VALUE], unknown_flags: Some(UnknownFlags::Error), ..Command::EMPTY }; let flag = argv(["--plain", "-1"]); assert_eq!( parse(&CMD, &flag), Err(Error::MissingFlagValue { flag: &PLAIN }) ); let positional = argv(["-1"]); assert_eq!( parse(&CMD, &positional), Err(Error::UnknownFlag { token: b"-1" }) ); } #[test] fn an_exact_declared_digit_short_outranks_a_negative_number() { static PRINT0: Flag = Flag { key: 92, name: "print0", shorts: b"0", ..Flag::BOOL }; static VALUE: Arg = Arg { key: 93, name: "value", required: false, allow_negative_numbers: true, ..Arg::REQUIRED }; static CMD: Command = Command { name: "fd", flags: &[&PRINT0], args: &[&VALUE], unknown_flags: Some(UnknownFlags::Error), ..Command::EMPTY }; assert_eq!( parse(&CMD, &argv(["-0"])), Ok(vec![Event::Flag { flag: &PRINT0, value: None, negated: false, }]) ); assert!(matches!( parse(&CMD, &argv(["-1"])), Ok(events) if matches!(events.as_slice(), [Event::Arg { value: b"-1", .. }]) )); } #[test] fn negation_of_value_flag_does_not_consume_a_value() { static MODE: Flag = Flag { key: 9, name: "mode", longs: &["mode"], negate: Some("no-mode"), ..Flag::VALUE }; static NEGATED_VALUE: Command = Command { name: "ex", flags: &[&MODE], args: &[&FILE], ..Command::EMPTY }; let a = argv(["--no-mode", "input"]); assert_eq!( parse(&NEGATED_VALUE, &a).unwrap(), vec![ Event::Flag { flag: &MODE, value: None, negated: true }, Event::Arg { arg: &FILE, value: b"input", delimit: true, } ] ); } #[test] fn no_abbreviation() { // A prefix names no flag, so by default it is a value like any other word. let a = argv(["--forc"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Arg { arg: &FILE, value: b"--forc", delimit: true, }] ); // And a CLI that owns its flags hears about it, which is the whole reason // the strict mode exists. assert!(matches!( parse(&STRICT, &a), Err(Error::UnknownFlag { token: b"--forc" }) )); } #[test] fn an_unknown_flag_is_a_value_by_default() { // The default, and the case it is for: a command line being forwarded to // something whose flags this spec does not know. let a = argv(["--wat", "keep"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![ Event::Arg { arg: &FILE, value: b"--wat", delimit: true, }, Event::Arg { arg: &REST, value: b"keep", delimit: true, }, ] ); // With nowhere to put it, it is an unexpected argument — the same error an // extra word gets, rather than a special one about flags. static ONE: Command = Command { name: "ex", args: &[&FILE], ..Command::EMPTY }; let a = argv(["a", "--wat"]); assert_eq!( parse(&ONE, &a), Err(Error::UnexpectedArg { token: b"--wat" }) ); } #[test] fn negation() { let a = argv(["--no-color"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Flag { flag: &COLOR, value: None, negated: true }] ); } #[test] fn short_bundle_and_attached_value() { let a = argv(["-fj8"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![ Event::Flag { flag: &FORCE, value: None, negated: false }, Event::Flag { flag: &JOBS, value: Some(b"8"), negated: false }, ] ); } #[test] fn short_value_strips_one_equals() { for (tokens, want) in [(["-j=8"], &b"8"[..]), (["-j==8"], &b"=8"[..])] { let a = argv(tokens); let Event::Flag { value, .. } = parse(&ROOT, &a).unwrap()[0] else { panic!("expected a flag"); }; assert_eq!(value, Some(want), "{tokens:?}"); } } #[test] fn bare_dash_is_a_value() { let a = argv(["-"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Arg { arg: &FILE, value: b"-", delimit: true, }] ); } #[test] fn positionals_then_variadic() { let a = argv(["one", "two", "three"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![ Event::Arg { arg: &FILE, value: b"one", delimit: true, }, Event::Arg { arg: &REST, value: b"two", delimit: true, }, Event::Arg { arg: &REST, value: b"three", delimit: true, }, ] ); } #[test] fn subcommand_and_alias_route_the_same() { for token in ["install", "i"] { let a = argv([token]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Command(&INSTALL)], "{token}" ); } } #[test] fn a_parent_argument_can_exclude_a_later_subcommand() { let a = argv(["--force", "install"]); assert!(matches!( parse(&ARGUMENT_CONFLICT, &a), Err(Error::SubcommandConflict { subcommand }) if subcommand.name == "install" )); } #[test] fn subcommand_only_routes_before_a_positional_is_filled() { let a = argv(["other", "install"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![ Event::Arg { arg: &FILE, value: b"other", delimit: true, }, Event::Arg { arg: &REST, value: b"install", delimit: true, }, ] ); } #[test] fn a_word_naming_no_subcommand_goes_to_the_default_one() { // usage-lib's answer, which this reproduces: `mise build` comes back as commands // `["mise", "run"]` with the word bound to *run's* argument — not to `mise`'s own // `[TASK]`, which is what makes this more than a synonym for a positional. let a = argv(["build"]); assert_eq!( parse(&DEFAULTING, &a).unwrap(), vec![ Event::Command(&RUN), Event::Arg { arg: &RUN_TASK, value: b"build", delimit: true, }, ] ); } // A shared table, as a flattened struct's would be. Declared outside the tests so both // can splice it, which is the arrangement it exists to model. static SHARED_QUIET: Flag = Flag { key: 300, name: "quiet", longs: &["quiet"], ..Flag::BOOL }; static SHARED_FLAGS: &[&Flag] = &[&SHARED_QUIET]; static SHARED_WHAT: Arg = Arg { key: 301, name: "what", ..Arg::REQUIRED }; static SHARED_ARGS: &[&Arg] = &[&SHARED_WHAT]; #[test] fn concatenating_tables_keeps_the_order_they_were_given_in() { // The property positional arguments depend on: a flattened group lands where the // field was written, not at the end. `[&FILE], SHARED, [&REST]` has to stay in that // order or `ex a b c` binds the wrong words. const ARGS: &[&[&Arg]] = &[&[&FILE], SHARED_ARGS, &[&REST]]; static TABLE: [&Arg; table_len(ARGS)] = concat_args(ARGS); assert_eq!( TABLE.iter().map(|a| a.name).collect::>(), ["file", "what", "rest"] ); // Empty groups contribute nothing and disturb nothing, which is what lets the derive // emit a group per field without checking whether it is empty first. const WITH_GAPS: &[&[&Flag]] = &[&[], &[&FORCE], &[], SHARED_FLAGS, &[]]; static FLAGS: [&Flag; table_len(WITH_GAPS)] = concat_flags(WITH_GAPS); // By long form: these fixtures do not all set `name`, and the placeholder's is also // empty — so comparing names could not tell a real entry from a leftover slot. assert_eq!( FLAGS.iter().map(|f| f.longs).collect::>(), [&["force"], &["quiet"]] ); } #[test] fn a_concatenated_table_parses_like_a_declared_one() { // The point of doing this at compile time: what the parser walks is one flat slice, // indistinguishable from a command that declared everything itself. const FLAG_GROUPS: &[&[&Flag]] = &[&[&FORCE], SHARED_FLAGS]; const ARG_GROUPS: &[&[&Arg]] = &[SHARED_ARGS, &[&REST]]; static FLAGS: [&Flag; table_len(FLAG_GROUPS)] = concat_flags(FLAG_GROUPS); static ARGS: [&Arg; table_len(ARG_GROUPS)] = concat_args(ARG_GROUPS); static JOINED: Command = Command { name: "joined", flags: &FLAGS, args: &ARGS, ..Command::EMPTY }; let a = argv(["--quiet", "one", "two", "--force"]); assert_eq!( parse(&JOINED, &a).unwrap(), vec![ Event::Flag { flag: &SHARED_QUIET, value: None, negated: false }, Event::Arg { arg: &SHARED_WHAT, value: b"one", delimit: true, }, Event::Arg { arg: &REST, value: b"two", delimit: true, }, Event::Flag { flag: &FORCE, value: None, negated: false }, ] ); } #[test] fn an_unknown_flag_is_not_routed() { // A dash-prefixed token that names no flag becomes a value here (the default for // `unknown_flags`), and it must not thereby become a *subcommand* word: usage-lib // stops looking for subcommands at an unrecognised flag, and binds it to the command // still in scope. Verified against usage-lib, where `ex --wat` comes back as commands // `["ex"]` with `ROOT_TASK = "--wat"`. for token in ["--wat", "-x"] { let a = argv([token]); assert_eq!( parse(&DEFAULTING, &a).unwrap(), vec![Event::Arg { arg: &TASK, value: token.as_bytes(), delimit: true, }], "{token} should bind where it was typed, not in the default subcommand" ); } } #[test] fn a_named_subcommand_is_not_routed() { // The default is for words that name nothing. A word that names a sibling still // selects it, and the root's own argument is still reachable behind one. let a = argv(["install"]); assert_eq!( parse(&DEFAULTING, &a).unwrap(), vec![Event::Command(&INSTALL)] ); } #[test] fn an_unmatched_word_is_forwarded_when_external_subcommand_is_set() { static CATCH: Command = Command { name: "ex", flags: &[&VERBOSE], subcommands: &[&INSTALL], external_subcommand: true, unknown_flags: Some(UnknownFlags::Error), ..Command::EMPTY }; let a = argv(["foo", "--help", "bar"]); assert_eq!( parse(&CATCH, &a).unwrap(), vec![Event::External { values: &a[..] }] ); let a = argv(["install"]); assert_eq!(parse(&CATCH, &a).unwrap(), vec![Event::Command(&INSTALL)]); let a = argv(["--verbose", "foo", "--verbose"]); assert_eq!( parse(&CATCH, &a).unwrap(), vec![ Event::Flag { flag: &VERBOSE, value: None, negated: false }, Event::External { values: &a[1..] } ] ); let a = argv(["--wat"]); assert_eq!( parse(&CATCH, &a), Err(Error::UnknownFlag { token: b"--wat" }) ); // A negative number is a value, not a flag, so it can be the unmatched word. let a = argv(["-1", "rest"]); assert_eq!( parse(&CATCH, &a).unwrap(), vec![Event::External { values: &a[..] }] ); } #[test] fn a_default_subcommand_outranks_an_external_one() { static CATCH_DEFAULT: Command = Command { name: "ex", subcommands: &[&RUN], default_subcommand: Some(&RUN), external_subcommand: true, ..Command::EMPTY }; let a = argv(["build"]); assert_eq!( parse(&CATCH_DEFAULT, &a).unwrap(), vec![ Event::Command(&RUN), Event::Arg { arg: &RUN_TASK, value: b"build", delimit: true, } ] ); } #[test] fn a_default_subcommand_starts_at_the_word_it_receives() { let a = argv(["build"]); let mut parser = Parser::new(&DEFAULTING, &a); assert_eq!(parser.next_event(), Some(Ok(Event::Command(&RUN)))); assert_eq!(parser.command_start(), 0); assert_eq!( parser.next_event(), Some(Ok(Event::Arg { arg: &RUN_TASK, value: b"build", delimit: true, })) ); } #[test] fn a_root_clause_separator_does_not_select_empty_default() { static CHILD: Command = Command { name: "run", ..Command::EMPTY }; static ROOT: Command = Command { name: "ex", clause: Some(&Clause { key: 400, name: "items", separator: Some(b":::"), flags: &[], args: &[], }), subcommands: &[&CHILD], default_subcommand: Some(&CHILD), default_subcommand_on_empty: true, ..Command::EMPTY }; let a = argv([":::"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::ClauseSeparator { clause: *ROOT.clause.unwrap(), }] ); } #[test] fn the_default_can_be_named_by_an_alias() { // usage-lib resolves the name against subcommand names, aliases and hidden aliases // alike, so a spec may point `default_subcommand` at any of them. static BY_ALIAS: Command = Command { name: "mise", args: &[&TASK], subcommands: &[&INSTALL], // `INSTALL` answers to "i" as well as to its name. default_subcommand: Some(find_subcommand(&[&INSTALL], "i")), ..Command::EMPTY }; assert!(::core::ptr::eq( BY_ALIAS.default_subcommand.expect("declared"), &INSTALL )); } #[test] fn a_name_outranks_another_commands_alias() { // A spec `assert_unique_subcommand_names` would reject, resolved anyway: a parser // handed a table nothing validated still has to answer, and the answer is the // command whose own name it is. Both orders, because taking the first candidate // that matched on either name or alias made this depend on which was listed first // — and usage-lib, building a map, took the last. static ALPHA: Command = Command { name: "alpha", aliases: &["run"], key: 300, ..Command::EMPTY }; static PLAIN_RUN: Command = Command { name: "run", key: 301, ..Command::EMPTY }; for subcommands in [&[&ALPHA, &PLAIN_RUN] as &[&Command], &[&PLAIN_RUN, &ALPHA]] { assert!(::core::ptr::eq( find_subcommand(subcommands, "run"), &PLAIN_RUN )); let root: Command = Command { name: "ex", subcommands, ..Command::EMPTY }; let a = argv(["run"]); assert_eq!(parse(&root, &a).unwrap(), vec![Event::Command(&PLAIN_RUN)]); // The alias still reaches its own command by every name it does not share. let a = argv(["alpha"]); assert_eq!(parse(&root, &a).unwrap(), vec![Event::Command(&ALPHA)]); // `ex help run` asks about the command `ex run` selects. These are separate // lookups — help resolves a path without descending — and answering differently // for a colliding word is the divergence this rule exists to end. let a = argv(["help", "run"]); match parse(&root, &a) { Err(Error::Help { cmd, .. }) => { assert!( ::core::ptr::eq(cmd, &PLAIN_RUN), "got help for {}", cmd.name ) } other => panic!("expected a help request, got {other:?}"), } } } #[test] #[should_panic(expected = "two subcommands answer to the same name")] fn an_alias_cannot_shadow_a_sibling_command() { static ADD: Command = Command { name: "add", aliases: &["install"], ..Command::EMPTY }; assert_unique_subcommand_names(&[&INSTALL, &ADD]); } #[test] fn the_word_is_re_examined_against_the_command_it_reached() { // The reason the cursor steps back rather than the token being consumed: `lint` names // nothing at the root, and once inside `run` it names a subcommand. mise's mounted // task names arrive exactly this way. let a = argv(["lint"]); assert_eq!( parse(&DEFAULTING, &a).unwrap(), vec![Event::Command(&RUN), Event::Command(&LINT)] ); } #[test] fn the_default_is_taken_at_most_once_per_parse() { // usage-lib latches this for the whole parse rather than per command, and the shape // that shows the difference needs two of them: `lint` routes through `run`, and `lint` // declares a default too. A second word there would descend again — walking a CLI // deeper than anything the user typed — so the answer is that it does not. let a = argv(["lint", "zzz"]); assert_eq!( parse(&DEFAULTING, &a), Err(Error::UnexpectedArg { token: b"zzz" }), "the second word must not reach `deep`" ); // Reached explicitly, the same command still takes it: the latch bounds routing, not // the tree. let a = argv(["lint", "deep", "zzz"]); assert_eq!( parse(&DEFAULTING, &a).unwrap(), vec![ Event::Command(&RUN), Event::Command(&LINT), Event::Command(&DEEP), Event::Arg { arg: &RUN_TASK, value: b"zzz", delimit: true, }, ] ); } #[test] fn a_flag_before_the_word_still_belongs_to_the_root() { // Routing happens at the word, so anything typed before it was addressed to the // command the user was actually at. let a = argv(["--verbose", "build"]); assert_eq!( parse(&DEFAULTING, &a).unwrap(), vec![ Event::Flag { flag: &VERBOSE, value: None, negated: false }, Event::Command(&RUN), Event::Arg { arg: &RUN_TASK, value: b"build", delimit: true, }, ] ); } #[test] fn nothing_routes_after_the_separator() { // Past `--` there are no subcommands left to select, so there is no default to reach // either: the words are values of whatever the command declares. let a = argv(["--", "build"]); assert_eq!( parse(&DEFAULTING, &a).unwrap(), vec![Event::Arg { arg: &TASK, value: b"build", delimit: true, }] ); } #[test] fn globals_are_inherited_but_plain_flags_are_not() { let a = argv(["install", "--verbose"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![ Event::Command(&INSTALL), Event::Flag { flag: &VERBOSE, value: None, negated: false } ] ); // `--jobs` belongs to the root and is not global, so it is not a flag here. // Strictly that is an unknown flag; leniently it is a word, and `install` // declares no argument to hold one — either way it is never read as the // root's flag, which is what this test is about. let a = argv(["install", "--jobs", "8"]); assert!(matches!(parse(&STRICT, &a), Err(Error::UnknownFlag { .. }))); assert!(matches!( parse(&ROOT, &a), Err(Error::UnexpectedArg { token: b"--jobs" }) )); } #[test] fn double_dash_protects_flaglike_values() { let a = argv(["--", "--force", "-x"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![ Event::Arg { arg: &FILE, value: b"--force", delimit: true, }, Event::Arg { arg: &REST, value: b"-x", delimit: true, }, ] ); } #[test] fn second_double_dash_is_a_value() { let a = argv(["--", "a", "--", "b"]); let values: Vec<&[u8]> = parse(&ROOT, &a) .unwrap() .iter() .filter_map(|e| match e { Event::Arg { value, .. } => Some(*value), _ => None, }) .collect(); assert_eq!(values, vec![&b"a"[..], &b"--"[..], &b"b"[..]]); } #[test] fn allow_hyphen_values_takes_a_flaglike_detached_value() { static ARGS: Flag = Flag { key: 6, name: "args", longs: &["args"], shorts: b"a", takes_value: true, allow_hyphen_values: true, ..Flag::BOOL }; static DIR: Flag = Flag { key: 7, name: "working-dir", longs: &["working-dir"], shorts: b"d", ..Flag::VALUE }; static HYPHEN: Command = Command { name: "ex", flags: &[&ARGS, &DIR], args: &[&REST], ..Command::EMPTY }; let a = argv(["-a", "-destroy"]); assert_eq!( parse(&HYPHEN, &a).unwrap(), vec![Event::Flag { flag: &ARGS, value: Some(b"-destroy"), negated: false }] ); let a = argv(["--args", "--", "-x"]); assert_eq!( parse(&HYPHEN, &a).unwrap(), vec![ Event::Flag { flag: &ARGS, value: Some(b"--"), negated: false }, Event::Arg { arg: &REST, value: b"-x", delimit: true, }, ] ); } #[test] fn require_equals_refuses_a_detached_value() { static INSPECT: Flag = Flag { key: 8, name: "inspect", longs: &["inspect"], shorts: b"i", takes_value: true, require_equals: true, ..Flag::BOOL }; static EQ: Command = Command { name: "ex", flags: &[&INSPECT], ..Command::EMPTY }; let a = argv(["--inspect=9229"]); assert_eq!( parse(&EQ, &a).unwrap(), vec![Event::Flag { flag: &INSPECT, value: Some(b"9229"), negated: false }] ); let a = argv(["--inspect", "9229"]); assert!(matches!( parse(&EQ, &a), Err(Error::MissingFlagValue { .. }) )); let a = argv(["-i9229"]); assert_eq!( parse(&EQ, &a).unwrap(), vec![Event::Flag { flag: &INSPECT, value: Some(b"9229"), negated: false }] ); static ALL: Flag = Flag { key: 9, name: "all", longs: &["all"], shorts: b"a", ..Flag::BOOL }; static BUNDLE: Command = Command { name: "ex", flags: &[&ALL, &INSPECT], ..Command::EMPTY }; let a = argv(["-ai", "9229"]); assert!( matches!(parse(&BUNDLE, &a), Err(Error::MissingFlagValue { .. })), "a require_equals short reached through a bundle still refuses the following word" ); } #[test] fn default_missing_binds_when_the_value_is_left_off() { static COLOR: Flag = Flag { key: 9, name: "color", longs: &["color"], takes_value: true, default_missing: Some(b"always"), ..Flag::BOOL }; static VERBOSE: Flag = Flag { key: 10, name: "verbose", longs: &["verbose"], ..Flag::BOOL }; static MISSING: Command = Command { name: "ex", flags: &[&COLOR, &VERBOSE], ..Command::EMPTY }; let a = argv(["--color"]); assert_eq!( parse(&MISSING, &a).unwrap(), vec![Event::Flag { flag: &COLOR, value: Some(b"always"), negated: false }] ); let a = argv(["--color=never"]); assert_eq!( parse(&MISSING, &a).unwrap(), vec![Event::Flag { flag: &COLOR, value: Some(b"never"), negated: false }] ); let a = argv(["--color", "--verbose"]); assert_eq!( parse(&MISSING, &a).unwrap(), vec![ Event::Flag { flag: &COLOR, value: Some(b"always"), negated: false }, Event::Flag { flag: &VERBOSE, value: None, negated: false }, ] ); let a = argv(["--color="]); assert_eq!( parse(&MISSING, &a).unwrap(), vec![Event::Flag { flag: &COLOR, value: Some(b""), negated: false }] ); } #[test] fn optional_flag_value_distinguishes_bare_and_explicit_forms() { static BUMP: Flag = Flag { key: 11, name: "bump", longs: &["bump"], takes_value: true, value_optional: true, ..Flag::BOOL }; static OPTIONAL: Command = Command { name: "ex", flags: &[&BUMP], ..Command::EMPTY }; assert_eq!(parse(&OPTIONAL, &argv([])).unwrap(), vec![]); assert_eq!( parse(&OPTIONAL, &argv(["--bump"])).unwrap(), vec![Event::Flag { flag: &BUMP, value: None, negated: false, }] ); assert_eq!( parse(&OPTIONAL, &argv(["--bump=5"])).unwrap(), vec![Event::Flag { flag: &BUMP, value: Some(b"5"), negated: false, }] ); static INCLUDE: Flag = Flag { key: 12, name: "include", longs: &["include"], takes_value: true, variadic: true, value_optional: true, ..Flag::BOOL }; static VERBOSE: Flag = Flag { key: 13, name: "verbose", longs: &["verbose"], ..Flag::BOOL }; static VARIADIC: Command = Command { name: "ex", flags: &[&INCLUDE, &VERBOSE], args: &[&REST], ..Command::EMPTY }; assert_eq!( parse(&VARIADIC, &argv(["--include", "--verbose", "file"])).unwrap(), vec![ Event::Flag { flag: &INCLUDE, value: None, negated: false, }, Event::Flag { flag: &VERBOSE, value: None, negated: false, }, Event::Arg { arg: &REST, value: b"file", delimit: true, }, ] ); } #[test] fn default_missing_with_require_equals_leaves_the_following_word() { static INSPECT: Flag = Flag { key: 11, name: "inspect", longs: &["inspect"], takes_value: true, require_equals: true, default_missing: Some(b"9229"), ..Flag::BOOL }; static BOTH: Command = Command { name: "ex", flags: &[&INSPECT], args: &[&REST], ..Command::EMPTY }; let a = argv(["--inspect"]); assert_eq!( parse(&BOTH, &a).unwrap(), vec![Event::Flag { flag: &INSPECT, value: Some(b"9229"), negated: false }] ); let a = argv(["--inspect", "80"]); assert_eq!( parse(&BOTH, &a).unwrap(), vec![ Event::Flag { flag: &INSPECT, value: Some(b"9229"), negated: false }, Event::Arg { arg: &REST, value: b"80", delimit: true, }, ] ); let a = argv(["--inspect="]); assert_eq!( parse(&BOTH, &a).unwrap(), vec![Event::Flag { flag: &INSPECT, value: Some(b""), negated: false }] ); } #[test] fn variadic_flag_collects_until_a_flaglike_token() { static INCLUDE: Flag = Flag { key: 5, name: "include", longs: &["include"], shorts: b"i", takes_value: true, variadic: true, ..Flag::BOOL }; static GREEDY: Command = Command { name: "ex", flags: &[&INCLUDE, &FORCE], args: &[&FILE], ..Command::EMPTY }; let a = argv(["--include", "x", "y", "--force"]); assert_eq!( parse(&GREEDY, &a).unwrap(), vec![ Event::Flag { flag: &INCLUDE, value: Some(b"x"), negated: false }, Event::Flag { flag: &INCLUDE, value: Some(b"y"), negated: false }, Event::Flag { flag: &FORCE, value: None, negated: false }, ] ); } #[test] fn value_terminators_end_variadic_owners_without_binding() { static INCLUDE: Flag = Flag { key: 92, name: "include", longs: &["include"], takes_value: true, variadic: true, value_terminator: Some(b";"), ..Flag::BOOL }; static ITEMS: Arg = Arg { key: 93, name: "items", var: true, value_terminator: Some(b";"), ..Arg::REQUIRED }; static AFTER: Arg = Arg { key: 94, name: "after", ..Arg::REQUIRED }; static FLAG_CMD: Command = Command { name: "ex", flags: &[&INCLUDE], args: &[&AFTER], ..Command::EMPTY }; static ARG_CMD: Command = Command { name: "ex", args: &[&ITEMS, &AFTER], ..Command::EMPTY }; let flag = argv(["--include", "a", ";", "tail"]); assert_eq!( parse(&FLAG_CMD, &flag).unwrap(), vec![ Event::Flag { flag: &INCLUDE, value: Some(b"a"), negated: false, }, Event::Arg { arg: &AFTER, value: b"tail", delimit: true, }, ] ); let positional = argv(["a", ";", "tail"]); assert_eq!( parse(&ARG_CMD, &positional).unwrap(), vec![ Event::Arg { arg: &ITEMS, value: b"a", delimit: true, }, Event::Arg { arg: &AFTER, value: b"tail", delimit: true, }, ] ); } #[test] fn a_non_variadic_flag_leaves_the_next_word_alone() { // The counterpart to the test above: a flag that takes one value must not // swallow the word after it, which would silently steal a positional. let a = argv(["--jobs", "8", "keep-me"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![ Event::Flag { flag: &JOBS, value: Some(b"8"), negated: false }, Event::Arg { arg: &FILE, value: b"keep-me", delimit: true, }, ] ); } #[test] fn double_dash_seen_means_a_separator_was_typed() { static FILES: Arg = Arg { key: 23, name: "files", double_dash: DoubleDash::Automatic, ..Arg::VAR }; static AUTO: Command = Command { name: "ex", flags: &[&FORCE], args: &[&FILES], ..Command::EMPTY }; let a = argv(["--", "x"]); let mut parser = Parser::new(&ROOT, &a); while parser.next_event().is_some() {} assert!(parser.double_dash_seen(), "a real separator was consumed"); // `automatic` stops flag interpretation without a separator being typed, // and reporting one would be a lie to any caller that forwards argv. let a = argv(["x", "--force"]); let mut parser = Parser::new(&AUTO, &a); while parser.next_event().is_some() {} assert!( !parser.double_dash_seen(), "automatic mode must not claim a separator was given" ); } #[test] fn a_wrapper_still_forwards_a_help_flag() { // Supplying `--help` must not take the two forwarding mechanisms away from a wrapper, // which is the one place a CLI means to hand the token on rather than answer it. static ARGS: Arg = Arg { key: 24, name: "args", ..Arg::VAR }; static WRAP: Command = Command { name: "wrap", args: &[&ARGS], ..Command::EMPTY }; // A typed separator: everything after it is a value, `--help` included. let a = argv(["--", "--help", "-h"]); assert_eq!( parse(&WRAP, &a).unwrap(), vec![ Event::Arg { arg: &ARGS, value: b"--help", delimit: true, }, Event::Arg { arg: &ARGS, value: b"-h", delimit: true, }, ] ); // And `automatic`, for the wrapper whose caller should not have to type one: the // first value stops flag interpretation, so the flags after it forward. static AUTO_ARGS: Arg = Arg { key: 25, name: "args", double_dash: DoubleDash::Automatic, ..Arg::VAR }; static AUTO_WRAP: Command = Command { name: "wrap", args: &[&AUTO_ARGS], ..Command::EMPTY }; let a = argv(["node", "--help"]); assert_eq!( parse(&AUTO_WRAP, &a).unwrap(), vec![ Event::Arg { arg: &AUTO_ARGS, value: b"node", delimit: true, }, Event::Arg { arg: &AUTO_ARGS, value: b"--help", delimit: true, }, ] ); // Before either takes effect, though, the wrapper's own help is what `--help` asks // for — `mise run --help` is a question about `run`, not a value for it. let a = argv(["--help"]); assert_eq!( parse(&AUTO_WRAP, &a).unwrap(), vec![Event::Flag { flag: &HELP_LONG, value: None, negated: false }] ); } #[test] fn double_dash_required_arg() { static CMD: Arg = Arg { key: 20, name: "cmd", double_dash: DoubleDash::Required, ..Arg::REQUIRED }; static EXEC: Command = Command { name: "ex", args: &[&CMD], ..Command::EMPTY }; let a = argv(["--", "ls"]); assert_eq!( parse(&EXEC, &a).unwrap(), vec![Event::Arg { arg: &CMD, value: b"ls", delimit: true, }] ); let a = argv(["ls"]); assert_eq!( parse(&EXEC, &a), Err(Error::ArgRequiresDoubleDash { arg: &CMD }) ); } #[test] fn double_dash_preserve_keeps_the_separator() { static ARGS: Arg = Arg { key: 21, name: "args", double_dash: DoubleDash::Preserve, ..Arg::VAR }; static WRAP: Command = Command { name: "ex", args: &[&ARGS], ..Command::EMPTY }; let a = argv(["a", "--", "b"]); let values: Vec<&[u8]> = parse(&WRAP, &a) .unwrap() .iter() .filter_map(|e| match e { Event::Arg { value, .. } => Some(*value), _ => None, }) .collect(); assert_eq!(values, vec![&b"a"[..], &b"--"[..], &b"b"[..]]); } #[test] fn double_dash_automatic_stops_flag_interpretation() { static FILES: Arg = Arg { key: 22, name: "files", double_dash: DoubleDash::Automatic, ..Arg::VAR }; static AUTO: Command = Command { name: "ex", flags: &[&FORCE], args: &[&FILES], ..Command::EMPTY }; // The flag before the first value is still a flag; the one after it is a // value. let a = argv(["-f", "one", "--force"]); assert_eq!( parse(&AUTO, &a).unwrap(), vec![ Event::Flag { flag: &FORCE, value: None, negated: false }, Event::Arg { arg: &FILES, value: b"one", delimit: true, }, Event::Arg { arg: &FILES, value: b"--force", delimit: true, }, ] ); } #[test] fn too_many_words() { static ONE: Command = Command { name: "ex", args: &[&FILE], ..Command::EMPTY }; let a = argv(["a", "b"]); assert_eq!(parse(&ONE, &a), Err(Error::UnexpectedArg { token: b"b" })); } #[test] fn unknown_letter_rejects_the_whole_bundle() { // `-f` is real and `-z` is not. The first event must be the error: if the // flag event came out first, a caller would have applied `-f` from a // command line that was rejected. let a = argv(["-fz"]); let mut parser = Parser::new(&STRICT, &a); assert_eq!( parser.next_event(), Some(Err(Error::UnknownFlag { token: b"-fz" })), "an unknown letter must reject the token before any of it is applied" ); assert!(parser.next_event().is_none()); // Leniently, the same token is a value — and `-f` is *not* applied, since // the token was never a bundle at all. let a = argv(["-fz"]); assert_eq!( parse(&ROOT, &a).unwrap(), vec![Event::Arg { arg: &FILE, value: b"-fz", delimit: true, }] ); } #[test] fn unknown_short_error_names_the_whole_token() { for (tokens, want) in [(["-z"], &b"-z"[..]), (["-fz"], &b"-fz"[..])] { let a = argv(tokens); assert_eq!( parse(&STRICT, &a), Err(Error::UnknownFlag { token: want }), "{tokens:?}" ); } } #[test] fn errors_are_terminal() { let a = argv(["--wat", "--force"]); let mut parser = Parser::new(&STRICT, &a); assert!(parser.next_event().unwrap().is_err()); assert!(parser.next_event().is_none()); } #[test] fn non_utf8_values_still_parse() { // A value that is not valid UTF-8 binds; only converting it fails, and // only if a caller asks. let raw = OsStr::new("--force"); let a = [raw]; assert!(parse(&ROOT, &a).is_ok()); assert!(as_str(b"ok").is_ok()); assert!(as_str(&[0xff, 0xfe]).is_err()); } #[test] fn a_multicall_applet_is_the_basename_unless_it_is_the_dispatcher() { assert_eq!(multicall_basename("/usr/bin/ls"), "ls"); assert_eq!(multicall_basename(r"C:\busybox\ls.exe"), "ls"); assert_eq!( multicall_applet("/usr/bin/ls", "busybox", Some("busybox")), Some("ls") ); assert_eq!( multicall_applet("/usr/bin/busybox", "busybox", Some("busybox")), None ); assert_eq!( multicall_applet("ls.exe", "busybox", Some("busybox")), Some("ls") ); assert_eq!( multicall_applet("/usr/bin/busybox", "BusyBox", Some("/opt/bin/busybox")), None ); assert_eq!( multicall_applet("busybox.exe", "BusyBox", Some("busybox.exe")), None ); } #[test] fn a_spec_request_is_the_first_word_and_nothing_else() { let request = [OsStr::new(SPEC_REQUEST)]; assert!(is_spec_request(&ROOT, &request)); // Anywhere but the front it is an ordinary value, which is what makes the endpoint // safe for a CLI whose arguments are arbitrary text. let later = ["install", SPEC_REQUEST].map(OsStr::new); assert!(!is_spec_request(&ROOT, &later)); assert!(!is_spec_request(&ROOT, &[])); assert!(!is_spec_request(&ROOT, &[OsStr::new("--help")])); } #[test] fn a_declared_command_of_that_name_keeps_it() { static DECLARED: Command = Command { name: SPEC_REQUEST, key: 200, ..Command::EMPTY }; static ALIASED: Command = Command { name: "describe", aliases: &[SPEC_REQUEST], key: 201, ..Command::EMPTY }; static DECLARES_IT: Command = Command { name: "ex", subcommands: &[&DECLARED], ..Command::EMPTY }; static ALIASES_IT: Command = Command { name: "ex", subcommands: &[&ALIASED], ..Command::EMPTY }; let request = [OsStr::new(SPEC_REQUEST)]; assert!(!is_spec_request(&DECLARES_IT, &request)); // An alias selects a command just as its name does, so it wins here too. assert!(!is_spec_request(&ALIASES_IT, &request)); } }