chore(vendor): add anyhow crate

This commit was merged in pull request #68.
This commit is contained in:
2026-09-17 14:13:23 -04:00
parent 7a3a3d6b90
commit 37539682d4
454 changed files with 8906 additions and 1468 deletions

View File

@@ -0,0 +1,461 @@
#[cfg(debug_assertions)]
use crate::util::AnyValueId;
use crate::builder::ValueRange;
/// Behavior of arguments when they are encountered while parsing
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "help")] {
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("special-help")
/// .short('?')
/// .action(clap::ArgAction::Help)
/// );
///
/// // Existing help still exists
/// let err = cmd.clone().try_get_matches_from(["mycmd", "-h"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
///
/// // New help available
/// let err = cmd.try_get_matches_from(["mycmd", "-?"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
/// # }
/// ```
#[derive(Clone, Debug)]
#[non_exhaustive]
#[allow(missing_copy_implementations)] // In the future, we may accept `Box<dyn ...>`
pub enum ArgAction {
/// When encountered, store the associated value(s) in [`ArgMatches`][crate::ArgMatches]
///
/// <div class="warning">
///
/// **NOTE:** If the argument has previously been seen, it will result in a
/// [`ArgumentConflict`][crate::error::ErrorKind::ArgumentConflict] unless
/// [`Command::args_override_self(true)`][crate::Command::args_override_self] is set.
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::Set)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "value"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_many::<String>("flag").unwrap_or_default().map(|v| v.as_str()).collect::<Vec<_>>(),
/// vec!["value"]
/// );
/// ```
Set,
/// When encountered, store the associated value(s) in [`ArgMatches`][crate::ArgMatches]
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::Append)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd", "--flag", "value1", "--flag", "value2"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_many::<String>("flag").unwrap_or_default().map(|v| v.as_str()).collect::<Vec<_>>(),
/// vec!["value1", "value2"]
/// );
/// ```
Append,
/// When encountered, act as if `"true"` was encountered on the command-line
///
/// If no [`default_value`][super::Arg::default_value] is set, it will be `false`.
///
/// No value is allowed. To optionally accept a value, see
/// [`Arg::default_missing_value`][super::Arg::default_missing_value]
///
/// <div class="warning">
///
/// **NOTE:** If the argument has previously been seen, it will result in a
/// [`ArgumentConflict`][crate::error::ErrorKind::ArgumentConflict] unless
/// [`Command::args_override_self(true)`][crate::Command::args_override_self] is set.
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::SetTrue)
/// );
///
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_flag("flag"),
/// true
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_flag("flag"),
/// false
/// );
/// ```
///
/// You can use [`TypedValueParser::map`][crate::builder::TypedValueParser::map] to have the
/// flag control an application-specific type:
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// # use clap::builder::TypedValueParser as _;
/// # use clap::builder::BoolishValueParser;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::SetTrue)
/// .value_parser(
/// BoolishValueParser::new()
/// .map(|b| -> usize {
/// if b { 10 } else { 5 }
/// })
/// )
/// );
///
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_one::<usize>("flag").copied(),
/// Some(10)
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_one::<usize>("flag").copied(),
/// Some(5)
/// );
/// ```
SetTrue,
/// When encountered, act as if `"false"` was encountered on the command-line
///
/// If no [`default_value`][super::Arg::default_value] is set, it will be `true`.
///
/// No value is allowed. To optionally accept a value, see
/// [`Arg::default_missing_value`][super::Arg::default_missing_value]
///
/// <div class="warning">
///
/// **NOTE:** If the argument has previously been seen, it will result in a
/// [`ArgumentConflict`][crate::error::ErrorKind::ArgumentConflict] unless
/// [`Command::args_override_self(true)`][crate::Command::args_override_self] is set.
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::SetFalse)
/// );
///
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_flag("flag"),
/// false
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_flag("flag"),
/// true
/// );
/// ```
SetFalse,
/// When encountered, increment a `u8` counter starting from `0`.
///
/// If no [`default_value`][super::Arg::default_value] is set, it will be `0`.
///
/// No value is allowed. To optionally accept a value, see
/// [`Arg::default_missing_value`][super::Arg::default_missing_value]
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("flag")
/// .long("flag")
/// .action(clap::ArgAction::Count)
/// );
///
/// let matches = cmd.clone().try_get_matches_from(["mycmd", "--flag", "--flag"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_count("flag"),
/// 2
/// );
///
/// let matches = cmd.try_get_matches_from(["mycmd"]).unwrap();
/// assert!(matches.contains_id("flag"));
/// assert_eq!(
/// matches.get_count("flag"),
/// 0
/// );
/// ```
Count,
/// When encountered, display [`Command::print_help`][super::Command::print_help]
///
/// Depending on the flag, [`Command::print_long_help`][super::Command::print_long_help] may be shown
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "help")] {
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("special-help")
/// .short('?')
/// .action(clap::ArgAction::Help)
/// );
///
/// // Existing help still exists
/// let err = cmd.clone().try_get_matches_from(["mycmd", "-h"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
///
/// // New help available
/// let err = cmd.try_get_matches_from(["mycmd", "-?"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
/// # }
/// ```
Help,
/// When encountered, display [`Command::print_help`][super::Command::print_help]
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "help")] {
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("special-help")
/// .short('?')
/// .action(clap::ArgAction::HelpShort)
/// );
///
/// // Existing help still exists
/// let err = cmd.clone().try_get_matches_from(["mycmd", "-h"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
///
/// // New help available
/// let err = cmd.try_get_matches_from(["mycmd", "-?"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
/// # }
/// ```
HelpShort,
/// When encountered, display [`Command::print_long_help`][super::Command::print_long_help]
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "help")] {
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .arg(
/// Arg::new("special-help")
/// .short('?')
/// .action(clap::ArgAction::HelpLong)
/// );
///
/// // Existing help still exists
/// let err = cmd.clone().try_get_matches_from(["mycmd", "-h"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
///
/// // New help available
/// let err = cmd.try_get_matches_from(["mycmd", "-?"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
/// # }
/// ```
HelpLong,
/// When encountered, display [`Command::version`][super::Command::version]
///
/// Depending on the flag, [`Command::long_version`][super::Command::long_version] may be shown
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// let cmd = Command::new("mycmd")
/// .version("1.0.0")
/// .arg(
/// Arg::new("special-version")
/// .long("special-version")
/// .action(clap::ArgAction::Version)
/// );
///
/// // Existing version still exists
/// let err = cmd.clone().try_get_matches_from(["mycmd", "--version"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
///
/// // New version available
/// let err = cmd.try_get_matches_from(["mycmd", "--special-version"]).unwrap_err();
/// assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
/// ```
Version,
}
impl ArgAction {
/// Returns whether this action accepts values on the command-line
///
/// [`default_values`][super::Arg::default_values] and [`env`][super::Arg::env] may still be
/// processed.
pub fn takes_values(&self) -> bool {
match self {
Self::Set => true,
Self::Append => true,
Self::SetTrue => false,
Self::SetFalse => false,
Self::Count => false,
Self::Help => false,
Self::HelpShort => false,
Self::HelpLong => false,
Self::Version => false,
}
}
#[cfg(debug_assertions)]
pub(crate) fn max_num_args(&self) -> ValueRange {
match self {
Self::Set => ValueRange::FULL,
Self::Append => ValueRange::FULL,
Self::SetTrue => ValueRange::OPTIONAL,
Self::SetFalse => ValueRange::OPTIONAL,
Self::Count => ValueRange::EMPTY,
Self::Help => ValueRange::EMPTY,
Self::HelpShort => ValueRange::EMPTY,
Self::HelpLong => ValueRange::EMPTY,
Self::Version => ValueRange::EMPTY,
}
}
pub(crate) fn default_num_args(&self) -> ValueRange {
match self {
Self::Set => ValueRange::SINGLE,
Self::Append => ValueRange::SINGLE,
Self::SetTrue => ValueRange::EMPTY,
Self::SetFalse => ValueRange::EMPTY,
Self::Count => ValueRange::EMPTY,
Self::Help => ValueRange::EMPTY,
Self::HelpShort => ValueRange::EMPTY,
Self::HelpLong => ValueRange::EMPTY,
Self::Version => ValueRange::EMPTY,
}
}
pub(crate) fn default_value(&self) -> Option<&'static std::ffi::OsStr> {
match self {
Self::Set => None,
Self::Append => None,
Self::SetTrue => Some(std::ffi::OsStr::new("false")),
Self::SetFalse => Some(std::ffi::OsStr::new("true")),
Self::Count => Some(std::ffi::OsStr::new("0")),
Self::Help => None,
Self::HelpShort => None,
Self::HelpLong => None,
Self::Version => None,
}
}
pub(crate) fn default_missing_value(&self) -> Option<&'static std::ffi::OsStr> {
match self {
Self::Set => None,
Self::Append => None,
Self::SetTrue => Some(std::ffi::OsStr::new("true")),
Self::SetFalse => Some(std::ffi::OsStr::new("false")),
Self::Count => None,
Self::Help => None,
Self::HelpShort => None,
Self::HelpLong => None,
Self::Version => None,
}
}
pub(crate) fn default_value_parser(&self) -> Option<super::ValueParser> {
match self {
Self::Set => None,
Self::Append => None,
Self::SetTrue => Some(super::ValueParser::bool()),
Self::SetFalse => Some(super::ValueParser::bool()),
Self::Count => Some(crate::value_parser!(u8).into()),
Self::Help => None,
Self::HelpShort => None,
Self::HelpLong => None,
Self::Version => None,
}
}
#[cfg(debug_assertions)]
pub(crate) fn value_type_id(&self) -> Option<AnyValueId> {
match self {
Self::Set => None,
Self::Append => None,
Self::SetTrue => None,
Self::SetFalse => None,
Self::Count => Some(AnyValueId::of::<CountType>()),
Self::Help => None,
Self::HelpShort => None,
Self::HelpLong => None,
Self::Version => None,
}
}
}
pub(crate) type CountType = u8;

View File

@@ -0,0 +1,88 @@
#[allow(unused)]
use crate::Arg;
#[allow(unused)]
use crate::Command;
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct AppFlags(u32);
impl AppFlags {
pub(crate) fn set(&mut self, setting: AppSettings) {
self.0 |= setting.bit();
}
pub(crate) fn unset(&mut self, setting: AppSettings) {
self.0 &= !setting.bit();
}
pub(crate) fn is_set(&self, setting: AppSettings) -> bool {
self.0 & setting.bit() != 0
}
pub(crate) fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
}
impl std::ops::BitOr for AppFlags {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
self.insert(rhs);
self
}
}
/// Application level settings, which affect how [`Command`] operates
///
/// <div class="warning">
///
/// **NOTE:** When these settings are used, they apply only to current command, and are *not*
/// propagated down or up through child or parent subcommands
///
/// </div>
///
/// [`Command`]: crate::Command
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub(crate) enum AppSettings {
IgnoreErrors,
AllowHyphenValues,
AllowNegativeNumbers,
AllArgsOverrideSelf,
AllowMissingPositional,
TrailingVarArg,
DontDelimitTrailingValues,
InferLongArgs,
InferSubcommands,
SubcommandRequired,
AllowExternalSubcommands,
Multicall,
SubcommandsNegateReqs,
ArgsNegateSubcommands,
SubcommandPrecedenceOverArg,
FlattenHelp,
ArgRequiredElseHelp,
NextLineHelp,
DisableColoredHelp,
DisableHelpFlag,
DisableHelpSubcommand,
DisableVersionFlag,
PropagateVersion,
Hidden,
HidePossibleValues,
HelpExpected,
NoBinaryName,
#[allow(dead_code)]
ColorAuto,
ColorAlways,
ColorNever,
Built,
BinNameBuilt,
}
impl AppSettings {
fn bit(self) -> u32 {
1 << (self as u8)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,615 @@
// Internal
use crate::builder::IntoResettable;
use crate::util::Id;
/// Specifies a logical group of [arguments]
///
/// You can use this for
/// - applying validation to an entire group, like [`ArgGroup::multiple`]
/// - validate relationships between an argument and a group, like [conflicts] or [requirements]
/// - check which argument in a group was specified on the command-line
///
/// For visually grouping arguments in help, see instead
/// [`Arg::help_heading`][crate::Arg::help_heading].
///
/// # Examples
///
/// The following example demonstrates using an `ArgGroup` to ensure that one, and only one, of
/// the arguments from the specified group is present at runtime.
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, arg, ArgGroup, error::ErrorKind};
/// let result = Command::new("cmd")
/// .arg(arg!(--"set-ver" <ver> "set the version manually"))
/// .arg(arg!(--major "auto increase major"))
/// .arg(arg!(--minor "auto increase minor"))
/// .arg(arg!(--patch "auto increase patch"))
/// .group(ArgGroup::new("vers")
/// .args(["set-ver", "major", "minor", "patch"])
/// .required(true))
/// .try_get_matches_from(vec!["cmd", "--major", "--patch"]);
/// // Because we used two args in the group it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
/// ```
///
/// This next example shows a passing parse of the same scenario
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, arg, ArgGroup, Id};
/// let result = Command::new("cmd")
/// .arg(arg!(--"set-ver" <ver> "set the version manually"))
/// .arg(arg!(--major "auto increase major"))
/// .arg(arg!(--minor "auto increase minor"))
/// .arg(arg!(--patch "auto increase patch"))
/// .group(ArgGroup::new("vers")
/// .args(["set-ver", "major", "minor","patch"])
/// .required(true))
/// .try_get_matches_from(vec!["cmd", "--major"]);
/// assert!(result.is_ok());
/// let matches = result.unwrap();
/// // We may not know which of the args was used, so we can test for the group...
/// assert!(matches.contains_id("vers"));
/// // We can also ask the group which arg was used
/// assert_eq!(matches
/// .get_one::<Id>("vers")
/// .expect("`vers` is required")
/// .as_str(),
/// "major"
/// );
/// // we could also alternatively check each arg individually (not shown here)
/// ```
/// [arguments]: crate::Arg
/// [conflicts]: crate::Arg::conflicts_with()
/// [requirements]: crate::Arg::requires()
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct ArgGroup {
pub(crate) id: Id,
pub(crate) args: Vec<Id>,
pub(crate) required: bool,
pub(crate) requires: Vec<Id>,
pub(crate) conflicts: Vec<Id>,
pub(crate) multiple: bool,
}
/// # Builder
impl ArgGroup {
/// Create a `ArgGroup` using a unique name.
///
/// The name will be used to get values from the group or refer to the group inside of conflict
/// and requirement rules.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, ArgGroup};
/// ArgGroup::new("config")
/// # ;
/// ```
pub fn new(id: impl Into<Id>) -> Self {
Self::default().id(id)
}
/// Sets the group name.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, ArgGroup};
/// ArgGroup::default().id("config")
/// # ;
/// ```
#[must_use]
pub fn id(mut self, id: impl Into<Id>) -> Self {
self.id = id.into();
self
}
/// Adds an [argument] to this group by name
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, ArgAction};
/// let m = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .arg("flag")
/// .arg("color"))
/// .get_matches_from(vec!["myprog", "-f"]);
/// // maybe we don't know which of the two flags was used...
/// assert!(m.contains_id("req_flags"));
/// // but we can also check individually if needed
/// assert!(m.contains_id("flag"));
/// ```
/// [argument]: crate::Arg
#[must_use]
pub fn arg(mut self, arg_id: impl IntoResettable<Id>) -> Self {
if let Some(arg_id) = arg_id.into_resettable().into_option() {
self.args.push(arg_id);
} else {
self.args.clear();
}
self
}
/// Adds multiple [arguments] to this group by name
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, ArgAction};
/// let m = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"]))
/// .get_matches_from(vec!["myprog", "-f"]);
/// // maybe we don't know which of the two flags was used...
/// assert!(m.contains_id("req_flags"));
/// // but we can also check individually if needed
/// assert!(m.contains_id("flag"));
/// ```
/// [arguments]: crate::Arg
#[must_use]
pub fn args(mut self, ns: impl IntoIterator<Item = impl Into<Id>>) -> Self {
for n in ns {
self = self.arg(n);
}
self
}
/// Getters for all args. It will return a vector of `Id`
///
/// # Example
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{ArgGroup};
/// let args: Vec<&str> = vec!["a1".into(), "a4".into()];
/// let grp = ArgGroup::new("program").args(&args);
///
/// for (pos, arg) in grp.get_args().enumerate() {
/// assert_eq!(*arg, args[pos]);
/// }
/// ```
pub fn get_args(&self) -> impl Iterator<Item = &Id> {
self.args.iter()
}
/// Allows more than one of the [`Arg`]s in this group to be used. (Default: `false`)
///
/// # Examples
///
/// Notice in this example we use *both* the `-f` and `-c` flags which are both part of the
/// group
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, ArgAction};
/// let m = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"])
/// .multiple(true))
/// .get_matches_from(vec!["myprog", "-f", "-c"]);
/// // maybe we don't know which of the two flags was used...
/// assert!(m.contains_id("req_flags"));
/// ```
/// In this next example, we show the default behavior (i.e. `multiple(false)`) which will throw
/// an error if more than one of the args in the group was used.
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
/// let result = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"]))
/// .try_get_matches_from(vec!["myprog", "-f", "-c"]);
/// // Because we used both args in the group it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
/// ```
///
/// [`Arg`]: crate::Arg
#[inline]
#[must_use]
pub fn multiple(mut self, yes: bool) -> Self {
self.multiple = yes;
self
}
/// Return true if the group allows more than one of the arguments
/// in this group to be used. (Default: `false`)
///
/// # Example
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{ArgGroup};
/// let mut group = ArgGroup::new("myprog")
/// .args(["f", "c"])
/// .multiple(true);
///
/// assert!(group.is_multiple());
/// ```
pub fn is_multiple(&mut self) -> bool {
self.multiple
}
/// Require an argument from the group to be present when parsing.
///
/// This is unless conflicting with another argument. A required group will be displayed in
/// the usage string of the application in the format `<arg|arg2|arg3>`.
///
/// <div class="warning">
///
/// **NOTE:** This setting only applies to the current [`Command`] / [`Subcommand`]s, and not
/// globally.
///
/// </div>
///
/// <div class="warning">
///
/// **NOTE:** By default, [`ArgGroup::multiple`] is set to `false` which when combined with
/// `ArgGroup::required(true)` states, "One and *only one* arg must be used from this group.
/// Use of more than one arg is an error." Vice setting `ArgGroup::multiple(true)` which
/// states, '*At least* one arg from this group must be used. Using multiple is OK."
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
/// let result = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"])
/// .required(true))
/// .try_get_matches_from(vec!["myprog"]);
/// // Because we didn't use any of the args in the group, it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
/// ```
///
/// [`Subcommand`]: crate::Subcommand
/// [`ArgGroup::multiple`]: ArgGroup::multiple()
/// [`Command`]: crate::Command
#[inline]
#[must_use]
pub fn required(mut self, yes: bool) -> Self {
self.required = yes;
self
}
/// Specify an argument or group that must be present when this group is.
///
/// This is not to be confused with a [required group]. Requirement rules function just like
/// [argument requirement rules], you can name other arguments or groups that must be present
/// when any one of the arguments from this group is used.
///
/// <div class="warning">
///
/// **NOTE:** The name provided may be an argument or group name
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
/// let result = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("debug")
/// .short('d')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"])
/// .requires("debug"))
/// .try_get_matches_from(vec!["myprog", "-c"]);
/// // because we used an arg from the group, and the group requires "-d" to be used, it's an
/// // error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
/// ```
/// [required group]: ArgGroup::required()
/// [argument requirement rules]: crate::Arg::requires()
#[must_use]
pub fn requires(mut self, id: impl IntoResettable<Id>) -> Self {
if let Some(id) = id.into_resettable().into_option() {
self.requires.push(id);
} else {
self.requires.clear();
}
self
}
/// Specify arguments or groups that must be present when this group is.
///
/// This is not to be confused with a [required group]. Requirement rules function just like
/// [argument requirement rules], you can name other arguments or groups that must be present
/// when one of the arguments from this group is used.
///
/// <div class="warning">
///
/// **NOTE:** The names provided may be an argument or group name
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
/// let result = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("debug")
/// .short('d')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("verb")
/// .short('v')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"])
/// .requires_all(["debug", "verb"]))
/// .try_get_matches_from(vec!["myprog", "-c", "-d"]);
/// // because we used an arg from the group, and the group requires "-d" and "-v" to be used,
/// // yet we only used "-d" it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
/// ```
/// [required group]: ArgGroup::required()
/// [argument requirement rules]: crate::Arg::requires_ifs()
#[must_use]
pub fn requires_all(mut self, ns: impl IntoIterator<Item = impl Into<Id>>) -> Self {
for n in ns {
self = self.requires(n);
}
self
}
/// Specify an argument or group that must **not** be present when this group is.
///
/// Exclusion (aka conflict) rules function just like [argument exclusion rules], you can name
/// other arguments or groups that must *not* be present when one of the arguments from this
/// group are used.
///
/// <div class="warning">
///
/// **NOTE:** The name provided may be an argument, or group name
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
/// let result = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("debug")
/// .short('d')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"])
/// .conflicts_with("debug"))
/// .try_get_matches_from(vec!["myprog", "-c", "-d"]);
/// // because we used an arg from the group, and the group conflicts with "-d", it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
/// ```
/// [argument exclusion rules]: crate::Arg::conflicts_with()
#[must_use]
pub fn conflicts_with(mut self, id: impl IntoResettable<Id>) -> Self {
if let Some(id) = id.into_resettable().into_option() {
self.conflicts.push(id);
} else {
self.conflicts.clear();
}
self
}
/// Specify arguments or groups that must **not** be present when this group is.
///
/// Exclusion rules function just like [argument exclusion rules], you can name other arguments
/// or groups that must *not* be present when one of the arguments from this group are used.
///
/// <div class="warning">
///
/// **NOTE:** The names provided may be an argument, or group name
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, ArgGroup, error::ErrorKind, ArgAction};
/// let result = Command::new("myprog")
/// .arg(Arg::new("flag")
/// .short('f')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("color")
/// .short('c')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("debug")
/// .short('d')
/// .action(ArgAction::SetTrue))
/// .arg(Arg::new("verb")
/// .short('v')
/// .action(ArgAction::SetTrue))
/// .group(ArgGroup::new("req_flags")
/// .args(["flag", "color"])
/// .conflicts_with_all(["debug", "verb"]))
/// .try_get_matches_from(vec!["myprog", "-c", "-v"]);
/// // because we used an arg from the group, and the group conflicts with either "-v" or "-d"
/// // it's an error
/// assert!(result.is_err());
/// let err = result.unwrap_err();
/// assert_eq!(err.kind(), ErrorKind::ArgumentConflict);
/// ```
///
/// [argument exclusion rules]: crate::Arg::conflicts_with_all()
#[must_use]
pub fn conflicts_with_all(mut self, ns: impl IntoIterator<Item = impl Into<Id>>) -> Self {
for n in ns {
self = self.conflicts_with(n);
}
self
}
}
/// # Reflection
impl ArgGroup {
/// Get the name of the group
#[inline]
pub fn get_id(&self) -> &Id {
&self.id
}
/// Reports whether [`ArgGroup::required`] is set
#[inline]
pub fn is_required_set(&self) -> bool {
self.required
}
}
impl From<&'_ Self> for ArgGroup {
fn from(g: &Self) -> Self {
g.clone()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn groups() {
let g = ArgGroup::new("test")
.arg("a1")
.arg("a4")
.args(["a2", "a3"])
.required(true)
.conflicts_with("c1")
.conflicts_with_all(["c2", "c3"])
.conflicts_with("c4")
.requires("r1")
.requires_all(["r2", "r3"])
.requires("r4");
let args: Vec<Id> = vec!["a1".into(), "a4".into(), "a2".into(), "a3".into()];
let reqs: Vec<Id> = vec!["r1".into(), "r2".into(), "r3".into(), "r4".into()];
let confs: Vec<Id> = vec!["c1".into(), "c2".into(), "c3".into(), "c4".into()];
assert_eq!(g.args, args);
assert_eq!(g.requires, reqs);
assert_eq!(g.conflicts, confs);
}
#[test]
fn test_from() {
let g = ArgGroup::new("test")
.arg("a1")
.arg("a4")
.args(["a2", "a3"])
.required(true)
.conflicts_with("c1")
.conflicts_with_all(["c2", "c3"])
.conflicts_with("c4")
.requires("r1")
.requires_all(["r2", "r3"])
.requires("r4");
let args: Vec<Id> = vec!["a1".into(), "a4".into(), "a2".into(), "a3".into()];
let reqs: Vec<Id> = vec!["r1".into(), "r2".into(), "r3".into(), "r4".into()];
let confs: Vec<Id> = vec!["c1".into(), "c2".into(), "c3".into(), "c4".into()];
let g2 = ArgGroup::from(&g);
assert_eq!(g2.args, args);
assert_eq!(g2.requires, reqs);
assert_eq!(g2.conflicts, confs);
}
// This test will *fail to compile* if ArgGroup is not Send + Sync
#[test]
fn arg_group_send_sync() {
fn foo<T: Send + Sync>(_: T) {}
foo(ArgGroup::new("test"));
}
#[test]
fn arg_group_expose_is_multiple_helper() {
let args: Vec<Id> = vec!["a1".into(), "a4".into()];
let mut grp_multiple = ArgGroup::new("test_multiple").args(&args).multiple(true);
assert!(grp_multiple.is_multiple());
let mut grp_not_multiple = ArgGroup::new("test_multiple").args(&args).multiple(false);
assert!(!grp_not_multiple.is_multiple());
}
#[test]
fn arg_group_expose_get_args_helper() {
let args: Vec<Id> = vec!["a1".into(), "a4".into()];
let grp = ArgGroup::new("program").args(&args);
for (pos, arg) in grp.get_args().enumerate() {
assert_eq!(*arg, args[pos]);
}
}
}

View File

@@ -0,0 +1,19 @@
use crate::builder::OsStr;
/// Operations to perform on argument values
///
/// These do not apply to [`ValueSource::DefaultValue`][crate::parser::ValueSource::DefaultValue]
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "unstable-v5", non_exhaustive)]
pub enum ArgPredicate {
/// Is the argument present?
IsPresent,
/// Does the argument match the specified value?
Equals(OsStr),
}
impl<S: Into<OsStr>> From<S> for ArgPredicate {
fn from(other: S) -> Self {
Self::Equals(other.into())
}
}

View File

@@ -0,0 +1,91 @@
#[allow(unused)]
use crate::Arg;
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct ArgFlags(u32);
impl ArgFlags {
pub(crate) fn set(&mut self, setting: ArgSettings) {
self.0 |= setting.bit();
}
pub(crate) fn unset(&mut self, setting: ArgSettings) {
self.0 &= !setting.bit();
}
pub(crate) fn is_set(&self, setting: ArgSettings) -> bool {
self.0 & setting.bit() != 0
}
pub(crate) fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
}
impl std::ops::BitOr for ArgFlags {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
self.insert(rhs);
self
}
}
/// Various settings that apply to arguments and may be set, unset, and checked via getter/setter
/// methods [`Arg::setting`], [`Arg::unset_setting`], and [`Arg::is_set`]. This is what the
/// [`Arg`] methods which accept a `bool` use internally.
///
/// [`Arg`]: crate::Arg
/// [`Arg::setting`]: crate::Arg::setting()
/// [`Arg::unset_setting`]: crate::Arg::unset_setting()
/// [`Arg::is_set`]: crate::Arg::is_set()
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub(crate) enum ArgSettings {
Required,
Global,
Hidden,
NextLineHelp,
HidePossibleValues,
AllowHyphenValues,
AllowNegativeNumbers,
RequireEquals,
Last,
TrailingVarArg,
HideDefaultValue,
IgnoreCase,
#[cfg(feature = "env")]
HideEnv,
#[cfg(feature = "env")]
HideEnvValues,
HiddenShortHelp,
HiddenLongHelp,
Exclusive,
}
impl ArgSettings {
fn bit(self) -> u32 {
1 << (self as u8)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Arg;
#[test]
fn setting() {
let m = Arg::new("setting").setting(ArgSettings::Required);
assert!(m.is_required_set());
}
#[test]
fn unset_setting() {
let m = Arg::new("unset_setting").setting(ArgSettings::Required);
assert!(m.is_required_set());
let m = m.unset_setting(ArgSettings::Required);
assert!(!m.is_required_set(), "{m:#?}");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,832 @@
use std::cmp::Ordering;
use crate::ArgAction;
use crate::INTERNAL_ERROR_MSG;
use crate::builder::ValueRange;
use crate::mkeymap::KeyType;
use crate::util::FlatSet;
use crate::util::Id;
use crate::{Arg, Command, ValueHint};
pub(crate) fn assert_app(cmd: &Command) {
debug!("Command::_debug_asserts");
let mut short_flags = vec![];
let mut long_flags = vec![];
// Invalid version flag settings
if cmd.get_version().is_none() && cmd.get_long_version().is_none() {
// PropagateVersion is meaningless if there is no version
assert!(
!cmd.is_propagate_version_set(),
"Command {}: No version information via Command::version or Command::long_version to propagate",
cmd.get_name(),
);
// Used `Command::mut_arg("version", ..) but did not provide any version information to display
let version_needed = cmd
.get_arguments()
.filter(|x| matches!(x.get_action(), ArgAction::Version))
.map(|x| x.get_id())
.collect::<Vec<_>>();
assert_eq!(
version_needed,
Vec::<&str>::new(),
"Command {}: `ArgAction::Version` used without providing Command::version or Command::long_version",
cmd.get_name()
);
}
for sc in cmd.get_subcommands() {
if let Some(s) = sc.get_short_flag().as_ref() {
short_flags.push(Flag::Command(format!("-{s}"), sc.get_name()));
}
for short_alias in sc.get_all_short_flag_aliases() {
short_flags.push(Flag::Command(format!("-{short_alias}"), sc.get_name()));
}
if let Some(l) = sc.get_long_flag().as_ref() {
assert!(
!l.starts_with('-'),
"Command {}: long_flag {:?} must not start with a `-`, that will be handled by the parser",
sc.get_name(),
l
);
long_flags.push(Flag::Command(format!("--{l}"), sc.get_name()));
}
for long_alias in sc.get_all_long_flag_aliases() {
long_flags.push(Flag::Command(format!("--{long_alias}"), sc.get_name()));
}
}
for arg in cmd.get_arguments() {
assert_arg(arg);
assert!(
!cmd.is_multicall_set(),
"Command {}: Arguments like {} cannot be set on a multicall command",
cmd.get_name(),
arg.get_id()
);
if let Some(s) = arg.get_short() {
short_flags.push(Flag::Arg(format!("-{s}"), arg.get_id().as_str()));
}
for (short_alias, _) in &arg.short_aliases {
short_flags.push(Flag::Arg(format!("-{short_alias}"), arg.get_id().as_str()));
}
if let Some(l) = arg.get_long() {
assert!(
!l.starts_with('-'),
"Argument {}: long {:?} must not start with a `-`, that will be handled by the parser",
arg.get_id(),
l
);
long_flags.push(Flag::Arg(format!("--{l}"), arg.get_id().as_str()));
}
for (long_alias, _) in &arg.aliases {
long_flags.push(Flag::Arg(format!("--{long_alias}"), arg.get_id().as_str()));
}
// Name conflicts
if let Some((first, second)) = cmd.two_args_of(|x| x.get_id() == arg.get_id()) {
panic!(
"Command {}: Argument names must be unique, but '{}' is in use by more than one argument or group{}",
cmd.get_name(),
arg.get_id(),
duplicate_tip(cmd, first, second),
);
}
// Long conflicts
if let Some(l) = arg.get_long() {
if let Some((first, second)) = cmd.two_args_of(|x| x.get_long() == Some(l)) {
panic!(
"Command {}: Long option names must be unique for each argument, \
but '--{}' is in use by both '{}' and '{}'{}",
cmd.get_name(),
l,
first.get_id(),
second.get_id(),
duplicate_tip(cmd, first, second)
)
}
}
// Short conflicts
if let Some(s) = arg.get_short() {
if let Some((first, second)) = cmd.two_args_of(|x| x.get_short() == Some(s)) {
panic!(
"Command {}: Short option names must be unique for each argument, \
but '-{}' is in use by both '{}' and '{}'{}",
cmd.get_name(),
s,
first.get_id(),
second.get_id(),
duplicate_tip(cmd, first, second),
)
}
}
// Index conflicts
if let Some(idx) = arg.index {
if let Some((first, second)) =
cmd.two_args_of(|x| x.is_positional() && x.get_index() == Some(idx))
{
panic!(
"Command {}: Argument '{}' has the same index as '{}' \
and they are both positional arguments\n\n\t \
Use `Arg::num_args(1..)` to allow one \
positional argument to take multiple values",
cmd.get_name(),
first.get_id(),
second.get_id()
)
}
}
// requires, r_if, r_unless
for (_predicate, req_id) in &arg.requires {
assert!(
&arg.id != req_id,
"Argument {} cannot require itself",
arg.get_id()
);
assert!(
cmd.id_exists(req_id),
"Command {}: Argument or group '{}' specified in 'requires*' for '{}' does not exist",
cmd.get_name(),
req_id,
arg.get_id(),
);
}
for req in &arg.r_ifs {
assert!(
!arg.is_required_set(),
"Argument {}: `required` conflicts with `required_if_eq*`",
arg.get_id()
);
assert!(
cmd.id_exists(&req.0),
"Command {}: Argument or group '{}' specified in 'required_if_eq*' for '{}' does not exist",
cmd.get_name(),
req.0,
arg.get_id()
);
}
for req in &arg.r_ifs_all {
assert!(
!arg.is_required_set(),
"Argument {}: `required` conflicts with `required_if_eq_all`",
arg.get_id()
);
assert!(
cmd.id_exists(&req.0),
"Command {}: Argument or group '{}' specified in 'required_if_eq_all' for '{}' does not exist",
cmd.get_name(),
req.0,
arg.get_id()
);
}
for req in &arg.r_unless {
assert!(
!arg.is_required_set(),
"Argument {}: `required` conflicts with `required_unless*`",
arg.get_id()
);
assert!(
cmd.id_exists(req),
"Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
cmd.get_name(),
req,
arg.get_id(),
);
}
for req in &arg.r_unless_all {
assert!(
!arg.is_required_set(),
"Argument {}: `required` conflicts with `required_unless*`",
arg.get_id()
);
assert!(
cmd.id_exists(req),
"Command {}: Argument or group '{}' specified in 'required_unless*' for '{}' does not exist",
cmd.get_name(),
req,
arg.get_id(),
);
}
// conflicts
for req in &arg.conflicts {
assert!(
cmd.id_exists(req),
"Command {}: Argument or group '{}' specified in 'conflicts_with*' for '{}' does not exist",
cmd.get_name(),
req,
arg.get_id(),
);
}
// overrides
for req in &arg.overrides {
assert!(
cmd.id_exists(req),
"Command {}: Argument or group '{}' specified in 'overrides_with*' for '{}' does not exist",
cmd.get_name(),
req,
arg.get_id(),
);
}
if arg.is_last_set() {
assert!(
arg.get_long().is_none(),
"Command {}: Flags or Options cannot have last(true) set. '{}' has both a long and last(true) set.",
cmd.get_name(),
arg.get_id()
);
assert!(
arg.get_short().is_none(),
"Command {}: Flags or Options cannot have last(true) set. '{}' has both a short and last(true) set.",
cmd.get_name(),
arg.get_id()
);
}
assert!(
!(arg.is_required_set() && arg.is_global_set()),
"Command {}: Global arguments cannot be required.\n\n\t'{}' is marked as both global and required",
cmd.get_name(),
arg.get_id()
);
if arg.get_value_hint() == ValueHint::CommandWithArguments {
assert!(
arg.is_positional(),
"Command {}: Argument '{}' has hint CommandWithArguments and must be positional.",
cmd.get_name(),
arg.get_id()
);
assert!(
arg.is_trailing_var_arg_set() || arg.is_last_set(),
"Command {}: Positional argument '{}' has hint CommandWithArguments, so Command must have `trailing_var_arg(true)` or `last(true)` set.",
cmd.get_name(),
arg.get_id()
);
}
}
for group in cmd.get_groups() {
// Name conflicts
assert!(
cmd.get_groups().filter(|x| x.id == group.id).count() < 2,
"Command {}: Argument group name must be unique\n\n\t'{}' is already in use",
cmd.get_name(),
group.get_id(),
);
// Groups should not have naming conflicts with Args
assert!(
!cmd.get_arguments().any(|x| x.get_id() == group.get_id()),
"Command {}: Argument group name '{}' must not conflict with argument name",
cmd.get_name(),
group.get_id(),
);
for arg in &group.args {
// Args listed inside groups should exist
assert!(
cmd.get_arguments().any(|x| x.get_id() == arg),
"Command {}: Argument group '{}' contains non-existent argument '{}'",
cmd.get_name(),
group.get_id(),
arg
);
}
for arg in &group.requires {
// Args listed inside groups should exist
assert!(
cmd.id_exists(arg),
"Command {}: Argument group '{}' requires non-existent '{}' id",
cmd.get_name(),
group.get_id(),
arg
);
}
for arg in &group.conflicts {
// Args listed inside groups should exist
assert!(
cmd.id_exists(arg),
"Command {}: Argument group '{}' conflicts with non-existent '{}' id",
cmd.get_name(),
group.get_id(),
arg
);
}
}
// Conflicts between flags and subcommands
long_flags.sort_unstable();
short_flags.sort_unstable();
detect_duplicate_flags(&long_flags, "long");
detect_duplicate_flags(&short_flags, "short");
let mut subs = FlatSet::new();
for sc in cmd.get_subcommands() {
assert!(
subs.insert(sc.get_name()),
"Command {}: command name `{}` is duplicated",
cmd.get_name(),
sc.get_name()
);
for alias in sc.get_all_aliases() {
assert!(
subs.insert(alias),
"Command {}: command `{}` alias `{}` is duplicated",
cmd.get_name(),
sc.get_name(),
alias
);
}
}
_verify_positionals(cmd);
#[cfg(feature = "help")]
if let Some(help_template) = cmd.get_help_template() {
assert!(
!help_template.to_string().contains("{flags}"),
"Command {}: {}",
cmd.get_name(),
"`{flags}` template variable was removed in clap3, they are now included in `{options}`",
);
assert!(
!help_template.to_string().contains("{unified}"),
"Command {}: {}",
cmd.get_name(),
"`{unified}` template variable was removed in clap3, use `{options}` instead"
);
#[cfg(feature = "unstable-v5")]
assert!(
!help_template.to_string().contains("{bin}"),
"Command {}: {}",
cmd.get_name(),
"`{bin}` template variable was removed in clap5, use `{name}` instead"
);
}
cmd._panic_on_missing_help(cmd.is_help_expected_set());
assert_app_flags(cmd);
}
fn duplicate_tip(cmd: &Command, first: &Arg, second: &Arg) -> &'static str {
if !cmd.is_disable_help_flag_set()
&& (first.get_id() == Id::HELP || second.get_id() == Id::HELP)
{
" (call `cmd.disable_help_flag(true)` to remove the auto-generated `--help`)"
} else if !cmd.is_disable_version_flag_set()
&& (first.get_id() == Id::VERSION || second.get_id() == Id::VERSION)
{
" (call `cmd.disable_version_flag(true)` to remove the auto-generated `--version`)"
} else {
""
}
}
#[derive(Eq)]
enum Flag<'a> {
Command(String, &'a str),
Arg(String, &'a str),
}
impl PartialEq for Flag<'_> {
fn eq(&self, other: &Flag<'_>) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl PartialOrd for Flag<'_> {
fn partial_cmp(&self, other: &Flag<'_>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Flag<'_> {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Flag::Command(s1, _), Flag::Command(s2, _))
| (Flag::Arg(s1, _), Flag::Arg(s2, _))
| (Flag::Command(s1, _), Flag::Arg(s2, _))
| (Flag::Arg(s1, _), Flag::Command(s2, _)) => {
if s1 == s2 {
Ordering::Equal
} else {
s1.cmp(s2)
}
}
}
}
}
fn detect_duplicate_flags(flags: &[Flag<'_>], short_or_long: &str) {
for (one, two) in find_duplicates(flags) {
match (one, two) {
(Flag::Command(flag, one), Flag::Command(_, another)) if one != another => panic!(
"the '{flag}' {short_or_long} flag is specified for both '{one}' and '{another}' subcommands"
),
(Flag::Arg(flag, one), Flag::Arg(_, another)) if one != another => panic!(
"{short_or_long} option names must be unique, but '{flag}' is in use by both '{one}' and '{another}'"
),
(Flag::Arg(flag, arg), Flag::Command(_, sub))
| (Flag::Command(flag, sub), Flag::Arg(_, arg)) => panic!(
"the '{flag}' {short_or_long} flag for the '{arg}' argument conflicts with the short flag \
for '{sub}' subcommand"
),
_ => {}
}
}
}
/// Find duplicates in a sorted array.
///
/// The algorithm is simple: the array is sorted, duplicates
/// must be placed next to each other, we can check only adjacent elements.
fn find_duplicates<T: PartialEq>(slice: &[T]) -> impl Iterator<Item = (&T, &T)> {
slice.windows(2).filter_map(|w| {
if w[0] == w[1] {
Some((&w[0], &w[1]))
} else {
None
}
})
}
fn assert_app_flags(cmd: &Command) {
macro_rules! checker {
($a:ident conflicts $($b:ident)|+) => {
if cmd.$a() {
let mut s = String::new();
$(
if cmd.$b() {
use std::fmt::Write;
write!(&mut s, " AppSettings::{} conflicts with AppSettings::{}.\n", std::stringify!($b), std::stringify!($a)).unwrap();
}
)+
if !s.is_empty() {
panic!("{}\n{}", cmd.get_name(), s)
}
}
};
}
checker!(is_multicall_set conflicts is_no_binary_name_set);
}
#[cfg(debug_assertions)]
fn _verify_positionals(cmd: &Command) -> bool {
debug!("Command::_verify_positionals");
// Because you must wait until all arguments have been supplied, this is the first chance
// to make assertions on positional argument indexes
//
// First we verify that the index highest supplied index, is equal to the number of
// positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
// but no 2)
let highest_idx = cmd
.get_keymap()
.keys()
.filter_map(|x| {
if let KeyType::Position(n) = x {
Some(*n)
} else {
None
}
})
.max()
.unwrap_or(0);
let num_p = cmd.get_keymap().keys().filter(|x| x.is_position()).count();
assert!(
highest_idx == num_p,
"Found positional argument whose index is {highest_idx} but there \
are only {num_p} positional arguments defined",
);
for arg in cmd.get_arguments() {
if arg.index.unwrap_or(0) == highest_idx {
assert!(
!arg.is_trailing_var_arg_set() || !arg.is_last_set(),
"{}:{}: `Arg::trailing_var_arg` and `Arg::last` cannot be used together",
cmd.get_name(),
arg.get_id()
);
if arg.is_trailing_var_arg_set() {
assert!(
arg.is_multiple(),
"{}:{}: `Arg::trailing_var_arg` must accept multiple values",
cmd.get_name(),
arg.get_id()
);
}
} else {
assert!(
!arg.is_trailing_var_arg_set(),
"{}:{}: `Arg::trailing_var_arg` can only apply to last positional",
cmd.get_name(),
arg.get_id()
);
}
}
// Next we verify that only the highest index has takes multiple arguments (if any)
let only_highest = |a: &Arg| a.is_multiple() && (a.get_index().unwrap_or(0) != highest_idx);
if cmd.get_positionals().any(only_highest) {
// First we make sure if there is a positional that allows multiple values
// the one before it (second to last) has one of these:
// * a value terminator
// * ArgSettings::Last
// * The last arg is Required
// We can't pass the closure (it.next()) to the macro directly because each call to
// find() (iterator, not macro) gets called repeatedly.
let last = &cmd.get_keymap()[&KeyType::Position(highest_idx)];
let second_to_last = &cmd.get_keymap()[&KeyType::Position(highest_idx - 1)];
// Either the final positional is required
// Or the second to last has a terminator or .last(true) set
let ok = last.is_required_set()
|| (second_to_last.terminator.is_some() || second_to_last.is_last_set())
|| last.is_last_set();
assert!(
ok,
"Positional argument `{last}` *must* have `required(true)` or `last(true)` set \
because a prior positional argument (`{second_to_last}`) has `num_args(1..)`"
);
// We make sure if the second to last is Multiple the last is ArgSettings::Last
let ok = second_to_last.is_multiple() || last.is_last_set();
assert!(
ok,
"Only the last positional argument, or second to last positional \
argument may be set to `.num_args(1..)`"
);
// Next we check how many have both Multiple and not a specific number of values set
let count = cmd
.get_positionals()
.filter(|p| {
p.is_multiple_values_set()
&& p.get_value_terminator().is_none()
&& !p.get_num_args().expect(INTERNAL_ERROR_MSG).is_fixed()
})
.count();
let ok = count <= 1
|| (last.is_last_set()
&& last.is_multiple()
&& second_to_last.is_multiple()
&& count == 2);
assert!(
ok,
"Only one positional argument with `.num_args(1..)` set is allowed per \
command, unless the second one also has .last(true) set"
);
}
let mut found = false;
if cmd.is_allow_missing_positional_set() {
// Check that if a required positional argument is found, all positions with a lower
// index are also required.
let mut foundx2 = false;
for p in cmd.get_positionals() {
if foundx2 && !p.is_required_set() {
assert!(
p.is_required_set(),
"Found non-required positional argument with a lower \
index than a required positional argument by two or more: {:?} \
index {:?}",
p.get_id(),
p.get_index()
);
} else if p.is_required_set() && !p.is_last_set() {
// Args that .last(true) don't count since they can be required and have
// positionals with a lower index that aren't required
// Imagine: prog <req1> [opt1] -- <req2>
// Both of these are valid invocations:
// $ prog r1 -- r2
// $ prog r1 o1 -- r2
if found {
foundx2 = true;
continue;
}
found = true;
} else {
found = false;
}
}
} else {
// Check that if a required positional argument is found, all positions with a lower
// index are also required
for p in (1..=num_p).rev().filter_map(|n| cmd.get_keymap().get(&n)) {
if found {
assert!(
p.is_required_set(),
"Found non-required positional argument with a lower \
index than a required positional argument: {:?} index {:?}",
p.get_id(),
p.get_index()
);
} else if p.is_required_set() && !p.is_last_set() {
// Args that .last(true) don't count since they can be required and have
// positionals with a lower index that aren't required
// Imagine: prog <req1> [opt1] -- <req2>
// Both of these are valid invocations:
// $ prog r1 -- r2
// $ prog r1 o1 -- r2
found = true;
}
}
}
assert!(
cmd.get_positionals().filter(|p| p.is_last_set()).count() < 2,
"Only one positional argument may have last(true) set. Found two."
);
if cmd
.get_positionals()
.any(|p| p.is_last_set() && p.is_required_set())
&& cmd.has_subcommands()
&& !cmd.is_subcommand_negates_reqs_set()
{
panic!(
"Having a required positional argument with .last(true) set *and* child \
subcommands without setting SubcommandsNegateReqs isn't compatible."
);
}
true
}
fn assert_arg(arg: &Arg) {
debug!("Arg::_debug_asserts:{}", arg.get_id());
// Self conflict
// TODO: this check should be recursive
assert!(
!arg.conflicts.iter().any(|x| x == arg.get_id()),
"Argument '{}' cannot conflict with itself",
arg.get_id(),
);
assert!(
arg.get_num_args().unwrap_or(1.into()).max_values()
<= arg.get_action().max_num_args().max_values(),
"Argument `{}`'s action {:?} is incompatible with `num_args({:?})`",
arg.get_id(),
arg.get_action(),
arg.get_num_args().unwrap_or(1.into())
);
if let Some(action_type_id) = arg.get_action().value_type_id() {
assert_eq!(
action_type_id,
arg.get_value_parser().type_id(),
"Argument `{}`'s selected action {:?} contradicts `value_parser` ({:?})",
arg.get_id(),
arg.get_action(),
arg.get_value_parser()
);
}
if arg.get_value_hint() != ValueHint::Unknown {
assert!(
arg.is_takes_value_set(),
"Argument '{}' has value hint but takes no value",
arg.get_id()
);
if arg.get_value_hint() == ValueHint::CommandWithArguments {
assert!(
arg.is_multiple_values_set(),
"Argument '{}' uses hint CommandWithArguments and must accept multiple values",
arg.get_id()
);
}
}
if arg.index.is_some() {
assert!(
arg.is_positional(),
"Argument '{}' is a positional argument and can't have short or long name versions",
arg.get_id()
);
assert!(
arg.is_takes_value_set(),
"Argument '{}' is positional and it must take a value but action is {:?}{}",
arg.get_id(),
arg.get_action(),
if arg.get_id() == Id::HELP {
" (`mut_arg` no longer works with implicit `--help`)"
} else if arg.get_id() == Id::VERSION {
" (`mut_arg` no longer works with implicit `--version`)"
} else {
""
}
);
}
let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG);
// This can be the cause of later asserts, so put this first
if num_vals != ValueRange::EMPTY {
// HACK: Don't check for flags to make the derive easier
let num_val_names = arg.get_value_names().unwrap_or(&[]).len();
if num_vals.max_values() < num_val_names {
panic!(
"Argument {}: Too many value names ({}) compared to `num_args` ({})",
arg.get_id(),
num_val_names,
num_vals
);
}
}
assert_eq!(
num_vals.is_multiple(),
arg.is_multiple_values_set(),
"Argument {}: mismatch between `num_args` ({}) and `multiple_values`",
arg.get_id(),
num_vals,
);
if 1 < num_vals.min_values() {
assert!(
!arg.is_require_equals_set(),
"Argument {}: cannot accept more than 1 arg (num_args={}) with require_equals",
arg.get_id(),
num_vals
);
}
if num_vals == ValueRange::SINGLE {
assert!(
!arg.is_multiple_values_set(),
"Argument {}: mismatch between `num_args` and `multiple_values`",
arg.get_id()
);
}
assert_arg_flags(arg);
}
fn assert_arg_flags(arg: &Arg) {
macro_rules! checker {
($a:ident requires $($b:ident)|+) => {
if arg.$a() {
let mut s = String::new();
$(
if !arg.$b() {
use std::fmt::Write;
write!(&mut s, " Arg::{} is required when Arg::{} is set.\n", std::stringify!($b), std::stringify!($a)).unwrap();
}
)+
if !s.is_empty() {
panic!("Argument {:?}\n{}", arg.get_id(), s)
}
}
}
}
checker!(is_hide_possible_values_set requires is_takes_value_set);
checker!(is_allow_hyphen_values_set requires is_takes_value_set);
checker!(is_allow_negative_numbers_set requires is_takes_value_set);
checker!(is_require_equals_set requires is_takes_value_set);
checker!(is_last_set requires is_takes_value_set);
checker!(is_hide_default_value_set requires is_takes_value_set);
checker!(is_multiple_values_set requires is_takes_value_set);
checker!(is_ignore_case_set requires is_takes_value_set);
}

View File

@@ -0,0 +1,46 @@
use crate::util::AnyValue;
use crate::util::AnyValueId;
use crate::util::FlatMap;
#[derive(Default, Clone, Debug)]
pub(crate) struct Extensions {
extensions: FlatMap<AnyValueId, AnyValue>,
}
impl Extensions {
#[allow(dead_code)]
pub(crate) fn get<T: Extension>(&self) -> Option<&T> {
let id = AnyValueId::of::<T>();
self.extensions.get(&id).map(|e| {
e.downcast_ref::<T>()
.expect("`Extensions` tracks values by type")
})
}
#[allow(dead_code)]
pub(crate) fn set<T: Extension>(&mut self, tagged: T) -> bool {
let value = AnyValue::new(tagged);
let id = value.type_id();
self.extensions.insert(id, value).is_some()
}
#[allow(dead_code)]
pub(crate) fn remove<T: Extension>(&mut self) -> Option<T> {
let id = AnyValueId::of::<T>();
self.extensions.remove(&id).map(|e| {
e.downcast_into::<T>()
.expect("`Extensions` tracks values by type")
})
}
pub(crate) fn update(&mut self, other: &Self) {
for (key, value) in other.extensions.iter() {
self.extensions.insert(*key, value.clone());
}
}
}
#[allow(unreachable_pub)]
pub trait Extension: std::fmt::Debug + Clone + std::any::Any + Send + Sync + 'static {}
impl<T> Extension for T where T: std::fmt::Debug + Clone + std::any::Any + Send + Sync + 'static {}

View File

@@ -0,0 +1,71 @@
//! Define [`Command`] line [arguments][`Arg`]
mod action;
mod app_settings;
mod arg;
mod arg_group;
mod arg_predicate;
mod arg_settings;
mod command;
mod ext;
mod os_str;
mod possible_value;
mod range;
mod resettable;
mod str;
mod styled_str;
mod value_hint;
mod value_parser;
#[cfg(debug_assertions)]
mod debug_asserts;
#[cfg(test)]
mod tests;
pub mod styling;
pub use self::str::Str;
pub use action::ArgAction;
pub use arg::Arg;
#[cfg(feature = "unstable-ext")]
pub use arg::ArgExt;
pub use arg_group::ArgGroup;
pub use arg_predicate::ArgPredicate;
pub use command::Command;
#[cfg(feature = "unstable-ext")]
pub use command::CommandExt;
pub use os_str::OsStr;
pub use possible_value::PossibleValue;
pub use range::ValueRange;
pub use resettable::IntoResettable;
pub use resettable::Resettable;
pub use styled_str::StyledStr;
pub use styling::Styles;
pub use value_hint::ValueHint;
pub use value_parser::_AnonymousValueParser;
pub use value_parser::_infer_ValueParser_for;
pub use value_parser::BoolValueParser;
pub use value_parser::BoolishValueParser;
pub use value_parser::EnumValueParser;
pub use value_parser::FalseyValueParser;
pub use value_parser::MapValueParser;
pub use value_parser::NonEmptyStringValueParser;
pub use value_parser::OsStringValueParser;
pub use value_parser::PathBufValueParser;
pub use value_parser::PossibleValuesParser;
pub use value_parser::RangedI64ValueParser;
pub use value_parser::RangedU64ValueParser;
pub use value_parser::StringValueParser;
pub use value_parser::TryMapValueParser;
pub use value_parser::TypedValueParser;
pub use value_parser::UnknownArgumentValueParser;
pub use value_parser::ValueParser;
pub use value_parser::ValueParserFactory;
pub use value_parser::impl_prelude;
#[allow(unused_imports)]
pub(crate) use self::str::Inner as StrInner;
pub(crate) use action::CountType;
pub(crate) use arg_settings::{ArgFlags, ArgSettings};
pub(crate) use command::AppExt;

View File

@@ -0,0 +1,364 @@
use crate::builder::Str;
#[cfg(feature = "string")]
use std::borrow::Cow;
/// A UTF-8-encoded fixed string
///
/// <div class="warning">
///
/// **NOTE:** To support dynamic values (i.e. `OsString`), enable the `string`
/// feature
///
/// </div>
#[derive(Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct OsStr {
name: Inner,
}
impl OsStr {
#[cfg(feature = "string")]
pub(crate) fn from_string(name: std::ffi::OsString) -> Self {
Self {
name: Inner::from_string(name),
}
}
#[cfg(feature = "string")]
pub(crate) fn from_ref(name: &std::ffi::OsStr) -> Self {
Self {
name: Inner::from_ref(name),
}
}
pub(crate) fn from_static_ref(name: &'static std::ffi::OsStr) -> Self {
Self {
name: Inner::from_static_ref(name),
}
}
/// Get the raw string as an `std::ffi::OsStr`
pub fn as_os_str(&self) -> &std::ffi::OsStr {
self.name.as_os_str()
}
/// Get the raw string as an `OsString`
pub fn to_os_string(&self) -> std::ffi::OsString {
self.as_os_str().to_owned()
}
}
impl From<&'_ Self> for OsStr {
fn from(id: &'_ Self) -> Self {
id.clone()
}
}
#[cfg(feature = "string")]
impl From<Str> for OsStr {
fn from(id: Str) -> Self {
match id.into_inner() {
crate::builder::StrInner::Static(s) => Self::from_static_ref(std::ffi::OsStr::new(s)),
crate::builder::StrInner::Owned(s) => Self::from_ref(std::ffi::OsStr::new(s.as_ref())),
}
}
}
#[cfg(not(feature = "string"))]
impl From<Str> for OsStr {
fn from(id: Str) -> Self {
Self::from_static_ref(std::ffi::OsStr::new(id.into_inner().0))
}
}
impl From<&'_ Str> for OsStr {
fn from(id: &'_ Str) -> Self {
id.clone().into()
}
}
#[cfg(feature = "string")]
impl From<std::ffi::OsString> for OsStr {
fn from(name: std::ffi::OsString) -> Self {
Self::from_string(name)
}
}
#[cfg(feature = "string")]
impl From<&'_ std::ffi::OsString> for OsStr {
fn from(name: &'_ std::ffi::OsString) -> Self {
Self::from_ref(name.as_os_str())
}
}
#[cfg(feature = "string")]
impl From<String> for OsStr {
fn from(name: String) -> Self {
Self::from_string(name.into())
}
}
#[cfg(feature = "string")]
impl From<&'_ String> for OsStr {
fn from(name: &'_ String) -> Self {
Self::from_ref(name.as_str().as_ref())
}
}
impl From<&'static std::ffi::OsStr> for OsStr {
fn from(name: &'static std::ffi::OsStr) -> Self {
Self::from_static_ref(name)
}
}
impl From<&'_ &'static std::ffi::OsStr> for OsStr {
fn from(name: &'_ &'static std::ffi::OsStr) -> Self {
Self::from_static_ref(name)
}
}
impl From<&'static str> for OsStr {
fn from(name: &'static str) -> Self {
Self::from_static_ref(name.as_ref())
}
}
impl From<&'_ &'static str> for OsStr {
fn from(name: &'_ &'static str) -> Self {
Self::from_static_ref((*name).as_ref())
}
}
#[cfg(feature = "string")]
impl From<Cow<'static, str>> for OsStr {
fn from(cow: Cow<'static, str>) -> Self {
match cow {
Cow::Borrowed(s) => Self::from(s),
Cow::Owned(s) => Self::from(s),
}
}
}
impl From<OsStr> for std::ffi::OsString {
fn from(name: OsStr) -> Self {
name.name.into_os_string()
}
}
impl From<OsStr> for std::path::PathBuf {
fn from(name: OsStr) -> Self {
std::ffi::OsString::from(name).into()
}
}
impl std::fmt::Debug for OsStr {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_os_str(), f)
}
}
impl std::ops::Deref for OsStr {
type Target = std::ffi::OsStr;
#[inline]
fn deref(&self) -> &std::ffi::OsStr {
self.as_os_str()
}
}
impl AsRef<std::ffi::OsStr> for OsStr {
#[inline]
fn as_ref(&self) -> &std::ffi::OsStr {
self.as_os_str()
}
}
impl AsRef<std::path::Path> for OsStr {
#[inline]
fn as_ref(&self) -> &std::path::Path {
std::path::Path::new(self)
}
}
impl std::borrow::Borrow<std::ffi::OsStr> for OsStr {
#[inline]
fn borrow(&self) -> &std::ffi::OsStr {
self.as_os_str()
}
}
impl PartialEq<str> for OsStr {
#[inline]
fn eq(&self, other: &str) -> bool {
PartialEq::eq(self.as_os_str(), other)
}
}
impl PartialEq<OsStr> for str {
#[inline]
fn eq(&self, other: &OsStr) -> bool {
PartialEq::eq(self, other.as_os_str())
}
}
impl PartialEq<&'_ str> for OsStr {
#[inline]
fn eq(&self, other: &&str) -> bool {
PartialEq::eq(self.as_os_str(), *other)
}
}
impl PartialEq<OsStr> for &'_ str {
#[inline]
fn eq(&self, other: &OsStr) -> bool {
PartialEq::eq(*self, other.as_os_str())
}
}
impl PartialEq<&'_ std::ffi::OsStr> for OsStr {
#[inline]
fn eq(&self, other: &&std::ffi::OsStr) -> bool {
PartialEq::eq(self.as_os_str(), *other)
}
}
impl PartialEq<OsStr> for &'_ std::ffi::OsStr {
#[inline]
fn eq(&self, other: &OsStr) -> bool {
PartialEq::eq(*self, other.as_os_str())
}
}
impl PartialEq<String> for OsStr {
#[inline]
fn eq(&self, other: &String) -> bool {
PartialEq::eq(self.as_os_str(), other.as_str())
}
}
impl PartialEq<OsStr> for String {
#[inline]
fn eq(&self, other: &OsStr) -> bool {
PartialEq::eq(self.as_str(), other.as_os_str())
}
}
impl PartialEq<std::ffi::OsString> for OsStr {
#[inline]
fn eq(&self, other: &std::ffi::OsString) -> bool {
PartialEq::eq(self.as_os_str(), other.as_os_str())
}
}
impl PartialEq<OsStr> for std::ffi::OsString {
#[inline]
fn eq(&self, other: &OsStr) -> bool {
PartialEq::eq(self.as_os_str(), other.as_os_str())
}
}
#[cfg(feature = "string")]
pub(crate) mod inner {
#[derive(Clone)]
pub(crate) enum Inner {
Static(&'static std::ffi::OsStr),
Owned(Box<std::ffi::OsStr>),
}
impl Inner {
pub(crate) fn from_string(name: std::ffi::OsString) -> Self {
Self::Owned(name.into_boxed_os_str())
}
pub(crate) fn from_ref(name: &std::ffi::OsStr) -> Self {
Self::Owned(Box::from(name))
}
pub(crate) fn from_static_ref(name: &'static std::ffi::OsStr) -> Self {
Self::Static(name)
}
pub(crate) fn as_os_str(&self) -> &std::ffi::OsStr {
match self {
Self::Static(s) => s,
Self::Owned(s) => s.as_ref(),
}
}
pub(crate) fn into_os_string(self) -> std::ffi::OsString {
self.as_os_str().to_owned()
}
}
}
#[cfg(not(feature = "string"))]
pub(crate) mod inner {
#[derive(Clone)]
pub(crate) struct Inner(&'static std::ffi::OsStr);
impl Inner {
pub(crate) fn from_static_ref(name: &'static std::ffi::OsStr) -> Self {
Self(name)
}
pub(crate) fn as_os_str(&self) -> &std::ffi::OsStr {
self.0
}
pub(crate) fn into_os_string(self) -> std::ffi::OsString {
self.as_os_str().to_owned()
}
}
}
pub(crate) use inner::Inner;
impl Default for Inner {
fn default() -> Self {
Self::from_static_ref(std::ffi::OsStr::new(""))
}
}
impl PartialEq for Inner {
fn eq(&self, other: &Self) -> bool {
self.as_os_str() == other.as_os_str()
}
}
impl PartialOrd for Inner {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Inner {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_os_str().cmp(other.as_os_str())
}
}
impl Eq for Inner {}
impl std::hash::Hash for Inner {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_os_str().hash(state);
}
}
#[cfg(test)]
#[cfg(feature = "string")]
mod tests {
use super::*;
#[test]
#[cfg(feature = "string")]
fn from_cow_borrowed() {
let cow = Cow::Borrowed("hello");
let osstr = OsStr::from(cow);
assert_eq!(osstr, OsStr::from("hello"));
}
#[test]
#[cfg(feature = "string")]
fn from_cow_owned() {
let cow = Cow::Owned("world".to_owned());
let osstr = OsStr::from(cow);
assert_eq!(osstr, OsStr::from("world"));
}
}

View File

@@ -0,0 +1,235 @@
use crate::builder::IntoResettable;
use crate::builder::Str;
use crate::builder::StyledStr;
#[cfg(feature = "help")]
use crate::util::Escape;
use crate::util::eq_ignore_case;
/// A possible value of an argument.
///
/// This is used for specifying [possible values] of [Args].
///
/// See also [`PossibleValuesParser`][crate::builder::PossibleValuesParser]
///
/// <div class="warning">
///
/// **NOTE:** Most likely you can use strings, rather than `PossibleValue` as it is only required
/// to [hide] single values from help messages and shell completions or to attach [help] to
/// possible values.
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Arg, builder::PossibleValue, ArgAction};
/// let cfg = Arg::new("config")
/// .action(ArgAction::Set)
/// .value_name("FILE")
/// .value_parser([
/// PossibleValue::new("fast"),
/// PossibleValue::new("slow").help("slower than fast"),
/// PossibleValue::new("secret speed").hide(true)
/// ]);
/// ```
///
/// [Args]: crate::Arg
/// [possible values]: crate::builder::ValueParser::possible_values
/// [hide]: PossibleValue::hide()
/// [help]: PossibleValue::help()
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct PossibleValue {
name: Str,
help: Option<StyledStr>,
aliases: Vec<Str>, // (name, visible)
hide: bool,
}
impl PossibleValue {
/// Create a [`PossibleValue`] with its name.
///
/// The name will be used to decide whether this value was provided by the user to an argument.
///
/// <div class="warning">
///
/// **NOTE:** In case it is not [hidden] it will also be shown in help messages for arguments
/// that use it as a [possible value] and have not hidden them through [`Arg::hide_possible_values(true)`].
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::PossibleValue;
/// PossibleValue::new("fast")
/// # ;
/// ```
/// [hidden]: PossibleValue::hide
/// [possible value]: crate::builder::PossibleValuesParser
/// [`Arg::hide_possible_values(true)`]: crate::Arg::hide_possible_values()
pub fn new(name: impl Into<Str>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
/// Sets the help description of the value.
///
/// This is typically displayed in completions (where supported) and should be a short, one-line
/// description.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::PossibleValue;
/// PossibleValue::new("slow")
/// .help("not fast")
/// # ;
/// ```
#[inline]
#[must_use]
pub fn help(mut self, help: impl IntoResettable<StyledStr>) -> Self {
self.help = help.into_resettable().into_option();
self
}
/// Hides this value from help and shell completions.
///
/// This is an alternative to hiding through [`Arg::hide_possible_values(true)`], if you only
/// want to hide some values.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::PossibleValue;
/// PossibleValue::new("secret")
/// .hide(true)
/// # ;
/// ```
/// [`Arg::hide_possible_values(true)`]: crate::Arg::hide_possible_values()
#[inline]
#[must_use]
pub fn hide(mut self, yes: bool) -> Self {
self.hide = yes;
self
}
/// Sets a *hidden* alias for this argument value.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::PossibleValue;
/// PossibleValue::new("slow")
/// .alias("not-fast")
/// # ;
/// ```
#[must_use]
pub fn alias(mut self, name: impl IntoResettable<Str>) -> Self {
if let Some(name) = name.into_resettable().into_option() {
self.aliases.push(name);
} else {
self.aliases.clear();
}
self
}
/// Sets multiple *hidden* aliases for this argument value.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::PossibleValue;
/// PossibleValue::new("slow")
/// .aliases(["not-fast", "snake-like"])
/// # ;
/// ```
#[must_use]
pub fn aliases(mut self, names: impl IntoIterator<Item = impl Into<Str>>) -> Self {
self.aliases.extend(names.into_iter().map(|a| a.into()));
self
}
}
/// Reflection
impl PossibleValue {
/// Get the name of the argument value
#[inline]
pub fn get_name(&self) -> &str {
self.name.as_str()
}
/// Get the help specified for this argument, if any
#[inline]
pub fn get_help(&self) -> Option<&StyledStr> {
self.help.as_ref()
}
/// Report if [`PossibleValue::hide`] is set
#[inline]
pub fn is_hide_set(&self) -> bool {
self.hide
}
/// Report if `PossibleValue` is not hidden and has a help message
pub(crate) fn should_show_help(&self) -> bool {
!self.hide && self.help.is_some()
}
/// Get the name if argument value is not hidden, `None` otherwise,
/// but wrapped in quotes if it contains whitespace
#[cfg(feature = "help")]
pub(crate) fn get_visible_quoted_name(&self) -> Option<std::borrow::Cow<'_, str>> {
if !self.hide {
Some(Escape(self.name.as_str()).to_cow())
} else {
None
}
}
/// Returns all valid values of the argument value.
///
/// Namely the name and all aliases.
pub fn get_name_and_aliases(&self) -> impl Iterator<Item = &str> + '_ {
std::iter::once(self.get_name()).chain(self.aliases.iter().map(|s| s.as_str()))
}
/// Tests if the value is valid for this argument value
///
/// The value is valid if it is either the name or one of the aliases.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::PossibleValue;
/// let arg_value = PossibleValue::new("fast").alias("not-slow");
///
/// assert!(arg_value.matches("fast", false));
/// assert!(arg_value.matches("not-slow", false));
///
/// assert!(arg_value.matches("FAST", true));
/// assert!(!arg_value.matches("FAST", false));
/// ```
pub fn matches(&self, value: &str, ignore_case: bool) -> bool {
if ignore_case {
self.get_name_and_aliases()
.any(|name| eq_ignore_case(name, value))
} else {
self.get_name_and_aliases().any(|name| name == value)
}
}
}
impl<S: Into<Str>> From<S> for PossibleValue {
fn from(s: S) -> Self {
Self::new(s)
}
}

View File

@@ -0,0 +1,305 @@
/// Values per occurrence for an argument
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ValueRange {
start_inclusive: usize,
end_inclusive: usize,
}
impl ValueRange {
/// Nor argument values, or a flag
pub const EMPTY: Self = Self {
start_inclusive: 0,
end_inclusive: 0,
};
/// A single argument value, the most common case for options
pub const SINGLE: Self = Self {
start_inclusive: 1,
end_inclusive: 1,
};
#[cfg(debug_assertions)]
pub(crate) const OPTIONAL: Self = Self {
start_inclusive: 0,
end_inclusive: 1,
};
pub(crate) const FULL: Self = Self {
start_inclusive: 0,
end_inclusive: usize::MAX,
};
/// Create a range
///
/// # Panics
///
/// In debug builds, if the range accepts no values. For an exclusive range
/// (`start..end`) that means `end` must be greater than `start`, so both
/// reversed ranges like `10..5` and empty ranges like `5..5` panic.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::ValueRange;
/// let range = ValueRange::new(5);
/// let range = ValueRange::new(5..10);
/// let range = ValueRange::new(5..=10);
/// let range = ValueRange::new(5..);
/// let range = ValueRange::new(..10);
/// let range = ValueRange::new(..=10);
/// ```
///
/// While these will panic:
/// ```should_panic
/// # use clap_builder as clap;
/// # use clap::builder::ValueRange;
/// let range = ValueRange::new(10..5); // Panics! (reversed)
/// ```
/// ```should_panic
/// # use clap_builder as clap;
/// # use clap::builder::ValueRange;
/// let range = ValueRange::new(5..5); // Panics! (empty)
/// ```
pub fn new(range: impl Into<Self>) -> Self {
range.into()
}
pub(crate) fn raw(start_inclusive: usize, end_inclusive: usize) -> Self {
debug_assert!(start_inclusive <= end_inclusive);
Self {
start_inclusive,
end_inclusive,
}
}
/// Fewest number of values the argument accepts
pub fn min_values(&self) -> usize {
self.start_inclusive
}
/// Most number of values the argument accepts
pub fn max_values(&self) -> usize {
self.end_inclusive
}
/// Report whether the argument takes any values (ie is a flag)
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::ValueRange;
/// let range = ValueRange::new(5);
/// assert!(range.takes_values());
///
/// let range = ValueRange::new(0);
/// assert!(!range.takes_values());
/// ```
pub fn takes_values(&self) -> bool {
self.end_inclusive != 0
}
pub(crate) fn is_unbounded(&self) -> bool {
self.end_inclusive == usize::MAX
}
pub(crate) fn is_fixed(&self) -> bool {
self.start_inclusive == self.end_inclusive
}
pub(crate) fn is_multiple(&self) -> bool {
self.start_inclusive != self.end_inclusive || 1 < self.start_inclusive
}
pub(crate) fn num_values(&self) -> Option<usize> {
self.is_fixed().then_some(self.start_inclusive)
}
pub(crate) fn accepts_more(&self, current: usize) -> bool {
current < self.end_inclusive
}
}
impl std::ops::RangeBounds<usize> for ValueRange {
fn start_bound(&self) -> std::ops::Bound<&usize> {
std::ops::Bound::Included(&self.start_inclusive)
}
fn end_bound(&self) -> std::ops::Bound<&usize> {
std::ops::Bound::Included(&self.end_inclusive)
}
}
impl Default for ValueRange {
fn default() -> Self {
Self::SINGLE
}
}
impl From<usize> for ValueRange {
fn from(fixed: usize) -> Self {
(fixed..=fixed).into()
}
}
impl From<std::ops::Range<usize>> for ValueRange {
fn from(range: std::ops::Range<usize>) -> Self {
let start_inclusive = range.start;
let end_inclusive = range.end.saturating_sub(1);
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<std::ops::RangeFull> for ValueRange {
fn from(_: std::ops::RangeFull) -> Self {
Self::FULL
}
}
impl From<std::ops::RangeFrom<usize>> for ValueRange {
fn from(range: std::ops::RangeFrom<usize>) -> Self {
let start_inclusive = range.start;
let end_inclusive = usize::MAX;
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<std::ops::RangeTo<usize>> for ValueRange {
fn from(range: std::ops::RangeTo<usize>) -> Self {
let start_inclusive = 0;
let end_inclusive = range.end.saturating_sub(1);
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<std::ops::RangeInclusive<usize>> for ValueRange {
fn from(range: std::ops::RangeInclusive<usize>) -> Self {
let start_inclusive = *range.start();
let end_inclusive = *range.end();
Self::raw(start_inclusive, end_inclusive)
}
}
impl From<std::ops::RangeToInclusive<usize>> for ValueRange {
fn from(range: std::ops::RangeToInclusive<usize>) -> Self {
let start_inclusive = 0;
let end_inclusive = range.end;
Self::raw(start_inclusive, end_inclusive)
}
}
impl std::fmt::Display for ValueRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ok!(self.start_inclusive.fmt(f));
if self.is_fixed() {
} else if self.end_inclusive == usize::MAX {
ok!("..".fmt(f));
} else {
ok!("..=".fmt(f));
ok!(self.end_inclusive.fmt(f));
}
Ok(())
}
}
impl std::fmt::Debug for ValueRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
}
#[cfg(test)]
mod test {
use super::*;
use std::ops::RangeBounds;
#[test]
fn from_fixed() {
let range: ValueRange = 5.into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&5));
assert!(range.is_fixed());
assert!(range.is_multiple());
assert_eq!(range.num_values(), Some(5));
assert!(range.takes_values());
}
#[test]
fn from_fixed_empty() {
let range: ValueRange = 0.into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&0));
assert!(range.is_fixed());
assert!(!range.is_multiple());
assert_eq!(range.num_values(), Some(0));
assert!(!range.takes_values());
}
#[test]
fn from_range() {
let range: ValueRange = (5..10).into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&9));
assert!(!range.is_fixed());
assert!(range.is_multiple());
assert_eq!(range.num_values(), None);
assert!(range.takes_values());
}
#[test]
fn from_range_inclusive() {
let range: ValueRange = (5..=10).into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&10));
assert!(!range.is_fixed());
assert!(range.is_multiple());
assert_eq!(range.num_values(), None);
assert!(range.takes_values());
}
#[test]
fn from_range_full() {
let range: ValueRange = (..).into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&usize::MAX));
assert!(!range.is_fixed());
assert!(range.is_multiple());
assert_eq!(range.num_values(), None);
assert!(range.takes_values());
}
#[test]
fn from_range_from() {
let range: ValueRange = (5..).into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&5));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&usize::MAX));
assert!(!range.is_fixed());
assert!(range.is_multiple());
assert_eq!(range.num_values(), None);
assert!(range.takes_values());
}
#[test]
fn from_range_to() {
let range: ValueRange = (..10).into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&9));
assert!(!range.is_fixed());
assert!(range.is_multiple());
assert_eq!(range.num_values(), None);
assert!(range.takes_values());
}
#[test]
fn from_range_to_inclusive() {
let range: ValueRange = (..=10).into();
assert_eq!(range.start_bound(), std::ops::Bound::Included(&0));
assert_eq!(range.end_bound(), std::ops::Bound::Included(&10));
assert!(!range.is_fixed());
assert!(range.is_multiple());
assert_eq!(range.num_values(), None);
assert!(range.takes_values());
}
}

View File

@@ -0,0 +1,212 @@
// Unlike `impl Into<Option<T>>` or `Option<impl Into<T>>`, this isn't ambiguous for the `None`
// case.
use crate::builder::ArgAction;
use crate::builder::OsStr;
use crate::builder::Str;
use crate::builder::StyledStr;
use crate::builder::ValueHint;
use crate::builder::ValueParser;
use crate::builder::ValueRange;
/// Clearable builder value
///
/// This allows a builder function to both accept any value that can [`Into::into`] `T` (like
/// `&str` into `OsStr`) as well as `None` to reset it to the default. This is needed to
/// workaround a limitation where you can't have a function argument that is `impl Into<Option<T>>`
/// where `T` is `impl Into<S>` accept `None` as its type is ambiguous.
///
/// # Example
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// fn common() -> Command {
/// Command::new("cli")
/// .arg(Arg::new("input").short('i').long("input"))
/// }
/// let mut command = common();
/// command.mut_arg("input", |arg| arg.short(None));
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Resettable<T> {
/// Overwrite builder value
Value(T),
/// Reset builder value
Reset,
}
impl<T> Resettable<T> {
pub(crate) fn into_option(self) -> Option<T> {
match self {
Self::Value(t) => Some(t),
Self::Reset => None,
}
}
}
impl<T> From<T> for Resettable<T> {
fn from(other: T) -> Self {
Self::Value(other)
}
}
impl<T> From<Option<T>> for Resettable<T> {
fn from(other: Option<T>) -> Self {
match other {
Some(inner) => Self::Value(inner),
None => Self::Reset,
}
}
}
/// Convert to the intended resettable type
pub trait IntoResettable<T> {
/// Convert to the intended resettable type
fn into_resettable(self) -> Resettable<T>;
}
impl IntoResettable<char> for Option<char> {
fn into_resettable(self) -> Resettable<char> {
match self {
Some(s) => Resettable::Value(s),
None => Resettable::Reset,
}
}
}
impl IntoResettable<usize> for Option<usize> {
fn into_resettable(self) -> Resettable<usize> {
match self {
Some(s) => Resettable::Value(s),
None => Resettable::Reset,
}
}
}
impl IntoResettable<ArgAction> for Option<ArgAction> {
fn into_resettable(self) -> Resettable<ArgAction> {
match self {
Some(s) => Resettable::Value(s),
None => Resettable::Reset,
}
}
}
impl IntoResettable<ValueHint> for Option<ValueHint> {
fn into_resettable(self) -> Resettable<ValueHint> {
match self {
Some(s) => Resettable::Value(s),
None => Resettable::Reset,
}
}
}
impl IntoResettable<ValueParser> for Option<ValueParser> {
fn into_resettable(self) -> Resettable<ValueParser> {
match self {
Some(s) => Resettable::Value(s),
None => Resettable::Reset,
}
}
}
impl IntoResettable<StyledStr> for Option<&'static str> {
fn into_resettable(self) -> Resettable<StyledStr> {
match self {
Some(s) => Resettable::Value(s.into()),
None => Resettable::Reset,
}
}
}
impl IntoResettable<OsStr> for Option<&'static str> {
fn into_resettable(self) -> Resettable<OsStr> {
match self {
Some(s) => Resettable::Value(s.into()),
None => Resettable::Reset,
}
}
}
impl IntoResettable<Str> for Option<&'static str> {
fn into_resettable(self) -> Resettable<Str> {
match self {
Some(s) => Resettable::Value(s.into()),
None => Resettable::Reset,
}
}
}
impl<T> IntoResettable<T> for Resettable<T> {
fn into_resettable(self) -> Self {
self
}
}
impl IntoResettable<Self> for char {
fn into_resettable(self) -> Resettable<Self> {
Resettable::Value(self)
}
}
impl IntoResettable<Self> for usize {
fn into_resettable(self) -> Resettable<Self> {
Resettable::Value(self)
}
}
impl IntoResettable<Self> for ArgAction {
fn into_resettable(self) -> Resettable<Self> {
Resettable::Value(self)
}
}
impl IntoResettable<Self> for ValueHint {
fn into_resettable(self) -> Resettable<Self> {
Resettable::Value(self)
}
}
impl<I: Into<ValueRange>> IntoResettable<ValueRange> for I {
fn into_resettable(self) -> Resettable<ValueRange> {
Resettable::Value(self.into())
}
}
impl<I: Into<ValueParser>> IntoResettable<ValueParser> for I {
fn into_resettable(self) -> Resettable<ValueParser> {
Resettable::Value(self.into())
}
}
impl<I: Into<String>> IntoResettable<String> for I {
fn into_resettable(self) -> Resettable<String> {
Resettable::Value(self.into())
}
}
impl<I: Into<StyledStr>> IntoResettable<StyledStr> for I {
fn into_resettable(self) -> Resettable<StyledStr> {
Resettable::Value(self.into())
}
}
impl<I: Into<OsStr>> IntoResettable<OsStr> for I {
fn into_resettable(self) -> Resettable<OsStr> {
Resettable::Value(self.into())
}
}
impl<I: Into<Str>> IntoResettable<Str> for I {
fn into_resettable(self) -> Resettable<Str> {
Resettable::Value(self.into())
}
}
impl<I: Into<crate::Id>> IntoResettable<crate::Id> for I {
fn into_resettable(self) -> Resettable<crate::Id> {
Resettable::Value(self.into())
}
}

View File

@@ -0,0 +1,349 @@
#[cfg(feature = "string")]
use std::borrow::Cow;
/// A UTF-8-encoded fixed string
///
/// <div class="warning">
///
/// **NOTE:** To support dynamic values (i.e. `String`), enable the `string`
/// feature
///
/// </div>
#[derive(Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Str {
name: Inner,
}
impl Str {
#[cfg(feature = "string")]
pub(crate) fn from_string(name: String) -> Self {
Self {
name: Inner::from_string(name),
}
}
#[cfg(feature = "string")]
pub(crate) fn from_ref(name: &str) -> Self {
Self {
name: Inner::from_ref(name),
}
}
pub(crate) fn from_static_ref(name: &'static str) -> Self {
Self {
name: Inner::from_static_ref(name),
}
}
pub(crate) fn into_inner(self) -> Inner {
self.name
}
/// Get the raw string of the `Str`
pub fn as_str(&self) -> &str {
self.name.as_str()
}
}
impl From<&'_ Self> for Str {
fn from(id: &'_ Self) -> Self {
id.clone()
}
}
#[cfg(feature = "string")]
impl From<String> for Str {
fn from(name: String) -> Self {
Self::from_string(name)
}
}
#[cfg(feature = "string")]
impl From<&'_ String> for Str {
fn from(name: &'_ String) -> Self {
Self::from_ref(name.as_str())
}
}
impl From<&'static str> for Str {
fn from(name: &'static str) -> Self {
Self::from_static_ref(name)
}
}
impl From<&'_ &'static str> for Str {
fn from(name: &'_ &'static str) -> Self {
Self::from_static_ref(name)
}
}
#[cfg(feature = "string")]
impl From<Cow<'static, str>> for Str {
fn from(cow: Cow<'static, str>) -> Self {
match cow {
Cow::Borrowed(s) => Self::from(s),
Cow::Owned(s) => Self::from(s),
}
}
}
impl From<Str> for String {
fn from(name: Str) -> Self {
name.name.into_string()
}
}
impl From<Str> for Vec<u8> {
fn from(name: Str) -> Self {
String::from(name).into()
}
}
impl From<Str> for std::ffi::OsString {
fn from(name: Str) -> Self {
String::from(name).into()
}
}
impl From<Str> for std::path::PathBuf {
fn from(name: Str) -> Self {
String::from(name).into()
}
}
impl std::fmt::Display for Str {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.as_str(), f)
}
}
impl std::fmt::Debug for Str {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_str(), f)
}
}
impl std::ops::Deref for Str {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for Str {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for Str {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<std::ffi::OsStr> for Str {
#[inline]
fn as_ref(&self) -> &std::ffi::OsStr {
(**self).as_ref()
}
}
impl AsRef<std::path::Path> for Str {
#[inline]
fn as_ref(&self) -> &std::path::Path {
std::path::Path::new(self)
}
}
impl std::borrow::Borrow<str> for Str {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl PartialEq<str> for Str {
#[inline]
fn eq(&self, other: &str) -> bool {
PartialEq::eq(self.as_str(), other)
}
}
impl PartialEq<Str> for str {
#[inline]
fn eq(&self, other: &Str) -> bool {
PartialEq::eq(self, other.as_str())
}
}
impl PartialEq<&'_ str> for Str {
#[inline]
fn eq(&self, other: &&str) -> bool {
PartialEq::eq(self.as_str(), *other)
}
}
impl PartialEq<Str> for &'_ str {
#[inline]
fn eq(&self, other: &Str) -> bool {
PartialEq::eq(*self, other.as_str())
}
}
impl PartialEq<std::ffi::OsStr> for Str {
#[inline]
fn eq(&self, other: &std::ffi::OsStr) -> bool {
PartialEq::eq(self.as_str(), other)
}
}
impl PartialEq<Str> for std::ffi::OsStr {
#[inline]
fn eq(&self, other: &Str) -> bool {
PartialEq::eq(self, other.as_str())
}
}
impl PartialEq<&'_ std::ffi::OsStr> for Str {
#[inline]
fn eq(&self, other: &&std::ffi::OsStr) -> bool {
PartialEq::eq(self.as_str(), *other)
}
}
impl PartialEq<Str> for &'_ std::ffi::OsStr {
#[inline]
fn eq(&self, other: &Str) -> bool {
PartialEq::eq(*self, other.as_str())
}
}
impl PartialEq<String> for Str {
#[inline]
fn eq(&self, other: &String) -> bool {
PartialEq::eq(self.as_str(), other.as_str())
}
}
impl PartialEq<Str> for String {
#[inline]
fn eq(&self, other: &Str) -> bool {
PartialEq::eq(self.as_str(), other.as_str())
}
}
#[cfg(feature = "string")]
pub(crate) mod inner {
#[derive(Clone)]
pub(crate) enum Inner {
Static(&'static str),
Owned(Box<str>),
}
impl Inner {
pub(crate) fn from_string(name: String) -> Self {
Self::Owned(name.into_boxed_str())
}
pub(crate) fn from_ref(name: &str) -> Self {
Self::Owned(Box::from(name))
}
pub(crate) fn from_static_ref(name: &'static str) -> Self {
Self::Static(name)
}
pub(crate) fn as_str(&self) -> &str {
match self {
Self::Static(s) => s,
Self::Owned(s) => s.as_ref(),
}
}
pub(crate) fn into_string(self) -> String {
match self {
Self::Static(s) => s.to_owned(),
Self::Owned(s) => s.into(),
}
}
}
}
#[cfg(not(feature = "string"))]
pub(crate) mod inner {
#[derive(Clone)]
pub(crate) struct Inner(pub(crate) &'static str);
impl Inner {
pub(crate) fn from_static_ref(name: &'static str) -> Self {
Self(name)
}
pub(crate) fn as_str(&self) -> &str {
self.0
}
pub(crate) fn into_string(self) -> String {
self.as_str().to_owned()
}
}
}
pub(crate) use inner::Inner;
impl Default for Inner {
fn default() -> Self {
Self::from_static_ref("")
}
}
impl PartialEq for Inner {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialOrd for Inner {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Inner {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl Eq for Inner {}
impl std::hash::Hash for Inner {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
#[cfg(test)]
#[cfg(feature = "string")]
mod tests {
use super::*;
#[test]
#[cfg(feature = "string")]
fn from_cow_borrowed() {
let cow = Cow::Borrowed("hello");
let str = Str::from(cow);
assert_eq!(str, Str::from("hello"));
}
#[test]
#[cfg(feature = "string")]
fn from_cow_owned() {
let cow = Cow::Owned("world".to_owned());
let str = Str::from(cow);
assert_eq!(str, Str::from("world"));
}
}

View File

@@ -0,0 +1,287 @@
#![cfg_attr(not(feature = "usage"), allow(dead_code))]
use std::borrow::Cow;
/// Terminal-styling container
///
/// Styling may be encoded as [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code)
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// // `cstr!` converts tags to ANSI codes
/// let after_help: &'static str = color_print::cstr!(
/// r#"<bold><underline>Examples</underline></bold>
///
/// <dim>$</dim> <bold>mybin --input file.toml</bold>
/// "#);
///
/// let cmd = clap::Command::new("mybin")
/// .after_help(after_help) // The `&str` gets converted into a `StyledStr`
/// // ...
/// # ;
/// ```
#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct StyledStr(String);
impl StyledStr {
/// Create an empty buffer
pub const fn new() -> Self {
Self(String::new())
}
/// Display using [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code) styling
#[cfg(feature = "color")]
pub fn ansi(&self) -> impl std::fmt::Display + '_ {
self.0.as_str()
}
/// May allow the compiler to consolidate the `Drop`s for `msg`, reducing code size compared to
/// `styled.push_str(&msg)`
pub(crate) fn push_string(&mut self, msg: String) {
self.0.push_str(&msg);
}
/// Appends a given string slice onto the end of this `StyledStr`.
pub fn push_str(&mut self, msg: &str) {
self.0.push_str(msg);
}
pub(crate) fn trim_start_lines(&mut self) {
if let Some(pos) = self.0.find('\n') {
let (leading, help) = self.0.split_at(pos + 1);
if leading.trim().is_empty() {
self.0 = help.to_owned();
}
}
}
pub(crate) fn trim_end(&mut self) {
self.0 = self.0.trim_end().to_owned();
}
#[cfg(feature = "help")]
pub(crate) fn replace_newline_var(&mut self) {
self.0 = self.0.replace("{n}", "\n");
}
#[cfg(feature = "help")]
pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
self.0.insert_str(0, initial);
let mut line_sep = "\n".to_owned();
line_sep.push_str(trailing);
self.0 = self.0.replace('\n', &line_sep);
}
#[cfg(all(not(feature = "wrap_help"), feature = "help"))]
pub(crate) fn wrap(&mut self, _hard_width: usize) {}
#[cfg(feature = "wrap_help")]
pub(crate) fn wrap(&mut self, hard_width: usize) {
let mut new = String::with_capacity(self.0.len());
let mut last = 0;
let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
for content in self.iter_text() {
// Preserve styling
let current = content.as_ptr() as usize - self.0.as_str().as_ptr() as usize;
if last != current {
new.push_str(&self.0.as_str()[last..current]);
}
last = current + content.len();
for (i, line) in content.split_inclusive('\n').enumerate() {
if 0 < i {
// reset char count on newline, skipping the start as we might have carried
// over from a prior block of styled text
wrapper.reset();
}
let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
.collect::<Vec<_>>();
new.extend(wrapper.wrap(line));
}
}
if last != self.0.len() {
new.push_str(&self.0.as_str()[last..]);
}
new = new.trim_end().to_owned();
self.0 = new;
}
#[inline(never)]
#[cfg(feature = "help")]
pub(crate) fn display_width(&self) -> usize {
let mut width = 0;
for c in self.iter_text() {
width += crate::output::display_width(c);
}
width
}
#[cfg(feature = "help")]
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[cfg(feature = "help")]
pub(crate) fn as_styled_str(&self) -> &str {
&self.0
}
#[cfg(feature = "color")]
pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
anstream::adapter::strip_str(&self.0)
}
#[cfg(not(feature = "color"))]
pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
[self.0.as_str()].into_iter()
}
pub(crate) fn push_styled(&mut self, other: &Self) {
self.0.push_str(&other.0);
}
pub(crate) fn write_to(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()> {
ok!(buffer.write_all(self.0.as_bytes()));
Ok(())
}
}
impl Default for &'_ StyledStr {
fn default() -> Self {
static DEFAULT: StyledStr = StyledStr::new();
&DEFAULT
}
}
impl From<String> for StyledStr {
fn from(name: String) -> Self {
Self(name)
}
}
impl From<&'_ String> for StyledStr {
fn from(name: &'_ String) -> Self {
let mut styled = Self::new();
styled.push_str(name);
styled
}
}
impl From<&'static str> for StyledStr {
fn from(name: &'static str) -> Self {
let mut styled = Self::new();
styled.push_str(name);
styled
}
}
impl From<&'_ &'static str> for StyledStr {
fn from(name: &'_ &'static str) -> Self {
Self::from(*name)
}
}
impl From<Cow<'static, str>> for StyledStr {
fn from(cow: Cow<'static, str>) -> Self {
match cow {
Cow::Borrowed(s) => Self::from(s),
Cow::Owned(s) => Self::from(s),
}
}
}
impl std::fmt::Write for StyledStr {
#[inline]
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
self.0.push_str(s);
Ok(())
}
#[inline]
fn write_char(&mut self, c: char) -> Result<(), std::fmt::Error> {
self.0.push(c);
Ok(())
}
}
/// Color-unaware printing. Never uses coloring.
impl std::fmt::Display for StyledStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for part in self.iter_text() {
part.fmt(f)?;
}
Ok(())
}
}
#[cfg(test)]
#[cfg(feature = "wrap_help")]
mod wrap_tests {
use super::*;
use snapbox::assert_data_eq;
use snapbox::str;
#[test]
#[cfg(feature = "wrap_help")]
fn wrap_unstyled() {
let style = anstyle::Style::new();
let input = format!(
"{style}12345{style:#} {style}12345{style:#} {style}12345{style:#} {style}12345{style:#}"
);
let mut actual = StyledStr::new();
actual.push_string(input);
actual.wrap(20);
assert_data_eq!(
actual.ansi().to_string(),
str![[r#"
12345 12345 12345
12345
"#]]
);
}
#[test]
#[cfg(feature = "wrap_help")]
fn wrap_styled() {
let style = anstyle::Style::new().bold();
let input = format!(
"{style}12345{style:#} {style}12345{style:#} {style}12345{style:#} {style}12345{style:#}"
);
let mut actual = StyledStr::new();
actual.push_string(input);
actual.wrap(20);
assert_data_eq!(
actual.ansi().to_string(),
str![[r#"
12345 12345 12345 
12345
"#]]
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_cow_borrowed() {
let cow = Cow::Borrowed("hello");
let styled = StyledStr::from(cow);
assert_eq!(styled, StyledStr::from("hello"));
}
#[test]
fn from_cow_owned() {
let cow = Cow::Owned("world".to_owned());
let styled = StyledStr::from(cow);
assert_eq!(styled, StyledStr::from("world"));
}
}

View File

@@ -0,0 +1,220 @@
//! Terminal [`Styles`] for help and error output
pub use anstyle::*;
/// Terminal styling definitions
///
/// See also [`Command::styles`][crate::Command::styles].
///
/// # Example
///
/// clap v3 styling
/// ```rust
/// # use clap_builder as clap;
/// # use clap::builder::styling::*;
/// let styles = Styles::styled()
/// .header(AnsiColor::Yellow.on_default())
/// .usage(AnsiColor::Green.on_default())
/// .literal(AnsiColor::Green.on_default())
/// .placeholder(AnsiColor::Green.on_default());
/// ```
#[derive(Clone, Debug)]
#[allow(missing_copy_implementations)] // Large enough type that I want an explicit `clone()` for now
pub struct Styles {
header: Style,
error: Style,
usage: Style,
literal: Style,
placeholder: Style,
valid: Style,
invalid: Style,
context: Style,
context_value: Option<Style>,
}
impl Styles {
/// No terminal styling
pub const fn plain() -> Self {
Self {
header: Style::new(),
error: Style::new(),
usage: Style::new(),
literal: Style::new(),
placeholder: Style::new(),
valid: Style::new(),
invalid: Style::new(),
context: Style::new(),
context_value: None,
}
}
/// Default terminal styling
pub const fn styled() -> Self {
#[cfg(feature = "color")]
{
Self {
header: Style::new().bold().underline(),
error: Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Red)))
.bold(),
usage: Style::new().bold().underline(),
literal: Style::new().bold(),
placeholder: Style::new(),
valid: Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green))),
invalid: Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))),
context: Style::new(),
context_value: None,
}
}
#[cfg(not(feature = "color"))]
{
Self::plain()
}
}
/// General Heading style, e.g. [`help_heading`][crate::Arg::help_heading]
#[inline]
pub const fn header(mut self, style: Style) -> Self {
self.header = style;
self
}
/// Error heading
#[inline]
pub const fn error(mut self, style: Style) -> Self {
self.error = style;
self
}
/// Usage heading
#[inline]
pub const fn usage(mut self, style: Style) -> Self {
self.usage = style;
self
}
/// Literal command-line syntax, e.g. `--help`
#[inline]
pub const fn literal(mut self, style: Style) -> Self {
self.literal = style;
self
}
/// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name]
#[inline]
pub const fn placeholder(mut self, style: Style) -> Self {
self.placeholder = style;
self
}
/// Highlight suggested usage
#[inline]
pub const fn valid(mut self, style: Style) -> Self {
self.valid = style;
self
}
/// Highlight invalid usage
#[inline]
pub const fn invalid(mut self, style: Style) -> Self {
self.invalid = style;
self
}
/// Highlight all specified contexts, e.g. `[default: false]`
///
/// To specialize the style of the value within the context, see [`Styles::context_value`]
#[inline]
pub const fn context(mut self, style: Style) -> Self {
self.context = style;
self
}
/// Highlight values within all of the context, e.g. the `false` in `[default: false]`
///
/// If not explicitly set, falls back to `context`'s style.
#[inline]
pub const fn context_value(mut self, style: Style) -> Self {
self.context_value = Some(style);
self
}
}
/// Reflection
impl Styles {
/// General Heading style, e.g. [`help_heading`][crate::Arg::help_heading]
#[inline(always)]
pub const fn get_header(&self) -> &Style {
&self.header
}
/// Error heading
#[inline(always)]
pub const fn get_error(&self) -> &Style {
&self.error
}
/// Usage heading
#[inline(always)]
pub const fn get_usage(&self) -> &Style {
&self.usage
}
/// Literal command-line syntax, e.g. `--help`
#[inline(always)]
pub const fn get_literal(&self) -> &Style {
&self.literal
}
/// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name]
#[inline(always)]
pub const fn get_placeholder(&self) -> &Style {
&self.placeholder
}
/// Highlight suggested usage
#[inline(always)]
pub const fn get_valid(&self) -> &Style {
&self.valid
}
/// Highlight invalid usage
#[inline(always)]
pub const fn get_invalid(&self) -> &Style {
&self.invalid
}
/// Highlight all specified contexts, e.g. `[default: false]`
///
/// To specialize the style of the value within the context, see [`Styles::context_value`]
#[inline(always)]
pub const fn get_context(&self) -> &Style {
&self.context
}
/// Highlight values within all of the context, e.g. the `false` in `[default: false]`
///
/// If not explicitly set, falls back to `context`'s style.
#[inline(always)]
pub const fn get_context_value(&self) -> &Style {
match &self.context_value {
Some(s) => s,
None => &self.context,
}
}
}
impl super::AppExt for Styles {}
impl Default for Styles {
fn default() -> Self {
Self::styled()
}
}
impl Default for &'_ Styles {
fn default() -> Self {
const STYLES: Styles = Styles::styled();
&STYLES
}
}

View File

@@ -0,0 +1,58 @@
use crate::Arg;
use crate::Command;
#[test]
fn propagate_version() {
let mut cmd = Command::new("test")
.propagate_version(true)
.version("1.1")
.subcommand(Command::new("sub1"));
cmd._propagate();
assert_eq!(
cmd.get_subcommands().next().unwrap().get_version(),
Some("1.1")
);
}
#[test]
fn global_setting() {
let mut cmd = Command::new("test")
.disable_version_flag(true)
.subcommand(Command::new("subcmd"));
cmd._propagate();
assert!(
cmd.get_subcommands()
.find(|s| s.get_name() == "subcmd")
.unwrap()
.is_disable_version_flag_set()
);
}
// This test will *fail to compile* if Command is not Send + Sync
#[test]
fn app_send_sync() {
fn foo<T: Send + Sync>(_: T) {}
foo(Command::new("test"));
}
#[test]
fn issue_2090() {
let mut cmd = Command::new("cmd")
.disable_version_flag(true)
.subcommand(Command::new("sub"));
cmd._build_self(false);
assert!(
cmd.get_subcommands()
.next()
.unwrap()
.is_disable_version_flag_set()
);
}
// This test will *fail to compile* if Arg is not Send + Sync
#[test]
fn arg_send_sync() {
fn foo<T: Send + Sync>(_: T) {}
foo(Arg::new("test"));
}

View File

@@ -0,0 +1,93 @@
use std::str::FromStr;
/// Provide shell with hint on how to complete an argument.
///
/// See [`Arg::value_hint`][crate::Arg::value_hint] to set this on an argument.
///
/// See the `clap_complete` crate for completion script generation.
///
/// Overview of which hints are supported by which shell:
///
/// | Hint | zsh | fish[^1] | dynamic |
/// | ---------------------- | --- | ---------|---------|
/// | `AnyPath` | Yes | Yes | Yes |
/// | `FilePath` | Yes | Yes | Yes |
/// | `DirPath` | Yes | Yes | Yes |
/// | `ExecutablePath` | Yes | Partial | Yes |
/// | `CommandName` | Yes | Yes | No |
/// | `CommandString` | Yes | Partial | No |
/// | `CommandWithArguments` | Yes | | No |
/// | `Username` | Yes | Yes | No |
/// | `Hostname` | Yes | Yes | No |
/// | `Url` | Yes | | No |
/// | `EmailAddress` | Yes | | No |
///
/// [^1]: fish completions currently only support named arguments (e.g. -o or --opt), not
/// positional arguments.
#[derive(Debug, Default, PartialEq, Eq, Hash, Copy, Clone)]
#[non_exhaustive]
pub enum ValueHint {
/// Default value if hint is not specified. Follows shell default behavior, which is usually
/// auto-completing filenames.
#[default]
Unknown,
/// None of the hints below apply. Disables shell completion for this argument.
Other,
/// Any existing path.
AnyPath,
/// Path to a file.
FilePath,
/// Path to a directory.
DirPath,
/// Path to an executable file.
ExecutablePath,
/// Name of a command, without arguments. May be relative to PATH, or full path to executable.
CommandName,
/// A single string containing a command and its arguments.
CommandString,
/// Capture the remaining arguments as a command name and arguments for that command. This is
/// common when writing shell wrappers that execute another command, for example `sudo` or `env`.
///
/// This hint is special, the argument must be a positional argument and have
/// [`.num_args(1..)`] and Command must use [`Command::trailing_var_arg(true)`]. The result is that the
/// command line `my_app ls -la /` will be parsed as `["ls", "-la", "/"]` and clap won't try to
/// parse the `-la` argument itself.
///
/// [`Command::trailing_var_arg(true)`]: crate::Command::trailing_var_arg
/// [`.num_args(1..)`]: crate::Arg::num_args()
CommandWithArguments,
/// Name of a local operating system user.
Username,
/// Host name of a computer.
/// Shells usually parse `/etc/hosts` and `.ssh/known_hosts` to complete hostnames.
Hostname,
/// Complete web address.
Url,
/// Email address.
EmailAddress,
}
#[cfg(feature = "unstable-ext")]
impl crate::builder::ArgExt for ValueHint {}
impl FromStr for ValueHint {
type Err = String;
fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
Ok(match &*s.to_ascii_lowercase() {
"unknown" => Self::Unknown,
"other" => Self::Other,
"anypath" => Self::AnyPath,
"filepath" => Self::FilePath,
"dirpath" => Self::DirPath,
"executablepath" => Self::ExecutablePath,
"commandname" => Self::CommandName,
"commandstring" => Self::CommandString,
"commandwitharguments" => Self::CommandWithArguments,
"username" => Self::Username,
"hostname" => Self::Hostname,
"url" => Self::Url,
"emailaddress" => Self::EmailAddress,
_ => return Err(format!("unknown ValueHint: `{s}`")),
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,453 @@
//! This module contains traits that are usable with the `#[derive(...)]`
//! macros in `clap_derive`.
use crate::builder::PossibleValue;
use crate::{ArgMatches, Command, Error};
use std::convert::Infallible;
use std::ffi::OsString;
/// Parse command-line arguments into `Self`.
///
/// The primary one-stop-shop trait used to create an instance of a `clap`
/// [`Command`], conduct the parsing, and turn the resulting [`ArgMatches`] back
/// into concrete instance of the user struct.
///
/// This trait is primarily a convenience on top of [`FromArgMatches`] +
/// [`CommandFactory`] which uses those two underlying traits to build the two
/// fundamental functions `parse` which uses the `std::env::args_os` iterator,
/// and `parse_from` which allows the consumer to supply the iterator (along
/// with fallible options for each).
///
/// See also [`Subcommand`] and [`Args`].
///
/// <div class="warning">
///
/// **NOTE:** Deriving requires the `derive` feature flag
///
/// </div>
pub trait Parser: FromArgMatches + CommandFactory + Sized {
/// Parse from `std::env::args_os()`, [exit][Error::exit] on error.
fn parse() -> Self {
let mut matches = <Self as CommandFactory>::command().get_matches();
let res = <Self as FromArgMatches>::from_arg_matches_mut(&mut matches)
.map_err(format_error::<Self>);
match res {
Ok(s) => s,
Err(e) => {
// Since this is more of a development-time error, we aren't doing as fancy of a quit
// as `get_matches`
e.exit()
}
}
}
/// Parse from `std::env::args_os()`, return Err on error.
fn try_parse() -> Result<Self, Error> {
let mut matches = ok!(<Self as CommandFactory>::command().try_get_matches());
<Self as FromArgMatches>::from_arg_matches_mut(&mut matches).map_err(format_error::<Self>)
}
/// Parse from iterator, [exit][Error::exit] on error.
fn parse_from<I, T>(itr: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut matches = <Self as CommandFactory>::command().get_matches_from(itr);
let res = <Self as FromArgMatches>::from_arg_matches_mut(&mut matches)
.map_err(format_error::<Self>);
match res {
Ok(s) => s,
Err(e) => {
// Since this is more of a development-time error, we aren't doing as fancy of a quit
// as `get_matches_from`
e.exit()
}
}
}
/// Parse from iterator, return Err on error.
fn try_parse_from<I, T>(itr: I) -> Result<Self, Error>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut matches = ok!(<Self as CommandFactory>::command().try_get_matches_from(itr));
<Self as FromArgMatches>::from_arg_matches_mut(&mut matches).map_err(format_error::<Self>)
}
/// Update from iterator, [exit][Error::exit] on error.
///
/// Unlike [`Parser::parse`], this works with an existing instance of `self`.
/// The assumption is that all required fields are already provided and any [`Args`] or
/// [`Subcommand`]s provided by the user will modify only what is specified.
fn update_from<I, T>(&mut self, itr: I)
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut matches = <Self as CommandFactory>::command_for_update().get_matches_from(itr);
let res = <Self as FromArgMatches>::update_from_arg_matches_mut(self, &mut matches)
.map_err(format_error::<Self>);
if let Err(e) = res {
// Since this is more of a development-time error, we aren't doing as fancy of a quit
// as `get_matches_from`
e.exit()
}
}
/// Update from iterator, return Err on error.
fn try_update_from<I, T>(&mut self, itr: I) -> Result<(), Error>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut matches =
ok!(<Self as CommandFactory>::command_for_update().try_get_matches_from(itr));
<Self as FromArgMatches>::update_from_arg_matches_mut(self, &mut matches)
.map_err(format_error::<Self>)
}
}
/// Create a [`Command`] relevant for a user-defined container.
///
/// Derived as part of [`Parser`].
pub trait CommandFactory: Sized {
/// Build a [`Command`] that can instantiate `Self`.
///
/// See [`FromArgMatches::from_arg_matches_mut`] for instantiating `Self`.
fn command() -> Command;
/// Build a [`Command`] that can update `self`.
///
/// See [`FromArgMatches::update_from_arg_matches_mut`] for updating `self`.
fn command_for_update() -> Command;
}
/// Converts an instance of [`ArgMatches`] to a user-defined container.
///
/// Derived as part of [`Parser`], [`Args`], and [`Subcommand`].
pub trait FromArgMatches: Sized {
/// Instantiate `Self` from [`ArgMatches`], parsing the arguments as needed.
///
/// Motivation: If our application had two CLI options, `--name
/// <STRING>` and the flag `--debug`, we may create a struct as follows:
///
/// ```rust
/// # #[cfg(feature = "derive")] {
/// struct Context {
/// name: String,
/// debug: bool
/// }
/// # }
/// ```
///
/// We then need to convert the `ArgMatches` that `clap` generated into our struct.
/// `from_arg_matches` serves as the equivalent of:
///
/// ```rust
/// # #[cfg(feature = "derive")] {
/// # use clap::ArgMatches;
/// # struct Context {
/// # name: String,
/// # debug: bool
/// # }
/// impl From<ArgMatches> for Context {
/// fn from(m: ArgMatches) -> Self {
/// Context {
/// name: m.get_one::<String>("name").unwrap().clone(),
/// debug: m.get_flag("debug"),
/// }
/// }
/// }
/// # }
/// ```
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, Error>;
/// Instantiate `Self` from [`ArgMatches`], parsing the arguments as needed.
///
/// Motivation: If our application had two CLI options, `--name
/// <STRING>` and the flag `--debug`, we may create a struct as follows:
///
/// ```rust
/// # #[cfg(feature = "derive")] {
/// struct Context {
/// name: String,
/// debug: bool
/// }
/// # }
/// ```
///
/// We then need to convert the `ArgMatches` that `clap` generated into our struct.
/// `from_arg_matches_mut` serves as the equivalent of:
///
/// ```rust
/// # #[cfg(feature = "derive")] {
/// # use clap::ArgMatches;
/// # struct Context {
/// # name: String,
/// # debug: bool
/// # }
/// impl From<ArgMatches> for Context {
/// fn from(m: ArgMatches) -> Self {
/// Context {
/// name: m.get_one::<String>("name").unwrap().to_string(),
/// debug: m.get_flag("debug"),
/// }
/// }
/// }
/// # }
/// ```
fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result<Self, Error> {
Self::from_arg_matches(matches)
}
/// Assign values from `ArgMatches` to `self`.
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error>;
/// Assign values from `ArgMatches` to `self`.
fn update_from_arg_matches_mut(&mut self, matches: &mut ArgMatches) -> Result<(), Error> {
self.update_from_arg_matches(matches)
}
}
/// Parse a set of arguments into a user-defined container.
///
/// Implementing this trait lets a parent container delegate argument parsing behavior to `Self`.
/// with:
/// - `#[command(flatten)] args: ChildArgs`: Attribute can only be used with struct fields that impl
/// `Args`.
/// - `Variant(ChildArgs)`: No attribute is used with enum variants that impl `Args`.
///
/// <div class="warning">
///
/// **NOTE:** Deriving requires the `derive` feature flag
///
/// </div>
pub trait Args: FromArgMatches + Sized {
/// Report the [`ArgGroup::id`][crate::ArgGroup::id] for this set of arguments
fn group_id() -> Option<crate::Id> {
None
}
/// Append to [`Command`] so it can instantiate `Self` via
/// [`FromArgMatches::from_arg_matches_mut`]
///
/// This is used to implement `#[command(flatten)]`
///
/// See also [`CommandFactory::command`].
fn augment_args(cmd: Command) -> Command;
/// Append to [`Command`] so it can instantiate `self` via
/// [`FromArgMatches::update_from_arg_matches_mut`]
///
/// This is used to implement `#[command(flatten)]`
///
/// See also [`CommandFactory::command_for_update`].
fn augment_args_for_update(cmd: Command) -> Command;
}
/// Parse a sub-command into a user-defined enum.
///
/// Implementing this trait lets a parent container delegate subcommand behavior to `Self`.
/// with:
/// - `#[command(subcommand)] field: SubCmd`: Attribute can be used with either struct fields or enum
/// variants that impl `Subcommand`.
/// - `#[command(flatten)] Variant(SubCmd)`: Attribute can only be used with enum variants that impl
/// `Subcommand`.
///
/// <div class="warning">
///
/// **NOTE:** Deriving requires the `derive` feature flag
///
/// </div>
pub trait Subcommand: FromArgMatches + Sized {
/// Append to [`Command`] so it can instantiate `Self` via
/// [`FromArgMatches::from_arg_matches_mut`]
///
/// This is used to implement `#[command(flatten)]`
///
/// See also [`CommandFactory::command`].
fn augment_subcommands(cmd: Command) -> Command;
/// Append to [`Command`] so it can instantiate `self` via
/// [`FromArgMatches::update_from_arg_matches_mut`]
///
/// This is used to implement `#[command(flatten)]`
///
/// See also [`CommandFactory::command_for_update`].
fn augment_subcommands_for_update(cmd: Command) -> Command;
/// Test whether `Self` can parse a specific subcommand
fn has_subcommand(name: &str) -> bool;
}
/// Parse arguments into enums.
///
/// When deriving [`Parser`], a field whose type implements `ValueEnum` can have the attribute
/// `#[arg(value_enum)]` which will
/// - Call [`EnumValueParser`][crate::builder::EnumValueParser]
/// - Allowing using the `#[arg(default_value_t)]` attribute without implementing `Display`.
///
/// <div class="warning">
///
/// **NOTE:** Deriving requires the `derive` feature flag
///
/// </div>
pub trait ValueEnum: Sized + Clone {
/// All possible argument values, in display order.
fn value_variants<'a>() -> &'a [Self];
/// Parse an argument into `Self`.
fn from_str(input: &str, ignore_case: bool) -> Result<Self, String> {
Self::value_variants()
.iter()
.find(|v| {
v.to_possible_value()
.expect("ValueEnum::value_variants contains only values with a corresponding ValueEnum::to_possible_value")
.matches(input, ignore_case)
})
.cloned()
.ok_or_else(|| format!("invalid variant: {input}"))
}
/// The canonical argument value.
///
/// The value is `None` for skipped variants.
fn to_possible_value(&self) -> Option<PossibleValue>;
}
impl<T: Parser> Parser for Box<T> {
fn parse() -> Self {
Self::new(<T as Parser>::parse())
}
fn try_parse() -> Result<Self, Error> {
<T as Parser>::try_parse().map(Self::new)
}
fn parse_from<I, It>(itr: I) -> Self
where
I: IntoIterator<Item = It>,
It: Into<OsString> + Clone,
{
Self::new(<T as Parser>::parse_from(itr))
}
fn try_parse_from<I, It>(itr: I) -> Result<Self, Error>
where
I: IntoIterator<Item = It>,
It: Into<OsString> + Clone,
{
<T as Parser>::try_parse_from(itr).map(Self::new)
}
}
impl<T: CommandFactory> CommandFactory for Box<T> {
fn command() -> Command {
<T as CommandFactory>::command()
}
fn command_for_update() -> Command {
<T as CommandFactory>::command_for_update()
}
}
impl<T: FromArgMatches> FromArgMatches for Box<T> {
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, Error> {
<T as FromArgMatches>::from_arg_matches(matches).map(Self::new)
}
fn from_arg_matches_mut(matches: &mut ArgMatches) -> Result<Self, Error> {
<T as FromArgMatches>::from_arg_matches_mut(matches).map(Self::new)
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> {
<T as FromArgMatches>::update_from_arg_matches(self, matches)
}
fn update_from_arg_matches_mut(&mut self, matches: &mut ArgMatches) -> Result<(), Error> {
<T as FromArgMatches>::update_from_arg_matches_mut(self, matches)
}
}
impl<T: Args> Args for Box<T> {
fn augment_args(cmd: Command) -> Command {
<T as Args>::augment_args(cmd)
}
fn augment_args_for_update(cmd: Command) -> Command {
<T as Args>::augment_args_for_update(cmd)
}
}
impl<T: Subcommand> Subcommand for Box<T> {
fn augment_subcommands(cmd: Command) -> Command {
<T as Subcommand>::augment_subcommands(cmd)
}
fn augment_subcommands_for_update(cmd: Command) -> Command {
<T as Subcommand>::augment_subcommands_for_update(cmd)
}
fn has_subcommand(name: &str) -> bool {
<T as Subcommand>::has_subcommand(name)
}
}
fn format_error<I: CommandFactory>(err: Error) -> Error {
let mut cmd = I::command();
err.format(&mut cmd)
}
impl FromArgMatches for () {
fn from_arg_matches(_matches: &ArgMatches) -> Result<Self, Error> {
Ok(())
}
fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), Error> {
Ok(())
}
}
impl Args for () {
fn augment_args(cmd: Command) -> Command {
cmd
}
fn augment_args_for_update(cmd: Command) -> Command {
cmd
}
}
impl Subcommand for () {
fn augment_subcommands(cmd: Command) -> Command {
cmd
}
fn augment_subcommands_for_update(cmd: Command) -> Command {
cmd
}
fn has_subcommand(_name: &str) -> bool {
false
}
}
impl FromArgMatches for Infallible {
fn from_arg_matches(_matches: &ArgMatches) -> Result<Self, Error> {
Err(Error::raw(
crate::error::ErrorKind::MissingSubcommand,
"a subcommand is required but one was not provided",
))
}
fn update_from_arg_matches(&mut self, _matches: &ArgMatches) -> Result<(), Error> {
unreachable!(
"there will never be an instance of Infallible and thus &mut self can never be called"
);
}
}
impl Subcommand for Infallible {
fn augment_subcommands(cmd: Command) -> Command {
cmd
}
fn augment_subcommands_for_update(cmd: Command) -> Command {
cmd
}
fn has_subcommand(_name: &str) -> bool {
false
}
}

View File

@@ -0,0 +1,114 @@
/// Semantics for a piece of error information
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[cfg(feature = "error-context")]
pub enum ContextKind {
/// The cause of the error
InvalidSubcommand,
/// The cause of the error
InvalidArg,
/// Existing arguments
PriorArg,
/// Accepted subcommands
ValidSubcommand,
/// Accepted values
ValidValue,
/// Rejected values
InvalidValue,
/// Number of values present
ActualNumValues,
/// Number of allowed values
ExpectedNumValues,
/// Minimum number of allowed values
MinValues,
/// Potential fix for the user
SuggestedCommand,
/// Potential fix for the user
SuggestedSubcommand,
/// Potential fix for the user
SuggestedArg,
/// Potential fix for the user
SuggestedValue,
/// Trailing argument
TrailingArg,
/// Potential fix for the user
Suggested,
/// A usage string
Usage,
/// An opaque message to the user
Custom,
}
impl ContextKind {
/// End-user description of the error case, where relevant
pub fn as_str(self) -> Option<&'static str> {
match self {
Self::InvalidSubcommand => Some("Invalid Subcommand"),
Self::InvalidArg => Some("Invalid Argument"),
Self::PriorArg => Some("Prior Argument"),
Self::ValidSubcommand => Some("Valid Subcommand"),
Self::ValidValue => Some("Valid Value"),
Self::InvalidValue => Some("Invalid Value"),
Self::ActualNumValues => Some("Actual Number of Values"),
Self::ExpectedNumValues => Some("Expected Number of Values"),
Self::MinValues => Some("Minimum Number of Values"),
Self::SuggestedCommand => Some("Suggested Command"),
Self::SuggestedSubcommand => Some("Suggested Subcommand"),
Self::SuggestedArg => Some("Suggested Argument"),
Self::SuggestedValue => Some("Suggested Value"),
Self::TrailingArg => Some("Trailing Argument"),
Self::Suggested => Some("Suggested"),
Self::Usage => None,
Self::Custom => None,
}
}
}
impl std::fmt::Display for ContextKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().unwrap_or_default().fmt(f)
}
}
/// A piece of error information
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[cfg(feature = "error-context")]
pub enum ContextValue {
/// [`ContextKind`] is self-sufficient, no additional information needed
None,
/// A single value
Bool(bool),
/// A single value
String(String),
/// Many values
Strings(Vec<String>),
/// A single value
StyledStr(crate::builder::StyledStr),
/// many value
StyledStrs(Vec<crate::builder::StyledStr>),
/// A single value
Number(isize),
}
impl std::fmt::Display for ContextValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => "".fmt(f),
Self::Bool(v) => v.fmt(f),
Self::String(v) => v.fmt(f),
Self::Strings(v) => v.join(", ").fmt(f),
Self::StyledStr(v) => v.fmt(f),
Self::StyledStrs(v) => {
for (i, v) in v.iter().enumerate() {
if i != 0 {
", ".fmt(f)?;
}
v.fmt(f)?;
}
Ok(())
}
Self::Number(v) => v.fmt(f),
}
}
}

View File

@@ -0,0 +1,494 @@
#![allow(missing_copy_implementations)]
#![allow(missing_debug_implementations)]
#![cfg_attr(not(feature = "error-context"), allow(dead_code))]
#![cfg_attr(not(feature = "error-context"), allow(unused_imports))]
use std::borrow::Cow;
use crate::ArgAction;
use crate::builder::Command;
use crate::builder::StyledStr;
use crate::builder::Styles;
#[cfg(feature = "error-context")]
use crate::error::ContextKind;
#[cfg(feature = "error-context")]
use crate::error::ContextValue;
use crate::error::ErrorKind;
use crate::output::TAB;
use crate::util::Escape;
/// Defines how to format an error for displaying to the user
pub trait ErrorFormatter: Sized {
/// Stylize the error for the terminal
fn format_error(error: &crate::error::Error<Self>) -> StyledStr;
}
/// Report [`ErrorKind`]
///
/// No context is included.
///
/// <div class="warning">
///
/// **NOTE:** Consider removing the `error-context` default feature if using this to remove all
/// overhead for [`RichFormatter`].
///
/// </div>
#[non_exhaustive]
pub struct KindFormatter;
impl ErrorFormatter for KindFormatter {
fn format_error(error: &crate::error::Error<Self>) -> StyledStr {
use std::fmt::Write as _;
let styles = &error.inner.styles;
let mut styled = StyledStr::new();
start_error(&mut styled, styles);
if let Some(msg) = error.kind().as_str() {
styled.push_str(msg);
} else if let Some(source) = error.inner.source.as_ref() {
let _ = write!(styled, "{source}");
} else {
styled.push_str("unknown cause");
}
styled.push_str("\n");
styled
}
}
/// Richly formatted error context
///
/// This follows the [rustc diagnostic style guide](https://rustc-dev-guide.rust-lang.org/diagnostics.html#suggestion-style-guide).
#[non_exhaustive]
#[cfg(feature = "error-context")]
pub struct RichFormatter;
#[cfg(feature = "error-context")]
impl ErrorFormatter for RichFormatter {
fn format_error(error: &crate::error::Error<Self>) -> StyledStr {
use std::fmt::Write as _;
let styles = &error.inner.styles;
let valid = &styles.get_valid();
let mut styled = StyledStr::new();
start_error(&mut styled, styles);
if !write_dynamic_context(error, &mut styled, styles) {
if let Some(msg) = error.kind().as_str() {
styled.push_str(msg);
} else if let Some(source) = error.inner.source.as_ref() {
let _ = write!(styled, "{source}");
} else {
styled.push_str("unknown cause");
}
}
let mut suggested = false;
if let Some(valid) = error.get(ContextKind::SuggestedSubcommand) {
styled.push_str("\n");
if !suggested {
styled.push_str("\n");
suggested = true;
}
did_you_mean(&mut styled, styles, "subcommand", valid);
}
if let Some(valid) = error.get(ContextKind::SuggestedArg) {
styled.push_str("\n");
if !suggested {
styled.push_str("\n");
suggested = true;
}
did_you_mean(&mut styled, styles, "argument", valid);
}
if let Some(valid) = error.get(ContextKind::SuggestedValue) {
styled.push_str("\n");
if !suggested {
styled.push_str("\n");
suggested = true;
}
did_you_mean(&mut styled, styles, "value", valid);
}
let suggestions = error.get(ContextKind::Suggested);
if let Some(ContextValue::StyledStrs(suggestions)) = suggestions {
if !suggested {
styled.push_str("\n");
}
for suggestion in suggestions {
let _ = write!(styled, "\n{TAB}{valid}tip:{valid:#} ",);
styled.push_styled(suggestion);
}
}
let usage = error.get(ContextKind::Usage);
if let Some(ContextValue::StyledStr(usage)) = usage {
put_usage(&mut styled, usage);
}
try_help(&mut styled, styles, error.inner.help_flag.as_deref());
styled
}
}
fn start_error(styled: &mut StyledStr, styles: &Styles) {
use std::fmt::Write as _;
let error = &styles.get_error();
let _ = write!(styled, "{error}error:{error:#} ");
}
#[must_use]
#[cfg(feature = "error-context")]
fn write_dynamic_context(
error: &crate::error::Error,
styled: &mut StyledStr,
styles: &Styles,
) -> bool {
use std::fmt::Write as _;
let valid = styles.get_valid();
let invalid = styles.get_invalid();
let literal = styles.get_literal();
match error.kind() {
ErrorKind::ArgumentConflict => {
let mut prior_arg = error.get(ContextKind::PriorArg);
if let Some(ContextValue::String(invalid_arg)) = error.get(ContextKind::InvalidArg) {
if Some(&ContextValue::String(invalid_arg.clone())) == prior_arg {
prior_arg = None;
let _ = write!(
styled,
"the argument '{invalid}{invalid_arg}{invalid:#}' cannot be used multiple times",
);
} else {
let _ = write!(
styled,
"the argument '{invalid}{invalid_arg}{invalid:#}' cannot be used with",
);
}
} else if let Some(ContextValue::String(invalid_arg)) =
error.get(ContextKind::InvalidSubcommand)
{
let _ = write!(
styled,
"the subcommand '{invalid}{invalid_arg}{invalid:#}' cannot be used with",
);
} else {
styled.push_str(error.kind().as_str().unwrap());
}
if let Some(prior_arg) = prior_arg {
match prior_arg {
ContextValue::Strings(values) => {
styled.push_str(":");
for v in values {
let _ = write!(styled, "\n{TAB}{invalid}{v}{invalid:#}",);
}
}
ContextValue::String(value) => {
let _ = write!(styled, " '{invalid}{value}{invalid:#}'",);
}
_ => {
styled.push_str(" one or more of the other specified arguments");
}
}
}
true
}
ErrorKind::NoEquals => {
let invalid_arg = error.get(ContextKind::InvalidArg);
if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
let _ = write!(
styled,
"equal sign is needed when assigning values to '{invalid}{invalid_arg}{invalid:#}'",
);
true
} else {
false
}
}
ErrorKind::InvalidValue => {
let invalid_arg = error.get(ContextKind::InvalidArg);
let invalid_value = error.get(ContextKind::InvalidValue);
if let (
Some(ContextValue::String(invalid_arg)),
Some(ContextValue::String(invalid_value)),
) = (invalid_arg, invalid_value)
{
if invalid_value.is_empty() {
let _ = write!(
styled,
"a value is required for '{invalid}{invalid_arg}{invalid:#}' but none was supplied",
);
} else {
let _ = write!(
styled,
"invalid value '{invalid}{invalid_value}{invalid:#}' for '{literal}{invalid_arg}{literal:#}'",
);
}
let values = error.get(ContextKind::ValidValue);
write_values_list("possible values", styled, valid, values);
true
} else {
false
}
}
ErrorKind::InvalidSubcommand => {
let invalid_sub = error.get(ContextKind::InvalidSubcommand);
if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
let _ = write!(
styled,
"unrecognized subcommand '{invalid}{invalid_sub}{invalid:#}'",
);
true
} else {
false
}
}
ErrorKind::MissingRequiredArgument => {
let invalid_arg = error.get(ContextKind::InvalidArg);
if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg {
styled.push_str("the following required arguments were not provided:");
for v in invalid_arg {
let _ = write!(styled, "\n{TAB}{valid}{v}{valid:#}",);
}
true
} else {
false
}
}
ErrorKind::MissingSubcommand => {
let invalid_sub = error.get(ContextKind::InvalidSubcommand);
if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
let _ = write!(
styled,
"'{invalid}{invalid_sub}{invalid:#}' requires a subcommand but one was not provided",
);
let values = error.get(ContextKind::ValidSubcommand);
write_values_list("subcommands", styled, valid, values);
true
} else {
false
}
}
ErrorKind::InvalidUtf8 => false,
ErrorKind::TooManyValues => {
let invalid_arg = error.get(ContextKind::InvalidArg);
let invalid_value = error.get(ContextKind::InvalidValue);
if let (
Some(ContextValue::String(invalid_arg)),
Some(ContextValue::String(invalid_value)),
) = (invalid_arg, invalid_value)
{
let _ = write!(
styled,
"unexpected value '{invalid}{invalid_value}{invalid:#}' for '{literal}{invalid_arg}{literal:#}' found; no more were expected",
);
true
} else {
false
}
}
ErrorKind::TooFewValues => {
let invalid_arg = error.get(ContextKind::InvalidArg);
let actual_num_values = error.get(ContextKind::ActualNumValues);
let min_values = error.get(ContextKind::MinValues);
if let (
Some(ContextValue::String(invalid_arg)),
Some(ContextValue::Number(actual_num_values)),
Some(ContextValue::Number(min_values)),
) = (invalid_arg, actual_num_values, min_values)
{
let were_provided = singular_or_plural(*actual_num_values as usize);
let _ = write!(
styled,
"{valid}{min_values}{valid:#} values required by '{literal}{invalid_arg}{literal:#}'; only {invalid}{actual_num_values}{invalid:#}{were_provided}",
);
true
} else {
false
}
}
ErrorKind::ValueValidation => {
let invalid_arg = error.get(ContextKind::InvalidArg);
let invalid_value = error.get(ContextKind::InvalidValue);
if let (
Some(ContextValue::String(invalid_arg)),
Some(ContextValue::String(invalid_value)),
) = (invalid_arg, invalid_value)
{
let _ = write!(
styled,
"invalid value '{invalid}{invalid_value}{invalid:#}' for '{literal}{invalid_arg}{literal:#}'",
);
if let Some(source) = error.inner.source.as_deref() {
let _ = write!(styled, ": {source}");
}
true
} else {
false
}
}
ErrorKind::WrongNumberOfValues => {
let invalid_arg = error.get(ContextKind::InvalidArg);
let actual_num_values = error.get(ContextKind::ActualNumValues);
let num_values = error.get(ContextKind::ExpectedNumValues);
if let (
Some(ContextValue::String(invalid_arg)),
Some(ContextValue::Number(actual_num_values)),
Some(ContextValue::Number(num_values)),
) = (invalid_arg, actual_num_values, num_values)
{
let were_provided = singular_or_plural(*actual_num_values as usize);
let _ = write!(
styled,
"{valid}{num_values}{valid:#} values required for '{literal}{invalid_arg}{literal:#}' but {invalid}{actual_num_values}{invalid:#}{were_provided}",
);
true
} else {
false
}
}
ErrorKind::UnknownArgument => {
let invalid_arg = error.get(ContextKind::InvalidArg);
if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
let _ = write!(
styled,
"unexpected argument '{invalid}{invalid_arg}{invalid:#}' found",
);
true
} else {
false
}
}
ErrorKind::DisplayHelp
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
| ErrorKind::DisplayVersion
| ErrorKind::Io
| ErrorKind::Format => false,
}
}
#[cfg(feature = "error-context")]
fn write_values_list(
list_name: &'static str,
styled: &mut StyledStr,
valid: &anstyle::Style,
possible_values: Option<&ContextValue>,
) {
use std::fmt::Write as _;
if let Some(ContextValue::Strings(possible_values)) = possible_values {
if !possible_values.is_empty() {
let _ = write!(styled, "\n{TAB}[{list_name}: ");
for (idx, val) in possible_values.iter().enumerate() {
if idx > 0 {
styled.push_str(", ");
}
let _ = write!(styled, "{valid}{}{valid:#}", Escape(val));
}
styled.push_str("]");
}
}
}
pub(crate) fn format_error_message(
message: &str,
styles: &Styles,
cmd: Option<&Command>,
usage: Option<&StyledStr>,
) -> StyledStr {
let mut styled = StyledStr::new();
start_error(&mut styled, styles);
styled.push_str(message);
if let Some(usage) = usage {
put_usage(&mut styled, usage);
}
if let Some(cmd) = cmd {
try_help(&mut styled, styles, get_help_flag(cmd).as_deref());
}
styled
}
/// Returns the singular or plural form on the verb to be based on the argument's value.
fn singular_or_plural(n: usize) -> &'static str {
if n > 1 {
" were provided"
} else {
" was provided"
}
}
fn put_usage(styled: &mut StyledStr, usage: &StyledStr) {
styled.push_str("\n\n");
styled.push_styled(usage);
}
pub(crate) fn get_help_flag(cmd: &Command) -> Option<Cow<'static, str>> {
if !cmd.is_disable_help_flag_set() {
Some(Cow::Borrowed("--help"))
} else if let Some(flag) = get_user_help_flag(cmd) {
Some(Cow::Owned(flag))
} else if cmd.has_subcommands() && !cmd.is_disable_help_subcommand_set() {
Some(Cow::Borrowed("help"))
} else {
None
}
}
fn get_user_help_flag(cmd: &Command) -> Option<String> {
let arg = cmd.get_arguments().find(|arg| match arg.get_action() {
ArgAction::Help | ArgAction::HelpShort | ArgAction::HelpLong => true,
ArgAction::Append
| ArgAction::Count
| ArgAction::SetTrue
| ArgAction::SetFalse
| ArgAction::Set
| ArgAction::Version => false,
})?;
arg.get_long()
.map(|long| format!("--{long}"))
.or_else(|| arg.get_short().map(|short| format!("-{short}")))
}
fn try_help(styled: &mut StyledStr, styles: &Styles, help: Option<&str>) {
if let Some(help) = help {
use std::fmt::Write as _;
let literal = &styles.get_literal();
let _ = write!(
styled,
"\n\nFor more information, try '{literal}{help}{literal:#}'.\n",
);
} else {
styled.push_str("\n");
}
}
#[cfg(feature = "error-context")]
fn did_you_mean(styled: &mut StyledStr, styles: &Styles, context: &str, possibles: &ContextValue) {
use std::fmt::Write as _;
let valid = &styles.get_valid();
let _ = write!(styled, "{TAB}{valid}tip:{valid:#}",);
if let ContextValue::String(possible) = possibles {
let _ = write!(
styled,
" a similar {context} exists: '{valid}{possible}{valid:#}'",
);
} else if let ContextValue::Strings(possibles) = possibles {
if possibles.len() == 1 {
let _ = write!(styled, " a similar {context} exists: ",);
} else {
let _ = write!(styled, " some similar {context}s exist: ",);
}
for (i, possible) in possibles.iter().enumerate() {
if i != 0 {
styled.push_str(", ");
}
let _ = write!(styled, "'{valid}{possible}{valid:#}'",);
}
}
}

View File

@@ -0,0 +1,365 @@
/// Command line argument parser kind of error
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
/// Occurs when an [`Arg`][crate::Arg] has a set of possible values,
/// and the user provides a value which isn't in that set.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind};
/// let result = Command::new("prog")
/// .arg(Arg::new("speed")
/// .value_parser(["fast", "slow"]))
/// .try_get_matches_from(vec!["prog", "other"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
/// ```
InvalidValue,
/// Occurs when a user provides a flag, option, argument or subcommand which isn't defined.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, arg, error::ErrorKind};
/// let result = Command::new("prog")
/// .arg(arg!(--flag "some flag"))
/// .try_get_matches_from(vec!["prog", "--other"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::UnknownArgument);
/// ```
UnknownArgument,
/// Occurs when the user provides an unrecognized [`Subcommand`] which meets the threshold for
/// being similar enough to an existing subcommand.
/// If it doesn't meet the threshold, or the 'suggestions' feature is disabled,
/// the more general [`UnknownArgument`] error is returned.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "suggestions")] {
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind, };
/// let result = Command::new("prog")
/// .subcommand(Command::new("config")
/// .about("Used for configuration")
/// .arg(Arg::new("config_file")
/// .help("The configuration file to use")))
/// .try_get_matches_from(vec!["prog", "confi"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidSubcommand);
/// # }
/// ```
///
/// [`Subcommand`]: crate::Subcommand
/// [`UnknownArgument`]: ErrorKind::UnknownArgument
InvalidSubcommand,
/// Occurs when the user doesn't use equals for an option that requires equal
/// sign to provide values.
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind, ArgAction};
/// let res = Command::new("prog")
/// .arg(Arg::new("color")
/// .action(ArgAction::Set)
/// .require_equals(true)
/// .long("color"))
/// .try_get_matches_from(vec!["prog", "--color", "red"]);
/// assert!(res.is_err());
/// assert_eq!(res.unwrap_err().kind(), ErrorKind::NoEquals);
/// ```
NoEquals,
/// Occurs when the user provides a value for an argument with a custom validation and the
/// value fails that validation.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind, value_parser};
/// fn is_numeric(val: &str) -> Result<(), String> {
/// match val.parse::<i64>() {
/// Ok(..) => Ok(()),
/// Err(..) => Err(String::from("value wasn't a number!")),
/// }
/// }
///
/// let result = Command::new("prog")
/// .arg(Arg::new("num")
/// .value_parser(value_parser!(u8)))
/// .try_get_matches_from(vec!["prog", "NotANumber"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::ValueValidation);
/// ```
ValueValidation,
/// Occurs when a user provides more values for an argument than were defined by setting
/// [`Arg::num_args`].
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind};
/// let result = Command::new("prog")
/// .arg(Arg::new("arg")
/// .num_args(1..=2))
/// .try_get_matches_from(vec!["prog", "too", "many", "values"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::TooManyValues);
/// ```
/// [`Arg::num_args`]: crate::Arg::num_args()
TooManyValues,
/// Occurs when the user provides fewer values for an argument than were defined by setting
/// [`Arg::num_args`].
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind};
/// let result = Command::new("prog")
/// .arg(Arg::new("some_opt")
/// .long("opt")
/// .num_args(3..))
/// .try_get_matches_from(vec!["prog", "--opt", "too", "few"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::TooFewValues);
/// ```
/// [`Arg::num_args`]: crate::Arg::num_args()
TooFewValues,
/// Occurs when the user provides a different number of values for an argument than what's
/// been defined by setting [`Arg::num_args`] or than was implicitly set by
/// [`Arg::value_names`].
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind, ArgAction};
/// let result = Command::new("prog")
/// .arg(Arg::new("some_opt")
/// .long("opt")
/// .action(ArgAction::Set)
/// .num_args(2))
/// .try_get_matches_from(vec!["prog", "--opt", "wrong"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::WrongNumberOfValues);
/// ```
///
/// [`Arg::num_args`]: crate::Arg::num_args()
/// [`Arg::value_names`]: crate::Arg::value_names()
WrongNumberOfValues,
/// Occurs when the user provides two values which conflict with each other and can't be used
/// together.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind, ArgAction};
/// let result = Command::new("prog")
/// .arg(Arg::new("debug")
/// .long("debug")
/// .action(ArgAction::SetTrue)
/// .conflicts_with("color"))
/// .arg(Arg::new("color")
/// .long("color")
/// .action(ArgAction::SetTrue))
/// .try_get_matches_from(vec!["prog", "--debug", "--color"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::ArgumentConflict);
/// ```
ArgumentConflict,
/// Occurs when the user does not provide one or more required arguments.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind};
/// let result = Command::new("prog")
/// .arg(Arg::new("debug")
/// .required(true))
/// .try_get_matches_from(vec!["prog"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::MissingRequiredArgument);
/// ```
MissingRequiredArgument,
/// Occurs when a subcommand is required (as defined by [`Command::subcommand_required`]),
/// but the user does not provide one.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, error::ErrorKind};
/// let err = Command::new("prog")
/// .subcommand_required(true)
/// .subcommand(Command::new("test"))
/// .try_get_matches_from(vec![
/// "myprog",
/// ]);
/// assert!(err.is_err());
/// assert_eq!(err.unwrap_err().kind(), ErrorKind::MissingSubcommand);
/// # ;
/// ```
///
/// [`Command::subcommand_required`]: crate::Command::subcommand_required
MissingSubcommand,
/// Occurs when the user provides a value containing invalid UTF-8.
///
/// To allow arbitrary data
/// - Set [`Arg::value_parser(value_parser!(OsString))`][crate::Arg::value_parser] for argument values
/// - Set [`Command::external_subcommand_value_parser`] for external-subcommand
/// values
///
/// # Platform Specific
///
/// Non-Windows platforms only (such as Linux, Unix, OSX, etc.)
///
/// # Examples
///
/// ```rust
/// # #[cfg(unix)] {
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind, ArgAction};
/// # use std::os::unix::ffi::OsStringExt;
/// # use std::ffi::OsString;
/// let result = Command::new("prog")
/// .arg(Arg::new("utf8")
/// .short('u')
/// .action(ArgAction::Set))
/// .try_get_matches_from(vec![OsString::from("myprog"),
/// OsString::from("-u"),
/// OsString::from_vec(vec![0xE9])]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidUtf8);
/// # }
/// ```
///
/// [`Command::external_subcommand_value_parser`]: crate::Command::external_subcommand_value_parser
InvalidUtf8,
/// Not a true "error" as it means `--help` or similar was used.
/// The help message will be sent to `stdout`.
///
/// **Note**: If the help is displayed due to an error (such as missing subcommands) it will
/// be sent to `stderr` instead of `stdout`.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "help")] {
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind};
/// let result = Command::new("prog")
/// .try_get_matches_from(vec!["prog", "--help"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::DisplayHelp);
/// # }
/// ```
DisplayHelp,
/// Occurs when either an argument or a [`Subcommand`] is required, as defined by
/// [`Command::arg_required_else_help`] , but the user did not provide
/// one.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind, };
/// let result = Command::new("prog")
/// .arg_required_else_help(true)
/// .subcommand(Command::new("config")
/// .about("Used for configuration")
/// .arg(Arg::new("config_file")
/// .help("The configuration file to use")))
/// .try_get_matches_from(vec!["prog"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand);
/// ```
///
/// [`Subcommand`]: crate::Subcommand
/// [`Command::arg_required_else_help`]: crate::Command::arg_required_else_help
DisplayHelpOnMissingArgumentOrSubcommand,
/// Not a true "error" as it means `--version` or similar was used.
/// The message will be sent to `stdout`.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, error::ErrorKind};
/// let result = Command::new("prog")
/// .version("3.0")
/// .try_get_matches_from(vec!["prog", "--version"]);
/// assert!(result.is_err());
/// assert_eq!(result.unwrap_err().kind(), ErrorKind::DisplayVersion);
/// ```
DisplayVersion,
/// Represents an [I/O error].
/// Can occur when writing to `stderr` or `stdout` or reading a configuration file.
///
/// [I/O error]: std::io::Error
Io,
/// Represents a [Format error] (which is a part of [`Display`]).
/// Typically caused by writing to `stderr` or `stdout`.
///
/// [`Display`]: std::fmt::Display
/// [Format error]: std::fmt::Error
Format,
}
impl ErrorKind {
/// End-user description of the error case, where relevant
pub fn as_str(self) -> Option<&'static str> {
match self {
Self::InvalidValue => Some("one of the values isn't valid for an argument"),
Self::UnknownArgument => Some("unexpected argument found"),
Self::InvalidSubcommand => Some("unrecognized subcommand"),
Self::NoEquals => Some("equal is needed when assigning values to one of the arguments"),
Self::ValueValidation => Some("invalid value for one of the arguments"),
Self::TooManyValues => Some("unexpected value for an argument found"),
Self::TooFewValues => Some("more values required for an argument"),
Self::WrongNumberOfValues => Some("too many or too few values for an argument"),
Self::ArgumentConflict => {
Some("an argument cannot be used with one or more of the other specified arguments")
}
Self::MissingRequiredArgument => {
Some("one or more required arguments were not provided")
}
Self::MissingSubcommand => Some("a subcommand is required but one was not provided"),
Self::InvalidUtf8 => Some("invalid UTF-8 was detected in one or more arguments"),
Self::DisplayHelp => None,
Self::DisplayHelpOnMissingArgumentOrSubcommand => None,
Self::DisplayVersion => None,
Self::Io => None,
Self::Format => None,
}
}
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().unwrap_or_default().fmt(f)
}
}

View File

@@ -0,0 +1,946 @@
//! Error reporting
#![cfg_attr(not(feature = "error-context"), allow(dead_code))]
#![cfg_attr(not(feature = "error-context"), allow(unused_imports))]
#![cfg_attr(not(feature = "error-context"), allow(unused_variables))]
#![cfg_attr(not(feature = "error-context"), allow(unused_mut))]
#![cfg_attr(not(feature = "error-context"), allow(clippy::let_and_return))]
// Std
use std::{
borrow::Cow,
convert::From,
error,
fmt::{self, Debug, Display, Formatter},
io,
result::Result as StdResult,
};
// Internal
use crate::Command;
use crate::builder::StyledStr;
use crate::builder::Styles;
use crate::output::fmt::Colorizer;
use crate::output::fmt::Stream;
use crate::parser::features::suggestions;
use crate::util::FlatMap;
use crate::util::{SUCCESS_CODE, USAGE_CODE, color::ColorChoice};
#[cfg(feature = "error-context")]
mod context;
mod format;
mod kind;
pub use format::ErrorFormatter;
pub use format::KindFormatter;
pub use kind::ErrorKind;
#[cfg(feature = "error-context")]
pub use context::ContextKind;
#[cfg(feature = "error-context")]
pub use context::ContextValue;
#[cfg(feature = "error-context")]
pub use format::RichFormatter;
#[cfg(not(feature = "error-context"))]
pub use KindFormatter as DefaultFormatter;
#[cfg(feature = "error-context")]
pub use RichFormatter as DefaultFormatter;
/// Short hand for [`Result`] type
///
/// [`Result`]: std::result::Result
pub type Result<T, E = Error> = StdResult<T, E>;
/// Command Line Argument Parser Error
///
/// See [`Command::error`] to create an error.
///
/// [`Command::error`]: crate::Command::error
pub struct Error<F: ErrorFormatter = DefaultFormatter> {
inner: Box<ErrorInner>,
phantom: std::marker::PhantomData<F>,
}
#[derive(Debug)]
struct ErrorInner {
kind: ErrorKind,
#[cfg(feature = "error-context")]
context: FlatMap<ContextKind, ContextValue>,
message: Option<Message>,
source: Option<Box<dyn error::Error + Send + Sync>>,
help_flag: Option<Cow<'static, str>>,
styles: Styles,
color_when: ColorChoice,
color_help_when: ColorChoice,
backtrace: Option<Backtrace>,
}
impl<F: ErrorFormatter> Error<F> {
/// Create an unformatted error
///
/// This is for you need to pass the error up to
/// a place that has access to the `Command` at which point you can call [`Error::format`].
///
/// Prefer [`Command::error`] for generating errors.
///
/// [`Command::error`]: crate::Command::error
pub fn raw(kind: ErrorKind, message: impl Display) -> Self {
Self::new(kind).set_message(message.to_string())
}
/// Format the existing message with the Command's context
#[must_use]
pub fn format(mut self, cmd: &mut Command) -> Self {
cmd._build_self(false);
let usage = cmd.render_usage_();
if let Some(message) = self.inner.message.as_mut() {
message.format(cmd, usage);
}
self.with_cmd(cmd)
}
/// Create an error with a pre-defined message
///
/// See also
/// - [`Error::insert`]
/// - [`Error::with_cmd`]
///
/// # Example
///
/// ```rust
/// # #[cfg(feature = "error-context")] {
/// # use clap_builder as clap;
/// # use clap::error::ErrorKind;
/// # use clap::error::ContextKind;
/// # use clap::error::ContextValue;
///
/// let cmd = clap::Command::new("prog");
///
/// let mut err = clap::Error::new(ErrorKind::ValueValidation)
/// .with_cmd(&cmd);
/// err.insert(ContextKind::InvalidArg, ContextValue::String("--foo".to_owned()));
/// err.insert(ContextKind::InvalidValue, ContextValue::String("bar".to_owned()));
///
/// err.print();
/// # }
/// ```
pub fn new(kind: ErrorKind) -> Self {
Self {
inner: Box::new(ErrorInner {
kind,
#[cfg(feature = "error-context")]
context: FlatMap::new(),
message: None,
source: None,
help_flag: None,
styles: Styles::plain(),
color_when: ColorChoice::Never,
color_help_when: ColorChoice::Never,
backtrace: Backtrace::new(),
}),
phantom: Default::default(),
}
}
/// Apply [`Command`]'s formatting to the error
///
/// Generally, this is used with [`Error::new`]
pub fn with_cmd(self, cmd: &Command) -> Self {
self.set_styles(cmd.get_styles().clone())
.set_color(cmd.get_color())
.set_colored_help(cmd.color_help())
.set_help_flag(format::get_help_flag(cmd))
}
/// Apply an alternative formatter to the error
///
/// # Example
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::Command;
/// # use clap::Arg;
/// # use clap::error::KindFormatter;
/// let cmd = Command::new("foo")
/// .arg(Arg::new("input").required(true));
/// let matches = cmd
/// .try_get_matches_from(["foo", "input.txt"])
/// .map_err(|e| e.apply::<KindFormatter>())
/// .unwrap_or_else(|e| e.exit());
/// ```
pub fn apply<EF: ErrorFormatter>(self) -> Error<EF> {
Error {
inner: self.inner,
phantom: Default::default(),
}
}
/// Type of error for programmatic processing
pub fn kind(&self) -> ErrorKind {
self.inner.kind
}
/// Additional information to further qualify the error
#[cfg(feature = "error-context")]
pub fn context(&self) -> impl Iterator<Item = (ContextKind, &ContextValue)> {
self.inner.context.iter().map(|(k, v)| (*k, v))
}
/// Lookup a piece of context
#[inline(never)]
#[cfg(feature = "error-context")]
pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> {
self.inner.context.get(&kind)
}
/// Insert a piece of context
///
/// If this `ContextKind` is already present, its value is replaced and the old value is returned.
#[inline(never)]
#[cfg(feature = "error-context")]
pub fn insert(&mut self, kind: ContextKind, value: ContextValue) -> Option<ContextValue> {
self.inner.context.insert(kind, value)
}
/// Remove a piece of context, return the old value if any
///
/// The context is currently implemented in a vector, so `remove` takes
/// linear time.
#[inline(never)]
#[cfg(feature = "error-context")]
pub fn remove(&mut self, kind: ContextKind) -> Option<ContextValue> {
self.inner.context.remove(&kind)
}
/// Should the message be written to `stdout` or not?
#[inline]
pub fn use_stderr(&self) -> bool {
self.stream() == Stream::Stderr
}
pub(crate) fn stream(&self) -> Stream {
match self.kind() {
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => Stream::Stdout,
_ => Stream::Stderr,
}
}
/// Returns the exit code that `.exit` will exit the process with.
///
/// When the error's kind would print to `stderr` this returns `2`,
/// else it returns `0`.
pub fn exit_code(&self) -> i32 {
if self.use_stderr() {
USAGE_CODE
} else {
SUCCESS_CODE
}
}
/// Prints the error and exits.
///
/// Depending on the error kind, this either prints to `stderr` and exits with a status of `2`
/// or prints to `stdout` and exits with a status of `0`.
pub fn exit(&self) -> ! {
// Swallow broken pipe errors
let _ = self.print();
std::process::exit(self.exit_code());
}
/// Prints formatted and colored error to `stdout` or `stderr` according to its error kind
///
/// # Example
/// ```no_run
/// # use clap_builder as clap;
/// use clap::Command;
///
/// match Command::new("Command").try_get_matches() {
/// Ok(matches) => {
/// // do_something
/// },
/// Err(err) => {
/// err.print().expect("Error writing Error");
/// // do_something
/// },
/// };
/// ```
pub fn print(&self) -> io::Result<()> {
let style = self.formatted();
let color_when = if matches!(
self.kind(),
ErrorKind::DisplayHelp | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
) {
self.inner.color_help_when
} else {
self.inner.color_when
};
let c = Colorizer::new(self.stream(), color_when).with_content(style.into_owned());
c.print()
}
/// Render the error message to a [`StyledStr`].
///
/// # Example
/// ```no_run
/// # use clap_builder as clap;
/// use clap::Command;
///
/// match Command::new("Command").try_get_matches() {
/// Ok(matches) => {
/// // do_something
/// },
/// Err(err) => {
/// let err = err.render();
/// println!("{err}");
/// // do_something
/// },
/// };
/// ```
pub fn render(&self) -> StyledStr {
self.formatted().into_owned()
}
#[inline(never)]
fn for_app(kind: ErrorKind, cmd: &Command, styled: StyledStr) -> Self {
Self::new(kind).set_message(styled).with_cmd(cmd)
}
pub(crate) fn set_message(mut self, message: impl Into<Message>) -> Self {
self.inner.message = Some(message.into());
self
}
pub(crate) fn set_source(mut self, source: Box<dyn error::Error + Send + Sync>) -> Self {
self.inner.source = Some(source);
self
}
pub(crate) fn set_styles(mut self, styles: Styles) -> Self {
self.inner.styles = styles;
self
}
pub(crate) fn set_color(mut self, color_when: ColorChoice) -> Self {
self.inner.color_when = color_when;
self
}
pub(crate) fn set_colored_help(mut self, color_help_when: ColorChoice) -> Self {
self.inner.color_help_when = color_help_when;
self
}
pub(crate) fn set_help_flag(mut self, help_flag: Option<Cow<'static, str>>) -> Self {
self.inner.help_flag = help_flag;
self
}
/// Does not verify if `ContextKind` is already present
#[inline(never)]
#[cfg(feature = "error-context")]
pub(crate) fn insert_context_unchecked(
mut self,
kind: ContextKind,
value: ContextValue,
) -> Self {
self.inner.context.insert_unchecked(kind, value);
self
}
/// Does not verify if `ContextKind` is already present
#[inline(never)]
#[cfg(feature = "error-context")]
pub(crate) fn extend_context_unchecked<const N: usize>(
mut self,
context: [(ContextKind, ContextValue); N],
) -> Self {
self.inner.context.extend_unchecked(context);
self
}
pub(crate) fn display_help(cmd: &Command, styled: StyledStr) -> Self {
Self::for_app(ErrorKind::DisplayHelp, cmd, styled)
}
pub(crate) fn display_help_error(cmd: &Command, styled: StyledStr) -> Self {
Self::for_app(
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
cmd,
styled,
)
}
pub(crate) fn display_version(cmd: &Command, styled: StyledStr) -> Self {
Self::for_app(ErrorKind::DisplayVersion, cmd, styled)
}
pub(crate) fn argument_conflict(
cmd: &Command,
arg: String,
mut others: Vec<String>,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
let others = match others.len() {
0 => ContextValue::None,
1 => ContextValue::String(others.pop().unwrap()),
_ => ContextValue::Strings(others),
};
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::PriorArg, others),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn subcommand_conflict(
cmd: &Command,
sub: String,
mut others: Vec<String>,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
let others = match others.len() {
0 => ContextValue::None,
1 => ContextValue::String(others.pop().unwrap()),
_ => ContextValue::Strings(others),
};
err = err.extend_context_unchecked([
(ContextKind::InvalidSubcommand, ContextValue::String(sub)),
(ContextKind::PriorArg, others),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self {
Self::invalid_value(cmd, "".to_owned(), good_vals, arg)
}
pub(crate) fn no_equals(cmd: &Command, arg: String, usage: Option<StyledStr>) -> Self {
let mut err = Self::new(ErrorKind::NoEquals).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err
.extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn invalid_value(
cmd: &Command,
bad_val: String,
good_vals: &[String],
arg: String,
) -> Self {
let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
let mut err = Self::new(ErrorKind::InvalidValue).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::InvalidValue, ContextValue::String(bad_val)),
(
ContextKind::ValidValue,
ContextValue::Strings(good_vals.iter().map(|s| (*s).clone()).collect()),
),
]);
if let Some(suggestion) = suggestion {
err = err.insert_context_unchecked(
ContextKind::SuggestedValue,
ContextValue::String(suggestion),
);
}
}
err
}
pub(crate) fn invalid_subcommand(
cmd: &Command,
subcmd: String,
did_you_mean: Vec<String>,
name: String,
suggested_trailing_arg: bool,
usage: Option<StyledStr>,
) -> Self {
use std::fmt::Write as _;
let styles = cmd.get_styles();
let invalid = &styles.get_invalid();
let valid = &styles.get_valid();
let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
let mut suggestions = vec![];
if suggested_trailing_arg {
let mut styled_suggestion = StyledStr::new();
let _ = write!(
styled_suggestion,
"to pass '{invalid}{subcmd}{invalid:#}' as a value, use '{valid}{name} -- {subcmd}{valid:#}'",
);
suggestions.push(styled_suggestion);
}
err = err.extend_context_unchecked([
(ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
(
ContextKind::SuggestedSubcommand,
ContextValue::Strings(did_you_mean),
),
(
ContextKind::Suggested,
ContextValue::StyledStrs(suggestions),
),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn unrecognized_subcommand(
cmd: &Command,
subcmd: String,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([(
ContextKind::InvalidSubcommand,
ContextValue::String(subcmd),
)]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn missing_required_argument(
cmd: &Command,
required: Vec<String>,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::MissingRequiredArgument).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([(
ContextKind::InvalidArg,
ContextValue::Strings(required),
)]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn missing_subcommand(
cmd: &Command,
parent: String,
available: Vec<String>,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidSubcommand, ContextValue::String(parent)),
(
ContextKind::ValidSubcommand,
ContextValue::Strings(available),
),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn invalid_utf8(cmd: &Command, usage: Option<StyledStr>) -> Self {
let mut err = Self::new(ErrorKind::InvalidUtf8).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn too_many_values(
cmd: &Command,
val: String,
arg: String,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::TooManyValues).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::InvalidValue, ContextValue::String(val)),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn too_few_values(
cmd: &Command,
arg: String,
min_vals: usize,
curr_vals: usize,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::TooFewValues).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(
ContextKind::MinValues,
ContextValue::Number(min_vals as isize),
),
(
ContextKind::ActualNumValues,
ContextValue::Number(curr_vals as isize),
),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn value_validation(
arg: String,
val: String,
err: Box<dyn error::Error + Send + Sync>,
) -> Self {
let mut err = Self::new(ErrorKind::ValueValidation).set_source(err);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::InvalidValue, ContextValue::String(val)),
]);
}
err
}
pub(crate) fn wrong_number_of_values(
cmd: &Command,
arg: String,
num_vals: usize,
curr_vals: usize,
usage: Option<StyledStr>,
) -> Self {
let mut err = Self::new(ErrorKind::WrongNumberOfValues).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(
ContextKind::ExpectedNumValues,
ContextValue::Number(num_vals as isize),
),
(
ContextKind::ActualNumValues,
ContextValue::Number(curr_vals as isize),
),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
pub(crate) fn unknown_argument(
cmd: &Command,
arg: String,
did_you_mean: Option<(String, Option<String>)>,
suggested_trailing_arg: bool,
usage: Option<StyledStr>,
) -> Self {
use std::fmt::Write as _;
let styles = cmd.get_styles();
let invalid = &styles.get_invalid();
let valid = &styles.get_valid();
let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
let mut suggestions = vec![];
if suggested_trailing_arg {
let mut styled_suggestion = StyledStr::new();
let _ = write!(
styled_suggestion,
"to pass '{invalid}{arg}{invalid:#}' as a value, use '{valid}-- {arg}{valid:#}'",
);
suggestions.push(styled_suggestion);
}
err = err
.extend_context_unchecked([(ContextKind::InvalidArg, ContextValue::String(arg))]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
match did_you_mean {
Some((flag, Some(sub))) => {
let mut styled_suggestion = StyledStr::new();
let _ = write!(styled_suggestion, "'{valid}{sub} {flag}{valid:#}' exists",);
suggestions.push(styled_suggestion);
}
Some((flag, None)) => {
err = err.insert_context_unchecked(
ContextKind::SuggestedArg,
ContextValue::String(flag),
);
}
None => {}
}
if !suggestions.is_empty() {
err = err.insert_context_unchecked(
ContextKind::Suggested,
ContextValue::StyledStrs(suggestions),
);
}
}
err
}
pub(crate) fn unnecessary_double_dash(
cmd: &Command,
arg: String,
usage: Option<StyledStr>,
) -> Self {
use std::fmt::Write as _;
let styles = cmd.get_styles();
let invalid = &styles.get_invalid();
let valid = &styles.get_valid();
let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
#[cfg(feature = "error-context")]
{
let mut styled_suggestion = StyledStr::new();
let _ = write!(
styled_suggestion,
"subcommand '{valid}{arg}{valid:#}' exists; to use it, remove the '{invalid}--{invalid:#}' before it",
);
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)),
(
ContextKind::Suggested,
ContextValue::StyledStrs(vec![styled_suggestion]),
),
]);
if let Some(usage) = usage {
err = err
.insert_context_unchecked(ContextKind::Usage, ContextValue::StyledStr(usage));
}
}
err
}
fn formatted(&self) -> Cow<'_, StyledStr> {
if let Some(message) = self.inner.message.as_ref() {
message.formatted(&self.inner.styles)
} else {
let styled = F::format_error(self);
Cow::Owned(styled)
}
}
}
impl<F: ErrorFormatter> From<io::Error> for Error<F> {
fn from(e: io::Error) -> Self {
Self::raw(ErrorKind::Io, e)
}
}
impl<F: ErrorFormatter> From<fmt::Error> for Error<F> {
fn from(e: fmt::Error) -> Self {
Self::raw(ErrorKind::Format, e)
}
}
impl<F: ErrorFormatter> Debug for Error<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
self.inner.fmt(f)
}
}
impl<F: ErrorFormatter> error::Error for Error<F> {
#[allow(trivial_casts)]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.inner.source.as_ref().map(|e| e.as_ref() as _)
}
}
impl<F: ErrorFormatter> Display for Error<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// Assuming `self.message` already has a trailing newline, from `try_help` or similar
ok!(write!(f, "{}", self.formatted()));
if let Some(backtrace) = self.inner.backtrace.as_ref() {
ok!(writeln!(f));
ok!(writeln!(f, "Backtrace:"));
ok!(writeln!(f, "{backtrace}"));
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub(crate) enum Message {
Raw(String),
Formatted(StyledStr),
}
impl Message {
fn format(&mut self, cmd: &Command, usage: Option<StyledStr>) {
match self {
Self::Raw(s) => {
let mut message = String::new();
std::mem::swap(s, &mut message);
let styled = format::format_error_message(
&message,
cmd.get_styles(),
Some(cmd),
usage.as_ref(),
);
*self = Self::Formatted(styled);
}
Self::Formatted(_) => {}
}
}
fn formatted(&self, styles: &Styles) -> Cow<'_, StyledStr> {
match self {
Self::Raw(s) => {
let styled = format::format_error_message(s, styles, None, None);
Cow::Owned(styled)
}
Self::Formatted(s) => Cow::Borrowed(s),
}
}
}
impl From<String> for Message {
fn from(inner: String) -> Self {
Self::Raw(inner)
}
}
impl From<StyledStr> for Message {
fn from(inner: StyledStr) -> Self {
Self::Formatted(inner)
}
}
#[cfg(feature = "debug")]
#[derive(Debug)]
struct Backtrace(backtrace::Backtrace);
#[cfg(feature = "debug")]
impl Backtrace {
fn new() -> Option<Self> {
Some(Self(backtrace::Backtrace::new()))
}
}
#[cfg(feature = "debug")]
impl Display for Backtrace {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// `backtrace::Backtrace` uses `Debug` instead of `Display`
write!(f, "{:?}", self.0)
}
}
#[cfg(not(feature = "debug"))]
#[derive(Debug)]
struct Backtrace;
#[cfg(not(feature = "debug"))]
impl Backtrace {
fn new() -> Option<Self> {
None
}
}
#[cfg(not(feature = "debug"))]
impl Display for Backtrace {
fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result {
Ok(())
}
}
#[test]
fn check_auto_traits() {
static_assertions::assert_impl_all!(Error: Send, Sync, Unpin);
}

View File

@@ -0,0 +1,53 @@
// Copyright ⓒ 2015-2016 Kevin B. Knapp and [`clap-rs` contributors](https://github.com/clap-rs/clap/graphs/contributors).
// Licensed under the MIT license
// (see LICENSE or <http://opensource.org/licenses/MIT>) All files in the project carrying such
// notice may not be copied, modified, or distributed except according to those terms.
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/clap-rs/clap/master/assets/clap.png")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(clippy::print_stderr)]
#![warn(clippy::print_stdout)]
#[cfg(not(feature = "std"))]
compile_error!("`std` feature is currently required to build `clap`");
pub use crate::builder::ArgAction;
pub use crate::builder::Command;
pub use crate::builder::ValueHint;
pub use crate::builder::{Arg, ArgGroup};
pub use crate::parser::ArgMatches;
pub use crate::util::Id;
pub use crate::util::color::ColorChoice;
/// Command Line Argument Parser Error
///
/// See [`Command::error`] to create an error.
///
/// [`Command::error`]: crate::Command::error
pub type Error = error::Error<error::DefaultFormatter>;
pub use crate::derive::{Args, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
#[macro_use]
#[allow(missing_docs)]
mod macros;
mod derive;
pub mod builder;
pub mod error;
pub mod parser;
mod mkeymap;
mod output;
mod util;
const INTERNAL_ERROR_MSG: &str = "Fatal internal error. Please consider filing a bug \
report at https://github.com/clap-rs/clap/issues";
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;

View File

@@ -0,0 +1,603 @@
/// Allows you to pull the version from your Cargo.toml at compile time as
/// `MAJOR.MINOR.PATCH_PKGVERSION_PRE`
///
/// # Examples
///
/// ```no_run
/// # use clap_builder as clap;
/// # use clap::crate_version;
/// # use clap::Command;
/// let m = Command::new("cmd")
/// .version(crate_version!())
/// .get_matches();
/// ```
#[cfg(feature = "cargo")]
#[macro_export]
macro_rules! crate_version {
() => {
env!("CARGO_PKG_VERSION")
};
}
/// Allows you to pull the authors for the command from your Cargo.toml at
/// compile time in the form:
/// `"author1 lastname <author1@example.com>:author2 lastname <author2@example.com>"`
///
/// You can replace the colons with a custom separator by supplying a
/// replacement string, so, for example,
/// `crate_authors!(",\n")` would become
/// `"author1 lastname <author1@example.com>,\nauthor2 lastname <author2@example.com>,\nauthor3 lastname <author3@example.com>"`
///
/// # Examples
///
/// ```no_run
/// # use clap_builder as clap;
/// # use clap::crate_authors;
/// # use clap::Command;
/// let m = Command::new("cmd")
/// .author(crate_authors!("\n"))
/// .get_matches();
/// ```
#[cfg(feature = "cargo")]
#[macro_export]
macro_rules! crate_authors {
($sep:expr) => {{
static AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
if AUTHORS.contains(':') {
static CACHED: std::sync::OnceLock<String> = std::sync::OnceLock::new();
let s = CACHED.get_or_init(|| AUTHORS.replace(':', $sep));
let s: &'static str = &*s;
s
} else {
AUTHORS
}
}};
() => {
env!("CARGO_PKG_AUTHORS")
};
}
/// Allows you to pull the description from your Cargo.toml at compile time.
///
/// # Examples
///
/// ```no_run
/// # use clap_builder as clap;
/// # use clap::crate_description;
/// # use clap::Command;
/// let m = Command::new("cmd")
/// .about(crate_description!())
/// .get_matches();
/// ```
#[cfg(feature = "cargo")]
#[macro_export]
macro_rules! crate_description {
() => {
env!("CARGO_PKG_DESCRIPTION")
};
}
/// Allows you to pull the name from your Cargo.toml at compile time.
///
/// <div class="warning">
///
/// **NOTE:** This macro extracts the name from an environment variable `CARGO_PKG_NAME`.
/// When the crate name is set to something different from the package name,
/// use environment variables `CARGO_CRATE_NAME` or `CARGO_BIN_NAME`.
/// See [the Cargo Book](https://doc.rust-lang.org/cargo/reference/environment-variables.html)
/// for more information.
///
/// </div>
///
/// # Examples
///
/// ```no_run
/// # use clap_builder as clap;
/// # use clap::crate_name;
/// # use clap::Command;
/// let m = Command::new(crate_name!())
/// .get_matches();
/// ```
#[cfg(feature = "cargo")]
#[macro_export]
macro_rules! crate_name {
() => {
env!("CARGO_PKG_NAME")
};
}
/// Allows you to build the `Command` instance from your Cargo.toml at compile time.
///
/// <div class="warning">
///
/// **NOTE:** Changing the values in your `Cargo.toml` does not trigger a re-build automatically,
/// and therefore won't change the generated output until you recompile.
///
/// In some cases you can "trick" the compiler into triggering a rebuild when your
/// `Cargo.toml` is changed by including this in your `src/main.rs` file
/// `include_str!("../Cargo.toml");`
///
/// </div>
///
/// # Examples
///
/// ```no_run
/// # use clap_builder as clap;
/// # use clap::command;
/// let m = command!().get_matches();
/// ```
#[cfg(feature = "cargo")]
#[macro_export]
macro_rules! command {
() => {{ $crate::command!($crate::crate_name!()) }};
($name:expr) => {{
let mut cmd = $crate::Command::new($name).version($crate::crate_version!());
let author = $crate::crate_authors!();
if !author.is_empty() {
cmd = cmd.author(author)
}
let about = $crate::crate_description!();
if !about.is_empty() {
cmd = cmd.about(about)
}
cmd
}};
}
/// Requires `cargo` feature flag to be enabled.
#[cfg(not(feature = "cargo"))]
#[macro_export]
macro_rules! command {
() => {{
compile_error!("`cargo` feature flag is required");
}};
($name:expr) => {{
compile_error!("`cargo` feature flag is required");
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! arg_impl {
( @string $val:ident ) => {
stringify!($val)
};
( @string $val:literal ) => {{
let ident_or_string_literal: &str = $val;
ident_or_string_literal
}};
( @string $val:tt ) => {
::std::compile_error!("Only identifiers or string literals supported");
};
( @string ) => {
None
};
( @char $val:ident ) => {{
let ident_or_char_literal = stringify!($val);
debug_assert_eq!(
ident_or_char_literal.len(),
1,
"Single-letter identifier expected, got {ident_or_char_literal}",
);
ident_or_char_literal.chars().next().unwrap()
}};
( @char $val:literal ) => {{
let ident_or_char_literal: char = $val;
ident_or_char_literal
}};
( @char ) => {{
None
}};
(
@arg
($arg:expr)
--$long:ident
$($tail:tt)*
) => {{
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
let mut arg = $arg;
let long = $crate::arg_impl! { @string $long };
if arg.get_id() == "" {
arg = arg.id(long);
}
let action = $crate::ArgAction::SetTrue;
let arg = arg
.long(long)
.action(action);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
--$long:literal
$($tail:tt)*
) => {{
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
let mut arg = $arg;
let long = $crate::arg_impl! { @string $long };
if arg.get_id() == "" {
arg = arg.id(long);
}
let action = $crate::ArgAction::SetTrue;
let arg = arg
.long(long)
.action(action);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
-$short:ident
$($tail:tt)*
) => {{
debug_assert_eq!($arg.get_long(), None, "Short flags should precede long flags");
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
let action = $crate::ArgAction::SetTrue;
let arg = $arg
.short($crate::arg_impl! { @char $short })
.action(action);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
-$short:literal
$($tail:tt)*
) => {{
debug_assert_eq!($arg.get_long(), None, "Short flags should precede long flags");
debug_assert_eq!($arg.get_value_names(), None, "Flags should precede values");
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
let action = $crate::ArgAction::SetTrue;
let arg = $arg
.short($crate::arg_impl! { @char $short })
.action(action);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
<$value_name:ident>
$($tail:tt)*
) => {{
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
debug_assert_eq!($arg.get_value_names(), None, "Multiple values not yet supported");
let mut arg = $arg;
if arg.get_long().is_none() && arg.get_short().is_none() {
arg = arg.required(true);
}
let value_name = $crate::arg_impl! { @string $value_name };
if arg.get_id() == "" {
arg = arg.id(value_name);
}
let arg = arg
.value_name(value_name)
.action($crate::ArgAction::Set);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
<$value_name:literal>
$($tail:tt)*
) => {{
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
debug_assert_eq!($arg.get_value_names(), None, "Multiple values not yet supported");
let mut arg = $arg;
if arg.get_long().is_none() && arg.get_short().is_none() {
arg = arg.required(true);
}
let value_name = $crate::arg_impl! { @string $value_name };
if arg.get_id() == "" {
arg = arg.id(value_name);
}
let arg = arg
.value_name(value_name)
.action($crate::ArgAction::Set);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
[$value_name:ident]
$($tail:tt)*
) => {{
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
debug_assert_eq!($arg.get_value_names(), None, "Multiple values not yet supported");
let mut arg = $arg;
if arg.get_long().is_none() && arg.get_short().is_none() {
arg = arg.required(false);
} else {
arg = arg.num_args(0..=1);
}
let value_name = $crate::arg_impl! { @string $value_name };
if arg.get_id() == "" {
arg = arg.id(value_name);
}
let arg = arg
.value_name(value_name)
.action($crate::ArgAction::Set);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
[$value_name:literal]
$($tail:tt)*
) => {{
debug_assert!(!matches!($arg.get_action(), $crate::ArgAction::Append), "Flags should precede `...`");
debug_assert_eq!($arg.get_value_names(), None, "Multiple values not yet supported");
let mut arg = $arg;
if arg.get_long().is_none() && arg.get_short().is_none() {
arg = arg.required(false);
} else {
arg = arg.num_args(0..=1);
}
let value_name = $crate::arg_impl! { @string $value_name };
if arg.get_id() == "" {
arg = arg.id(value_name);
}
let arg = arg
.value_name(value_name)
.action($crate::ArgAction::Set);
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
...
$($tail:tt)*
) => {{
let arg = match $arg.get_action() {
$crate::ArgAction::Set => {
if $arg.get_long().is_none() && $arg.get_short().is_none() {
$arg.num_args(1..)
// Allow collecting arguments interleaved with flags
.action($crate::ArgAction::Append)
} else {
$arg.action($crate::ArgAction::Append)
}
},
$crate::ArgAction::SetTrue | $crate::ArgAction::Help | $crate::ArgAction::Version => {
$arg.action($crate::ArgAction::Count)
}
action => {
panic!("Unexpected action {action:?}")
}
};
let arg = $crate::arg_impl! {
@arg (arg) $($tail)*
};
arg
}};
(
@arg
($arg:expr)
$help:literal
) => {{
$arg.help($help)
}};
(
@arg
($arg:expr)
) => {{
$arg
}};
}
/// Create an [`Arg`] from a usage string.
///
/// Allows creation of basic settings for the [`Arg`].
///
/// <div class="warning">
///
/// **NOTE**: Not all settings may be set using the usage string method. Some properties are
/// only available via the builder pattern.
///
/// </div>
///
/// # Syntax
///
/// Usage strings typically following the form:
///
/// ```notrust
/// [explicit name] [short] [long] [value names] [...] [help string]
/// ```
///
/// ### Explicit Name
///
/// The name may be either a bare-word or a string, followed by a `:`, like `name:` or
/// `"name":`.
///
/// *Note:* This is an optional field, if it's omitted the argument will use one of the additional
/// fields as the name using the following priority order:
///
/// 1. Explicit Name
/// 2. Long
/// 3. Value Name
///
/// See [`Arg::id`][crate::Arg::id].
///
/// ### Short
///
/// A short flag is a `-` followed by either a bare-character or quoted character, like `-f` or
/// `-'f'`.
///
/// See [`Arg::short`][crate::Arg::short].
///
/// ### Long
///
/// A long flag is a `--` followed by either a bare-word or a string, like `--foo` or
/// `--"foo"`.
///
/// <div class="warning">
///
/// **NOTE:** Dashes in the long name (e.g. `--foo-bar`) is not supported and quoting is required
/// (e.g. `--"foo-bar"`).
///
/// </div>
///
/// See [`Arg::long`][crate::Arg::long].
///
/// ### Values (Value Notation)
///
/// This is set by placing bare-word between:
/// - `[]` like `[FOO]`
/// - Positional argument: optional
/// - Named argument: optional value
/// - `<>` like `<FOO>`: required
///
/// See [`Arg::value_name`][crate::Arg::value_name].
///
/// ### `...`
///
/// `...` (three consecutive dots/periods) specifies that this argument may occur multiple
/// times (not to be confused with multiple values per occurrence).
///
/// See [`ArgAction::Count`][crate::ArgAction::Count] and [`ArgAction::Append`][crate::ArgAction::Append].
///
/// ### Help String
///
/// The help string is denoted between a pair of double quotes `""` and may contain any
/// characters.
///
/// # Examples
///
/// ```rust
/// # use clap_builder as clap;
/// # use clap::{Command, Arg, arg};
/// let cmd = Command::new("prog")
/// .args(&[
/// arg!(--config <FILE> "a required file for the configuration and no short"),
/// arg!(-d --debug ... "turns on debugging information and allows multiples"),
/// arg!([input] "an optional input file to use")
/// ]);
///
/// let m = cmd.try_get_matches_from(["prog", "--config", "file.toml"]).unwrap();
/// assert_eq!(m.get_one::<String>("config").unwrap(), "file.toml");
/// assert_eq!(*m.get_one::<u8>("debug").unwrap(), 0);
/// assert_eq!(m.get_one::<String>("input"), None);
/// ```
/// [`Arg`]: crate::Arg
#[macro_export]
macro_rules! arg {
( -$($tail:tt)+ ) => {{
let arg = $crate::Arg::default();
let arg = $crate::arg_impl! {
@arg (arg) -$($tail)+
};
debug_assert_ne!(arg.get_id(), "", "Without a value or long flag, the `name:` prefix is required");
arg
}};
( $name:ident: $($tail:tt)+ ) => {{
let arg = $crate::Arg::new($crate::arg_impl! { @string $name });
let arg = $crate::arg_impl! {
@arg (arg) $($tail)+
};
arg
}};
( $name:literal: $($tail:tt)+ ) => {{
let arg = $crate::Arg::new($crate::arg_impl! { @string $name });
let arg = $crate::arg_impl! {
@arg (arg) $($tail)+
};
arg
}};
( $($tail:tt)+ ) => {{
let arg = $crate::Arg::default();
let arg = $crate::arg_impl! {
@arg (arg) $($tail)+
};
debug_assert_ne!(arg.get_id(), "", "Without a value or long flag, the `name:` prefix is required");
arg
}};
}
#[cfg(feature = "debug")]
macro_rules! debug {
($($arg:tt)*) => ({
use std::fmt::Write as _;
let hint = anstyle::Style::new().dimmed();
let module_path = module_path!();
let body = format!($($arg)*);
let mut styled = $crate::builder::StyledStr::new();
let _ = write!(styled, "{hint}[{module_path:>28}]{body}{hint:#}\n");
let color = $crate::output::fmt::Colorizer::new($crate::output::fmt::Stream::Stderr, $crate::ColorChoice::Auto).with_content(styled);
let _ = color.print();
})
}
#[cfg(not(feature = "debug"))]
macro_rules! debug {
($($arg:tt)*) => {};
}
macro_rules! ok {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => {
return Err(err);
}
}
};
}
macro_rules! some {
($expr:expr) => {
match $expr {
Some(val) => val,
None => {
return None;
}
}
};
}

View File

@@ -0,0 +1,188 @@
use std::iter::Iterator;
use std::ops::Index;
use crate::Arg;
use crate::INTERNAL_ERROR_MSG;
use crate::builder::OsStr;
#[derive(PartialEq, Eq, Debug, Clone)]
pub(crate) struct Key {
key: KeyType,
index: usize,
}
#[derive(Default, PartialEq, Eq, Debug, Clone)]
pub(crate) struct MKeyMap {
/// All of the arguments.
args: Vec<Arg>,
// Cache part:
/// Will be set after `_build()`.
keys: Vec<Key>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) enum KeyType {
Short(char),
Long(OsStr),
Position(usize),
}
impl KeyType {
pub(crate) fn is_position(&self) -> bool {
matches!(self, Self::Position(_))
}
}
impl PartialEq<usize> for KeyType {
fn eq(&self, rhs: &usize) -> bool {
match self {
Self::Position(x) => x == rhs,
_ => false,
}
}
}
impl PartialEq<&str> for KeyType {
fn eq(&self, rhs: &&str) -> bool {
match self {
Self::Long(l) => l == rhs,
_ => false,
}
}
}
impl PartialEq<str> for KeyType {
fn eq(&self, rhs: &str) -> bool {
match self {
Self::Long(l) => l == rhs,
_ => false,
}
}
}
impl PartialEq<OsStr> for KeyType {
fn eq(&self, rhs: &OsStr) -> bool {
match self {
Self::Long(l) => l == rhs,
_ => false,
}
}
}
impl PartialEq<char> for KeyType {
fn eq(&self, rhs: &char) -> bool {
match self {
Self::Short(c) => c == rhs,
_ => false,
}
}
}
impl MKeyMap {
/// If any arg has corresponding key in this map, we can search the key with
/// `u64` (for positional argument), `char` (for short flag), `&str` and `OsString`
/// (for long flag)
pub(crate) fn contains<K>(&self, key: K) -> bool
where
KeyType: PartialEq<K>,
{
self.keys.iter().any(|x| x.key == key)
}
/// Push an argument in the map.
pub(crate) fn push(&mut self, new_arg: Arg) {
self.args.push(new_arg);
}
/// Find the arg have corresponding key in this map, we can search the key
/// with `u64` (for positional argument), `char` (for short flag), `&str` and
/// `OsString` (for long flag)
pub(crate) fn get<K: ?Sized>(&self, key: &K) -> Option<&Arg>
where
KeyType: PartialEq<K>,
{
self.keys
.iter()
.find(|k| &k.key == key)
.map(|k| &self.args[k.index])
}
/// Return iterators of all keys.
pub(crate) fn keys(&self) -> impl Iterator<Item = &KeyType> {
self.keys.iter().map(|x| &x.key)
}
/// Return iterators of all args.
pub(crate) fn args(&self) -> impl Iterator<Item = &Arg> {
self.args.iter()
}
/// Return mutable iterators of all args.
pub(crate) fn args_mut(&mut self) -> impl Iterator<Item = &mut Arg> {
self.args.iter_mut()
}
/// Mutate every argument.
pub(crate) fn mut_args<F>(&mut self, f: F)
where
F: FnMut(Arg) -> Arg,
{
let mut args = std::mem::take(&mut self.args);
self.args.extend(args.drain(..).map(f));
}
/// We need a lazy build here since some we may change args after creating
/// the map, you can checkout who uses `args_mut`.
pub(crate) fn _build(&mut self) {
// There will be at least as many keys as args, so that is a good starting point
self.keys.reserve(self.args.len());
for (i, arg) in self.args.iter().enumerate() {
append_keys(&mut self.keys, arg, i);
}
}
/// Remove an arg in the graph by Id, usually used by `mut_arg`. Return
/// `Some(arg)` if removed.
pub(crate) fn remove_by_name(&mut self, name: &str) -> Option<Arg> {
self.args
.iter()
.position(|arg| arg.id == name)
// since it's a cold function, using this wouldn't hurt much
.map(|i| self.args.remove(i))
}
}
impl Index<&'_ KeyType> for MKeyMap {
type Output = Arg;
fn index(&self, key: &KeyType) -> &Self::Output {
self.get(key).expect(INTERNAL_ERROR_MSG)
}
}
/// Generate key types for an specific Arg.
fn append_keys(keys: &mut Vec<Key>, arg: &Arg, index: usize) {
if let Some(pos_index) = arg.index {
let key = KeyType::Position(pos_index);
keys.push(Key { key, index });
} else {
if let Some(short) = arg.short {
let key = KeyType::Short(short);
keys.push(Key { key, index });
}
if let Some(long) = arg.long.clone() {
let key = KeyType::Long(long.into());
keys.push(Key { key, index });
}
for (short, _) in arg.short_aliases.iter() {
let key = KeyType::Short(*short);
keys.push(Key { key, index });
}
for (long, _) in arg.aliases.iter() {
let key = KeyType::Long(long.into());
keys.push(Key { key, index });
}
}
}

View File

@@ -0,0 +1,83 @@
use crate::builder::StyledStr;
use crate::util::color::ColorChoice;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Stream {
Stdout,
Stderr,
}
#[derive(Clone, Debug)]
pub(crate) struct Colorizer {
stream: Stream,
#[allow(unused)]
color_when: ColorChoice,
content: StyledStr,
}
impl Colorizer {
pub(crate) fn new(stream: Stream, color_when: ColorChoice) -> Self {
Self {
stream,
color_when,
content: Default::default(),
}
}
pub(crate) fn with_content(mut self, content: StyledStr) -> Self {
self.content = content;
self
}
}
/// Printing methods.
impl Colorizer {
#[cfg(feature = "color")]
pub(crate) fn print(&self) -> std::io::Result<()> {
let color_when = match self.color_when {
ColorChoice::Always => anstream::ColorChoice::Always,
ColorChoice::Auto => anstream::ColorChoice::Auto,
ColorChoice::Never => anstream::ColorChoice::Never,
};
let mut stdout;
let mut stderr;
let writer: &mut dyn std::io::Write = match self.stream {
Stream::Stderr => {
stderr = anstream::AutoStream::new(std::io::stderr().lock(), color_when);
&mut stderr
}
Stream::Stdout => {
stdout = anstream::AutoStream::new(std::io::stdout().lock(), color_when);
&mut stdout
}
};
self.content.write_to(writer)
}
#[cfg(not(feature = "color"))]
pub(crate) fn print(&self) -> std::io::Result<()> {
// [e]println can't be used here because it panics
// if something went wrong. We don't want that.
match self.stream {
Stream::Stdout => {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
self.content.write_to(&mut stdout)
}
Stream::Stderr => {
let stderr = std::io::stderr();
let mut stderr = stderr.lock();
self.content.write_to(&mut stderr)
}
}
}
}
/// Color-unaware printing. Never uses coloring.
impl std::fmt::Display for Colorizer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.content.fmt(f)
}
}

View File

@@ -0,0 +1,39 @@
#![cfg_attr(not(feature = "help"), allow(unused_variables))]
// Internal
use crate::builder::Command;
use crate::builder::StyledStr;
use crate::output::Usage;
/// Writes the parser help to the wrapped stream.
pub(crate) fn write_help(writer: &mut StyledStr, cmd: &Command, usage: &Usage<'_>, use_long: bool) {
debug!("write_help");
if let Some(h) = cmd.get_override_help() {
writer.push_styled(h);
} else {
#[cfg(feature = "help")]
{
use super::AutoHelp;
use super::HelpTemplate;
if let Some(tmpl) = cmd.get_help_template() {
HelpTemplate::new(writer, cmd, usage, use_long)
.write_templated_help(tmpl.as_styled_str());
} else {
AutoHelp::new(writer, cmd, usage, use_long).write_help();
}
}
#[cfg(not(feature = "help"))]
{
debug!("write_help: no help, `Command::override_help` and `help` is missing");
}
}
// Remove any lines from unused sections
writer.trim_start_lines();
// Remove any whitespace caused by book keeping
writer.trim_end();
// Ensure there is still a trailing newline
writer.push_str("\n");
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
mod help;
#[cfg(feature = "help")]
mod help_template;
mod usage;
pub(crate) mod fmt;
#[cfg(feature = "help")]
pub(crate) mod textwrap;
pub(crate) use self::help::write_help;
#[cfg(feature = "help")]
pub(crate) use self::help_template::AutoHelp;
#[cfg(feature = "help")]
pub(crate) use self::help_template::HelpTemplate;
#[cfg(feature = "help")]
pub(crate) use self::textwrap::core::display_width;
#[cfg(feature = "help")]
pub(crate) use self::textwrap::wrap;
pub(crate) use self::usage::Usage;
pub(crate) const TAB: &str = " ";
#[cfg(feature = "help")]
pub(crate) const TAB_WIDTH: usize = TAB.len();

View File

@@ -0,0 +1,158 @@
/// Compute the display width of `text`
///
/// # Examples
///
/// **Note:** When the `unicode` Cargo feature is disabled, all characters are presumed to take up
/// 1 width. With the feature enabled, function will correctly deal with [combining characters] in
/// their decomposed form (see [Unicode equivalence]).
///
/// An example of a decomposed character is “é”, which can be decomposed into: “e” followed by a
/// combining acute accent: “◌́”. Without the `unicode` Cargo feature, every `char` has a width of
/// 1. This includes the combining accent:
///
/// ## Emojis and CJK Characters
///
/// Characters such as emojis and [CJK characters] used in the
/// Chinese, Japanese, and Korean languages are seen as double-width,
/// even if the `unicode-width` feature is disabled:
///
/// # Limitations
///
/// The displayed width of a string cannot always be computed from the
/// string alone. This is because the width depends on the rendering
/// engine used. This is particularly visible with [emoji modifier
/// sequences] where a base emoji is modified with, e.g., skin tone or
/// hair color modifiers. It is up to the rendering engine to detect
/// this and to produce a suitable emoji.
///
/// A simple example is “❤️”, which consists of “❤” (U+2764: Black
/// Heart Symbol) followed by U+FE0F (Variation Selector-16). By
/// itself, “❤” is a black heart, but if you follow it with the
/// variant selector, you may get a wider red heart.
///
/// A more complex example would be “👨‍🦰” which should depict a man
/// with red hair. Here the computed width is too large — and the
/// width differs depending on the use of the `unicode-width` feature:
///
/// This happens because the grapheme consists of three code points:
/// “👨” (U+1F468: Man), Zero Width Joiner (U+200D), and “🦰”
/// (U+1F9B0: Red Hair). You can see them above in the test. With
/// `unicode-width` enabled, the ZWJ is correctly seen as having zero
/// width, without it is counted as a double-width character.
///
/// ## Terminal Support
///
/// Modern browsers typically do a great job at combining characters
/// as shown above, but terminals often struggle more. As an example,
/// Gnome Terminal version 3.38.1, shows “❤️” as a big red heart, but
/// shows "👨‍🦰" as “👨🦰”.
///
/// [combining characters]: https://en.wikipedia.org/wiki/Combining_character
/// [Unicode equivalence]: https://en.wikipedia.org/wiki/Unicode_equivalence
/// [CJK characters]: https://en.wikipedia.org/wiki/CJK_characters
/// [emoji modifier sequences]: https://unicode.org/emoji/charts/full-emoji-modifiers.html
#[inline(never)]
pub(crate) fn display_width(text: &str) -> usize {
let mut width = 0;
let mut control_sequence = false;
let control_terminate: char = 'm';
for ch in text.chars() {
if ch.is_ascii_control() {
control_sequence = true;
} else if control_sequence && ch == control_terminate {
control_sequence = false;
continue;
}
if !control_sequence {
width += ch_width(ch);
}
}
width
}
#[cfg(feature = "unicode")]
fn ch_width(ch: char) -> usize {
unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
}
#[cfg(not(feature = "unicode"))]
fn ch_width(_: char) -> usize {
1
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "unicode")]
use unicode_width::UnicodeWidthChar;
#[test]
fn emojis_have_correct_width() {
use unic_emoji_char::is_emoji;
// Emojis in the Basic Latin (ASCII) and Latin-1 Supplement
// blocks all have a width of 1 column. This includes
// characters such as '#' and '©'.
for ch in '\u{1}'..'\u{FF}' {
if is_emoji(ch) {
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
#[cfg(feature = "unicode")]
assert_eq!(ch.width().unwrap(), 1, "char: {desc}");
#[cfg(not(feature = "unicode"))]
assert_eq!(ch_width(ch), 1, "char: {desc}");
}
}
// Emojis in the remaining blocks of the Basic Multilingual
// Plane (BMP), in the Supplementary Multilingual Plane (SMP),
// and in the Supplementary Ideographic Plane (SIP), are all 1
// or 2 columns wide when unicode-width is used, and always 2
// columns wide otherwise. This includes all of our favorite
// emojis such as 😊.
for ch in '\u{FF}'..'\u{2FFFF}' {
if is_emoji(ch) {
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
#[cfg(feature = "unicode")]
assert!(ch.width().unwrap() <= 2, "char: {desc}");
#[cfg(not(feature = "unicode"))]
assert_eq!(ch_width(ch), 1, "char: {desc}");
}
}
// The remaining planes contain almost no assigned code points
// and thus also no emojis.
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_works() {
assert_eq!("Café Plain".len(), 11); // “é” is two bytes
assert_eq!(display_width("Café Plain"), 10);
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_narrow_emojis() {
assert_eq!(display_width("⁉"), 1);
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_narrow_emojis_variant_selector() {
assert_eq!(display_width("⁉\u{fe0f}"), 1);
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_emojis() {
assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
}
}

View File

@@ -0,0 +1,122 @@
//! Fork of `textwrap` crate
//!
//! Benefits of forking:
//! - Pull in only what we need rather than relying on the compiler to remove what we don't need
//! - `LineWrapper` is able to incrementally wrap which will help with `StyledStr`
pub(crate) mod core;
#[cfg(feature = "wrap_help")]
pub(crate) mod word_separators;
#[cfg(feature = "wrap_help")]
pub(crate) mod wrap_algorithms;
#[cfg(feature = "wrap_help")]
pub(crate) fn wrap(content: &str, hard_width: usize) -> String {
let mut wrapper = wrap_algorithms::LineWrapper::new(hard_width);
let mut total = Vec::new();
for line in content.split_inclusive('\n') {
wrapper.reset();
let line = word_separators::find_words_ascii_space(line).collect::<Vec<_>>();
total.extend(wrapper.wrap(line));
}
total.join("")
}
#[cfg(not(feature = "wrap_help"))]
pub(crate) fn wrap(content: &str, _hard_width: usize) -> String {
content.to_owned()
}
#[cfg(test)]
#[cfg(feature = "wrap_help")]
mod test {
/// Compatibility shim to keep textwrap's tests
fn wrap(content: &str, hard_width: usize) -> Vec<String> {
super::wrap(content, hard_width)
.trim_end()
.split('\n')
.map(|s| s.to_owned())
.collect::<Vec<_>>()
}
#[test]
fn no_wrap() {
assert_eq!(wrap("foo", 10), vec!["foo"]);
}
#[test]
fn wrap_simple() {
assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
}
#[test]
fn to_be_or_not() {
assert_eq!(
wrap("To be, or not to be, that is the question.", 10),
vec!["To be, or", "not to be,", "that is", "the", "question."]
);
}
#[test]
fn multiple_words_on_first_line() {
assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
}
#[test]
fn long_word() {
assert_eq!(wrap("foo", 0), vec!["foo"]);
}
#[test]
fn long_words() {
assert_eq!(wrap("foo bar", 0), vec!["foo", "bar"]);
}
#[test]
fn max_width() {
assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]);
let text = "Hello there! This is some English text. \
It should not be wrapped given the extents below.";
assert_eq!(wrap(text, usize::MAX), vec![text]);
}
#[test]
fn leading_whitespace() {
assert_eq!(wrap(" foo bar", 6), vec![" foo", " bar"]);
}
#[test]
fn leading_whitespace_empty_first_line() {
// If there is no space for the first word, the first line
// will be empty. This is because the string is split into
// words like [" ", "foobar ", "baz"], which puts "foobar " on
// the second line. We never output trailing whitespace
assert_eq!(wrap(" foobar baz", 6), vec![" foobar", " baz"]);
}
#[test]
fn trailing_whitespace() {
// Whitespace is only significant inside a line. After a line
// gets too long and is broken, the first word starts in
// column zero and is not indented.
assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]);
}
#[test]
fn issue_99() {
// We did not reset the in_whitespace flag correctly and did
// not handle single-character words after a line break.
assert_eq!(
wrap("aaabbbccc x yyyzzzwww", 9),
vec!["aaabbbccc", "x", "yyyzzzwww"]
);
}
#[test]
fn issue_129() {
// The dash is an em-dash which takes up four bytes. We used
// to panic since we tried to index into the character.
assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]);
}
}

View File

@@ -0,0 +1,92 @@
pub(crate) fn find_words_ascii_space(line: &str) -> impl Iterator<Item = &'_ str> + '_ {
let mut start = 0;
let mut in_whitespace = false;
let mut char_indices = line.char_indices();
std::iter::from_fn(move || {
for (idx, ch) in char_indices.by_ref() {
let next_whitespace = ch == ' ';
if in_whitespace && !next_whitespace {
let word = &line[start..idx];
start = idx;
in_whitespace = next_whitespace;
return Some(word);
}
in_whitespace = next_whitespace;
}
if start < line.len() {
let word = &line[start..];
start = line.len();
return Some(word);
}
None
})
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! test_find_words {
($ascii_name:ident,
$([ $line:expr, $ascii_words:expr ]),+) => {
#[test]
fn $ascii_name() {
$(
let expected_words: Vec<&str> = $ascii_words.to_vec();
let actual_words = find_words_ascii_space($line)
.collect::<Vec<_>>();
assert_eq!(actual_words, expected_words, "Line: {:?}", $line);
)+
}
};
}
test_find_words!(ascii_space_empty, ["", []]);
test_find_words!(ascii_single_word, ["foo", ["foo"]]);
test_find_words!(ascii_two_words, ["foo bar", ["foo ", "bar"]]);
test_find_words!(
ascii_multiple_words,
["foo bar", ["foo ", "bar"]],
["x y z", ["x ", "y ", "z"]]
);
test_find_words!(ascii_only_whitespace, [" ", [" "]], [" ", [" "]]);
test_find_words!(
ascii_inter_word_whitespace,
["foo bar", ["foo ", "bar"]]
);
test_find_words!(ascii_trailing_whitespace, ["foo ", ["foo "]]);
test_find_words!(ascii_leading_whitespace, [" foo", [" ", "foo"]]);
test_find_words!(
ascii_multi_column_char,
["\u{1f920}", ["\u{1f920}"]] // cowboy emoji 🤠
);
test_find_words!(
ascii_hyphens,
["foo-bar", ["foo-bar"]],
["foo- bar", ["foo- ", "bar"]],
["foo - bar", ["foo ", "- ", "bar"]],
["foo -bar", ["foo ", "-bar"]]
);
test_find_words!(ascii_newline, ["foo\nbar", ["foo\nbar"]]);
test_find_words!(ascii_tab, ["foo\tbar", ["foo\tbar"]]);
test_find_words!(
ascii_non_breaking_space,
["foo\u{00A0}bar", ["foo\u{00A0}bar"]]
);
}

View File

@@ -0,0 +1,68 @@
use super::core::display_width;
#[derive(Debug)]
pub(crate) struct LineWrapper<'w> {
hard_width: usize,
line_width: usize,
indentation: Option<&'w str>,
}
impl<'w> LineWrapper<'w> {
pub(crate) fn new(hard_width: usize) -> Self {
Self {
hard_width,
line_width: 0,
indentation: None,
}
}
pub(crate) fn reset(&mut self) {
self.line_width = 0;
self.indentation = None;
}
pub(crate) fn wrap(&mut self, mut words: Vec<&'w str>) -> Vec<&'w str> {
let mut first_word = false;
if self.indentation.is_none() {
first_word = true;
if let Some(word) = words.first() {
if word.trim().is_empty() {
self.indentation = Some(*word);
} else {
self.indentation = Some("");
}
}
}
let mut i = 0;
while i < words.len() {
let word = &words[i];
let trimmed = word.trim_end();
let word_width = display_width(trimmed);
let trimmed_delta = word.len() - trimmed.len();
if first_word && 0 < word_width {
// Never try to wrap the first word
first_word = false;
} else if self.hard_width < self.line_width + word_width {
if 0 < i {
let prev = i - 1;
let trimmed = words[prev].trim_end();
words[prev] = trimmed;
}
self.line_width = 0;
words.insert(i, "\n");
i += 1;
if let Some(indentation) = self.indentation {
words.insert(i, indentation);
self.line_width += indentation.len();
i += 1;
}
}
self.line_width += word_width + trimmed_delta;
i += 1;
}
words
}
}

View File

@@ -0,0 +1,494 @@
#![cfg_attr(not(feature = "usage"), allow(unused_imports))]
#![cfg_attr(not(feature = "usage"), allow(unused_variables))]
#![cfg_attr(not(feature = "usage"), allow(clippy::manual_map))]
#![cfg_attr(not(feature = "usage"), allow(dead_code))]
// Internal
use crate::builder::ArgAction;
use crate::builder::StyledStr;
use crate::builder::Styles;
use crate::builder::{ArgPredicate, Command};
use crate::parser::ArgMatcher;
use crate::util::ChildGraph;
use crate::util::FlatSet;
use crate::util::Id;
static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND";
const USAGE_SEP: &str = "\n ";
pub(crate) struct Usage<'cmd> {
cmd: &'cmd Command,
styles: &'cmd Styles,
required: Option<&'cmd ChildGraph<Id>>,
}
impl<'cmd> Usage<'cmd> {
pub(crate) fn new(cmd: &'cmd Command) -> Self {
Usage {
cmd,
styles: cmd.get_styles(),
required: None,
}
}
pub(crate) fn required(mut self, required: &'cmd ChildGraph<Id>) -> Self {
self.required = Some(required);
self
}
// Creates a usage string for display. This happens just after all arguments were parsed, but before
// any subcommands have been parsed (so as to give subcommands their own usage recursively)
pub(crate) fn create_usage_with_title(&self, used: &[Id]) -> Option<StyledStr> {
debug!("Usage::create_usage_with_title");
use std::fmt::Write as _;
let mut styled = StyledStr::new();
let _ = write!(
styled,
"{}Usage:{} ",
self.styles.get_usage().render(),
self.styles.get_usage().render_reset()
);
if self.write_usage_no_title(&mut styled, used) {
styled.trim_end();
} else {
return None;
}
debug!("Usage::create_usage_with_title: usage={styled}");
Some(styled)
}
// Creates a usage string (*without title*) if one was not provided by the user manually.
pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> Option<StyledStr> {
debug!("Usage::create_usage_no_title");
let mut styled = StyledStr::new();
if self.write_usage_no_title(&mut styled, used) {
styled.trim_end();
debug!("Usage::create_usage_no_title: usage={styled}");
Some(styled)
} else {
None
}
}
// Creates a usage string (*without title*) if one was not provided by the user manually.
fn write_usage_no_title(&self, styled: &mut StyledStr, used: &[Id]) -> bool {
debug!("Usage::create_usage_no_title");
if let Some(u) = self.cmd.get_overridden_usage() {
styled.push_styled(u);
true
} else {
#[cfg(feature = "usage")]
{
if used.is_empty() {
self.write_help_usage(styled);
} else {
self.write_smart_usage(styled, used);
}
true
}
#[cfg(not(feature = "usage"))]
{
false
}
}
}
}
#[cfg(feature = "usage")]
impl Usage<'_> {
// Creates a usage string for display in help messages (i.e. not for errors)
fn write_help_usage(&self, styled: &mut StyledStr) {
debug!("Usage::write_help_usage");
use std::fmt::Write;
if self.cmd.has_visible_subcommands() && self.cmd.is_flatten_help_set() {
if !self.cmd.is_subcommand_required_set()
|| self.cmd.is_args_conflicts_with_subcommands_set()
{
self.write_arg_usage(styled, &[], true);
styled.trim_end();
let _ = write!(styled, "{USAGE_SEP}");
}
let mut cmd = self.cmd.clone();
cmd.build();
for (i, sub) in cmd
.get_subcommands()
.filter(|c| !c.is_hide_set())
.enumerate()
{
if i != 0 {
styled.trim_end();
let _ = write!(styled, "{USAGE_SEP}");
}
Usage::new(sub).write_usage_no_title(styled, &[]);
}
} else {
self.write_arg_usage(styled, &[], true);
self.write_subcommand_usage(styled);
}
}
// Creates a context aware usage string, or "smart usage" from currently used
// args, and requirements
fn write_smart_usage(&self, styled: &mut StyledStr, used: &[Id]) {
debug!("Usage::create_smart_usage");
use std::fmt::Write;
let placeholder = &self.styles.get_placeholder();
self.write_arg_usage(styled, used, true);
if self.cmd.is_subcommand_required_set() {
let value_name = self
.cmd
.get_subcommand_value_name()
.unwrap_or(DEFAULT_SUB_VALUE_NAME);
let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
}
}
fn write_arg_usage(&self, styled: &mut StyledStr, used: &[Id], incl_reqs: bool) {
debug!("Usage::write_arg_usage; incl_reqs={incl_reqs:?}");
use std::fmt::Write as _;
let literal = &self.styles.get_literal();
let placeholder = &self.styles.get_placeholder();
let bin_name = self.cmd.get_usage_name_fallback();
if !bin_name.is_empty() {
// the trim won't properly remove a leading space due to the formatting
let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
}
if used.is_empty() && self.needs_options_tag() {
let _ = write!(styled, "{placeholder}[OPTIONS]{placeholder:#} ",);
}
self.write_args(styled, used, !incl_reqs);
}
fn write_subcommand_usage(&self, styled: &mut StyledStr) {
debug!("Usage::write_subcommand_usage");
use std::fmt::Write as _;
// incl_reqs is only false when this function is called recursively
if self.cmd.has_visible_subcommands() || self.cmd.is_allow_external_subcommands_set() {
let literal = &self.styles.get_literal();
let placeholder = &self.styles.get_placeholder();
let value_name = self
.cmd
.get_subcommand_value_name()
.unwrap_or(DEFAULT_SUB_VALUE_NAME);
if self.cmd.is_subcommand_negates_reqs_set()
|| self.cmd.is_args_conflicts_with_subcommands_set()
{
styled.trim_end();
let _ = write!(styled, "{USAGE_SEP}");
if self.cmd.is_args_conflicts_with_subcommands_set() {
let bin_name = self.cmd.get_usage_name_fallback();
// Short-circuit full usage creation since no args will be relevant
let _ = write!(styled, "{literal}{bin_name}{literal:#} ",);
} else {
self.write_arg_usage(styled, &[], false);
}
let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
} else if self.cmd.is_subcommand_required_set() {
let _ = write!(styled, "{placeholder}<{value_name}>{placeholder:#}",);
} else {
let _ = write!(styled, "{placeholder}[{value_name}]{placeholder:#}",);
}
}
}
// Determines if we need the `[OPTIONS]` tag in the usage string
fn needs_options_tag(&self) -> bool {
debug!("Usage::needs_options_tag");
'outer: for f in self.cmd.get_non_positionals() {
debug!("Usage::needs_options_tag:iter: f={}", f.get_id());
// Don't print `[OPTIONS]` just for help or version
if f.get_long() == Some("help") || f.get_long() == Some("version") {
debug!("Usage::needs_options_tag:iter Option is built-in");
continue;
}
match f.get_action() {
ArgAction::Set
| ArgAction::Append
| ArgAction::SetTrue
| ArgAction::SetFalse
| ArgAction::Count => {}
ArgAction::Help
| ArgAction::HelpShort
| ArgAction::HelpLong
| ArgAction::Version => {
debug!("Usage::needs_options_tag:iter Option is built-in");
continue;
}
}
if f.is_hide_set() {
debug!("Usage::needs_options_tag:iter Option is hidden");
continue;
}
if f.is_required_set() {
debug!("Usage::needs_options_tag:iter Option is required");
continue;
}
for grp_s in self.cmd.groups_for_arg(f.get_id()) {
debug!("Usage::needs_options_tag:iter:iter: grp_s={grp_s:?}");
if self.cmd.get_groups().any(|g| g.id == grp_s && g.required) {
debug!("Usage::needs_options_tag:iter:iter: Group is required");
continue 'outer;
}
}
debug!("Usage::needs_options_tag:iter: [OPTIONS] required");
return true;
}
debug!("Usage::needs_options_tag: [OPTIONS] not required");
false
}
// Returns the required args in usage string form by fully unrolling all groups
pub(crate) fn write_args(&self, styled: &mut StyledStr, incls: &[Id], force_optional: bool) {
debug!("Usage::write_args: incls={incls:?}",);
use std::fmt::Write as _;
let literal = &self.styles.get_literal();
let required_owned;
let required = if let Some(required) = self.required {
required
} else {
required_owned = self.cmd.required_graph();
&required_owned
};
let mut unrolled_reqs = Vec::new();
for a in required.iter() {
let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
let required = match val {
ArgPredicate::Equals(_) => false,
ArgPredicate::IsPresent => true,
};
required.then(|| req_arg.clone())
};
for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
// if we don't check for duplicates here this causes duplicate error messages
// see https://github.com/clap-rs/clap/issues/2770
unrolled_reqs.push(aa);
}
// always include the required arg itself. it will not be enumerated
// by unroll_requirements_for_arg.
unrolled_reqs.push(a.clone());
}
debug!("Usage::get_args: unrolled_reqs={unrolled_reqs:?}");
let mut required_groups_members = FlatSet::new();
let mut required_groups = FlatSet::new();
for req in unrolled_reqs.iter().chain(incls.iter()) {
if self.cmd.find_group(req).is_some() {
let group_members = self.cmd.unroll_args_in_group(req);
let elem = self.cmd.format_group(req);
required_groups.insert(elem);
required_groups_members.extend(group_members);
} else {
debug_assert!(self.cmd.find(req).is_some());
}
}
let mut required_opts = FlatSet::new();
let mut required_positionals = Vec::new();
for req in unrolled_reqs.iter().chain(incls.iter()) {
if let Some(arg) = self.cmd.find(req) {
if required_groups_members.contains(arg.get_id()) {
continue;
}
let stylized = arg.stylized(self.styles, Some(!force_optional));
if let Some(index) = arg.get_index() {
let new_len = index + 1;
if required_positionals.len() < new_len {
required_positionals.resize(new_len, None);
}
required_positionals[index] = Some(stylized);
} else {
required_opts.insert(stylized);
}
} else {
debug_assert!(self.cmd.find_group(req).is_some());
}
}
for pos in self.cmd.get_positionals() {
if pos.is_hide_set() {
continue;
}
if required_groups_members.contains(pos.get_id()) {
continue;
}
let index = pos.get_index().unwrap();
let new_len = index + 1;
if required_positionals.len() < new_len {
required_positionals.resize(new_len, None);
}
if required_positionals[index].is_some() {
if pos.is_last_set() {
let styled = required_positionals[index].take().unwrap();
let mut new = StyledStr::new();
let _ = write!(new, "{literal}--{literal:#} ");
new.push_styled(&styled);
required_positionals[index] = Some(new);
}
} else {
let mut styled;
if pos.is_last_set() {
styled = StyledStr::new();
let _ = write!(styled, "{literal}[--{literal:#} ");
styled.push_styled(&pos.stylized(self.styles, Some(true)));
let _ = write!(styled, "{literal}]{literal:#}");
} else {
styled = pos.stylized(self.styles, Some(false));
}
required_positionals[index] = Some(styled);
}
if pos.is_last_set() && force_optional {
required_positionals[index] = None;
}
}
if !force_optional {
for arg in required_opts {
styled.push_styled(&arg);
styled.push_str(" ");
}
for arg in required_groups {
styled.push_styled(&arg);
styled.push_str(" ");
}
}
for arg in required_positionals.into_iter().flatten() {
styled.push_styled(&arg);
styled.push_str(" ");
}
}
pub(crate) fn get_required_usage_from(
&self,
incls: &[Id],
matcher: Option<&ArgMatcher>,
incl_last: bool,
) -> Vec<StyledStr> {
debug!(
"Usage::get_required_usage_from: incls={:?}, matcher={:?}, incl_last={:?}",
incls,
matcher.is_some(),
incl_last
);
let required_owned;
let required = if let Some(required) = self.required {
required
} else {
required_owned = self.cmd.required_graph();
&required_owned
};
let mut unrolled_reqs = Vec::new();
for a in required.iter() {
let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
let required = match val {
ArgPredicate::Equals(_) => {
if let Some(matcher) = matcher {
matcher.check_explicit(a, val)
} else {
false
}
}
ArgPredicate::IsPresent => true,
};
required.then(|| req_arg.clone())
};
for aa in self.cmd.unroll_arg_requires(is_relevant, a) {
// if we don't check for duplicates here this causes duplicate error messages
// see https://github.com/clap-rs/clap/issues/2770
unrolled_reqs.push(aa);
}
// always include the required arg itself. it will not be enumerated
// by unroll_requirements_for_arg.
unrolled_reqs.push(a.clone());
}
debug!("Usage::get_required_usage_from: unrolled_reqs={unrolled_reqs:?}");
let mut required_groups_members = FlatSet::new();
let mut required_groups = FlatSet::new();
for req in unrolled_reqs.iter().chain(incls.iter()) {
if self.cmd.find_group(req).is_some() {
let group_members = self.cmd.unroll_args_in_group(req);
let is_present = matcher
.map(|m| {
group_members
.iter()
.any(|arg| m.check_explicit(arg, &ArgPredicate::IsPresent))
})
.unwrap_or(false);
debug!("Usage::get_required_usage_from:iter:{req:?} group is_present={is_present}");
if is_present {
continue;
}
let elem = self.cmd.format_group(req);
required_groups.insert(elem);
required_groups_members.extend(group_members);
} else {
debug_assert!(self.cmd.find(req).is_some(), "`{req}` must exist");
}
}
let mut required_opts = FlatSet::new();
let mut required_positionals = Vec::new();
for req in unrolled_reqs.iter().chain(incls.iter()) {
if let Some(arg) = self.cmd.find(req) {
if required_groups_members.contains(arg.get_id()) {
continue;
}
let is_present = matcher
.map(|m| m.check_explicit(req, &ArgPredicate::IsPresent))
.unwrap_or(false);
debug!("Usage::get_required_usage_from:iter:{req:?} arg is_present={is_present}");
if is_present {
continue;
}
let stylized = arg.stylized(self.styles, Some(true));
if let Some(index) = arg.get_index() {
if !arg.is_last_set() || incl_last {
let new_len = index + 1;
if required_positionals.len() < new_len {
required_positionals.resize(new_len, None);
}
required_positionals[index] = Some(stylized);
}
} else {
required_opts.insert(stylized);
}
} else {
debug_assert!(self.cmd.find_group(req).is_some());
}
}
let mut ret_val = Vec::new();
ret_val.extend(required_opts);
ret_val.extend(required_groups);
for pos in required_positionals.into_iter().flatten() {
ret_val.push(pos);
}
debug!("Usage::get_required_usage_from: ret_val={ret_val:?}");
ret_val
}
}

View File

@@ -0,0 +1,238 @@
// Std
use std::ffi::OsString;
use std::mem;
use std::ops::Deref;
// Internal
use crate::INTERNAL_ERROR_MSG;
use crate::builder::{Arg, ArgPredicate, Command};
use crate::parser::Identifier;
use crate::parser::PendingArg;
use crate::parser::{ArgMatches, MatchedArg, SubCommand, ValueSource};
use crate::util::AnyValue;
use crate::util::FlatMap;
use crate::util::Id;
#[derive(Debug, Default)]
pub(crate) struct ArgMatcher {
matches: ArgMatches,
pending: Option<PendingArg>,
}
impl ArgMatcher {
pub(crate) fn new(_cmd: &Command) -> Self {
Self {
matches: ArgMatches {
#[cfg(debug_assertions)]
valid_args: {
let args = _cmd.get_arguments().map(|a| a.get_id().clone());
let groups = _cmd.get_groups().map(|g| g.get_id().clone());
args.chain(groups).collect()
},
#[cfg(debug_assertions)]
valid_subcommands: _cmd
.get_subcommands()
.map(|sc| sc.get_name_str().clone())
.collect(),
..Default::default()
},
pending: None,
}
}
pub(crate) fn into_inner(self) -> ArgMatches {
self.matches
}
pub(crate) fn propagate_globals(&mut self, global_arg_vec: &[Id]) {
debug!("ArgMatcher::get_global_values: global_arg_vec={global_arg_vec:?}");
let mut vals_map = FlatMap::new();
self.fill_in_global_values(global_arg_vec, &mut vals_map);
}
fn fill_in_global_values(
&mut self,
global_arg_vec: &[Id],
vals_map: &mut FlatMap<Id, MatchedArg>,
) {
for global_arg in global_arg_vec {
if let Some(ma) = self.get(global_arg) {
// We have to check if the parent's global arg wasn't used but still exists
// such as from a default value.
//
// For example, `myprog subcommand --global-arg=value` where `--global-arg` defines
// a default value of `other` myprog would have an existing MatchedArg for
// `--global-arg` where the value is `other`
let to_update = if let Some(parent_ma) = vals_map.get(global_arg) {
if parent_ma.source() > ma.source() {
parent_ma
} else {
ma
}
} else {
ma
}
.clone();
vals_map.insert(global_arg.clone(), to_update);
}
}
if let Some(ref mut sc) = self.matches.subcommand {
let mut am = Self {
matches: mem::take(&mut sc.matches),
pending: None,
};
am.fill_in_global_values(global_arg_vec, vals_map);
mem::swap(&mut am.matches, &mut sc.matches);
}
for (name, matched_arg) in vals_map.iter_mut() {
self.matches.args.insert(name.clone(), matched_arg.clone());
}
}
pub(crate) fn get(&self, arg: &Id) -> Option<&MatchedArg> {
self.matches.args.get(arg)
}
pub(crate) fn get_mut(&mut self, arg: &Id) -> Option<&mut MatchedArg> {
self.matches.args.get_mut(arg)
}
pub(crate) fn remove(&mut self, arg: &Id) -> bool {
self.matches.args.remove(arg).is_some()
}
pub(crate) fn contains(&self, arg: &Id) -> bool {
self.matches.args.contains_key(arg)
}
pub(crate) fn arg_ids(&self) -> std::slice::Iter<'_, Id> {
self.matches.args.keys()
}
pub(crate) fn args(&self) -> crate::util::flat_map::Iter<'_, Id, MatchedArg> {
self.matches.args.iter()
}
pub(crate) fn entry(&mut self, arg: Id) -> crate::util::Entry<'_, Id, MatchedArg> {
self.matches.args.entry(arg)
}
pub(crate) fn subcommand(&mut self, sc: SubCommand) {
self.matches.subcommand = Some(Box::new(sc));
}
pub(crate) fn subcommand_name(&self) -> Option<&str> {
self.matches.subcommand_name()
}
pub(crate) fn check_explicit(&self, arg: &Id, predicate: &ArgPredicate) -> bool {
self.get(arg)
.map(|a| a.check_explicit(predicate))
.unwrap_or_default()
}
pub(crate) fn start_custom_arg(&mut self, arg: &Arg, source: ValueSource) {
let id = arg.get_id().clone();
debug!("ArgMatcher::start_custom_arg: id={id:?}, source={source:?}");
let ma = self.entry(id).or_insert(MatchedArg::new_arg(arg));
debug_assert_eq!(ma.type_id(), Some(arg.get_value_parser().type_id()));
ma.set_source(source);
ma.new_val_group();
}
pub(crate) fn start_custom_group(&mut self, id: Id, source: ValueSource) {
debug!("ArgMatcher::start_custom_arg: id={id:?}, source={source:?}");
let ma = self.entry(id).or_insert(MatchedArg::new_group());
debug_assert_eq!(ma.type_id(), None);
ma.set_source(source);
ma.new_val_group();
}
pub(crate) fn start_occurrence_of_external(&mut self, cmd: &Command) {
let id = Id::from_static_ref(Id::EXTERNAL);
debug!("ArgMatcher::start_occurrence_of_external: id={id:?}");
let ma = self.entry(id).or_insert(MatchedArg::new_external(cmd));
debug_assert_eq!(
ma.type_id(),
Some(
cmd.get_external_subcommand_value_parser()
.expect(INTERNAL_ERROR_MSG)
.type_id()
)
);
ma.set_source(ValueSource::CommandLine);
ma.new_val_group();
}
pub(crate) fn add_val_to(&mut self, arg: &Id, val: AnyValue, raw_val: OsString) {
let ma = self.get_mut(arg).expect(INTERNAL_ERROR_MSG);
ma.append_val(val, raw_val);
}
pub(crate) fn add_index_to(&mut self, arg: &Id, idx: usize) {
let ma = self.get_mut(arg).expect(INTERNAL_ERROR_MSG);
ma.push_index(idx);
}
pub(crate) fn needs_more_vals(&self, o: &Arg) -> bool {
let num_pending = self
.pending
.as_ref()
.and_then(|p| (p.id == *o.get_id()).then_some(p.raw_vals.len()))
.unwrap_or(0);
debug!(
"ArgMatcher::needs_more_vals: o={}, pending={}",
o.get_id(),
num_pending
);
let expected = o.get_num_args().expect(INTERNAL_ERROR_MSG);
debug!("ArgMatcher::needs_more_vals: expected={expected}, actual={num_pending}");
expected.accepts_more(num_pending)
}
pub(crate) fn pending_arg_id(&self) -> Option<&Id> {
self.pending.as_ref().map(|p| &p.id)
}
pub(crate) fn pending_values_mut(
&mut self,
id: &Id,
ident: Option<Identifier>,
trailing_values: bool,
) -> &mut Vec<OsString> {
let pending = self.pending.get_or_insert_with(|| PendingArg {
id: id.clone(),
ident,
raw_vals: Default::default(),
trailing_idx: None,
});
debug_assert_eq!(pending.id, *id, "{INTERNAL_ERROR_MSG}");
if ident.is_some() {
debug_assert_eq!(pending.ident, ident, "{INTERNAL_ERROR_MSG}");
}
if trailing_values {
pending.trailing_idx.get_or_insert(pending.raw_vals.len());
}
&mut pending.raw_vals
}
pub(crate) fn start_trailing(&mut self) {
if let Some(pending) = &mut self.pending {
// Allow asserting its started on subsequent calls
pending.trailing_idx.get_or_insert(pending.raw_vals.len());
}
}
pub(crate) fn take_pending(&mut self) -> Option<PendingArg> {
self.pending.take()
}
}
impl Deref for ArgMatcher {
type Target = ArgMatches;
fn deref(&self) -> &Self::Target {
&self.matches
}
}

View File

@@ -0,0 +1,66 @@
use crate::util::AnyValueId;
/// Violation of [`ArgMatches`][crate::ArgMatches] assumptions
#[derive(Clone, Debug)]
#[allow(missing_copy_implementations)] // We might add non-Copy types in the future
#[non_exhaustive]
pub enum MatchesError {
/// Failed to downcast `AnyValue` to the specified type
#[non_exhaustive]
Downcast {
/// Type for value stored in [`ArgMatches`][crate::ArgMatches]
actual: AnyValueId,
/// The target type to downcast to
expected: AnyValueId,
},
/// Argument not defined in [`Command`][crate::Command]
#[non_exhaustive]
UnknownArgument {
// Missing `id` but blocked on a public id type which will hopefully come with `unstable-v4`
},
}
impl MatchesError {
#[cfg_attr(debug_assertions, track_caller)]
pub(crate) fn unwrap<T>(id: &str, r: Result<T, Self>) -> T {
let err = match r {
Ok(t) => {
return t;
}
Err(err) => err,
};
panic!("Mismatch between definition and access of `{id}`. {err}",)
}
}
impl std::error::Error for MatchesError {}
impl std::fmt::Display for MatchesError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Downcast { actual, expected } => {
writeln!(
f,
"Could not downcast to {expected:?}, need to downcast to {actual:?}"
)
}
Self::UnknownArgument {} => {
writeln!(
f,
"Unknown argument or group id. Make sure you are using the argument id and not the short or long flags"
)
}
}
}
}
#[test]
fn check_auto_traits() {
static_assertions::assert_impl_all!(
MatchesError: Send,
Sync,
std::panic::RefUnwindSafe,
std::panic::UnwindSafe,
Unpin
);
}

View File

@@ -0,0 +1 @@
pub(crate) mod suggestions;

View File

@@ -0,0 +1,178 @@
// Internal
use crate::builder::Command;
/// Find strings from an iterable of `possible_values` similar to a given value `v`
/// Returns a Vec of all possible values that exceed a similarity threshold
/// sorted by ascending similarity, most similar comes last
#[cfg(feature = "suggestions")]
pub(crate) fn did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String>
where
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
use std::cmp::Ordering;
let mut candidates: Vec<(f64, String)> = Vec::new();
for pv in possible_values {
// GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong
// causing strings with common prefix >=10 to be considered perfectly similar
let confidence = strsim::jaro(v, pv.as_ref());
if confidence > 0.7 {
let new_elem = (confidence, pv.as_ref().to_owned());
let pos = candidates
.binary_search_by(|probe| {
if probe.0 > confidence {
Ordering::Greater
} else {
Ordering::Less
}
})
.unwrap_or_else(|e| e);
candidates.insert(pos, new_elem);
}
}
candidates.into_iter().map(|(_, pv)| pv).collect()
}
#[cfg(not(feature = "suggestions"))]
pub(crate) fn did_you_mean<T, I>(_: &str, _: I) -> Vec<String>
where
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
Vec::new()
}
/// Returns a suffix that can be empty, or is the standard 'did you mean' phrase
pub(crate) fn did_you_mean_flag<'a, 'help, I, T>(
arg: &str,
remaining_args: &[&std::ffi::OsStr],
longs: I,
subcommands: impl IntoIterator<Item = &'a mut Command>,
) -> Option<(String, Option<String>)>
where
'help: 'a,
T: AsRef<str>,
I: IntoIterator<Item = T>,
{
use crate::mkeymap::KeyType;
match did_you_mean(arg, longs).pop() {
Some(candidate) => Some((candidate, None)),
None => subcommands
.into_iter()
.filter_map(|subcommand| {
subcommand._build_self(false);
let longs = subcommand.get_keymap().keys().filter_map(|a| {
if let KeyType::Long(v) = a {
Some(v.to_string_lossy().into_owned())
} else {
None
}
});
let subcommand_name = subcommand.get_name();
let candidate = some!(did_you_mean(arg, longs).pop());
let score = some!(remaining_args.iter().position(|x| subcommand_name == *x));
Some((score, (candidate, Some(subcommand_name.to_owned()))))
})
.min_by_key(|(x, _)| *x)
.map(|(_, suggestion)| suggestion),
}
}
#[cfg(all(test, feature = "suggestions"))]
mod test {
use super::*;
#[test]
fn missing_letter() {
let p_vals = ["test", "possible", "values"];
assert_eq!(did_you_mean("tst", p_vals.iter()), vec!["test"]);
}
#[test]
fn ambiguous() {
let p_vals = ["test", "temp", "possible", "values"];
assert_eq!(did_you_mean("te", p_vals.iter()), vec!["test", "temp"]);
}
#[test]
fn unrelated() {
let p_vals = ["test", "possible", "values"];
assert_eq!(
did_you_mean("hahaahahah", p_vals.iter()),
Vec::<String>::new()
);
}
#[test]
fn best_fit() {
let p_vals = [
"test",
"possible",
"values",
"alignmentStart",
"alignmentScore",
];
assert_eq!(
did_you_mean("alignmentScorr", p_vals.iter()),
vec!["alignmentStart", "alignmentScore"]
);
}
#[test]
fn best_fit_long_common_prefix_issue_4660() {
let p_vals = ["alignmentScore", "alignmentStart"];
assert_eq!(
did_you_mean("alignmentScorr", p_vals.iter()),
vec!["alignmentStart", "alignmentScore"]
);
}
#[test]
fn flag_missing_letter() {
let p_vals = ["test", "possible", "values"];
assert_eq!(
did_you_mean_flag("tst", &[], p_vals.iter(), []),
Some(("test".to_owned(), None))
);
}
#[test]
fn flag_ambiguous() {
let p_vals = ["test", "temp", "possible", "values"];
assert_eq!(
did_you_mean_flag("te", &[], p_vals.iter(), []),
Some(("temp".to_owned(), None))
);
}
#[test]
fn flag_unrelated() {
let p_vals = ["test", "possible", "values"];
assert_eq!(
did_you_mean_flag("hahaahahah", &[], p_vals.iter(), []),
None
);
}
#[test]
fn flag_best_fit() {
let p_vals = [
"test",
"possible",
"values",
"alignmentStart",
"alignmentScore",
];
assert_eq!(
did_you_mean_flag("alignmentScorr", &[], p_vals.iter(), []),
Some(("alignmentScore".to_owned(), None))
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,221 @@
// Std
use std::{
ffi::{OsStr, OsString},
iter::{Cloned, Flatten},
slice::Iter,
};
use crate::INTERNAL_ERROR_MSG;
use crate::builder::ArgPredicate;
use crate::parser::ValueSource;
use crate::util::AnyValue;
use crate::util::AnyValueId;
use crate::util::eq_ignore_case;
#[derive(Debug, Clone)]
pub(crate) struct MatchedArg {
source: Option<ValueSource>,
indices: Vec<usize>,
type_id: Option<AnyValueId>,
vals: Vec<Vec<AnyValue>>,
raw_vals: Vec<Vec<OsString>>,
ignore_case: bool,
}
impl MatchedArg {
pub(crate) fn new_arg(arg: &crate::Arg) -> Self {
let ignore_case = arg.is_ignore_case_set();
Self {
source: None,
indices: Vec::new(),
type_id: Some(arg.get_value_parser().type_id()),
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
}
}
pub(crate) fn new_group() -> Self {
let ignore_case = false;
Self {
source: None,
indices: Vec::new(),
type_id: None,
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
}
}
pub(crate) fn new_external(cmd: &crate::Command) -> Self {
let ignore_case = false;
Self {
source: None,
indices: Vec::new(),
type_id: Some(
cmd.get_external_subcommand_value_parser()
.expect(INTERNAL_ERROR_MSG)
.type_id(),
),
vals: Vec::new(),
raw_vals: Vec::new(),
ignore_case,
}
}
pub(crate) fn indices(&self) -> Cloned<Iter<'_, usize>> {
self.indices.iter().cloned()
}
pub(crate) fn get_index(&self, index: usize) -> Option<usize> {
self.indices.get(index).cloned()
}
pub(crate) fn push_index(&mut self, index: usize) {
self.indices.push(index);
}
pub(crate) fn vals(&self) -> Iter<'_, Vec<AnyValue>> {
self.vals.iter()
}
pub(crate) fn into_vals(self) -> Vec<Vec<AnyValue>> {
self.vals
}
pub(crate) fn vals_flatten(&self) -> Flatten<Iter<'_, Vec<AnyValue>>> {
self.vals.iter().flatten()
}
pub(crate) fn into_vals_flatten(self) -> Flatten<std::vec::IntoIter<Vec<AnyValue>>> {
self.vals.into_iter().flatten()
}
pub(crate) fn raw_vals(&self) -> Iter<'_, Vec<OsString>> {
self.raw_vals.iter()
}
pub(crate) fn raw_vals_flatten(&self) -> Flatten<Iter<'_, Vec<OsString>>> {
self.raw_vals.iter().flatten()
}
pub(crate) fn first(&self) -> Option<&AnyValue> {
self.vals_flatten().next()
}
#[cfg(test)]
pub(crate) fn first_raw(&self) -> Option<&OsString> {
self.raw_vals_flatten().next()
}
pub(crate) fn new_val_group(&mut self) {
self.vals.push(vec![]);
self.raw_vals.push(vec![]);
}
pub(crate) fn append_val(&mut self, val: AnyValue, raw_val: OsString) {
// We assume there is always a group created before.
self.vals.last_mut().expect(INTERNAL_ERROR_MSG).push(val);
self.raw_vals
.last_mut()
.expect(INTERNAL_ERROR_MSG)
.push(raw_val);
}
pub(crate) fn num_vals(&self) -> usize {
self.vals.iter().map(|v| v.len()).sum()
}
// Will be used later
#[allow(dead_code)]
pub(crate) fn num_vals_last_group(&self) -> usize {
self.vals.last().map(|x| x.len()).unwrap_or(0)
}
pub(crate) fn check_explicit(&self, predicate: &ArgPredicate) -> bool {
if self.source.map(|s| !s.is_explicit()).unwrap_or(false) {
return false;
}
match predicate {
ArgPredicate::Equals(val) => self.raw_vals_flatten().any(|v| {
if self.ignore_case {
// If `v` isn't utf8, it can't match `val`, so `OsStr::to_str` should be fine
eq_ignore_case(&v.to_string_lossy(), &val.to_string_lossy())
} else {
OsString::as_os_str(v) == OsStr::new(val)
}
}),
ArgPredicate::IsPresent => true,
}
}
pub(crate) fn source(&self) -> Option<ValueSource> {
self.source
}
pub(crate) fn set_source(&mut self, source: ValueSource) {
if let Some(existing) = self.source {
self.source = Some(existing.max(source));
} else {
self.source = Some(source);
}
}
pub(crate) fn type_id(&self) -> Option<AnyValueId> {
self.type_id
}
pub(crate) fn infer_type_id(&self, expected: AnyValueId) -> AnyValueId {
self.type_id()
.or_else(|| {
self.vals_flatten()
.map(|v| v.type_id())
.find(|actual| *actual != expected)
})
.unwrap_or(expected)
}
}
impl PartialEq for MatchedArg {
fn eq(&self, other: &Self) -> bool {
let Self {
source: self_source,
indices: self_indices,
type_id: self_type_id,
vals: _,
raw_vals: self_raw_vals,
ignore_case: self_ignore_case,
} = self;
let Self {
source: other_source,
indices: other_indices,
type_id: other_type_id,
vals: _,
raw_vals: other_raw_vals,
ignore_case: other_ignore_case,
} = other;
self_source == other_source
&& self_indices == other_indices
&& self_type_id == other_type_id
&& self_raw_vals == other_raw_vals
&& self_ignore_case == other_ignore_case
}
}
impl Eq for MatchedArg {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grouped_vals_first() {
let mut m = MatchedArg::new_group();
m.new_val_group();
m.new_val_group();
m.append_val(AnyValue::new(String::from("bbb")), "bbb".into());
m.append_val(AnyValue::new(String::from("ccc")), "ccc".into());
assert_eq!(m.first_raw(), Some(&OsString::from("bbb")));
}
}

View File

@@ -0,0 +1,13 @@
mod arg_matches;
mod matched_arg;
mod value_source;
pub use arg_matches::IdsRef;
pub use arg_matches::RawValues;
pub use arg_matches::Values;
pub use arg_matches::ValuesRef;
pub use arg_matches::{ArgMatches, Indices};
pub use value_source::ValueSource;
pub(crate) use arg_matches::SubCommand;
pub(crate) use matched_arg::MatchedArg;

View File

@@ -0,0 +1,17 @@
/// Origin of the argument's value
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ValueSource {
/// Value came [`Arg::default_value`][crate::Arg::default_value]
DefaultValue,
/// Value came [`Arg::env`][crate::Arg::env]
EnvVariable,
/// Value was passed in on the command-line
CommandLine,
}
impl ValueSource {
pub(crate) fn is_explicit(self) -> bool {
self != Self::DefaultValue
}
}

View File

@@ -0,0 +1,25 @@
//! [`Command`][crate::Command] line argument parser
mod arg_matcher;
mod error;
mod matches;
#[allow(clippy::module_inception)]
mod parser;
mod validator;
pub(crate) mod features;
pub(crate) use self::arg_matcher::ArgMatcher;
pub(crate) use self::matches::{MatchedArg, SubCommand};
pub(crate) use self::parser::Identifier;
pub(crate) use self::parser::Parser;
pub(crate) use self::parser::PendingArg;
pub(crate) use self::validator::Validator;
pub(crate) use self::validator::get_possible_values_cli;
pub use self::matches::IdsRef;
pub use self::matches::RawValues;
pub use self::matches::Values;
pub use self::matches::ValuesRef;
pub use self::matches::{ArgMatches, Indices, ValueSource};
pub use error::MatchesError;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,537 @@
// Internal
use crate::INTERNAL_ERROR_MSG;
use crate::builder::StyledStr;
use crate::builder::{Arg, ArgGroup, ArgPredicate, Command, PossibleValue};
use crate::error::{Error, Result as ClapResult};
use crate::output::Usage;
use crate::parser::ArgMatcher;
use crate::util::ChildGraph;
use crate::util::FlatMap;
use crate::util::FlatSet;
use crate::util::Id;
pub(crate) struct Validator<'cmd> {
cmd: &'cmd Command,
required: ChildGraph<Id>,
}
impl<'cmd> Validator<'cmd> {
pub(crate) fn new(cmd: &'cmd Command) -> Self {
let required = cmd.required_graph();
Validator { cmd, required }
}
pub(crate) fn validate(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate");
let conflicts = Conflicts::with_args(self.cmd, matcher);
let has_subcmd = matcher.subcommand_name().is_some();
if !has_subcmd && self.cmd.is_arg_required_else_help_set() {
let num_user_values = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.count();
if num_user_values == 0 {
let message = self.cmd.write_help_err(false);
return Err(Error::display_help_error(self.cmd, message));
}
}
if !has_subcmd && self.cmd.is_subcommand_required_set() {
let bn = self.cmd.get_bin_name_fallback();
return Err(Error::missing_subcommand(
self.cmd,
bn.to_owned(),
self.cmd
.all_subcommand_names()
.map(|s| s.to_owned())
.collect::<Vec<_>>(),
Usage::new(self.cmd)
.required(&self.required)
.create_usage_with_title(&[]),
));
}
ok!(self.validate_conflicts(matcher, &conflicts));
if !(self.cmd.is_subcommand_negates_reqs_set() && has_subcmd) {
ok!(self.validate_required(matcher, &conflicts));
}
Ok(())
}
fn validate_conflicts(
&mut self,
matcher: &ArgMatcher,
conflicts: &Conflicts,
) -> ClapResult<()> {
debug!("Validator::validate_conflicts");
ok!(self.validate_exclusive(matcher));
for (arg_id, _) in matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.filter(|(arg_id, _)| self.cmd.find(arg_id).is_some())
{
debug!("Validator::validate_conflicts::iter: id={arg_id:?}");
let conflicts = conflicts.gather_conflicts(self.cmd, arg_id);
ok!(self.build_conflict_err(arg_id, &conflicts, matcher));
}
Ok(())
}
fn validate_exclusive(&self, matcher: &ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate_exclusive");
let args_count = matcher
.args()
.filter(|(arg_id, matched)| {
matched.check_explicit(&ArgPredicate::IsPresent)
// Avoid including our own groups by checking none of them. If a group is present, the
// args for the group will be.
&& self.cmd.find(arg_id).is_some()
})
.count();
if args_count <= 1 {
// Nothing present to conflict with
return Ok(());
}
matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.find_map(|(id, _)| {
debug!("Validator::validate_exclusive:iter:{id:?}");
self.cmd
.find(id)
// Find `arg`s which are exclusive but also appear with other args.
.filter(|&arg| arg.is_exclusive_set() && args_count > 1)
})
.map(|arg| {
// Throw an error for the first conflict found.
Err(Error::argument_conflict(
self.cmd,
arg.to_string(),
Vec::new(),
Usage::new(self.cmd)
.required(&self.required)
.create_usage_with_title(&[]),
))
})
.unwrap_or(Ok(()))
}
fn build_conflict_err(
&self,
name: &Id,
conflict_ids: &[Id],
matcher: &ArgMatcher,
) -> ClapResult<()> {
if conflict_ids.is_empty() {
return Ok(());
}
debug!("Validator::build_conflict_err: name={name:?}");
let conflict_ids = conflict_ids
.iter()
.flat_map(|c_id| {
if self.cmd.find_group(c_id).is_some() {
self.cmd.unroll_args_in_group(c_id)
} else {
vec![c_id.clone()]
}
})
.collect::<FlatSet<_>>()
.into_vec();
let conflicts = conflict_ids
.iter()
.map(|c_id| {
let c_arg = self.cmd.find(c_id).expect(INTERNAL_ERROR_MSG);
c_arg.to_string()
})
.collect();
let former_arg = self.cmd.find(name).expect(INTERNAL_ERROR_MSG);
let usg = self.build_conflict_err_usage(matcher, &conflict_ids);
Err(Error::argument_conflict(
self.cmd,
former_arg.to_string(),
conflicts,
usg,
))
}
fn build_conflict_err_usage(
&self,
matcher: &ArgMatcher,
conflicting_keys: &[Id],
) -> Option<StyledStr> {
let used_filtered: Vec<Id> = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.map(|(n, _)| n)
.filter(|n| {
// Filter out the args we don't want to specify.
self.cmd
.find(n)
.map(|a| !a.is_hide_set())
.unwrap_or_default()
})
.filter(|key| !conflicting_keys.contains(key))
.cloned()
.collect();
let required: Vec<Id> = used_filtered
.iter()
.filter_map(|key| self.cmd.find(key))
.flat_map(|arg| arg.requires.iter().map(|item| &item.1))
.filter(|key| !used_filtered.contains(key) && !conflicting_keys.contains(key))
.chain(used_filtered.iter())
.cloned()
.collect();
Usage::new(self.cmd)
.required(&self.required)
.create_usage_with_title(&required)
}
fn gather_requires(&mut self, matcher: &ArgMatcher) {
debug!("Validator::gather_requires");
for (name, matched) in matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
{
debug!("Validator::gather_requires:iter:{name:?}");
if let Some(arg) = self.cmd.find(name) {
let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
let required = matched.check_explicit(val);
required.then(|| req_arg.clone())
};
for req in self.cmd.unroll_arg_requires(is_relevant, arg.get_id()) {
self.required.insert(req);
}
} else if let Some(g) = self.cmd.find_group(name) {
debug!("Validator::gather_requires:iter:{name:?}:group");
for r in &g.requires {
self.required.insert(r.clone());
}
}
}
}
fn validate_required(&mut self, matcher: &ArgMatcher, conflicts: &Conflicts) -> ClapResult<()> {
debug!("Validator::validate_required: required={:?}", self.required);
self.gather_requires(matcher);
let mut missing_required = Vec::new();
let mut highest_index = 0;
let is_exclusive_present = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.any(|(id, _)| {
self.cmd
.find(id)
.map(|arg| arg.is_exclusive_set())
.unwrap_or_default()
});
debug!("Validator::validate_required: is_exclusive_present={is_exclusive_present}");
for arg_or_group in self
.required
.iter()
.filter(|r| !matcher.check_explicit(r, &ArgPredicate::IsPresent))
{
debug!("Validator::validate_required:iter:aog={arg_or_group:?}");
if let Some(arg) = self.cmd.find(arg_or_group) {
debug!("Validator::validate_required:iter: This is an arg");
if !is_exclusive_present && !self.is_missing_required_ok(arg, conflicts) {
debug!(
"Validator::validate_required:iter: Missing {:?}",
arg.get_id()
);
missing_required.push(arg.get_id().clone());
if !arg.is_last_set() {
highest_index = highest_index.max(arg.get_index().unwrap_or(0));
}
}
} else if let Some(group) = self.cmd.find_group(arg_or_group) {
debug!("Validator::validate_required:iter: This is a group");
if !self
.cmd
.unroll_args_in_group(&group.id)
.iter()
.any(|a| matcher.check_explicit(a, &ArgPredicate::IsPresent))
{
debug!(
"Validator::validate_required:iter: Missing {:?}",
group.get_id()
);
missing_required.push(group.get_id().clone());
}
}
}
// Validate the conditionally required args
for a in self
.cmd
.get_arguments()
.filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent))
{
let mut required = false;
for (other, val) in &a.r_ifs {
if matcher.check_explicit(other, &ArgPredicate::Equals(val.into())) {
debug!(
"Validator::validate_required:iter: Missing {:?}",
a.get_id()
);
required = true;
}
}
let match_all = a.r_ifs_all.iter().all(|(other, val)| {
matcher.check_explicit(other, &ArgPredicate::Equals(val.into()))
});
if match_all && !a.r_ifs_all.is_empty() {
debug!(
"Validator::validate_required:iter: Missing {:?}",
a.get_id()
);
required = true;
}
if (!a.r_unless.is_empty() || !a.r_unless_all.is_empty())
&& self.fails_arg_required_unless(a, matcher)
{
debug!(
"Validator::validate_required:iter: Missing {:?}",
a.get_id()
);
required = true;
}
if !is_exclusive_present && required {
missing_required.push(a.get_id().clone());
if !a.is_last_set() {
highest_index = highest_index.max(a.get_index().unwrap_or(0));
}
}
}
// For display purposes, include all of the preceding positional arguments
if !self.cmd.is_allow_missing_positional_set() {
for pos in self
.cmd
.get_positionals()
.filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent))
{
if pos.get_index() < Some(highest_index) {
debug!(
"Validator::validate_required:iter: Missing {:?}",
pos.get_id()
);
missing_required.push(pos.get_id().clone());
}
}
}
if !missing_required.is_empty() {
ok!(self.missing_required_error(matcher, missing_required));
}
Ok(())
}
fn is_missing_required_ok(&self, a: &Arg, conflicts: &Conflicts) -> bool {
debug!("Validator::is_missing_required_ok: {}", a.get_id());
if !conflicts.gather_conflicts(self.cmd, a.get_id()).is_empty() {
debug!("Validator::is_missing_required_ok: true (self)");
return true;
}
for group_id in self.cmd.groups_for_arg(a.get_id()) {
if !conflicts.gather_conflicts(self.cmd, &group_id).is_empty() {
debug!("Validator::is_missing_required_ok: true ({group_id})");
return true;
}
}
false
}
// Failing a required unless means, the arg's "unless" wasn't present, and neither were they
fn fails_arg_required_unless(&self, a: &Arg, matcher: &ArgMatcher) -> bool {
debug!("Validator::fails_arg_required_unless: a={:?}", a.get_id());
let exists = |id| matcher.check_explicit(id, &ArgPredicate::IsPresent);
(a.r_unless_all.is_empty() || !a.r_unless_all.iter().all(exists))
&& !a.r_unless.iter().any(exists)
}
// `req_args`: an arg to include in the error even if not used
fn missing_required_error(
&self,
matcher: &ArgMatcher,
raw_req_args: Vec<Id>,
) -> ClapResult<()> {
debug!("Validator::missing_required_error; incl={raw_req_args:?}");
debug!(
"Validator::missing_required_error: reqs={:?}",
self.required
);
let usg = Usage::new(self.cmd).required(&self.required);
let req_args = {
#[cfg(feature = "usage")]
{
usg.get_required_usage_from(&raw_req_args, Some(matcher), true)
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
}
#[cfg(not(feature = "usage"))]
{
raw_req_args
.iter()
.map(|id| {
if let Some(arg) = self.cmd.find(id) {
arg.to_string()
} else if let Some(_group) = self.cmd.find_group(id) {
self.cmd.format_group(id).to_string()
} else {
debug_assert!(false, "id={id:?} is unknown");
"".to_owned()
}
})
.collect::<FlatSet<_>>()
.into_iter()
.collect::<Vec<_>>()
}
};
debug!("Validator::missing_required_error: req_args={req_args:#?}");
let used: Vec<Id> = matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.map(|(n, _)| n)
.filter(|n| {
// Filter out the args we don't want to specify.
self.cmd
.find(n)
.map(|a| !a.is_hide_set())
.unwrap_or_default()
})
.cloned()
.chain(raw_req_args)
.collect();
Err(Error::missing_required_argument(
self.cmd,
req_args,
usg.create_usage_with_title(&used),
))
}
}
#[derive(Default, Clone, Debug)]
struct Conflicts {
potential: FlatMap<Id, Vec<Id>>,
}
impl Conflicts {
fn with_args(cmd: &Command, matcher: &ArgMatcher) -> Self {
let mut potential = FlatMap::new();
potential.extend_unchecked(
matcher
.args()
.filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
.map(|(id, _)| {
let conf = gather_direct_conflicts(cmd, id);
(id.clone(), conf)
}),
);
Self { potential }
}
fn gather_conflicts(&self, cmd: &Command, arg_id: &Id) -> Vec<Id> {
debug!("Conflicts::gather_conflicts: arg={arg_id:?}");
let mut conflicts = Vec::new();
let arg_id_conflicts_storage;
let arg_id_conflicts = if let Some(arg_id_conflicts) = self.get_direct_conflicts(arg_id) {
arg_id_conflicts
} else {
// `is_missing_required_ok` is a case where we check not-present args for conflicts
arg_id_conflicts_storage = gather_direct_conflicts(cmd, arg_id);
&arg_id_conflicts_storage
};
for (other_arg_id, other_arg_id_conflicts) in self.potential.iter() {
if arg_id == other_arg_id {
continue;
}
if arg_id_conflicts.contains(other_arg_id) {
conflicts.push(other_arg_id.clone());
}
if other_arg_id_conflicts.contains(arg_id) {
conflicts.push(other_arg_id.clone());
}
}
debug!("Conflicts::gather_conflicts: conflicts={conflicts:?}");
conflicts
}
fn get_direct_conflicts(&self, arg_id: &Id) -> Option<&[Id]> {
self.potential.get(arg_id).map(Vec::as_slice)
}
}
fn gather_direct_conflicts(cmd: &Command, id: &Id) -> Vec<Id> {
let conf = if let Some(arg) = cmd.find(id) {
gather_arg_direct_conflicts(cmd, arg)
} else if let Some(group) = cmd.find_group(id) {
gather_group_direct_conflicts(group)
} else {
debug_assert!(false, "id={id:?} is unknown");
Vec::new()
};
debug!("Conflicts::gather_direct_conflicts id={id:?}, conflicts={conf:?}",);
conf
}
fn gather_arg_direct_conflicts(cmd: &Command, arg: &Arg) -> Vec<Id> {
let mut conf = arg.conflicts.clone();
for group_id in cmd.groups_for_arg(arg.get_id()) {
let group = cmd.find_group(&group_id).expect(INTERNAL_ERROR_MSG);
conf.extend(group.conflicts.iter().cloned());
if !group.multiple {
for member_id in &group.args {
if member_id != arg.get_id() {
conf.push(member_id.clone());
}
}
}
}
// Overrides are implicitly conflicts
conf.extend(arg.overrides.iter().cloned());
conf
}
fn gather_group_direct_conflicts(group: &ArgGroup) -> Vec<Id> {
group.conflicts.clone()
}
pub(crate) fn get_possible_values_cli(a: &Arg) -> Vec<PossibleValue> {
if !a.is_takes_value_set() {
vec![]
} else {
a.get_value_parser()
.possible_values()
.map(|pvs| pvs.collect())
.unwrap_or_default()
}
}

View File

@@ -0,0 +1,127 @@
#[derive(Clone)]
pub(crate) struct AnyValue {
inner: std::sync::Arc<dyn std::any::Any + Send + Sync + 'static>,
// While we can extract `TypeId` from `inner`, the debug repr is of a number, so let's track
// the type_name in debug builds.
id: AnyValueId,
}
impl AnyValue {
pub(crate) fn new<V: std::any::Any + Clone + Send + Sync + 'static>(inner: V) -> Self {
let id = AnyValueId::of::<V>();
let inner = std::sync::Arc::new(inner);
Self { inner, id }
}
pub(crate) fn downcast_ref<T: std::any::Any + Clone + Send + Sync + 'static>(
&self,
) -> Option<&T> {
self.inner.downcast_ref::<T>()
}
pub(crate) fn downcast_into<T: std::any::Any + Clone + Send + Sync>(self) -> Result<T, Self> {
let id = self.id;
let value =
ok!(std::sync::Arc::downcast::<T>(self.inner).map_err(|inner| Self { inner, id }));
let value = std::sync::Arc::try_unwrap(value).unwrap_or_else(|arc| (*arc).clone());
Ok(value)
}
pub(crate) fn type_id(&self) -> AnyValueId {
self.id
}
}
impl std::fmt::Debug for AnyValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("AnyValue").field("inner", &self.id).finish()
}
}
#[derive(Copy, Clone)]
pub struct AnyValueId {
type_id: std::any::TypeId,
#[cfg(debug_assertions)]
type_name: &'static str,
}
impl AnyValueId {
pub(crate) fn of<A: ?Sized + 'static>() -> Self {
Self {
type_id: std::any::TypeId::of::<A>(),
#[cfg(debug_assertions)]
type_name: std::any::type_name::<A>(),
}
}
}
impl PartialEq for AnyValueId {
fn eq(&self, other: &Self) -> bool {
self.type_id == other.type_id
}
}
impl Eq for AnyValueId {}
impl PartialOrd for AnyValueId {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq<std::any::TypeId> for AnyValueId {
fn eq(&self, other: &std::any::TypeId) -> bool {
self.type_id == *other
}
}
impl Ord for AnyValueId {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.type_id.cmp(&other.type_id)
}
}
impl std::hash::Hash for AnyValueId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.type_id.hash(state);
}
}
impl std::fmt::Debug for AnyValueId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
#[cfg(not(debug_assertions))]
{
self.type_id.fmt(f)
}
#[cfg(debug_assertions)]
{
f.debug_struct(self.type_name).finish()
}
}
}
impl<'a, A: ?Sized + 'static> From<&'a A> for AnyValueId {
fn from(_: &'a A) -> Self {
Self::of::<A>()
}
}
#[cfg(test)]
mod test {
#[test]
#[cfg(debug_assertions)]
fn debug_impl() {
use super::*;
assert_eq!(format!("{:?}", AnyValue::new(5)), "AnyValue { inner: i32 }");
}
#[test]
fn eq_to_type_id() {
use super::*;
let any_value_id = AnyValueId::of::<i32>();
let type_id = std::any::TypeId::of::<i32>();
assert_eq!(any_value_id, type_id);
}
}

View File

@@ -0,0 +1,103 @@
use crate::builder::PossibleValue;
use crate::derive::ValueEnum;
/// Represents the color preferences for program output
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ColorChoice {
/// Enables colored output only when the output is going to a terminal or TTY.
///
/// <div class="warning">
///
/// **NOTE:** This is the default behavior of `clap`.
///
/// </div>
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "color")] {
/// # use clap_builder as clap;
/// # use clap::{Command, ColorChoice};
/// Command::new("myprog")
/// .color(ColorChoice::Auto)
/// .get_matches();
/// # }
/// ```
#[default]
Auto,
/// Enables colored output regardless of whether or not the output is going to a terminal/TTY.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "color")] {
/// # use clap_builder as clap;
/// # use clap::{Command, ColorChoice};
/// Command::new("myprog")
/// .color(ColorChoice::Always)
/// .get_matches();
/// # }
/// ```
Always,
/// Disables colored output no matter if the output is going to a terminal/TTY, or not.
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "color")] {
/// # use clap_builder as clap;
/// # use clap::{Command, ColorChoice};
/// Command::new("myprog")
/// .color(ColorChoice::Never)
/// .get_matches();
/// # }
/// ```
Never,
}
impl ColorChoice {
/// Report all `possible_values`
pub fn possible_values() -> impl Iterator<Item = PossibleValue> {
Self::value_variants()
.iter()
.filter_map(ValueEnum::to_possible_value)
}
}
impl std::fmt::Display for ColorChoice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value()
.expect("no values are skipped")
.get_name()
.fmt(f)
}
}
impl std::str::FromStr for ColorChoice {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for variant in Self::value_variants() {
if variant.to_possible_value().unwrap().matches(s, false) {
return Ok(*variant);
}
}
Err(format!("invalid variant: {s}"))
}
}
impl ValueEnum for ColorChoice {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Auto, Self::Always, Self::Never]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(match self {
Self::Auto => PossibleValue::new("auto"),
Self::Always => PossibleValue::new("always"),
Self::Never => PossibleValue::new("never"),
})
}
}

View File

@@ -0,0 +1,29 @@
#[cfg(feature = "help")]
use std::borrow::Cow;
pub(crate) struct Escape<'s>(pub(crate) &'s str);
impl<'s> Escape<'s> {
pub(crate) fn needs_escaping(&self) -> bool {
self.0.is_empty() || self.0.contains(char::is_whitespace)
}
#[cfg(feature = "help")]
pub(crate) fn to_cow(&self) -> Cow<'s, str> {
if self.needs_escaping() {
Cow::Owned(format!("{:?}", self.0))
} else {
Cow::Borrowed(self.0)
}
}
}
impl std::fmt::Display for Escape<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.needs_escaping() {
std::fmt::Debug::fmt(self.0, f)
} else {
self.0.fmt(f)
}
}
}

View File

@@ -0,0 +1,259 @@
#![allow(dead_code)]
use std::borrow::Borrow;
/// Flat (Vec) backed map
///
/// This preserves insertion order
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FlatMap<K, V> {
keys: Vec<K>,
values: Vec<V>,
}
impl<K: PartialEq + Eq, V> FlatMap<K, V> {
pub(crate) fn new() -> Self {
Default::default()
}
pub(crate) fn insert(&mut self, key: K, mut value: V) -> Option<V> {
for (index, existing) in self.keys.iter().enumerate() {
if *existing == key {
std::mem::swap(&mut self.values[index], &mut value);
return Some(value);
}
}
self.insert_unchecked(key, value);
None
}
pub(crate) fn insert_unchecked(&mut self, key: K, value: V) {
self.keys.push(key);
self.values.push(value);
}
pub(crate) fn extend_unchecked(&mut self, iter: impl IntoIterator<Item = (K, V)>) {
for (key, value) in iter {
self.insert_unchecked(key, value);
}
}
pub(crate) fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Eq,
{
for existing in &self.keys {
if existing.borrow() == key {
return true;
}
}
false
}
pub(crate) fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: std::hash::Hash + Eq,
{
self.remove_entry(key).map(|(_, v)| v)
}
pub(crate) fn remove_entry<Q: ?Sized>(&mut self, key: &Q) -> Option<(K, V)>
where
K: Borrow<Q>,
Q: std::hash::Hash + Eq,
{
let index = some!(
self.keys
.iter()
.enumerate()
.find_map(|(i, k)| (k.borrow() == key).then_some(i))
);
let key = self.keys.remove(index);
let value = self.values.remove(index);
Some((key, value))
}
pub(crate) fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub(crate) fn entry(&mut self, key: K) -> Entry<'_, K, V> {
for (index, existing) in self.keys.iter().enumerate() {
if *existing == key {
return Entry::Occupied(OccupiedEntry { v: self, index });
}
}
Entry::Vacant(VacantEntry { v: self, key })
}
pub(crate) fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Eq,
{
for (index, existing) in self.keys.iter().enumerate() {
if existing.borrow() == k {
return Some(&self.values[index]);
}
}
None
}
pub(crate) fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Eq,
{
for (index, existing) in self.keys.iter().enumerate() {
if existing.borrow() == k {
return Some(&mut self.values[index]);
}
}
None
}
pub(crate) fn keys(&self) -> std::slice::Iter<'_, K> {
self.keys.iter()
}
pub(crate) fn values(&self) -> std::slice::Iter<'_, V> {
self.values.iter()
}
pub(crate) fn iter(&self) -> Iter<'_, K, V> {
Iter {
keys: self.keys.iter(),
values: self.values.iter(),
}
}
pub(crate) fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut {
keys: self.keys.iter_mut(),
values: self.values.iter_mut(),
}
}
}
impl<K: PartialEq + Eq, V> Default for FlatMap<K, V> {
fn default() -> Self {
Self {
keys: Default::default(),
values: Default::default(),
}
}
}
pub(crate) enum Entry<'a, K, V> {
Vacant(VacantEntry<'a, K, V>),
Occupied(OccupiedEntry<'a, K, V>),
}
impl<'a, K: 'a, V: 'a> Entry<'a, K, V> {
pub(crate) fn or_insert(self, default: V) -> &'a mut V {
match self {
Entry::Occupied(entry) => &mut entry.v.values[entry.index],
Entry::Vacant(entry) => {
entry.v.keys.push(entry.key);
entry.v.values.push(default);
entry.v.values.last_mut().unwrap()
}
}
}
pub(crate) fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
match self {
Entry::Occupied(entry) => &mut entry.v.values[entry.index],
Entry::Vacant(entry) => {
entry.v.keys.push(entry.key);
entry.v.values.push(default());
entry.v.values.last_mut().unwrap()
}
}
}
}
pub(crate) struct VacantEntry<'a, K, V> {
v: &'a mut FlatMap<K, V>,
key: K,
}
pub(crate) struct OccupiedEntry<'a, K, V> {
v: &'a mut FlatMap<K, V>,
index: usize,
}
pub(crate) struct Iter<'a, K, V> {
keys: std::slice::Iter<'a, K>,
values: std::slice::Iter<'a, V>,
}
impl<'a, K, V> Iterator for Iter<'a, K, V> {
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<(&'a K, &'a V)> {
match self.keys.next() {
Some(k) => {
let v = self.values.next().unwrap();
Some((k, v))
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.keys.size_hint()
}
}
impl<'a, K, V> DoubleEndedIterator for Iter<'a, K, V> {
fn next_back(&mut self) -> Option<(&'a K, &'a V)> {
match self.keys.next_back() {
Some(k) => {
let v = self.values.next_back().unwrap();
Some((k, v))
}
None => None,
}
}
}
impl<K, V> ExactSizeIterator for Iter<'_, K, V> {}
pub(crate) struct IterMut<'a, K, V> {
keys: std::slice::IterMut<'a, K>,
values: std::slice::IterMut<'a, V>,
}
impl<'a, K, V> Iterator for IterMut<'a, K, V> {
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<(&'a K, &'a mut V)> {
match self.keys.next() {
Some(k) => {
let v = self.values.next().unwrap();
Some((k, v))
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.keys.size_hint()
}
}
impl<'a, K, V> DoubleEndedIterator for IterMut<'a, K, V> {
fn next_back(&mut self) -> Option<(&'a K, &'a mut V)> {
match self.keys.next_back() {
Some(k) => {
let v = self.values.next_back().unwrap();
Some((k, v))
}
None => None,
}
}
}
impl<K, V> ExactSizeIterator for IterMut<'_, K, V> {}

View File

@@ -0,0 +1,111 @@
#![allow(dead_code)]
use std::borrow::Borrow;
/// Flat (Vec) backed set
///
/// This preserves insertion order
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FlatSet<T> {
inner: Vec<T>,
}
impl<T: PartialEq + Eq> FlatSet<T> {
pub(crate) fn new() -> Self {
Default::default()
}
pub(crate) fn insert(&mut self, value: T) -> bool {
for existing in &self.inner {
if *existing == value {
return false;
}
}
self.inner.push(value);
true
}
pub(crate) fn contains<Q: ?Sized>(&self, value: &Q) -> bool
where
T: Borrow<Q>,
Q: Eq,
{
for existing in &self.inner {
if existing.borrow() == value {
return true;
}
}
false
}
pub(crate) fn retain<F>(&mut self, f: F)
where
F: FnMut(&T) -> bool,
{
self.inner.retain(f);
}
pub(crate) fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub(crate) fn iter(&self) -> std::slice::Iter<'_, T> {
self.inner.iter()
}
pub(crate) fn sort_by_key<K, F>(&mut self, f: F)
where
F: FnMut(&T) -> K,
K: Ord,
{
self.inner.sort_by_key(f);
}
pub(crate) fn into_vec(self) -> Vec<T> {
self.inner
}
}
impl<T: PartialEq + Eq> Default for FlatSet<T> {
fn default() -> Self {
Self {
inner: Default::default(),
}
}
}
impl<T: PartialEq + Eq> IntoIterator for FlatSet<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
impl<'s, T: PartialEq + Eq> IntoIterator for &'s FlatSet<T> {
type Item = &'s T;
type IntoIter = std::slice::Iter<'s, T>;
fn into_iter(self) -> Self::IntoIter {
self.inner.iter()
}
}
impl<T: PartialEq + Eq> Extend<T> for FlatSet<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for value in iter {
self.insert(value);
}
}
}
impl<T: PartialEq + Eq> FromIterator<T> for FlatSet<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut set = Self::new();
for value in iter {
set.insert(value);
}
set
}
}

View File

@@ -0,0 +1,49 @@
#[derive(Debug)]
struct Child<T> {
id: T,
children: Vec<usize>,
}
impl<T> Child<T> {
fn new(id: T) -> Self {
Self {
id,
children: vec![],
}
}
}
#[derive(Debug)]
pub(crate) struct ChildGraph<T>(Vec<Child<T>>);
impl<T> ChildGraph<T>
where
T: Sized + PartialEq + Clone,
{
pub(crate) fn with_capacity(s: usize) -> Self {
Self(Vec::with_capacity(s))
}
pub(crate) fn insert(&mut self, req: T) -> usize {
self.0.iter().position(|e| e.id == req).unwrap_or_else(|| {
let idx = self.0.len();
self.0.push(Child::new(req));
idx
})
}
pub(crate) fn insert_child(&mut self, parent: usize, child: T) -> usize {
let c_idx = self.0.len();
self.0.push(Child::new(child));
self.0[parent].children.push(c_idx);
c_idx
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &T> {
self.0.iter().map(|r| &r.id)
}
pub(crate) fn contains(&self, req: &T) -> bool {
self.0.iter().any(|r| r.id == *req)
}
}

View File

@@ -0,0 +1,195 @@
use crate::builder::Str;
#[cfg(feature = "string")]
use std::borrow::Cow;
/// [`Arg`][crate::Arg] or [`ArgGroup`][crate::ArgGroup] identifier
///
/// This is used for accessing the value in [`ArgMatches`][crate::ArgMatches] or defining
/// relationships between `Arg`s and `ArgGroup`s with functions like
/// [`Arg::conflicts_with`][crate::Arg::conflicts_with].
#[derive(Default, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Id(Str);
impl Id {
pub(crate) const HELP: &'static str = "help";
pub(crate) const VERSION: &'static str = "version";
pub(crate) const EXTERNAL: &'static str = "";
pub(crate) fn from_static_ref(name: &'static str) -> Self {
Self(Str::from_static_ref(name))
}
/// Get the raw string of the `Id`
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub(crate) fn as_internal_str(&self) -> &Str {
&self.0
}
}
impl From<&'_ Self> for Id {
fn from(id: &'_ Self) -> Self {
id.clone()
}
}
impl From<Str> for Id {
fn from(name: Str) -> Self {
Self(name)
}
}
impl From<&'_ Str> for Id {
fn from(name: &'_ Str) -> Self {
Self(name.into())
}
}
#[cfg(feature = "string")]
impl From<String> for Id {
fn from(name: String) -> Self {
Self(name.into())
}
}
#[cfg(feature = "string")]
impl From<&'_ String> for Id {
fn from(name: &'_ String) -> Self {
Self(name.into())
}
}
impl From<&'static str> for Id {
fn from(name: &'static str) -> Self {
Self(name.into())
}
}
impl From<&'_ &'static str> for Id {
fn from(name: &'_ &'static str) -> Self {
Self(name.into())
}
}
impl From<Id> for Str {
fn from(name: Id) -> Self {
name.0
}
}
#[cfg(feature = "string")]
impl From<Cow<'static, str>> for Id {
fn from(name: Cow<'static, str>) -> Self {
Self(name.into())
}
}
impl From<Id> for String {
fn from(name: Id) -> Self {
Str::from(name).into()
}
}
impl std::fmt::Display for Id {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.as_str(), f)
}
}
impl std::fmt::Debug for Id {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_str(), f)
}
}
impl AsRef<str> for Id {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::borrow::Borrow<str> for Id {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl PartialEq<str> for Id {
#[inline]
fn eq(&self, other: &str) -> bool {
PartialEq::eq(self.as_str(), other)
}
}
impl PartialEq<Id> for str {
#[inline]
fn eq(&self, other: &Id) -> bool {
PartialEq::eq(self, other.as_str())
}
}
impl PartialEq<&'_ str> for Id {
#[inline]
fn eq(&self, other: &&str) -> bool {
PartialEq::eq(self.as_str(), *other)
}
}
impl PartialEq<Id> for &'_ str {
#[inline]
fn eq(&self, other: &Id) -> bool {
PartialEq::eq(*self, other.as_str())
}
}
impl PartialEq<Str> for Id {
#[inline]
fn eq(&self, other: &Str) -> bool {
PartialEq::eq(self.as_str(), other.as_str())
}
}
impl PartialEq<Id> for Str {
#[inline]
fn eq(&self, other: &Id) -> bool {
PartialEq::eq(self.as_str(), other.as_str())
}
}
impl PartialEq<String> for Id {
#[inline]
fn eq(&self, other: &String) -> bool {
PartialEq::eq(self.as_str(), other.as_str())
}
}
impl PartialEq<Id> for String {
#[inline]
fn eq(&self, other: &Id) -> bool {
PartialEq::eq(other, self)
}
}
#[cfg(test)]
#[cfg(feature = "string")]
mod tests {
use super::*;
#[test]
#[cfg(feature = "string")]
fn from_cow_borrowed() {
let cow = Cow::Borrowed("hello");
let id = Id::from(cow);
assert_eq!(id, Id::from("hello"));
}
#[test]
#[cfg(feature = "string")]
fn from_cow_owned() {
let cow = Cow::Owned("world".to_owned());
let id = Id::from(cow);
assert_eq!(id, Id::from("world"));
}
}

View File

@@ -0,0 +1,40 @@
#![allow(clippy::single_component_path_imports)]
mod any_value;
mod escape;
pub(crate) mod flat_map;
pub(crate) mod flat_set;
mod graph;
mod id;
mod str_to_bool;
pub use self::id::Id;
pub(crate) use self::any_value::AnyValue;
pub(crate) use self::any_value::AnyValueId;
pub(crate) use self::escape::Escape;
pub(crate) use self::flat_map::Entry;
pub(crate) use self::flat_map::FlatMap;
pub(crate) use self::flat_set::FlatSet;
pub(crate) use self::graph::ChildGraph;
pub(crate) use self::str_to_bool::FALSE_LITERALS;
pub(crate) use self::str_to_bool::TRUE_LITERALS;
pub(crate) use self::str_to_bool::str_to_bool;
pub(crate) mod color;
pub(crate) const SUCCESS_CODE: i32 = 0;
// While sysexists.h defines EX_USAGE as 64, this doesn't seem to be used much in practice but
// instead 2 seems to be frequently used.
// Examples
// - GNU `ls` returns 2
// - Python's `argparse` returns 2
pub(crate) const USAGE_CODE: i32 = 2;
#[cfg(not(feature = "unicode"))]
pub(crate) fn eq_ignore_case(left: &str, right: &str) -> bool {
left.eq_ignore_ascii_case(right)
}
#[cfg(feature = "unicode")]
pub(crate) use unicase::eq as eq_ignore_case;

View File

@@ -0,0 +1,21 @@
/// True values are `y`, `yes`, `t`, `true`, `on`, and `1`.
pub(crate) const TRUE_LITERALS: [&str; 6] = ["y", "yes", "t", "true", "on", "1"];
/// False values are `n`, `no`, `f`, `false`, `off`, and `0`.
pub(crate) const FALSE_LITERALS: [&str; 6] = ["n", "no", "f", "false", "off", "0"];
/// Converts a string literal representation of truth to true or false.
///
/// `false` values are `n`, `no`, `f`, `false`, `off`, and `0` (case insensitive).
///
/// Any other value will be considered as `true`.
pub(crate) fn str_to_bool(val: impl AsRef<str>) -> Option<bool> {
let pat: &str = &val.as_ref().to_lowercase();
if TRUE_LITERALS.contains(&pat) {
Some(true)
} else if FALSE_LITERALS.contains(&pat) {
Some(false)
} else {
None
}
}