summaryrefslogtreecommitdiffstats
path: root/third_party/rust/clap_builder/src/error/format.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/clap_builder/src/error/format.rs')
-rw-r--r--third_party/rust/clap_builder/src/error/format.rs545
1 files changed, 545 insertions, 0 deletions
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)
+ }
+ }
+}