Skip to content

Commit

Permalink
feat: Expose clap-style errors to users
Browse files Browse the repository at this point in the history
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 clap-rs#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`, 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 clap-rs#2718 despite us not being fully resolved on that
approach yet.
  • Loading branch information
epage committed Oct 16, 2021
1 parent 2795ccd commit 07932a7
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 14 deletions.
17 changes: 16 additions & 1 deletion src/build/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down
48 changes: 35 additions & 13 deletions src/parse/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<io::Error> 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<fmt::Error> 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)
}
}

Expand Down

0 comments on commit 07932a7

Please sign in to comment.