feat(error): Break out error-context feature flag

This is a cheap pass at creating this to allow cutting out the cost of
rich error information / programmatic error information.

This cuts about 20 KiB off of the binary.

There is more we could cut out, like collecting of used arguments for
the usage, but I want to keep the conditionals simple.
This commit is contained in:
Ed Page 2022-09-19 09:59:04 -05:00
parent b4788d51f1
commit 2d83a7b12e
24 changed files with 568 additions and 348 deletions

View file

@ -268,6 +268,7 @@ Behavior Changes
- Allow resetting most builder methods - Allow resetting most builder methods
- Can now pass runtime generated data to `Command`, `Arg`, `ArgGroup`, `PossibleValue`, etc without managing lifetimes with the `string` feature flag (#2150, #4223) - Can now pass runtime generated data to `Command`, `Arg`, `ArgGroup`, `PossibleValue`, etc without managing lifetimes with the `string` feature flag (#2150, #4223)
- *(error)* `Error::apply` for changing the formatter for dropping binary size (#4111) - *(error)* `Error::apply` for changing the formatter for dropping binary size (#4111)
- *(error)* New default `error-context` feature flag that can be turned off for smaller binaries
- *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312) - *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312)
- *(help)* New `{tab}` variable for `Command::help_template` (#4161) - *(help)* New `{tab}` variable for `Command::help_template` (#4161)

View file

@ -57,6 +57,7 @@ pre-release-replacements = [
default = [ default = [
"std", "std",
"color", "color",
"error-context",
"suggestions", "suggestions",
] ]
debug = ["clap_derive/debug", "dep:backtrace"] # Enables debug messages debug = ["clap_derive/debug", "dep:backtrace"] # Enables debug messages
@ -65,7 +66,8 @@ unstable-doc = ["derive", "cargo", "wrap_help", "env", "unicode", "string", "uns
# Used in default # Used in default
std = [] # support for no_std in a backwards-compatible way std = [] # support for no_std in a backwards-compatible way
color = ["dep:atty", "dep:termcolor"] color = ["dep:atty", "dep:termcolor"]
suggestions = ["dep:strsim"] error-context = []
suggestions = ["dep:strsim", "error-context"]
# Optional # Optional
deprecated = ["clap_derive?/deprecated"] # Guided experience to prepare for next breaking release (at different stages of development, this may become default) deprecated = ["clap_derive?/deprecated"] # Guided experience to prepare for next breaking release (at different stages of development, this may become default)

View file

@ -6,6 +6,7 @@
//! //!
//! * **std**: _Not Currently Used._ Placeholder for supporting `no_std` environments in a backwards compatible manner. //! * **std**: _Not Currently Used._ Placeholder for supporting `no_std` environments in a backwards compatible manner.
//! * **color**: Turns on colored error messages. //! * **color**: Turns on colored error messages.
//! * **error-context**: Include contextual information for errors (which arg failed, etc)
//! * **suggestions**: Turns on the `Did you mean '--myoption'?` feature for when users make typos. //! * **suggestions**: Turns on the `Did you mean '--myoption'?` feature for when users make typos.
//! //!
//! #### Optional features //! #### Optional features

View file

@ -29,6 +29,7 @@ impl StyledStr {
self.stylize_(Some(Style::Good), msg.into()); self.stylize_(Some(Style::Good), msg.into());
} }
#[cfg_attr(not(feature = "error-context"), allow(dead_code))]
pub(crate) fn warning(&mut self, msg: impl Into<String>) { pub(crate) fn warning(&mut self, msg: impl Into<String>) {
self.stylize_(Some(Style::Warning), msg.into()); self.stylize_(Some(Style::Warning), msg.into());
} }

View file

@ -1,6 +1,7 @@
/// Semantics for a piece of error information /// Semantics for a piece of error information
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
#[cfg(feature = "error-context")]
pub enum ContextKind { pub enum ContextKind {
/// The cause of the error /// The cause of the error
InvalidSubcommand, InvalidSubcommand,
@ -66,6 +67,7 @@ impl std::fmt::Display for ContextKind {
/// A piece of error information /// A piece of error information
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
#[cfg(feature = "error-context")]
pub enum ContextValue { pub enum ContextValue {
/// [`ContextKind`] is self-sufficient, no additional information needed /// [`ContextKind`] is self-sufficient, no additional information needed
None, None,

View file

@ -1,9 +1,13 @@
#![allow(missing_copy_implementations)] #![allow(missing_copy_implementations)]
#![allow(missing_debug_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::Command;
use crate::builder::StyledStr; use crate::builder::StyledStr;
#[cfg(feature = "error-context")]
use crate::error::ContextKind; use crate::error::ContextKind;
#[cfg(feature = "error-context")]
use crate::error::ContextValue; use crate::error::ContextValue;
use crate::error::ErrorKind; use crate::error::ErrorKind;
use crate::output::TAB; use crate::output::TAB;
@ -16,7 +20,10 @@ pub trait ErrorFormatter: Sized {
/// Report [`ErrorKind`] /// Report [`ErrorKind`]
/// ///
/// No context is included /// No context is included.
///
/// **NOTE:** Consider removing the [`error-context`][crate::_features] default feature if using this to remove all
/// overhead for [`RichFormatter`].
#[non_exhaustive] #[non_exhaustive]
pub struct KindFormatter; pub struct KindFormatter;
@ -38,8 +45,10 @@ impl ErrorFormatter for KindFormatter {
/// Dump the error context reported /// Dump the error context reported
#[non_exhaustive] #[non_exhaustive]
#[cfg(feature = "error-context")]
pub struct RawFormatter; pub struct RawFormatter;
#[cfg(feature = "error-context")]
impl ErrorFormatter for RawFormatter { impl ErrorFormatter for RawFormatter {
fn format_error(error: &crate::Error<Self>) -> StyledStr { fn format_error(error: &crate::Error<Self>) -> StyledStr {
let mut styled = StyledStr::new(); let mut styled = StyledStr::new();
@ -73,8 +82,10 @@ impl ErrorFormatter for RawFormatter {
/// Richly formatted error context /// Richly formatted error context
#[non_exhaustive] #[non_exhaustive]
#[cfg(feature = "error-context")]
pub struct RichFormatter; pub struct RichFormatter;
#[cfg(feature = "error-context")]
impl ErrorFormatter for RichFormatter { impl ErrorFormatter for RichFormatter {
fn format_error(error: &crate::Error<Self>) -> StyledStr { fn format_error(error: &crate::Error<Self>) -> StyledStr {
let mut styled = StyledStr::new(); let mut styled = StyledStr::new();
@ -107,6 +118,7 @@ fn start_error(styled: &mut StyledStr) {
} }
#[must_use] #[must_use]
#[cfg(feature = "error-context")]
fn write_dynamic_context(error: &crate::Error, styled: &mut StyledStr) -> bool { fn write_dynamic_context(error: &crate::Error, styled: &mut StyledStr) -> bool {
match error.kind() { match error.kind() {
ErrorKind::ArgumentConflict => { ErrorKind::ArgumentConflict => {

View file

@ -1,5 +1,11 @@
//! Error reporting //! Error reporting
#![cfg_attr(not(feature = "error-context"), allow(dead_code))]
#![cfg_attr(not(feature = "error-context"), allow(unused_imports))]
#![cfg_attr(not(feature = "error-context"), allow(unused_variables))]
#![cfg_attr(not(feature = "error-context"), allow(unused_mut))]
#![cfg_attr(not(feature = "error-context"), allow(clippy::let_and_return))]
// Std // Std
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -19,18 +25,27 @@ use crate::util::FlatMap;
use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE}; use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE};
use crate::Command; use crate::Command;
#[cfg(feature = "error-context")]
mod context; mod context;
mod format; mod format;
mod kind; mod kind;
pub use context::ContextKind;
pub use context::ContextValue;
pub use format::ErrorFormatter; pub use format::ErrorFormatter;
pub use format::KindFormatter; pub use format::KindFormatter;
pub use format::RawFormatter;
pub use format::RichFormatter;
pub use kind::ErrorKind; pub use kind::ErrorKind;
#[cfg(feature = "error-context")]
pub use context::ContextKind;
#[cfg(feature = "error-context")]
pub use context::ContextValue;
#[cfg(feature = "error-context")]
pub use format::RawFormatter;
#[cfg(feature = "error-context")]
pub use format::RichFormatter;
#[cfg(not(feature = "error-context"))]
pub use KindFormatter as DefaultFormatter;
#[cfg(feature = "error-context")]
pub use RichFormatter as DefaultFormatter; pub use RichFormatter as DefaultFormatter;
/// Short hand for [`Result`] type /// Short hand for [`Result`] type
@ -51,6 +66,7 @@ pub struct Error<F: ErrorFormatter = DefaultFormatter> {
#[derive(Debug)] #[derive(Debug)]
struct ErrorInner { struct ErrorInner {
kind: ErrorKind, kind: ErrorKind,
#[cfg(feature = "error-context")]
context: FlatMap<ContextKind, ContextValue>, context: FlatMap<ContextKind, ContextValue>,
message: Option<Message>, message: Option<Message>,
source: Option<Box<dyn error::Error + Send + Sync>>, source: Option<Box<dyn error::Error + Send + Sync>>,
@ -112,12 +128,14 @@ impl<F: ErrorFormatter> Error<F> {
} }
/// Additional information to further qualify the error /// Additional information to further qualify the error
#[cfg(feature = "error-context")]
pub fn context(&self) -> impl Iterator<Item = (ContextKind, &ContextValue)> { pub fn context(&self) -> impl Iterator<Item = (ContextKind, &ContextValue)> {
self.inner.context.iter().map(|(k, v)| (*k, v)) self.inner.context.iter().map(|(k, v)| (*k, v))
} }
/// Lookup a piece of context /// Lookup a piece of context
#[inline(never)] #[inline(never)]
#[cfg(feature = "error-context")]
pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> { pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> {
self.inner.context.get(&kind) self.inner.context.get(&kind)
} }
@ -183,6 +201,7 @@ impl<F: ErrorFormatter> Error<F> {
Self { Self {
inner: Box::new(ErrorInner { inner: Box::new(ErrorInner {
kind, kind,
#[cfg(feature = "error-context")]
context: FlatMap::new(), context: FlatMap::new(),
message: None, message: None,
source: None, source: None,
@ -233,6 +252,7 @@ impl<F: ErrorFormatter> Error<F> {
/// Does not verify if `ContextKind` is already present /// Does not verify if `ContextKind` is already present
#[inline(never)] #[inline(never)]
#[cfg(feature = "error-context")]
pub(crate) fn insert_context_unchecked( pub(crate) fn insert_context_unchecked(
mut self, mut self,
kind: ContextKind, kind: ContextKind,
@ -244,6 +264,7 @@ impl<F: ErrorFormatter> Error<F> {
/// Does not verify if `ContextKind` is already present /// Does not verify if `ContextKind` is already present
#[inline(never)] #[inline(never)]
#[cfg(feature = "error-context")]
pub(crate) fn extend_context_unchecked<const N: usize>( pub(crate) fn extend_context_unchecked<const N: usize>(
mut self, mut self,
context: [(ContextKind, ContextValue); N], context: [(ContextKind, ContextValue); N],
@ -274,18 +295,23 @@ impl<F: ErrorFormatter> Error<F> {
mut others: Vec<String>, mut others: Vec<String>,
usage: StyledStr, usage: StyledStr,
) -> Self { ) -> Self {
let others = match others.len() { let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd);
0 => ContextValue::None,
1 => ContextValue::String(others.pop().unwrap()), #[cfg(feature = "error-context")]
_ => ContextValue::Strings(others), {
}; let others = match others.len() {
Self::new(ErrorKind::ArgumentConflict) 0 => ContextValue::None,
.with_cmd(cmd) 1 => ContextValue::String(others.pop().unwrap()),
.extend_context_unchecked([ _ => ContextValue::Strings(others),
};
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::PriorArg, others), (ContextKind::PriorArg, others),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self { pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self {
@ -293,12 +319,17 @@ impl<F: ErrorFormatter> Error<F> {
} }
pub(crate) fn no_equals(cmd: &Command, arg: String, usage: StyledStr) -> Self { pub(crate) fn no_equals(cmd: &Command, arg: String, usage: StyledStr) -> Self {
Self::new(ErrorKind::NoEquals) let mut err = Self::new(ErrorKind::NoEquals).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn invalid_value( pub(crate) fn invalid_value(
@ -308,9 +339,11 @@ impl<F: ErrorFormatter> Error<F> {
arg: String, arg: String,
) -> Self { ) -> Self {
let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop(); let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
let mut err = Self::new(ErrorKind::InvalidValue) let mut err = Self::new(ErrorKind::InvalidValue).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::InvalidValue, ContextValue::String(bad_val)), (ContextKind::InvalidValue, ContextValue::String(bad_val)),
( (
@ -318,12 +351,14 @@ impl<F: ErrorFormatter> Error<F> {
ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()), ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()),
), ),
]); ]);
if let Some(suggestion) = suggestion { if let Some(suggestion) = suggestion {
err = err.insert_context_unchecked( err = err.insert_context_unchecked(
ContextKind::SuggestedValue, ContextKind::SuggestedValue,
ContextValue::String(suggestion), ContextValue::String(suggestion),
); );
}
} }
err err
} }
@ -335,9 +370,11 @@ impl<F: ErrorFormatter> Error<F> {
usage: StyledStr, usage: StyledStr,
) -> Self { ) -> Self {
let suggestion = format!("{} -- {}", name, subcmd); let suggestion = format!("{} -- {}", name, subcmd);
Self::new(ErrorKind::InvalidSubcommand) let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidSubcommand, ContextValue::String(subcmd)), (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
( (
ContextKind::SuggestedSubcommand, ContextKind::SuggestedSubcommand,
@ -348,16 +385,24 @@ impl<F: ErrorFormatter> Error<F> {
ContextValue::String(suggestion), ContextValue::String(suggestion),
), ),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn unrecognized_subcommand(cmd: &Command, subcmd: String, usage: StyledStr) -> Self { pub(crate) fn unrecognized_subcommand(cmd: &Command, subcmd: String, usage: StyledStr) -> Self {
Self::new(ErrorKind::InvalidSubcommand) let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidSubcommand, ContextValue::String(subcmd)), (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn missing_required_argument( pub(crate) fn missing_required_argument(
@ -365,27 +410,43 @@ impl<F: ErrorFormatter> Error<F> {
required: Vec<String>, required: Vec<String>,
usage: StyledStr, usage: StyledStr,
) -> Self { ) -> Self {
Self::new(ErrorKind::MissingRequiredArgument) let mut err = Self::new(ErrorKind::MissingRequiredArgument).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::Strings(required)), (ContextKind::InvalidArg, ContextValue::Strings(required)),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn missing_subcommand(cmd: &Command, name: String, usage: StyledStr) -> Self { pub(crate) fn missing_subcommand(cmd: &Command, name: String, usage: StyledStr) -> Self {
Self::new(ErrorKind::MissingSubcommand) let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidSubcommand, ContextValue::String(name)), (ContextKind::InvalidSubcommand, ContextValue::String(name)),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn invalid_utf8(cmd: &Command, usage: StyledStr) -> Self { pub(crate) fn invalid_utf8(cmd: &Command, usage: StyledStr) -> Self {
Self::new(ErrorKind::InvalidUtf8) let mut err = Self::new(ErrorKind::InvalidUtf8).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([(ContextKind::Usage, ContextValue::StyledStr(usage))]) #[cfg(feature = "error-context")]
{
err = err
.extend_context_unchecked([(ContextKind::Usage, ContextValue::StyledStr(usage))]);
}
err
} }
pub(crate) fn too_many_values( pub(crate) fn too_many_values(
@ -394,13 +455,18 @@ impl<F: ErrorFormatter> Error<F> {
arg: String, arg: String,
usage: StyledStr, usage: StyledStr,
) -> Self { ) -> Self {
Self::new(ErrorKind::TooManyValues) let mut err = Self::new(ErrorKind::TooManyValues).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::InvalidValue, ContextValue::String(val)), (ContextKind::InvalidValue, ContextValue::String(val)),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn too_few_values( pub(crate) fn too_few_values(
@ -410,9 +476,11 @@ impl<F: ErrorFormatter> Error<F> {
curr_vals: usize, curr_vals: usize,
usage: StyledStr, usage: StyledStr,
) -> Self { ) -> Self {
Self::new(ErrorKind::TooFewValues) let mut err = Self::new(ErrorKind::TooFewValues).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
( (
ContextKind::MinValues, ContextKind::MinValues,
@ -423,7 +491,10 @@ impl<F: ErrorFormatter> Error<F> {
ContextValue::Number(curr_vals as isize), ContextValue::Number(curr_vals as isize),
), ),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn value_validation( pub(crate) fn value_validation(
@ -431,12 +502,17 @@ impl<F: ErrorFormatter> Error<F> {
val: String, val: String,
err: Box<dyn error::Error + Send + Sync>, err: Box<dyn error::Error + Send + Sync>,
) -> Self { ) -> Self {
Self::new(ErrorKind::ValueValidation) let mut err = Self::new(ErrorKind::ValueValidation).set_source(err);
.set_source(err)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::InvalidValue, ContextValue::String(val)), (ContextKind::InvalidValue, ContextValue::String(val)),
]) ]);
}
err
} }
pub(crate) fn wrong_number_of_values( pub(crate) fn wrong_number_of_values(
@ -446,9 +522,11 @@ impl<F: ErrorFormatter> Error<F> {
curr_vals: usize, curr_vals: usize,
usage: StyledStr, usage: StyledStr,
) -> Self { ) -> Self {
Self::new(ErrorKind::WrongNumberOfValues) let mut err = Self::new(ErrorKind::WrongNumberOfValues).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
( (
ContextKind::ExpectedNumValues, ContextKind::ExpectedNumValues,
@ -459,7 +537,10 @@ impl<F: ErrorFormatter> Error<F> {
ContextValue::Number(curr_vals as isize), ContextValue::Number(curr_vals as isize),
), ),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
pub(crate) fn unknown_argument( pub(crate) fn unknown_argument(
@ -468,35 +549,44 @@ impl<F: ErrorFormatter> Error<F> {
did_you_mean: Option<(String, Option<String>)>, did_you_mean: Option<(String, Option<String>)>,
usage: StyledStr, usage: StyledStr,
) -> Self { ) -> Self {
let mut err = Self::new(ErrorKind::UnknownArgument) let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]); ]);
if let Some((flag, sub)) = did_you_mean { if let Some((flag, sub)) = did_you_mean {
err = err.insert_context_unchecked(
ContextKind::SuggestedArg,
ContextValue::String(format!("--{}", flag)),
);
if let Some(sub) = sub {
err = err.insert_context_unchecked( err = err.insert_context_unchecked(
ContextKind::SuggestedSubcommand, ContextKind::SuggestedArg,
ContextValue::String(sub), ContextValue::String(format!("--{}", flag)),
); );
if let Some(sub) = sub {
err = err.insert_context_unchecked(
ContextKind::SuggestedSubcommand,
ContextValue::String(sub),
);
}
} }
} }
err err
} }
pub(crate) fn unnecessary_double_dash(cmd: &Command, arg: String, usage: StyledStr) -> Self { pub(crate) fn unnecessary_double_dash(cmd: &Command, arg: String, usage: StyledStr) -> Self {
Self::new(ErrorKind::UnknownArgument) let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd);
.with_cmd(cmd)
.extend_context_unchecked([ #[cfg(feature = "error-context")]
{
err = err.extend_context_unchecked([
(ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidArg, ContextValue::String(arg)),
(ContextKind::TrailingArg, ContextValue::Bool(true)), (ContextKind::TrailingArg, ContextValue::Bool(true)),
(ContextKind::Usage, ContextValue::StyledStr(usage)), (ContextKind::Usage, ContextValue::StyledStr(usage)),
]) ]);
}
err
} }
fn formatted(&self) -> Cow<'_, StyledStr> { fn formatted(&self) -> Cow<'_, StyledStr> {

View file

@ -1,7 +1,8 @@
use super::utils;
use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command}; use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command};
#[cfg(feature = "error-context")]
use super::utils;
#[test] #[test]
fn flag_conflict() { fn flag_conflict() {
let result = Command::new("flag_conflict") let result = Command::new("flag_conflict")
@ -271,6 +272,7 @@ fn get_arg_conflicts_with_group() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn conflict_output() { fn conflict_output() {
static CONFLICT_ERR: &str = "\ static CONFLICT_ERR: &str = "\
error: The argument '--flag...' cannot be used with '-F' error: The argument '--flag...' cannot be used with '-F'
@ -289,6 +291,7 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn conflict_output_rev() { fn conflict_output_rev() {
static CONFLICT_ERR_REV: &str = "\ static CONFLICT_ERR_REV: &str = "\
error: The argument '-F' cannot be used with '--flag...' error: The argument '-F' cannot be used with '--flag...'
@ -307,6 +310,7 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn conflict_output_with_required() { fn conflict_output_with_required() {
static CONFLICT_ERR: &str = "\ static CONFLICT_ERR: &str = "\
error: The argument '--flag...' cannot be used with '-F' error: The argument '--flag...' cannot be used with '-F'
@ -325,6 +329,7 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn conflict_output_rev_with_required() { fn conflict_output_rev_with_required() {
static CONFLICT_ERR_REV: &str = "\ static CONFLICT_ERR_REV: &str = "\
error: The argument '-F' cannot be used with '--flag...' error: The argument '-F' cannot be used with '--flag...'
@ -343,6 +348,7 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn conflict_output_three_conflicting() { fn conflict_output_three_conflicting() {
static CONFLICT_ERR_THREE: &str = "\ static CONFLICT_ERR_THREE: &str = "\
error: The argument '--one' cannot be used with: error: The argument '--one' cannot be used with:
@ -382,6 +388,7 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn two_conflicting_arguments() { fn two_conflicting_arguments() {
let a = Command::new("two_conflicting_arguments") let a = Command::new("two_conflicting_arguments")
.arg( .arg(
@ -409,6 +416,7 @@ fn two_conflicting_arguments() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn three_conflicting_arguments() { fn three_conflicting_arguments() {
let a = Command::new("three_conflicting_arguments") let a = Command::new("three_conflicting_arguments")
.arg( .arg(

View file

@ -260,6 +260,7 @@ fn delimited_missing_value() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[test] #[test]
#[cfg(feature = "error-context")]
#[should_panic = "Argument `arg`'s default_missing_value=\"value\" failed validation: error: \"value\" isn't a valid value for '[arg]'"] #[should_panic = "Argument `arg`'s default_missing_value=\"value\" failed validation: error: \"value\" isn't a valid value for '[arg]'"]
fn default_missing_values_are_possible_values() { fn default_missing_values_are_possible_values() {
use clap::{Arg, Command}; use clap::{Arg, Command};
@ -275,6 +276,7 @@ fn default_missing_values_are_possible_values() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[test] #[test]
#[cfg(feature = "error-context")]
#[should_panic = "Argument `arg`'s default_missing_value=\"value\" failed validation: error: Invalid value \"value\" for '[arg]"] #[should_panic = "Argument `arg`'s default_missing_value=\"value\" failed validation: error: Invalid value \"value\" for '[arg]"]
fn default_missing_values_are_valid() { fn default_missing_values_are_valid() {
use clap::{Arg, Command}; use clap::{Arg, Command};

View file

@ -1,9 +1,13 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::ffi::OsString; use std::ffi::OsString;
use super::utils;
use clap::builder::ArgPredicate; use clap::builder::ArgPredicate;
use clap::{arg, error::ErrorKind, value_parser, Arg, ArgAction, Command}; #[cfg(feature = "error-context")]
use clap::error::ErrorKind;
use clap::{arg, value_parser, Arg, ArgAction, Command};
#[cfg(feature = "error-context")]
use super::utils;
#[test] #[test]
fn opts() { fn opts() {
@ -30,6 +34,7 @@ fn default_has_index() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn opt_without_value_fail() { fn opt_without_value_fail() {
let r = Command::new("df") let r = Command::new("df")
.arg( .arg(
@ -694,6 +699,7 @@ fn multiple_defaults_override() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn default_vals_donnot_show_in_smart_usage() { fn default_vals_donnot_show_in_smart_usage() {
let cmd = Command::new("bug") let cmd = Command::new("bug")
.arg( .arg(
@ -773,6 +779,7 @@ fn required_args_with_default_values() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[test] #[test]
#[cfg(feature = "error-context")]
#[should_panic = "Argument `arg`'s default_value=\"value\" failed validation: error: \"value\" isn't a valid value for '[arg]'"] #[should_panic = "Argument `arg`'s default_value=\"value\" failed validation: error: \"value\" isn't a valid value for '[arg]'"]
fn default_values_are_possible_values() { fn default_values_are_possible_values() {
use clap::{Arg, Command}; use clap::{Arg, Command};
@ -788,6 +795,7 @@ fn default_values_are_possible_values() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[test] #[test]
#[cfg(feature = "error-context")]
#[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"] #[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"]
fn invalid_default_values() { fn invalid_default_values() {
use clap::{Arg, Command}; use clap::{Arg, Command};
@ -817,6 +825,7 @@ fn valid_delimited_default_values() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[test] #[test]
#[cfg(feature = "error-context")]
#[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"] #[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"]
fn invalid_delimited_default_values() { fn invalid_delimited_default_values() {
use clap::{Arg, Command}; use clap::{Arg, Command};

View file

@ -10,24 +10,6 @@ Options:
-h, --help Print help information -h, --help Print help information
"; ";
static ONLY_B_ERROR: &str = "\
error: The following required arguments were not provided:
-c
Usage: prog -b -c
For more information try '--help'
";
static ONLY_C_ERROR: &str = "\
error: The following required arguments were not provided:
-b
Usage: prog -c -b
For more information try '--help'
";
fn cmd() -> Command { fn cmd() -> Command {
Command::new("prog") Command::new("prog")
.arg( .arg(
@ -74,13 +56,32 @@ fn help_text() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn no_duplicate_error() { fn no_duplicate_error() {
static ONLY_B_ERROR: &str = "\
error: The following required arguments were not provided:
-c
Usage: prog -b -c
For more information try '--help'
";
let res = cmd().try_get_matches_from(vec!["", "-b"]); let res = cmd().try_get_matches_from(vec!["", "-b"]);
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();
assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument); assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument);
assert_eq!(err.to_string(), ONLY_B_ERROR); assert_eq!(err.to_string(), ONLY_B_ERROR);
static ONLY_C_ERROR: &str = "\
error: The following required arguments were not provided:
-b
Usage: prog -c -b
For more information try '--help'
";
let res = cmd().try_get_matches_from(vec!["", "-c"]); let res = cmd().try_get_matches_from(vec!["", "-c"]);
assert!(res.is_err()); assert!(res.is_err());
let err = res.unwrap_err(); let err = res.unwrap_err();

View file

@ -1,7 +1,8 @@
use super::utils;
use clap::{error::ErrorKind, Arg, ArgAction, Command}; use clap::{error::ErrorKind, Arg, ArgAction, Command};
#[cfg(feature = "error-context")]
use super::utils;
#[test] #[test]
fn empty_values() { fn empty_values() {
let m = Command::new("config") let m = Command::new("config")
@ -102,6 +103,7 @@ fn no_empty_values_without_equals() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn no_empty_values_without_equals_but_requires_equals() { fn no_empty_values_without_equals_but_requires_equals() {
let cmd = Command::new("config").arg( let cmd = Command::new("config").arg(
Arg::new("config") Arg::new("config")

View file

@ -97,6 +97,7 @@ Options:
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn raw_prints_help() { fn raw_prints_help() {
let cmd = Command::new("test"); let cmd = Command::new("test");
let res = cmd let res = cmd
@ -130,6 +131,7 @@ error: Found an argument which wasn't expected or isn't valid in this context
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn rich_formats_validation_error() { fn rich_formats_validation_error() {
let cmd = Command::new("test"); let cmd = Command::new("test");
let res = cmd.try_get_matches_from(["test", "unused"]); let res = cmd.try_get_matches_from(["test", "unused"]);
@ -147,6 +149,7 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn raw_formats_validation_error() { fn raw_formats_validation_error() {
let cmd = Command::new("test"); let cmd = Command::new("test");
let res = cmd let res = cmd

View file

@ -1,15 +1,7 @@
use super::utils;
use clap::{arg, Arg, ArgAction, Command}; use clap::{arg, Arg, ArgAction, Command};
const USE_FLAG_AS_ARGUMENT: &str = "\ #[cfg(feature = "error-context")]
error: Found argument '--another-flag' which wasn't expected, or isn't valid in this context use super::utils;
If you tried to supply `--another-flag` as a value rather than a flag, use `-- --another-flag`
Usage: mycat [OPTIONS] [filename]
For more information try '--help'
";
#[test] #[test]
fn flag_using_short() { fn flag_using_short() {
@ -143,7 +135,18 @@ fn multiple_flags_in_single() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn issue_1284_argument_in_flag_style() { fn issue_1284_argument_in_flag_style() {
const USE_FLAG_AS_ARGUMENT: &str = "\
error: Found argument '--another-flag' which wasn't expected, or isn't valid in this context
If you tried to supply `--another-flag` as a value rather than a flag, use `-- --another-flag`
Usage: mycat [OPTIONS] [filename]
For more information try '--help'
";
let cmd = Command::new("mycat") let cmd = Command::new("mycat")
.arg(Arg::new("filename")) .arg(Arg::new("filename"))
.arg(Arg::new("a-flag").long("a-flag").action(ArgAction::SetTrue)); .arg(Arg::new("a-flag").long("a-flag").action(ArgAction::SetTrue));
@ -176,6 +179,7 @@ fn issue_1284_argument_in_flag_style() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn issue_2308_multiple_dashes() { fn issue_2308_multiple_dashes() {
static MULTIPLE_DASHES: &str = "\ static MULTIPLE_DASHES: &str = "\
error: Found argument '-----' which wasn't expected, or isn't valid in this context error: Found argument '-----' which wasn't expected, or isn't valid in this context

View file

@ -1,30 +1,6 @@
use super::utils;
use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command, Id}; use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command, Id};
static REQ_GROUP_USAGE: &str = "error: The following required arguments were not provided: use super::utils;
<base|--delete>
Usage: clap-test <base|--delete>
For more information try '--help'
";
static REQ_GROUP_CONFLICT_USAGE: &str = "\
error: The argument '--delete' cannot be used with '[base]'
Usage: clap-test <base|--delete>
For more information try '--help'
";
static REQ_GROUP_CONFLICT_ONLY_OPTIONS: &str = "\
error: The argument '--delete' cannot be used with '--all'
Usage: clap-test <--all|--delete>
For more information try '--help'
";
#[test] #[test]
fn required_group_missing_arg() { fn required_group_missing_arg() {
@ -150,7 +126,16 @@ fn empty_group() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn req_group_usage_string() { fn req_group_usage_string() {
static REQ_GROUP_USAGE: &str = "error: The following required arguments were not provided:
<base|--delete>
Usage: clap-test <base|--delete>
For more information try '--help'
";
let cmd = Command::new("req_group") let cmd = Command::new("req_group")
.arg(arg!([base] "Base commit")) .arg(arg!([base] "Base commit"))
.arg(arg!( .arg(arg!(
@ -166,7 +151,16 @@ fn req_group_usage_string() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn req_group_with_conflict_usage_string() { fn req_group_with_conflict_usage_string() {
static REQ_GROUP_CONFLICT_USAGE: &str = "\
error: The argument '--delete' cannot be used with '[base]'
Usage: clap-test <base|--delete>
For more information try '--help'
";
let cmd = Command::new("req_group") let cmd = Command::new("req_group")
.arg(arg!([base] "Base commit").conflicts_with("delete")) .arg(arg!([base] "Base commit").conflicts_with("delete"))
.arg(arg!( .arg(arg!(
@ -187,7 +181,16 @@ fn req_group_with_conflict_usage_string() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn req_group_with_conflict_usage_string_only_options() { fn req_group_with_conflict_usage_string_only_options() {
static REQ_GROUP_CONFLICT_ONLY_OPTIONS: &str = "\
error: The argument '--delete' cannot be used with '--all'
Usage: clap-test <--all|--delete>
For more information try '--help'
";
let cmd = Command::new("req_group") let cmd = Command::new("req_group")
.arg(arg!(-a --all "All").conflicts_with("delete")) .arg(arg!(-a --all "All").conflicts_with("delete"))
.arg(arg!( .arg(arg!(

View file

@ -1,7 +1,7 @@
use super::utils;
use clap::{arg, builder::PossibleValue, error::ErrorKind, Arg, ArgAction, ArgGroup, Command}; use clap::{arg, builder::PossibleValue, error::ErrorKind, Arg, ArgAction, ArgGroup, Command};
use super::utils;
fn setup() -> Command { fn setup() -> Command {
Command::new("test") Command::new("test")
.author("Kevin K.") .author("Kevin K.")
@ -52,6 +52,7 @@ fn help_subcommand() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn help_multi_subcommand_error() { fn help_multi_subcommand_error() {
let cmd = Command::new("ctest").subcommand( let cmd = Command::new("ctest").subcommand(
Command::new("subcmd").subcommand( Command::new("subcmd").subcommand(

View file

@ -1,32 +1,7 @@
use super::utils;
use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgMatches, Command}; use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgMatches, Command};
#[cfg(feature = "suggestions")] #[cfg(feature = "error-context")]
static DYM: &str = "\ use super::utils;
error: Found argument '--optio' which wasn't expected, or isn't valid in this context
Did you mean '--option'?
If you tried to supply `--optio` as a value rather than a flag, use `-- --optio`
Usage: clap-test --option <opt>... [positional] [positional2] [positional3]...
For more information try '--help'
";
#[cfg(feature = "suggestions")]
static DYM_ISSUE_1073: &str = "\
error: Found argument '--files-without-matches' which wasn't expected, or isn't valid in this context
Did you mean '--files-without-match'?
If you tried to supply `--files-without-matches` as a value rather than a flag, use `-- --files-without-matches`
Usage: ripgrep-616 --files-without-match
For more information try '--help'
";
#[test] #[test]
fn require_equals_fail() { fn require_equals_fail() {
@ -44,6 +19,7 @@ fn require_equals_fail() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn require_equals_fail_message() { fn require_equals_fail_message() {
static NO_EQUALS: &str = static NO_EQUALS: &str =
"error: Equal sign is needed when assigning values to '--config=<cfg>'. "error: Equal sign is needed when assigning values to '--config=<cfg>'.
@ -468,7 +444,20 @@ fn leading_hyphen_with_only_pos_follows() {
#[test] #[test]
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
#[cfg(feature = "error-context")]
fn did_you_mean() { fn did_you_mean() {
static DYM: &str = "\
error: Found argument '--optio' which wasn't expected, or isn't valid in this context
Did you mean '--option'?
If you tried to supply `--optio` as a value rather than a flag, use `-- --optio`
Usage: clap-test --option <opt>... [positional] [positional2] [positional3]...
For more information try '--help'
";
utils::assert_output(utils::complex_app(), "clap-test --optio=foo", DYM, true); utils::assert_output(utils::complex_app(), "clap-test --optio=foo", DYM, true);
} }
@ -555,7 +544,20 @@ fn issue_1105_empty_value_short_explicit_no_space() {
#[test] #[test]
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
#[cfg(feature = "error-context")]
fn issue_1073_suboptimal_flag_suggestion() { fn issue_1073_suboptimal_flag_suggestion() {
static DYM_ISSUE_1073: &str = "\
error: Found argument '--files-without-matches' which wasn't expected, or isn't valid in this context
Did you mean '--files-without-match'?
If you tried to supply `--files-without-matches` as a value rather than a flag, use `-- --files-without-matches`
Usage: ripgrep-616 --files-without-match
For more information try '--help'
";
let cmd = Command::new("ripgrep-616") let cmd = Command::new("ripgrep-616")
.arg( .arg(
Arg::new("files-with-matches") Arg::new("files-with-matches")

View file

@ -1,42 +1,7 @@
use super::utils;
use clap::{builder::PossibleValue, error::ErrorKind, Arg, ArgAction, Command}; use clap::{builder::PossibleValue, error::ErrorKind, Arg, ArgAction, Command};
#[cfg(feature = "suggestions")] #[cfg(feature = "error-context")]
static PV_ERROR: &str = "\ use super::utils;
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
Did you mean \"slow\"?
For more information try '--help'
";
#[cfg(not(feature = "suggestions"))]
static PV_ERROR: &str = "\
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
#[cfg(feature = "suggestions")]
static PV_ERROR_ESCAPED: &str = "\
error: \"ludicrous\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
Did you mean \"ludicrous speed\"?
For more information try '--help'
";
#[cfg(not(feature = "suggestions"))]
static PV_ERROR_ESCAPED: &str = "\
error: \"ludicrous\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
#[test] #[test]
fn possible_values_of_positional() { fn possible_values_of_positional() {
@ -209,7 +174,26 @@ fn possible_values_of_option_multiple_fail() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn possible_values_output() { fn possible_values_output() {
#[cfg(feature = "suggestions")]
static PV_ERROR: &str = "\
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
Did you mean \"slow\"?
For more information try '--help'
";
#[cfg(not(feature = "suggestions"))]
static PV_ERROR: &str = "\
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
utils::assert_output( utils::assert_output(
Command::new("test").arg( Command::new("test").arg(
Arg::new("option") Arg::new("option")
@ -224,7 +208,26 @@ fn possible_values_output() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn possible_values_alias_output() { fn possible_values_alias_output() {
#[cfg(feature = "suggestions")]
static PV_ERROR: &str = "\
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
Did you mean \"slow\"?
For more information try '--help'
";
#[cfg(not(feature = "suggestions"))]
static PV_ERROR: &str = "\
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
utils::assert_output( utils::assert_output(
Command::new("test").arg( Command::new("test").arg(
Arg::new("option") Arg::new("option")
@ -243,7 +246,26 @@ fn possible_values_alias_output() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn possible_values_hidden_output() { fn possible_values_hidden_output() {
#[cfg(feature = "suggestions")]
static PV_ERROR: &str = "\
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
Did you mean \"slow\"?
For more information try '--help'
";
#[cfg(not(feature = "suggestions"))]
static PV_ERROR: &str = "\
error: \"slo\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
utils::assert_output( utils::assert_output(
Command::new("test").arg( Command::new("test").arg(
Arg::new("option") Arg::new("option")
@ -263,7 +285,26 @@ fn possible_values_hidden_output() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn escaped_possible_values_output() { fn escaped_possible_values_output() {
#[cfg(feature = "suggestions")]
static PV_ERROR_ESCAPED: &str = "\
error: \"ludicrous\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
Did you mean \"ludicrous speed\"?
For more information try '--help'
";
#[cfg(not(feature = "suggestions"))]
static PV_ERROR_ESCAPED: &str = "\
error: \"ludicrous\" isn't a valid value for '-O <option>'
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
utils::assert_output( utils::assert_output(
Command::new("test").arg( Command::new("test").arg(
Arg::new("option") Arg::new("option")
@ -278,7 +319,15 @@ fn escaped_possible_values_output() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn missing_possible_value_error() { fn missing_possible_value_error() {
static MISSING_PV_ERROR: &str = "\
error: The argument '-O <option>' requires a value but none was supplied
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
utils::assert_output( utils::assert_output(
Command::new("test").arg( Command::new("test").arg(
Arg::new("option") Arg::new("option")
@ -297,13 +346,6 @@ fn missing_possible_value_error() {
); );
} }
static MISSING_PV_ERROR: &str = "\
error: The argument '-O <option>' requires a value but none was supplied
[possible values: slow, fast, \"ludicrous speed\"]
For more information try '--help'
";
#[test] #[test]
fn alias() { fn alias() {
let m = Command::new("pv") let m = Command::new("pv")

View file

@ -1,54 +1,8 @@
use super::utils;
use clap::builder::ArgPredicate; use clap::builder::ArgPredicate;
use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command}; use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command};
static REQUIRE_EQUALS: &str = "\ #[cfg(feature = "error-context")]
error: The following required arguments were not provided: use super::utils;
--opt=<FILE>
Usage: clap-test --opt=<FILE>
For more information try '--help'
";
static REQUIRE_EQUALS_FILTERED: &str = "\
error: The following required arguments were not provided:
--opt=<FILE>
Usage: clap-test --opt=<FILE> --foo=<FILE>
For more information try '--help'
";
static REQUIRE_EQUALS_FILTERED_GROUP: &str = "\
error: The following required arguments were not provided:
--opt=<FILE>
Usage: clap-test --opt=<FILE> --foo=<FILE> <--g1=<FILE>|--g2=<FILE>>
For more information try '--help'
";
static MISSING_REQ: &str = "\
error: The following required arguments were not provided:
--long-option-2 <option2>
<positional>
<positional2>
Usage: clap-test --long-option-2 <option2> -F <positional> <positional2> [positional3]...
For more information try '--help'
";
static COND_REQ_IN_USAGE: &str = "\
error: The following required arguments were not provided:
--output <output>
Usage: test --target <target> --input <input> --output <output>
For more information try '--help'
";
#[test] #[test]
fn flag_required() { fn flag_required() {
@ -127,16 +81,9 @@ fn positional_required_2() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn positional_required_with_requires() { fn positional_required_with_requires() {
let cmd = Command::new("positional_required") static POSITIONAL_REQ: &str = "\
.arg(Arg::new("flag").required(true).requires("opt"))
.arg(Arg::new("opt"))
.arg(Arg::new("bar"));
utils::assert_output(cmd, "clap-test", POSITIONAL_REQ, true);
}
static POSITIONAL_REQ: &str = "\
error: The following required arguments were not provided: error: The following required arguments were not provided:
<flag> <flag>
<opt> <opt>
@ -146,17 +93,18 @@ Usage: clap-test <flag> <opt> [bar]
For more information try '--help' For more information try '--help'
"; ";
#[test]
fn positional_required_with_requires_if_no_value() {
let cmd = Command::new("positional_required") let cmd = Command::new("positional_required")
.arg(Arg::new("flag").required(true).requires_if("val", "opt")) .arg(Arg::new("flag").required(true).requires("opt"))
.arg(Arg::new("opt")) .arg(Arg::new("opt"))
.arg(Arg::new("bar")); .arg(Arg::new("bar"));
utils::assert_output(cmd, "clap-test", POSITIONAL_REQ_IF_NO_VAL, true); utils::assert_output(cmd, "clap-test", POSITIONAL_REQ, true);
} }
static POSITIONAL_REQ_IF_NO_VAL: &str = "\ #[test]
#[cfg(feature = "error-context")]
fn positional_required_with_requires_if_no_value() {
static POSITIONAL_REQ_IF_NO_VAL: &str = "\
error: The following required arguments were not provided: error: The following required arguments were not provided:
<flag> <flag>
@ -165,18 +113,18 @@ Usage: clap-test <flag> [opt] [bar]
For more information try '--help' For more information try '--help'
"; ";
#[test]
fn positional_required_with_requires_if_value() {
let cmd = Command::new("positional_required") let cmd = Command::new("positional_required")
.arg(Arg::new("flag").required(true).requires_if("val", "opt")) .arg(Arg::new("flag").required(true).requires_if("val", "opt"))
.arg(Arg::new("foo").required(true))
.arg(Arg::new("opt")) .arg(Arg::new("opt"))
.arg(Arg::new("bar")); .arg(Arg::new("bar"));
utils::assert_output(cmd, "clap-test val", POSITIONAL_REQ_IF_VAL, true); utils::assert_output(cmd, "clap-test", POSITIONAL_REQ_IF_NO_VAL, true);
} }
static POSITIONAL_REQ_IF_VAL: &str = "\ #[test]
#[cfg(feature = "error-context")]
fn positional_required_with_requires_if_value() {
static POSITIONAL_REQ_IF_VAL: &str = "\
error: The following required arguments were not provided: error: The following required arguments were not provided:
<foo> <foo>
<opt> <opt>
@ -186,6 +134,15 @@ Usage: clap-test <flag> <foo> <opt> [bar]
For more information try '--help' For more information try '--help'
"; ";
let cmd = Command::new("positional_required")
.arg(Arg::new("flag").required(true).requires_if("val", "opt"))
.arg(Arg::new("foo").required(true))
.arg(Arg::new("opt"))
.arg(Arg::new("bar"));
utils::assert_output(cmd, "clap-test val", POSITIONAL_REQ_IF_VAL, true);
}
#[test] #[test]
fn group_required() { fn group_required() {
let result = Command::new("group_required") let result = Command::new("group_required")
@ -553,7 +510,19 @@ fn required_unless_any_err() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn missing_required_output() { fn missing_required_output() {
static MISSING_REQ: &str = "\
error: The following required arguments were not provided:
--long-option-2 <option2>
<positional>
<positional2>
Usage: clap-test --long-option-2 <option2> -F <positional> <positional2> [positional3]...
For more information try '--help'
";
utils::assert_output(utils::complex_app(), "clap-test -F", MISSING_REQ, true); utils::assert_output(utils::complex_app(), "clap-test -F", MISSING_REQ, true);
} }
@ -786,7 +755,17 @@ fn required_if_any_all_values_present_fail() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn list_correct_required_args() { fn list_correct_required_args() {
static COND_REQ_IN_USAGE: &str = "\
error: The following required arguments were not provided:
--output <output>
Usage: test --target <target> --input <input> --output <output>
For more information try '--help'
";
let cmd = Command::new("Test cmd") let cmd = Command::new("Test cmd")
.version("1.0") .version("1.0")
.author("F0x06") .author("F0x06")
@ -820,7 +799,17 @@ fn list_correct_required_args() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn required_if_val_present_fail_error_output() { fn required_if_val_present_fail_error_output() {
static COND_REQ_IN_USAGE: &str = "\
error: The following required arguments were not provided:
--output <output>
Usage: test --target <target> --input <input> --output <output>
For more information try '--help'
";
let cmd = Command::new("Test cmd") let cmd = Command::new("Test cmd")
.version("1.0") .version("1.0")
.author("F0x06") .author("F0x06")
@ -935,7 +924,17 @@ fn required_ifs_wrong_val_mult_fail() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn require_eq() { fn require_eq() {
static REQUIRE_EQUALS: &str = "\
error: The following required arguments were not provided:
--opt=<FILE>
Usage: clap-test --opt=<FILE>
For more information try '--help'
";
let cmd = Command::new("clap-test").version("v1.4.8").arg( let cmd = Command::new("clap-test").version("v1.4.8").arg(
Arg::new("opt") Arg::new("opt")
.long("opt") .long("opt")
@ -949,7 +948,17 @@ fn require_eq() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn require_eq_filtered() { fn require_eq_filtered() {
static REQUIRE_EQUALS_FILTERED: &str = "\
error: The following required arguments were not provided:
--opt=<FILE>
Usage: clap-test --opt=<FILE> --foo=<FILE>
For more information try '--help'
";
let cmd = Command::new("clap-test") let cmd = Command::new("clap-test")
.version("v1.4.8") .version("v1.4.8")
.arg( .arg(
@ -974,7 +983,17 @@ fn require_eq_filtered() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn require_eq_filtered_group() { fn require_eq_filtered_group() {
static REQUIRE_EQUALS_FILTERED_GROUP: &str = "\
error: The following required arguments were not provided:
--opt=<FILE>
Usage: clap-test --opt=<FILE> --foo=<FILE> <--g1=<FILE>|--g2=<FILE>>
For more information try '--help'
";
let cmd = Command::new("clap-test") let cmd = Command::new("clap-test")
.version("v1.4.8") .version("v1.4.8")
.arg( .arg(
@ -1020,17 +1039,6 @@ fn require_eq_filtered_group() {
); );
} }
static ISSUE_1158: &str = "\
error: The following required arguments were not provided:
-x <X>
-y <Y>
-z <Z>
Usage: example -x <X> -y <Y> -z <Z> <ID>
For more information try '--help'
";
fn issue_1158_app() -> Command { fn issue_1158_app() -> Command {
Command::new("example") Command::new("example")
.arg( .arg(
@ -1054,6 +1062,7 @@ fn issue_1158_app() -> Command {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn multiple_required_unless_usage_printing() { fn multiple_required_unless_usage_printing() {
static MULTIPLE_REQUIRED_UNLESS_USAGE: &str = "\ static MULTIPLE_REQUIRED_UNLESS_USAGE: &str = "\
error: The following required arguments were not provided: error: The following required arguments were not provided:
@ -1097,7 +1106,19 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn issue_1158_conflicting_requirements() { fn issue_1158_conflicting_requirements() {
static ISSUE_1158: &str = "\
error: The following required arguments were not provided:
-x <X>
-y <Y>
-z <Z>
Usage: example -x <X> -y <Y> -z <Z> <ID>
For more information try '--help'
";
let cmd = issue_1158_app(); let cmd = issue_1158_app();
utils::assert_output(cmd, "example id", ISSUE_1158, true); utils::assert_output(cmd, "example id", ISSUE_1158, true);
@ -1409,6 +1430,7 @@ fn required_unless_all_on_default_value() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn required_error_doesnt_duplicate() { fn required_error_doesnt_duplicate() {
let cmd = Command::new("Clap-created-USAGE-string-bug") let cmd = Command::new("Clap-created-USAGE-string-bug")
.arg(Arg::new("a").required(true)) .arg(Arg::new("a").required(true))
@ -1435,6 +1457,7 @@ For more information try '--help'
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn required_require_with_group_shows_flag() { fn required_require_with_group_shows_flag() {
let cmd = Command::new("test") let cmd = Command::new("test")
.arg(arg!(--"require-first").requires("first")) .arg(arg!(--"require-first").requires("first"))

View file

@ -1,66 +1,6 @@
use super::utils;
use clap::{arg, error::ErrorKind, Arg, ArgAction, Command}; use clap::{arg, error::ErrorKind, Arg, ArgAction, Command};
static VISIBLE_ALIAS_HELP: &str = "\ use super::utils;
Usage: clap-test [COMMAND]
Commands:
test Some help [aliases: dongle, done]
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help information
-V, --version Print version information
";
static INVISIBLE_ALIAS_HELP: &str = "\
Usage: clap-test [COMMAND]
Commands:
test Some help
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help information
-V, --version Print version information
";
#[cfg(feature = "suggestions")]
static DYM_SUBCMD: &str = "\
error: The subcommand 'subcm' wasn't recognized
Did you mean 'subcmd'?
If you believe you received this message in error, try re-running with 'dym -- subcm'
Usage: dym [COMMAND]
For more information try '--help'
";
#[cfg(feature = "suggestions")]
static DYM_SUBCMD_AMBIGUOUS: &str = "\
error: The subcommand 'te' wasn't recognized
Did you mean 'test' or 'temp'?
If you believe you received this message in error, try re-running with 'dym -- te'
Usage: dym [COMMAND]
For more information try '--help'
";
static SUBCMD_AFTER_DOUBLE_DASH: &str = "\
error: Found argument 'subcmd' which wasn't expected, or isn't valid in this context
If you tried to supply `subcmd` as a subcommand, remove the '--' before it.
Usage: cmd [COMMAND]
For more information try '--help'
";
#[test] #[test]
fn subcommand() { fn subcommand() {
@ -154,14 +94,42 @@ fn multiple_aliases() {
#[test] #[test]
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
#[cfg(feature = "error-context")]
fn subcmd_did_you_mean_output() { fn subcmd_did_you_mean_output() {
#[cfg(feature = "suggestions")]
static DYM_SUBCMD: &str = "\
error: The subcommand 'subcm' wasn't recognized
Did you mean 'subcmd'?
If you believe you received this message in error, try re-running with 'dym -- subcm'
Usage: dym [COMMAND]
For more information try '--help'
";
let cmd = Command::new("dym").subcommand(Command::new("subcmd")); let cmd = Command::new("dym").subcommand(Command::new("subcmd"));
utils::assert_output(cmd, "dym subcm", DYM_SUBCMD, true); utils::assert_output(cmd, "dym subcm", DYM_SUBCMD, true);
} }
#[test] #[test]
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
#[cfg(feature = "error-context")]
fn subcmd_did_you_mean_output_ambiguous() { fn subcmd_did_you_mean_output_ambiguous() {
#[cfg(feature = "suggestions")]
static DYM_SUBCMD_AMBIGUOUS: &str = "\
error: The subcommand 'te' wasn't recognized
Did you mean 'test' or 'temp'?
If you believe you received this message in error, try re-running with 'dym -- te'
Usage: dym [COMMAND]
For more information try '--help'
";
let cmd = Command::new("dym") let cmd = Command::new("dym")
.subcommand(Command::new("test")) .subcommand(Command::new("test"))
.subcommand(Command::new("temp")); .subcommand(Command::new("temp"));
@ -170,6 +138,7 @@ fn subcmd_did_you_mean_output_ambiguous() {
#[test] #[test]
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
#[cfg(feature = "error-context")]
fn subcmd_did_you_mean_output_arg() { fn subcmd_did_you_mean_output_arg() {
static EXPECTED: &str = "\ static EXPECTED: &str = "\
error: Found argument '--subcmarg' which wasn't expected, or isn't valid in this context error: Found argument '--subcmarg' which wasn't expected, or isn't valid in this context
@ -191,6 +160,7 @@ For more information try '--help'
#[test] #[test]
#[cfg(feature = "suggestions")] #[cfg(feature = "suggestions")]
#[cfg(feature = "error-context")]
fn subcmd_did_you_mean_output_arg_false_positives() { fn subcmd_did_you_mean_output_arg_false_positives() {
static EXPECTED: &str = "\ static EXPECTED: &str = "\
error: Found argument '--subcmarg' which wasn't expected, or isn't valid in this context error: Found argument '--subcmarg' which wasn't expected, or isn't valid in this context
@ -219,6 +189,18 @@ fn alias_help() {
#[test] #[test]
fn visible_aliases_help_output() { fn visible_aliases_help_output() {
static VISIBLE_ALIAS_HELP: &str = "\
Usage: clap-test [COMMAND]
Commands:
test Some help [aliases: dongle, done]
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help information
-V, --version Print version information
";
let cmd = Command::new("clap-test").version("2.6").subcommand( let cmd = Command::new("clap-test").version("2.6").subcommand(
Command::new("test") Command::new("test")
.about("Some help") .about("Some help")
@ -231,6 +213,18 @@ fn visible_aliases_help_output() {
#[test] #[test]
fn invisible_aliases_help_output() { fn invisible_aliases_help_output() {
static INVISIBLE_ALIAS_HELP: &str = "\
Usage: clap-test [COMMAND]
Commands:
test Some help
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help information
-V, --version Print version information
";
let cmd = Command::new("clap-test") let cmd = Command::new("clap-test")
.version("2.6") .version("2.6")
.subcommand(Command::new("test").about("Some help").alias("invisible")); .subcommand(Command::new("test").about("Some help").alias("invisible"));
@ -361,7 +355,18 @@ fn subcommand_placeholder_test() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn subcommand_used_after_double_dash() { fn subcommand_used_after_double_dash() {
static SUBCMD_AFTER_DOUBLE_DASH: &str = "\
error: Found argument 'subcmd' which wasn't expected, or isn't valid in this context
If you tried to supply `subcmd` as a subcommand, remove the '--' before it.
Usage: cmd [COMMAND]
For more information try '--help'
";
let cmd = Command::new("cmd").subcommand(Command::new("subcmd")); let cmd = Command::new("cmd").subcommand(Command::new("subcmd"));
utils::assert_output(cmd, "cmd -- subcmd", SUBCMD_AFTER_DOUBLE_DASH, true); utils::assert_output(cmd, "cmd -- subcmd", SUBCMD_AFTER_DOUBLE_DASH, true);
@ -421,6 +426,7 @@ fn issue_2494_subcommand_is_present() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn subcommand_not_recognized() { fn subcommand_not_recognized() {
let cmd = Command::new("fake") let cmd = Command::new("fake")
.subcommand(Command::new("sub")) .subcommand(Command::new("sub"))
@ -494,6 +500,7 @@ fn hostname_like_multicall() {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn bad_multicall_command_error() { fn bad_multicall_command_error() {
let cmd = Command::new("repl") let cmd = Command::new("repl")
.version("1.0.0") .version("1.0.0")

View file

@ -68,6 +68,7 @@ struct HexOpt {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn test_parse_hex() { fn test_parse_hex() {
assert_eq!( assert_eq!(
HexOpt { number: 5 }, HexOpt { number: 5 },

View file

@ -136,6 +136,7 @@ struct HexOpt {
} }
#[test] #[test]
#[cfg(feature = "error-context")]
fn test_parse_hex_function_path() { fn test_parse_hex_function_path() {
assert_eq!( assert_eq!(
HexOpt { number: 5 }, HexOpt { number: 5 },

View file

@ -1,6 +1,7 @@
#![cfg(not(tarpaulin))] #![cfg(not(tarpaulin))]
#[test] #[test]
#[cfg(feature = "error-context")]
fn example_tests() { fn example_tests() {
let t = trycmd::TestCases::new(); let t = trycmd::TestCases::new();
let features = [ let features = [

View file

@ -1,6 +1,7 @@
#![cfg(not(tarpaulin))] #![cfg(not(tarpaulin))]
#[test] #[test]
#[cfg(feature = "error-context")]
fn ui_tests() { fn ui_tests() {
let t = trycmd::TestCases::new(); let t = trycmd::TestCases::new();
let features = [ let features = [