diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/clap_builder/src/error/context.rs | 114 | ||||
-rw-r--r-- | third_party/rust/clap_builder/src/error/format.rs | 545 | ||||
-rw-r--r-- | third_party/rust/clap_builder/src/error/kind.rs | 366 | ||||
-rw-r--r-- | third_party/rust/clap_builder/src/error/mod.rs | 923 |
4 files changed, 1948 insertions, 0 deletions
diff --git a/third_party/rust/clap_builder/src/error/context.rs b/third_party/rust/clap_builder/src/error/context.rs new file mode 100644 index 0000000000..045923c477 --- /dev/null +++ b/third_party/rust/clap_builder/src/error/context.rs @@ -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), + } + } +} diff --git a/third_party/rust/clap_builder/src/error/format.rs b/third_party/rust/clap_builder/src/error/format.rs new file mode 100644 index 0000000000..49e617d071 --- /dev/null +++ b/third_party/rust/clap_builder/src/error/format.rs @@ -0,0 +1,545 @@ +#![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 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; + +/// 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. +/// +/// **NOTE:** Consider removing the `error-context` default feature if using this to remove all +/// overhead for [`RichFormatter`]. +#[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}{}tip:{} ", + valid.render(), + valid.render_reset() + ); + 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); + + styled + } +} + +fn start_error(styled: &mut StyledStr, styles: &Styles) { + use std::fmt::Write as _; + let error = &styles.get_error(); + let _ = write!(styled, "{}error:{} ", error.render(), error.render_reset()); +} + +#[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 invalid_arg = error.get(ContextKind::InvalidArg); + let prior_arg = error.get(ContextKind::PriorArg); + if let (Some(ContextValue::String(invalid_arg)), Some(prior_arg)) = + (invalid_arg, prior_arg) + { + if ContextValue::String(invalid_arg.clone()) == *prior_arg { + let _ = write!( + styled, + "the argument '{}{invalid_arg}{}' cannot be used multiple times", + invalid.render(), + invalid.render_reset() + ); + } else { + let _ = write!( + styled, + "the argument '{}{invalid_arg}{}' cannot be used with", + invalid.render(), + invalid.render_reset() + ); + + match prior_arg { + ContextValue::Strings(values) => { + styled.push_str(":"); + for v in values { + let _ = write!( + styled, + "\n{TAB}{}{v}{}", + invalid.render(), + invalid.render_reset() + ); + } + } + ContextValue::String(value) => { + let _ = write!( + styled, + " '{}{value}{}'", + invalid.render(), + invalid.render_reset() + ); + } + _ => { + styled.push_str(" one or more of the other specified arguments"); + } + } + } + true + } else { + false + } + } + 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_arg}{}'", + invalid.render(), + invalid.render_reset() + ); + 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_arg}{}' but none was supplied", + invalid.render(), + invalid.render_reset() + ); + } else { + let _ = write!( + styled, + "invalid value '{}{invalid_value}{}' for '{}{invalid_arg}{}'", + invalid.render(), + invalid.render_reset(), + literal.render(), + literal.render_reset() + ); + } + + 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_sub}{}'", + invalid.render(), + invalid.render_reset() + ); + 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}{}{v}{}", + valid.render(), + valid.render_reset() + ); + } + 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_sub}{}' requires a subcommand but one was not provided", + invalid.render(), + invalid.render_reset() + ); + 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_value}{}' for '{}{invalid_arg}{}' found; no more were expected", + invalid.render(), + invalid.render_reset(), + literal.render(), + literal.render_reset(), + ); + 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, + "{}{min_values}{} more values required by '{}{invalid_arg}{}'; only {}{actual_num_values}{}{were_provided}", + valid.render(), + valid.render_reset(), + literal.render(), + literal.render_reset(), + invalid.render(), + invalid.render_reset(), + ); + 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_value}{}' for '{}{invalid_arg}{}'", + invalid.render(), + invalid.render_reset(), + literal.render(), + literal.render_reset(), + ); + 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, + "{}{num_values}{} values required for '{}{invalid_arg}{}' but {}{actual_num_values}{}{were_provided}", + valid.render(), + valid.render_reset(), + literal.render(), + literal.render_reset(), + invalid.render(), + invalid.render_reset(), + ); + 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_arg}{}' found", + invalid.render(), + invalid.render_reset(), + ); + 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}: "); + + let style = valid.render(); + let reset = valid.render_reset(); + for (idx, val) in possible_values.iter().enumerate() { + if idx > 0 { + styled.push_str(", "); + } + let _ = write!(styled, "{style}{}{reset}", 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)); + } + 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<&'static str> { + if !cmd.is_disable_help_flag_set() { + Some("--help") + } else if cmd.has_subcommands() && !cmd.is_disable_help_subcommand_set() { + Some("help") + } else { + None + } +} + +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 '{}{help}{}'.\n", + literal.render(), + literal.render_reset() + ); + } else { + styled.push_str("\n"); + } +} + +#[cfg(feature = "error-context")] +fn did_you_mean(styled: &mut StyledStr, styles: &Styles, context: &str, valid: &ContextValue) { + use std::fmt::Write as _; + + let _ = write!( + styled, + "{TAB}{}tip:{}", + styles.get_valid().render(), + styles.get_valid().render_reset() + ); + if let ContextValue::String(valid) = valid { + let _ = write!( + styled, + " a similar {context} exists: '{}{valid}{}'", + styles.get_valid().render(), + styles.get_valid().render_reset() + ); + } else if let ContextValue::Strings(valid) = valid { + if valid.len() == 1 { + let _ = write!(styled, " a similar {context} exists: ",); + } else { + let _ = write!(styled, " some similar {context}s exist: ",); + } + for (i, valid) in valid.iter().enumerate() { + if i != 0 { + styled.push_str(", "); + } + let _ = write!( + styled, + "'{}{valid}{}'", + styles.get_valid().render(), + styles.get_valid().render_reset() + ); + } + } +} + +struct Escape<'s>(&'s str); + +impl<'s> std::fmt::Display for Escape<'s> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.0.contains(char::is_whitespace) { + std::fmt::Debug::fmt(self.0, f) + } else { + self.0.fmt(f) + } + } +} diff --git a/third_party/rust/clap_builder/src/error/kind.rs b/third_party/rust/clap_builder/src/error/kind.rs new file mode 100644 index 0000000000..a9d576c6f5 --- /dev/null +++ b/third_party/rust/clap_builder/src/error/kind.rs @@ -0,0 +1,366 @@ +/// 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))`] 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); + /// # } + /// ``` + /// + /// [`Arg::allow_invalid_utf8`]: crate::Arg::allow_invalid_utf8 + /// [`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) + } +} diff --git a/third_party/rust/clap_builder/src/error/mod.rs b/third_party/rust/clap_builder/src/error/mod.rs new file mode 100644 index 0000000000..af90e2756f --- /dev/null +++ b/third_party/rust/clap_builder/src/error/mod.rs @@ -0,0 +1,923 @@ +//! 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::{self}, + result::Result as StdResult, +}; + +// Internal +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::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE}; +use crate::Command; + +#[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<&'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 std::fmt::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 + #[inline(never)] + #[cfg(feature = "error-context")] + pub fn insert(&mut self, kind: ContextKind, value: ContextValue) -> Option<ContextValue> { + self.inner.context.insert(kind, value) + } + + /// 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(); + safe_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<&'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 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).to_owned()).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 '{}{subcmd}{}' as a value, use '{}{name} -- {subcmd}{}'", + invalid.render(), + invalid.render_reset(), + valid.render(), + valid.render_reset() + ); + 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 '{}{arg}{}' as a value, use '{}-- {arg}{}'", + invalid.render(), + invalid.render_reset(), + valid.render(), + valid.render_reset() + ); + 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, + "'{}{sub} {flag}{}' exists", + valid.render(), + valid.render_reset() + ); + 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 '{}{arg}{}' exists; to use it, remove the '{}--{}' before it", + valid.render(), + valid.render_reset(), + invalid.render(), + invalid.render_reset() + ); + + 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 { + Error::raw(ErrorKind::Io, e) + } +} + +impl<F: ErrorFormatter> From<fmt::Error> for Error<F> { + fn from(e: fmt::Error) -> Self { + Error::raw(ErrorKind::Format, e) + } +} + +impl<F: ErrorFormatter> std::fmt::Debug for Error<F> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::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 { + Message::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); + } + Message::Formatted(_) => {} + } + } + + fn formatted(&self, styles: &Styles) -> Cow<StyledStr> { + match self { + Message::Raw(s) => { + let styled = format::format_error_message(s, styles, None, None); + + Cow::Owned(styled) + } + Message::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); +} |