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,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);
}