mirror of
https://github.com/clap-rs/clap
synced 2025-01-07 10:18:48 +00:00
perf(error): Allow custmizing formatting
For now, there isn't much a custom implementation can do. Going from `Rich` to `Null` drops about 6 KiB from the binary This is a part of #1365 and #1384
This commit is contained in:
parent
d16a531e54
commit
ef5f9f956a
7 changed files with 638 additions and 382 deletions
|
@ -60,6 +60,7 @@ MSRV is now 1.60.0
|
||||||
|
|
||||||
- `Arg::num_args` now accepts ranges, allowing setting both the minimum and maximum number of values per occurrence
|
- `Arg::num_args` now accepts ranges, allowing setting both the minimum and maximum number of values per occurrence
|
||||||
- Added `TypedValueParser::map` to make it easier to reuse existing value parsers
|
- Added `TypedValueParser::map` to make it easier to reuse existing value parsers
|
||||||
|
- *(error)* `Error::apply` for changing the formatter for dropping binary size
|
||||||
- *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312)
|
- *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312)
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub use possible_value::PossibleValue;
|
||||||
pub use range::ValueRange;
|
pub use range::ValueRange;
|
||||||
pub use resettable::IntoResettable;
|
pub use resettable::IntoResettable;
|
||||||
pub use resettable::Resettable;
|
pub use resettable::Resettable;
|
||||||
|
pub use styled_str::StyledStr;
|
||||||
pub use value_hint::ValueHint;
|
pub use value_hint::ValueHint;
|
||||||
pub use value_parser::_AutoValueParser;
|
pub use value_parser::_AutoValueParser;
|
||||||
pub use value_parser::via_prelude;
|
pub use value_parser::via_prelude;
|
||||||
|
@ -55,7 +56,6 @@ pub use value_parser::_AnonymousValueParser;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub(crate) use self::str::Inner as StrInner;
|
pub(crate) use self::str::Inner as StrInner;
|
||||||
pub(crate) use self::styled_str::StyledStr;
|
|
||||||
pub(crate) use action::CountType;
|
pub(crate) use action::CountType;
|
||||||
pub(crate) use arg::render_arg_val;
|
pub(crate) use arg::render_arg_val;
|
||||||
pub(crate) use arg_settings::{ArgFlags, ArgSettings};
|
pub(crate) use arg_settings::{ArgFlags, ArgSettings};
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
/// Terminal-styling container
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||||
pub(crate) struct StyledStr {
|
pub struct StyledStr {
|
||||||
pieces: Vec<(Option<Style>, String)>,
|
pieces: Vec<(Option<Style>, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledStr {
|
impl StyledStr {
|
||||||
pub(crate) const fn new() -> Self {
|
/// Create an empty buffer
|
||||||
|
pub const fn new() -> Self {
|
||||||
Self { pieces: Vec::new() }
|
Self { pieces: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,35 @@ pub enum ContextKind {
|
||||||
Custom,
|
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::ValidValue => Some("Value 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::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
|
/// A piece of error information
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
@ -49,3 +78,15 @@ pub enum ContextValue {
|
||||||
/// A single value
|
/// A single value
|
||||||
Number(isize),
|
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::Number(v) => v.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
448
src/error/format.rs
Normal file
448
src/error/format.rs
Normal file
|
@ -0,0 +1,448 @@
|
||||||
|
#![allow(missing_copy_implementations)]
|
||||||
|
#![allow(missing_debug_implementations)]
|
||||||
|
|
||||||
|
use crate::builder::Command;
|
||||||
|
use crate::builder::StyledStr;
|
||||||
|
use crate::error::ContextKind;
|
||||||
|
use crate::error::ContextValue;
|
||||||
|
use crate::error::ErrorKind;
|
||||||
|
|
||||||
|
/// 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<Self>) -> StyledStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// No error information reported
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct NullFormatter;
|
||||||
|
|
||||||
|
impl ErrorFormatter for NullFormatter {
|
||||||
|
fn format_error(_error: &crate::Error<Self>) -> StyledStr {
|
||||||
|
StyledStr::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dump the error information reported
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct RawFormatter;
|
||||||
|
|
||||||
|
impl ErrorFormatter for RawFormatter {
|
||||||
|
fn format_error(error: &crate::Error<Self>) -> StyledStr {
|
||||||
|
let mut styled = StyledStr::new();
|
||||||
|
start_error(&mut styled);
|
||||||
|
if let Some(msg) = error.kind().as_str() {
|
||||||
|
styled.none(msg.to_owned());
|
||||||
|
} else if let Some(source) = error.inner.source.as_ref() {
|
||||||
|
styled.none(source.to_string());
|
||||||
|
} else {
|
||||||
|
styled.none("Unknown cause");
|
||||||
|
}
|
||||||
|
styled.none("\n");
|
||||||
|
|
||||||
|
if error.context().next().is_some() {
|
||||||
|
styled.none("\n");
|
||||||
|
}
|
||||||
|
for (kind, value) in error.context() {
|
||||||
|
if let Some(kind) = kind.as_str() {
|
||||||
|
styled.none(kind);
|
||||||
|
styled.none(": ");
|
||||||
|
styled.none(value.to_string());
|
||||||
|
} else {
|
||||||
|
styled.none(value.to_string());
|
||||||
|
}
|
||||||
|
styled.none("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
styled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default, rich error format
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct RichFormatter;
|
||||||
|
|
||||||
|
impl ErrorFormatter for RichFormatter {
|
||||||
|
fn format_error(error: &crate::Error<Self>) -> StyledStr {
|
||||||
|
let mut styled = StyledStr::new();
|
||||||
|
start_error(&mut styled);
|
||||||
|
|
||||||
|
if !write_dynamic_context(error, &mut styled) {
|
||||||
|
if let Some(msg) = error.kind().as_str() {
|
||||||
|
styled.none(msg.to_owned());
|
||||||
|
} else if let Some(source) = error.inner.source.as_ref() {
|
||||||
|
styled.none(source.to_string());
|
||||||
|
} else {
|
||||||
|
styled.none("Unknown cause");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let usage = error.get(ContextKind::Usage);
|
||||||
|
if let Some(ContextValue::String(usage)) = usage {
|
||||||
|
put_usage(&mut styled, usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
try_help(&mut styled, error.inner.help_flag);
|
||||||
|
|
||||||
|
styled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_error(styled: &mut StyledStr) {
|
||||||
|
styled.error("error:");
|
||||||
|
styled.none(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn write_dynamic_context(error: &crate::Error, styled: &mut StyledStr) -> bool {
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
styled.none("The argument '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
styled.none("' cannot be used with");
|
||||||
|
|
||||||
|
match prior_arg {
|
||||||
|
ContextValue::Strings(values) => {
|
||||||
|
styled.none(":");
|
||||||
|
for v in values {
|
||||||
|
styled.none("\n ");
|
||||||
|
styled.warning(&**v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextValue::String(value) => {
|
||||||
|
styled.none(" '");
|
||||||
|
styled.warning(value);
|
||||||
|
styled.none("'");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
styled.none(" 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 {
|
||||||
|
styled.none("Equal sign is needed when assigning values to '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
styled.none("'.");
|
||||||
|
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() {
|
||||||
|
styled.none("The argument '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
styled.none("' requires a value but none was supplied");
|
||||||
|
} else {
|
||||||
|
styled.none(quote(invalid_value));
|
||||||
|
styled.none(" isn't a valid value for '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
styled.none("'");
|
||||||
|
}
|
||||||
|
|
||||||
|
let possible_values = error.get(ContextKind::ValidValue);
|
||||||
|
if let Some(ContextValue::Strings(possible_values)) = possible_values {
|
||||||
|
if !possible_values.is_empty() {
|
||||||
|
styled.none("\n\t[possible values: ");
|
||||||
|
if let Some((last, elements)) = possible_values.split_last() {
|
||||||
|
for v in elements {
|
||||||
|
styled.good(escape(v));
|
||||||
|
styled.none(", ");
|
||||||
|
}
|
||||||
|
styled.good(escape(last));
|
||||||
|
}
|
||||||
|
styled.none("]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let suggestion = error.get(ContextKind::SuggestedValue);
|
||||||
|
if let Some(ContextValue::String(suggestion)) = suggestion {
|
||||||
|
styled.none("\n\n\tDid you mean ");
|
||||||
|
styled.good(quote(suggestion));
|
||||||
|
styled.none("?");
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorKind::InvalidSubcommand => {
|
||||||
|
let invalid_sub = error.get(ContextKind::InvalidSubcommand);
|
||||||
|
if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
|
||||||
|
styled.none("The subcommand '");
|
||||||
|
styled.warning(invalid_sub);
|
||||||
|
styled.none("' wasn't recognized");
|
||||||
|
|
||||||
|
let valid_sub = error.get(ContextKind::SuggestedSubcommand);
|
||||||
|
if let Some(ContextValue::String(valid_sub)) = valid_sub {
|
||||||
|
styled.none("\n\n\tDid you mean ");
|
||||||
|
styled.good(valid_sub);
|
||||||
|
styled.none("?");
|
||||||
|
}
|
||||||
|
|
||||||
|
let suggestion = error.get(ContextKind::SuggestedCommand);
|
||||||
|
if let Some(ContextValue::String(suggestion)) = suggestion {
|
||||||
|
styled.none(
|
||||||
|
"\n\nIf you believe you received this message in error, try re-running with '",
|
||||||
|
);
|
||||||
|
styled.good(suggestion);
|
||||||
|
styled.none("'");
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorKind::MissingRequiredArgument => {
|
||||||
|
let invalid_arg = error.get(ContextKind::InvalidArg);
|
||||||
|
if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg {
|
||||||
|
styled.none("The following required arguments were not provided:");
|
||||||
|
for v in invalid_arg {
|
||||||
|
styled.none("\n ");
|
||||||
|
styled.good(&**v);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorKind::MissingSubcommand => {
|
||||||
|
let invalid_sub = error.get(ContextKind::InvalidSubcommand);
|
||||||
|
if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
|
||||||
|
styled.none("'");
|
||||||
|
styled.warning(invalid_sub);
|
||||||
|
styled.none("' requires a subcommand but one was not provided");
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
styled.none("The value '");
|
||||||
|
styled.warning(invalid_value);
|
||||||
|
styled.none("' was provided to '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
styled.none("' but it wasn't expecting any more values");
|
||||||
|
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);
|
||||||
|
styled.none("The argument '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
styled.none("' requires at least ");
|
||||||
|
styled.warning(min_values.to_string());
|
||||||
|
styled.none(" values but only ");
|
||||||
|
styled.warning(actual_num_values.to_string());
|
||||||
|
styled.none(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)
|
||||||
|
{
|
||||||
|
styled.none("Invalid value ");
|
||||||
|
styled.warning(quote(invalid_value));
|
||||||
|
styled.none(" for '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
if let Some(source) = error.inner.source.as_deref() {
|
||||||
|
styled.none("': ");
|
||||||
|
styled.none(source.to_string());
|
||||||
|
} else {
|
||||||
|
styled.none("'");
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
styled.none("The argument '");
|
||||||
|
styled.warning(invalid_arg);
|
||||||
|
styled.none("' requires ");
|
||||||
|
styled.warning(num_values.to_string());
|
||||||
|
styled.none(" values, but ");
|
||||||
|
styled.warning(actual_num_values.to_string());
|
||||||
|
styled.none(were_provided);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorKind::UnknownArgument => {
|
||||||
|
let invalid_arg = error.get(ContextKind::InvalidArg);
|
||||||
|
if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
|
||||||
|
styled.none("Found argument '");
|
||||||
|
styled.warning(invalid_arg.to_string());
|
||||||
|
styled.none("' which wasn't expected, or isn't valid in this context");
|
||||||
|
|
||||||
|
let valid_sub = error.get(ContextKind::SuggestedSubcommand);
|
||||||
|
let valid_arg = error.get(ContextKind::SuggestedArg);
|
||||||
|
match (valid_sub, valid_arg) {
|
||||||
|
(
|
||||||
|
Some(ContextValue::String(valid_sub)),
|
||||||
|
Some(ContextValue::String(valid_arg)),
|
||||||
|
) => {
|
||||||
|
styled.none("\n\n\tDid you mean ");
|
||||||
|
styled.none("to put '");
|
||||||
|
styled.good(valid_arg);
|
||||||
|
styled.none("' after the subcommand '");
|
||||||
|
styled.good(valid_sub);
|
||||||
|
styled.none("'?");
|
||||||
|
}
|
||||||
|
(None, Some(ContextValue::String(valid_arg))) => {
|
||||||
|
styled.none("\n\n\tDid you mean '");
|
||||||
|
styled.good(valid_arg);
|
||||||
|
styled.none("'?");
|
||||||
|
}
|
||||||
|
(_, _) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let invalid_arg = error.get(ContextKind::InvalidArg);
|
||||||
|
if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
|
||||||
|
if invalid_arg.starts_with('-') {
|
||||||
|
styled.none(format!(
|
||||||
|
"\n\n\tIf you tried to supply `{}` as a value rather than a flag, use `-- {}`",
|
||||||
|
invalid_arg, invalid_arg
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let trailing_arg = error.get(ContextKind::TrailingArg);
|
||||||
|
if trailing_arg == Some(&ContextValue::Bool(true)) {
|
||||||
|
styled.none(format!(
|
||||||
|
"\n\n\tIf you tried to supply `{}` as a subcommand, remove the '--' before it.",
|
||||||
|
invalid_arg
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorKind::DisplayHelp
|
||||||
|
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
||||||
|
| ErrorKind::DisplayVersion
|
||||||
|
| ErrorKind::Io
|
||||||
|
| ErrorKind::Format => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn format_error_message(
|
||||||
|
message: &str,
|
||||||
|
cmd: Option<&Command>,
|
||||||
|
usage: Option<String>,
|
||||||
|
) -> StyledStr {
|
||||||
|
let mut styled = StyledStr::new();
|
||||||
|
start_error(&mut styled);
|
||||||
|
styled.none(message);
|
||||||
|
if let Some(usage) = usage {
|
||||||
|
put_usage(&mut styled, usage);
|
||||||
|
}
|
||||||
|
if let Some(cmd) = cmd {
|
||||||
|
try_help(&mut styled, 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: impl Into<String>) {
|
||||||
|
styled.none("\n\n");
|
||||||
|
styled.none(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, help: Option<&str>) {
|
||||||
|
if let Some(help) = help {
|
||||||
|
styled.none("\n\nFor more information try ");
|
||||||
|
styled.good(help.to_owned());
|
||||||
|
styled.none("\n");
|
||||||
|
} else {
|
||||||
|
styled.none("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quote(s: impl AsRef<str>) -> String {
|
||||||
|
let s = s.as_ref();
|
||||||
|
format!("{:?}", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape(s: impl AsRef<str>) -> String {
|
||||||
|
let s = s.as_ref();
|
||||||
|
if s.contains(char::is_whitespace) {
|
||||||
|
quote(s)
|
||||||
|
} else {
|
||||||
|
s.to_owned()
|
||||||
|
}
|
||||||
|
}
|
423
src/error/mod.rs
423
src/error/mod.rs
|
@ -20,10 +20,15 @@ use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE};
|
||||||
use crate::Command;
|
use crate::Command;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
|
mod format;
|
||||||
mod kind;
|
mod kind;
|
||||||
|
|
||||||
pub use context::ContextKind;
|
pub use context::ContextKind;
|
||||||
pub use context::ContextValue;
|
pub use context::ContextValue;
|
||||||
|
pub use format::ErrorFormatter;
|
||||||
|
pub use format::NullFormatter;
|
||||||
|
pub use format::RawFormatter;
|
||||||
|
pub use format::RichFormatter;
|
||||||
pub use kind::ErrorKind;
|
pub use kind::ErrorKind;
|
||||||
|
|
||||||
/// Short hand for [`Result`] type
|
/// Short hand for [`Result`] type
|
||||||
|
@ -36,9 +41,9 @@ pub type Result<T, E = Error> = StdResult<T, E>;
|
||||||
/// See [`Command::error`] to create an error.
|
/// See [`Command::error`] to create an error.
|
||||||
///
|
///
|
||||||
/// [`Command::error`]: crate::Command::error
|
/// [`Command::error`]: crate::Command::error
|
||||||
#[derive(Debug)]
|
pub struct Error<F: ErrorFormatter = RichFormatter> {
|
||||||
pub struct Error {
|
|
||||||
inner: Box<ErrorInner>,
|
inner: Box<ErrorInner>,
|
||||||
|
phantom: std::marker::PhantomData<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -53,7 +58,7 @@ struct ErrorInner {
|
||||||
backtrace: Option<Backtrace>,
|
backtrace: Option<Backtrace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl<F: ErrorFormatter> Error<F> {
|
||||||
/// Create an unformatted error
|
/// Create an unformatted error
|
||||||
///
|
///
|
||||||
/// This is for you need to pass the error up to
|
/// This is for you need to pass the error up to
|
||||||
|
@ -77,6 +82,28 @@ impl Error {
|
||||||
self.with_cmd(cmd)
|
self.with_cmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply an alternative formatter to the error
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use clap::Command;
|
||||||
|
/// # use clap::Arg;
|
||||||
|
/// # use clap::error::NullFormatter;
|
||||||
|
/// 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::<NullFormatter>())
|
||||||
|
/// .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
|
/// Type of error for programmatic processing
|
||||||
pub fn kind(&self) -> ErrorKind {
|
pub fn kind(&self) -> ErrorKind {
|
||||||
self.inner.kind
|
self.inner.kind
|
||||||
|
@ -162,6 +189,7 @@ impl Error {
|
||||||
color_help_when: ColorChoice::Never,
|
color_help_when: ColorChoice::Never,
|
||||||
backtrace: Backtrace::new(),
|
backtrace: Backtrace::new(),
|
||||||
}),
|
}),
|
||||||
|
phantom: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +201,7 @@ impl Error {
|
||||||
pub(crate) fn with_cmd(self, cmd: &Command) -> Self {
|
pub(crate) fn with_cmd(self, cmd: &Command) -> Self {
|
||||||
self.set_color(cmd.get_color())
|
self.set_color(cmd.get_color())
|
||||||
.set_colored_help(cmd.color_help())
|
.set_colored_help(cmd.color_help())
|
||||||
.set_help_flag(get_help_flag(cmd))
|
.set_help_flag(format::get_help_flag(cmd))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_message(mut self, message: impl Into<Message>) -> Self {
|
pub(crate) fn set_message(mut self, message: impl Into<Message>) -> Self {
|
||||||
|
@ -468,349 +496,38 @@ impl Error {
|
||||||
if let Some(message) = self.inner.message.as_ref() {
|
if let Some(message) = self.inner.message.as_ref() {
|
||||||
message.formatted()
|
message.formatted()
|
||||||
} else {
|
} else {
|
||||||
let mut styled = StyledStr::new();
|
let styled = F::format_error(self);
|
||||||
start_error(&mut styled);
|
|
||||||
|
|
||||||
if !self.write_dynamic_context(&mut styled) {
|
|
||||||
if let Some(msg) = self.kind().as_str() {
|
|
||||||
styled.none(msg.to_owned());
|
|
||||||
} else if let Some(source) = self.inner.source.as_ref() {
|
|
||||||
styled.none(source.to_string());
|
|
||||||
} else {
|
|
||||||
styled.none("Unknown cause");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let usage = self.get(ContextKind::Usage);
|
|
||||||
if let Some(ContextValue::String(usage)) = usage {
|
|
||||||
put_usage(&mut styled, usage);
|
|
||||||
}
|
|
||||||
|
|
||||||
try_help(&mut styled, self.inner.help_flag);
|
|
||||||
|
|
||||||
Cow::Owned(styled)
|
Cow::Owned(styled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
fn write_dynamic_context(&self, styled: &mut StyledStr) -> bool {
|
|
||||||
match self.kind() {
|
|
||||||
ErrorKind::ArgumentConflict => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
let prior_arg = self.get(ContextKind::PriorArg);
|
|
||||||
if let (Some(ContextValue::String(invalid_arg)), Some(prior_arg)) =
|
|
||||||
(invalid_arg, prior_arg)
|
|
||||||
{
|
|
||||||
styled.none("The argument '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
styled.none("' cannot be used with");
|
|
||||||
|
|
||||||
match prior_arg {
|
|
||||||
ContextValue::Strings(values) => {
|
|
||||||
styled.none(":");
|
|
||||||
for v in values {
|
|
||||||
styled.none("\n ");
|
|
||||||
styled.warning(&**v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ContextValue::String(value) => {
|
|
||||||
styled.none(" '");
|
|
||||||
styled.warning(value);
|
|
||||||
styled.none("'");
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
styled.none(" one or more of the other specified arguments");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::NoEquals => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
|
|
||||||
styled.none("Equal sign is needed when assigning values to '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
styled.none("'.");
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::InvalidValue => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
let invalid_value = self.get(ContextKind::InvalidValue);
|
|
||||||
if let (
|
|
||||||
Some(ContextValue::String(invalid_arg)),
|
|
||||||
Some(ContextValue::String(invalid_value)),
|
|
||||||
) = (invalid_arg, invalid_value)
|
|
||||||
{
|
|
||||||
if invalid_value.is_empty() {
|
|
||||||
styled.none("The argument '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
styled.none("' requires a value but none was supplied");
|
|
||||||
} else {
|
|
||||||
styled.none(quote(invalid_value));
|
|
||||||
styled.none(" isn't a valid value for '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
styled.none("'");
|
|
||||||
}
|
|
||||||
|
|
||||||
let possible_values = self.get(ContextKind::ValidValue);
|
|
||||||
if let Some(ContextValue::Strings(possible_values)) = possible_values {
|
|
||||||
if !possible_values.is_empty() {
|
|
||||||
styled.none("\n\t[possible values: ");
|
|
||||||
if let Some((last, elements)) = possible_values.split_last() {
|
|
||||||
for v in elements {
|
|
||||||
styled.good(escape(v));
|
|
||||||
styled.none(", ");
|
|
||||||
}
|
|
||||||
styled.good(escape(last));
|
|
||||||
}
|
|
||||||
styled.none("]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let suggestion = self.get(ContextKind::SuggestedValue);
|
|
||||||
if let Some(ContextValue::String(suggestion)) = suggestion {
|
|
||||||
styled.none("\n\n\tDid you mean ");
|
|
||||||
styled.good(quote(suggestion));
|
|
||||||
styled.none("?");
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::InvalidSubcommand => {
|
|
||||||
let invalid_sub = self.get(ContextKind::InvalidSubcommand);
|
|
||||||
if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
|
|
||||||
styled.none("The subcommand '");
|
|
||||||
styled.warning(invalid_sub);
|
|
||||||
styled.none("' wasn't recognized");
|
|
||||||
|
|
||||||
let valid_sub = self.get(ContextKind::SuggestedSubcommand);
|
|
||||||
if let Some(ContextValue::String(valid_sub)) = valid_sub {
|
|
||||||
styled.none("\n\n\tDid you mean ");
|
|
||||||
styled.good(valid_sub);
|
|
||||||
styled.none("?");
|
|
||||||
}
|
|
||||||
|
|
||||||
let suggestion = self.get(ContextKind::SuggestedCommand);
|
|
||||||
if let Some(ContextValue::String(suggestion)) = suggestion {
|
|
||||||
styled.none(
|
|
||||||
"\n\nIf you believe you received this message in error, try re-running with '",
|
|
||||||
);
|
|
||||||
styled.good(suggestion);
|
|
||||||
styled.none("'");
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::MissingRequiredArgument => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg {
|
|
||||||
styled.none("The following required arguments were not provided:");
|
|
||||||
for v in invalid_arg {
|
|
||||||
styled.none("\n ");
|
|
||||||
styled.good(&**v);
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::MissingSubcommand => {
|
|
||||||
let invalid_sub = self.get(ContextKind::InvalidSubcommand);
|
|
||||||
if let Some(ContextValue::String(invalid_sub)) = invalid_sub {
|
|
||||||
styled.none("'");
|
|
||||||
styled.warning(invalid_sub);
|
|
||||||
styled.none("' requires a subcommand but one was not provided");
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::InvalidUtf8 => false,
|
|
||||||
ErrorKind::TooManyValues => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
let invalid_value = self.get(ContextKind::InvalidValue);
|
|
||||||
if let (
|
|
||||||
Some(ContextValue::String(invalid_arg)),
|
|
||||||
Some(ContextValue::String(invalid_value)),
|
|
||||||
) = (invalid_arg, invalid_value)
|
|
||||||
{
|
|
||||||
styled.none("The value '");
|
|
||||||
styled.warning(invalid_value);
|
|
||||||
styled.none("' was provided to '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
styled.none("' but it wasn't expecting any more values");
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::TooFewValues => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
let actual_num_values = self.get(ContextKind::ActualNumValues);
|
|
||||||
let min_values = self.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 = Error::singular_or_plural(*actual_num_values as usize);
|
|
||||||
styled.none("The argument '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
styled.none("' requires at least ");
|
|
||||||
styled.warning(min_values.to_string());
|
|
||||||
styled.none(" values but only ");
|
|
||||||
styled.warning(actual_num_values.to_string());
|
|
||||||
styled.none(were_provided);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::ValueValidation => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
let invalid_value = self.get(ContextKind::InvalidValue);
|
|
||||||
if let (
|
|
||||||
Some(ContextValue::String(invalid_arg)),
|
|
||||||
Some(ContextValue::String(invalid_value)),
|
|
||||||
) = (invalid_arg, invalid_value)
|
|
||||||
{
|
|
||||||
styled.none("Invalid value ");
|
|
||||||
styled.warning(quote(invalid_value));
|
|
||||||
styled.none(" for '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
if let Some(source) = self.inner.source.as_deref() {
|
|
||||||
styled.none("': ");
|
|
||||||
styled.none(source.to_string());
|
|
||||||
} else {
|
|
||||||
styled.none("'");
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::WrongNumberOfValues => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
let actual_num_values = self.get(ContextKind::ActualNumValues);
|
|
||||||
let num_values = self.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 = Error::singular_or_plural(*actual_num_values as usize);
|
|
||||||
styled.none("The argument '");
|
|
||||||
styled.warning(invalid_arg);
|
|
||||||
styled.none("' requires ");
|
|
||||||
styled.warning(num_values.to_string());
|
|
||||||
styled.none(" values, but ");
|
|
||||||
styled.warning(actual_num_values.to_string());
|
|
||||||
styled.none(were_provided);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::UnknownArgument => {
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
|
|
||||||
styled.none("Found argument '");
|
|
||||||
styled.warning(invalid_arg.to_string());
|
|
||||||
styled.none("' which wasn't expected, or isn't valid in this context");
|
|
||||||
|
|
||||||
let valid_sub = self.get(ContextKind::SuggestedSubcommand);
|
|
||||||
let valid_arg = self.get(ContextKind::SuggestedArg);
|
|
||||||
match (valid_sub, valid_arg) {
|
|
||||||
(
|
|
||||||
Some(ContextValue::String(valid_sub)),
|
|
||||||
Some(ContextValue::String(valid_arg)),
|
|
||||||
) => {
|
|
||||||
styled.none("\n\n\tDid you mean ");
|
|
||||||
styled.none("to put '");
|
|
||||||
styled.good(valid_arg);
|
|
||||||
styled.none("' after the subcommand '");
|
|
||||||
styled.good(valid_sub);
|
|
||||||
styled.none("'?");
|
|
||||||
}
|
|
||||||
(None, Some(ContextValue::String(valid_arg))) => {
|
|
||||||
styled.none("\n\n\tDid you mean '");
|
|
||||||
styled.good(valid_arg);
|
|
||||||
styled.none("'?");
|
|
||||||
}
|
|
||||||
(_, _) => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let invalid_arg = self.get(ContextKind::InvalidArg);
|
|
||||||
if let Some(ContextValue::String(invalid_arg)) = invalid_arg {
|
|
||||||
if invalid_arg.starts_with('-') {
|
|
||||||
styled.none(format!(
|
|
||||||
"\n\n\tIf you tried to supply `{}` as a value rather than a flag, use `-- {}`",
|
|
||||||
invalid_arg, invalid_arg
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let trailing_arg = self.get(ContextKind::TrailingArg);
|
|
||||||
if trailing_arg == Some(&ContextValue::Bool(true)) {
|
|
||||||
styled.none(format!(
|
|
||||||
"\n\n\tIf you tried to supply `{}` as a subcommand, remove the '--' before it.",
|
|
||||||
invalid_arg
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorKind::DisplayHelp
|
|
||||||
| ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand
|
|
||||||
| ErrorKind::DisplayVersion
|
|
||||||
| ErrorKind::Io
|
|
||||||
| ErrorKind::Format => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
impl<F: ErrorFormatter> From<io::Error> for Error<F> {
|
||||||
fn from(e: io::Error) -> Self {
|
fn from(e: io::Error) -> Self {
|
||||||
Error::raw(ErrorKind::Io, e)
|
Error::raw(ErrorKind::Io, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<fmt::Error> for Error {
|
impl<F: ErrorFormatter> From<fmt::Error> for Error<F> {
|
||||||
fn from(e: fmt::Error) -> Self {
|
fn from(e: fmt::Error) -> Self {
|
||||||
Error::raw(ErrorKind::Format, e)
|
Error::raw(ErrorKind::Format, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for Error {
|
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)]
|
#[allow(trivial_casts)]
|
||||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
self.inner.source.as_ref().map(|e| e.as_ref() as _)
|
self.inner.source.as_ref().map(|e| e.as_ref() as _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl<F: ErrorFormatter> Display for Error<F> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
// Assuming `self.message` already has a trailing newline, from `try_help` or similar
|
// Assuming `self.message` already has a trailing newline, from `try_help` or similar
|
||||||
write!(f, "{}", self.formatted())?;
|
write!(f, "{}", self.formatted())?;
|
||||||
|
@ -823,50 +540,6 @@ impl Display for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_error(styled: &mut StyledStr) {
|
|
||||||
styled.error("error:");
|
|
||||||
styled.none(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn put_usage(styled: &mut StyledStr, usage: impl Into<String>) {
|
|
||||||
styled.none("\n\n");
|
|
||||||
styled.none(usage);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, help: Option<&str>) {
|
|
||||||
if let Some(help) = help {
|
|
||||||
styled.none("\n\nFor more information try ");
|
|
||||||
styled.good(help.to_owned());
|
|
||||||
styled.none("\n");
|
|
||||||
} else {
|
|
||||||
styled.none("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quote(s: impl AsRef<str>) -> String {
|
|
||||||
let s = s.as_ref();
|
|
||||||
format!("{:?}", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn escape(s: impl AsRef<str>) -> String {
|
|
||||||
let s = s.as_ref();
|
|
||||||
if s.contains(char::is_whitespace) {
|
|
||||||
quote(s)
|
|
||||||
} else {
|
|
||||||
s.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum Message {
|
pub(crate) enum Message {
|
||||||
Raw(String),
|
Raw(String),
|
||||||
|
@ -880,11 +553,7 @@ impl Message {
|
||||||
let mut message = String::new();
|
let mut message = String::new();
|
||||||
std::mem::swap(s, &mut message);
|
std::mem::swap(s, &mut message);
|
||||||
|
|
||||||
let mut styled = StyledStr::new();
|
let styled = format::format_error_message(&message, Some(cmd), Some(usage));
|
||||||
start_error(&mut styled);
|
|
||||||
styled.none(message);
|
|
||||||
put_usage(&mut styled, usage);
|
|
||||||
try_help(&mut styled, get_help_flag(cmd));
|
|
||||||
|
|
||||||
*self = Self::Formatted(styled);
|
*self = Self::Formatted(styled);
|
||||||
}
|
}
|
||||||
|
@ -895,9 +564,7 @@ impl Message {
|
||||||
fn formatted(&self) -> Cow<StyledStr> {
|
fn formatted(&self) -> Cow<StyledStr> {
|
||||||
match self {
|
match self {
|
||||||
Message::Raw(s) => {
|
Message::Raw(s) => {
|
||||||
let mut styled = StyledStr::new();
|
let styled = format::format_error_message(s, None, None);
|
||||||
start_error(&mut styled);
|
|
||||||
styled.none(s);
|
|
||||||
|
|
||||||
Cow::Owned(styled)
|
Cow::Owned(styled)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,12 @@ use super::utils;
|
||||||
use clap::{arg, error::ErrorKind, value_parser, Arg, Command, Error};
|
use clap::{arg, error::ErrorKind, value_parser, Arg, Command, Error};
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_error(err: Error, expected_kind: ErrorKind, expected_output: &str, stderr: bool) {
|
fn assert_error<F: clap::error::ErrorFormatter>(
|
||||||
|
err: Error<F>,
|
||||||
|
expected_kind: ErrorKind,
|
||||||
|
expected_output: &str,
|
||||||
|
stderr: bool,
|
||||||
|
) {
|
||||||
let actual_output = err.to_string();
|
let actual_output = err.to_string();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stderr,
|
stderr,
|
||||||
|
@ -73,3 +78,95 @@ fn value_validation_has_newline() {
|
||||||
err.to_string()
|
err.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_prints_help() {
|
||||||
|
let cmd = Command::new("test");
|
||||||
|
let res = cmd
|
||||||
|
.try_get_matches_from(["test", "--help"])
|
||||||
|
.map_err(|e| e.apply::<clap::error::NullFormatter>());
|
||||||
|
assert!(res.is_err());
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
let expected_kind = ErrorKind::DisplayHelp;
|
||||||
|
static MESSAGE: &str = "\
|
||||||
|
test
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
test
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Print help information
|
||||||
|
";
|
||||||
|
assert_error(err, expected_kind, MESSAGE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_prints_help() {
|
||||||
|
let cmd = Command::new("test");
|
||||||
|
let res = cmd
|
||||||
|
.try_get_matches_from(["test", "--help"])
|
||||||
|
.map_err(|e| e.apply::<clap::error::RawFormatter>());
|
||||||
|
assert!(res.is_err());
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
let expected_kind = ErrorKind::DisplayHelp;
|
||||||
|
static MESSAGE: &str = "\
|
||||||
|
test
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
test
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Print help information
|
||||||
|
";
|
||||||
|
assert_error(err, expected_kind, MESSAGE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_ignores_validation_error() {
|
||||||
|
let cmd = Command::new("test");
|
||||||
|
let res = cmd
|
||||||
|
.try_get_matches_from(["test", "unused"])
|
||||||
|
.map_err(|e| e.apply::<clap::error::NullFormatter>());
|
||||||
|
assert!(res.is_err());
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
let expected_kind = ErrorKind::UnknownArgument;
|
||||||
|
static MESSAGE: &str = "";
|
||||||
|
assert_error(err, expected_kind, MESSAGE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rich_formats_validation_error() {
|
||||||
|
let cmd = Command::new("test");
|
||||||
|
let res = cmd.try_get_matches_from(["test", "unused"]);
|
||||||
|
assert!(res.is_err());
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
let expected_kind = ErrorKind::UnknownArgument;
|
||||||
|
static MESSAGE: &str = "\
|
||||||
|
error: Found argument 'unused' which wasn't expected, or isn't valid in this context
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
test
|
||||||
|
|
||||||
|
For more information try --help
|
||||||
|
";
|
||||||
|
assert_error(err, expected_kind, MESSAGE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_formats_validation_error() {
|
||||||
|
let cmd = Command::new("test");
|
||||||
|
let res = cmd
|
||||||
|
.try_get_matches_from(["test", "unused"])
|
||||||
|
.map_err(|e| e.apply::<clap::error::RawFormatter>());
|
||||||
|
assert!(res.is_err());
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
let expected_kind = ErrorKind::UnknownArgument;
|
||||||
|
static MESSAGE: &str = "\
|
||||||
|
error: Found an argument which wasn't expected or isn't valid in this context
|
||||||
|
|
||||||
|
Invalid Argument: unused
|
||||||
|
USAGE:
|
||||||
|
test
|
||||||
|
";
|
||||||
|
assert_error(err, expected_kind, MESSAGE, true);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue