From 79fe7190b8737c19427ad017caff9cb90610699c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 15 Oct 2021 16:06:21 -0500 Subject: [PATCH] feat: Expose clap-style errors to users This gives users the basic error template for quick and dirty messages. In addition to the lack of customization, they are not given anything to help them with coloring or for programmayic use (info, source). This is something I've wanted many times for one-off validation that can't be expressed with clap's validation or it just wasn't worth the hoops. The more pressing need is for #2255, I need `clap_derive` to be able to create error messages and `Error::with_description` seemed too disjoint from the rest of the clap experience that it seemed like users would immediately create issues about it showing up. With this available, I've gone ahead and deprecated `Error::with_description` (added in 58512f2fc), assuming this will be sufficient for users needs (or they can use IO Errors as a back door). I did so according to the pattern in #2718 despite us not being fully resolved on that approach yet. --- src/build/app/mod.rs | 17 +++++++++++++++- src/parse/errors.rs | 48 ++++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/build/app/mod.rs b/src/build/app/mod.rs index 0284579eb45..423731bdc0f 100644 --- a/src/build/app/mod.rs +++ b/src/build/app/mod.rs @@ -29,7 +29,7 @@ use crate::{ output::{fmt::Colorizer, Help, HelpWriter, Usage}, parse::{ArgMatcher, ArgMatches, Input, Parser}, util::{color::ColorChoice, safe_exit, Id, Key, USAGE_CODE}, - Result as ClapResult, INTERNAL_ERROR_MSG, + Error, ErrorKind, Result as ClapResult, INTERNAL_ERROR_MSG, }; /// Represents a command line interface which is made up of all possible @@ -1764,6 +1764,21 @@ impl<'help> App<'help> { self } + /// Custom error message for post-parsing validation + /// + /// # Examples + /// + /// ```rust + /// # use clap::{App, ErrorKind}; + /// let mut app = App::new("myprog"); + /// let err = app.error(ErrorKind::InvalidValue, "Some failure case"); + /// ``` + pub fn error(&mut self, kind: ErrorKind, message: impl std::fmt::Display) -> Error { + self._build(); + let usage = self.render_usage(); + Error::user_error(self, usage, kind, message) + } + /// Prints the full help message to [`io::stdout()`] using a [`BufWriter`] using the same /// method as if someone ran `-h` to request the help message. /// diff --git a/src/parse/errors.rs b/src/parse/errors.rs index 50e8ca59509..b7f0f650b3e 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -423,6 +423,10 @@ pub enum ErrorKind { } /// Command Line Argument Parser Error +/// +/// See [`App::error`] to create an error. +/// +/// [`App::error`]: crate::App::value_of_t() #[derive(Debug)] pub struct Error { /// Formatted error message, enhancing the cause message with extra information @@ -540,6 +544,27 @@ impl Error { } } + pub(crate) fn user_error( + app: &App, + usage: String, + kind: ErrorKind, + message: impl std::fmt::Display, + ) -> Self { + let mut c = Colorizer::new(true, app.get_color()); + + start_error(&mut c, message.to_string()); + put_usage(&mut c, usage); + try_help(app, &mut c); + + Self { + message: c, + kind, + info: vec![], + source: None, + backtrace: Backtrace::new(), + } + } + pub(crate) fn argument_conflict( app: &App, arg: &Arg, @@ -1077,34 +1102,31 @@ impl Error { } } - /// Create an error with a custom description. + /// Deprecated, see [`App::error`] /// - /// This can be used in combination with `Error::exit` to exit your program - /// with a custom error message. + /// [`App::error`]: crate::App::value_of_t() + #[deprecated(since = "3.0.0", note = "Replaced with `App::error`")] pub fn with_description(description: String, kind: ErrorKind) -> Self { let mut c = Colorizer::new(true, ColorChoice::Auto); start_error(&mut c, description); - - Error { - message: c, - kind, - info: vec![], - source: None, - backtrace: Backtrace::new(), - } + Error::new(c, kind) } } impl From for Error { fn from(e: io::Error) -> Self { - Error::with_description(e.to_string(), ErrorKind::Io) + let mut c = Colorizer::new(true, ColorChoice::Auto); + start_error(&mut c, e.to_string()); + Error::new(c, ErrorKind::Io) } } impl From for Error { fn from(e: fmt::Error) -> Self { - Error::with_description(e.to_string(), ErrorKind::Format) + let mut c = Colorizer::new(true, ColorChoice::Auto); + start_error(&mut c, e.to_string()); + Error::new(c, ErrorKind::Format) } }