diff options
Diffstat (limited to 'vendor/clap_builder/src/error/format.rs')
-rw-r--r-- | vendor/clap_builder/src/error/format.rs | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/vendor/clap_builder/src/error/format.rs b/vendor/clap_builder/src/error/format.rs new file mode 100644 index 0000000..49e617d --- /dev/null +++ b/vendor/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) + } + } +} |