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 #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 58512f2), 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.
  • Loading branch information
epage committed Oct 16, 2021
1 parent 6eacd8a commit 72566ab
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 15 deletions.
17 changes: 16 additions & 1 deletion src/build/app/mod.rs
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
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::error
#[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::error
#[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
2 changes: 1 addition & 1 deletion tests/utils.rs
Expand Up @@ -7,7 +7,7 @@ use regex::Regex;

use clap::{App, Arg, ArgGroup};

fn compare<S, S2>(l: S, r: S2) -> bool
pub fn compare<S, S2>(l: S, r: S2) -> bool
where
S: AsRef<str>,
S2: AsRef<str>,
Expand Down

0 comments on commit 72566ab

Please sign in to comment.