From 2d83a7b12ee8258932f5528084578717d5b96c72 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 19 Sep 2022 09:59:04 -0500 Subject: [PATCH] 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. --- CHANGELOG.md | 1 + Cargo.toml | 4 +- src/_features.rs | 1 + src/builder/styled_str.rs | 1 + src/error/context.rs | 2 + src/error/format.rs | 14 +- src/error/mod.rs | 240 +++++++++++++++++-------- tests/builder/conflicts.rs | 12 +- tests/builder/default_missing_vals.rs | 2 + tests/builder/default_vals.rs | 13 +- tests/builder/double_require.rs | 37 ++-- tests/builder/empty_values.rs | 6 +- tests/builder/error.rs | 3 + tests/builder/flags.rs | 24 +-- tests/builder/groups.rs | 53 +++--- tests/builder/help.rs | 5 +- tests/builder/opts.rs | 56 +++--- tests/builder/possible_values.rs | 130 +++++++++----- tests/builder/require.rs | 179 ++++++++++-------- tests/builder/subcommands.rs | 129 ++++++------- tests/derive/custom_string_parsers.rs | 1 + tests/derive/non_literal_attributes.rs | 1 + tests/examples.rs | 1 + tests/ui.rs | 1 + 24 files changed, 568 insertions(+), 348 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d1cbdae..a739c5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -268,6 +268,7 @@ Behavior Changes - 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) - *(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)* New `{tab}` variable for `Command::help_template` (#4161) diff --git a/Cargo.toml b/Cargo.toml index b18b4ca1..81b9f161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ pre-release-replacements = [ default = [ "std", "color", + "error-context", "suggestions", ] 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 std = [] # support for no_std in a backwards-compatible way color = ["dep:atty", "dep:termcolor"] -suggestions = ["dep:strsim"] +error-context = [] +suggestions = ["dep:strsim", "error-context"] # Optional deprecated = ["clap_derive?/deprecated"] # Guided experience to prepare for next breaking release (at different stages of development, this may become default) diff --git a/src/_features.rs b/src/_features.rs index 7c12ef15..3cb1a343 100644 --- a/src/_features.rs +++ b/src/_features.rs @@ -6,6 +6,7 @@ //! //! * **std**: _Not Currently Used._ Placeholder for supporting `no_std` environments in a backwards compatible manner. //! * **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. //! //! #### Optional features diff --git a/src/builder/styled_str.rs b/src/builder/styled_str.rs index 503bf333..7e4b3893 100644 --- a/src/builder/styled_str.rs +++ b/src/builder/styled_str.rs @@ -29,6 +29,7 @@ impl StyledStr { 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) { self.stylize_(Some(Style::Warning), msg.into()); } diff --git a/src/error/context.rs b/src/error/context.rs index dbaeebcb..d9921b4e 100644 --- a/src/error/context.rs +++ b/src/error/context.rs @@ -1,6 +1,7 @@ /// Semantics for a piece of error information #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] +#[cfg(feature = "error-context")] pub enum ContextKind { /// The cause of the error InvalidSubcommand, @@ -66,6 +67,7 @@ impl std::fmt::Display for ContextKind { /// A piece of error information #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] +#[cfg(feature = "error-context")] pub enum ContextValue { /// [`ContextKind`] is self-sufficient, no additional information needed None, diff --git a/src/error/format.rs b/src/error/format.rs index 4b0307d6..941a40d6 100644 --- a/src/error/format.rs +++ b/src/error/format.rs @@ -1,9 +1,13 @@ #![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; +#[cfg(feature = "error-context")] use crate::error::ContextKind; +#[cfg(feature = "error-context")] use crate::error::ContextValue; use crate::error::ErrorKind; use crate::output::TAB; @@ -16,7 +20,10 @@ pub trait ErrorFormatter: Sized { /// 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] pub struct KindFormatter; @@ -38,8 +45,10 @@ impl ErrorFormatter for KindFormatter { /// Dump the error context reported #[non_exhaustive] +#[cfg(feature = "error-context")] pub struct RawFormatter; +#[cfg(feature = "error-context")] impl ErrorFormatter for RawFormatter { fn format_error(error: &crate::Error) -> StyledStr { let mut styled = StyledStr::new(); @@ -73,8 +82,10 @@ impl ErrorFormatter for RawFormatter { /// Richly formatted error context #[non_exhaustive] +#[cfg(feature = "error-context")] pub struct RichFormatter; +#[cfg(feature = "error-context")] impl ErrorFormatter for RichFormatter { fn format_error(error: &crate::Error) -> StyledStr { let mut styled = StyledStr::new(); @@ -107,6 +118,7 @@ fn start_error(styled: &mut StyledStr) { } #[must_use] +#[cfg(feature = "error-context")] fn write_dynamic_context(error: &crate::Error, styled: &mut StyledStr) -> bool { match error.kind() { ErrorKind::ArgumentConflict => { diff --git a/src/error/mod.rs b/src/error/mod.rs index d7e182b2..40e021f7 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,5 +1,11 @@ //! 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 use std::{ borrow::Cow, @@ -19,18 +25,27 @@ use crate::util::FlatMap; use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE}; use crate::Command; +#[cfg(feature = "error-context")] mod context; mod format; mod kind; -pub use context::ContextKind; -pub use context::ContextValue; pub use format::ErrorFormatter; pub use format::KindFormatter; -pub use format::RawFormatter; -pub use format::RichFormatter; 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; /// Short hand for [`Result`] type @@ -51,6 +66,7 @@ pub struct Error { #[derive(Debug)] struct ErrorInner { kind: ErrorKind, + #[cfg(feature = "error-context")] context: FlatMap, message: Option, source: Option>, @@ -112,12 +128,14 @@ impl Error { } /// Additional information to further qualify the error + #[cfg(feature = "error-context")] pub fn context(&self) -> impl Iterator { self.inner.context.iter().map(|(k, v)| (*k, v)) } /// Lookup a piece of context #[inline(never)] + #[cfg(feature = "error-context")] pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> { self.inner.context.get(&kind) } @@ -183,6 +201,7 @@ impl Error { Self { inner: Box::new(ErrorInner { kind, + #[cfg(feature = "error-context")] context: FlatMap::new(), message: None, source: None, @@ -233,6 +252,7 @@ impl Error { /// Does not verify if `ContextKind` is already present #[inline(never)] + #[cfg(feature = "error-context")] pub(crate) fn insert_context_unchecked( mut self, kind: ContextKind, @@ -244,6 +264,7 @@ impl Error { /// Does not verify if `ContextKind` is already present #[inline(never)] + #[cfg(feature = "error-context")] pub(crate) fn extend_context_unchecked( mut self, context: [(ContextKind, ContextValue); N], @@ -274,18 +295,23 @@ impl Error { mut others: Vec, usage: StyledStr, ) -> Self { - let others = match others.len() { - 0 => ContextValue::None, - 1 => ContextValue::String(others.pop().unwrap()), - _ => ContextValue::Strings(others), - }; - Self::new(ErrorKind::ArgumentConflict) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::ArgumentConflict).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + let others = match others.len() { + 0 => ContextValue::None, + 1 => ContextValue::String(others.pop().unwrap()), + _ => ContextValue::Strings(others), + }; + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::PriorArg, others), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn empty_value(cmd: &Command, good_vals: &[String], arg: String) -> Self { @@ -293,12 +319,17 @@ impl Error { } pub(crate) fn no_equals(cmd: &Command, arg: String, usage: StyledStr) -> Self { - Self::new(ErrorKind::NoEquals) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::NoEquals).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn invalid_value( @@ -308,9 +339,11 @@ impl Error { arg: String, ) -> Self { let suggestion = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop(); - let mut err = Self::new(ErrorKind::InvalidValue) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::InvalidValue).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(bad_val)), ( @@ -318,12 +351,14 @@ impl Error { ContextValue::Strings(good_vals.iter().map(|s| (*s).to_owned()).collect()), ), ]); - if let Some(suggestion) = suggestion { - err = err.insert_context_unchecked( - ContextKind::SuggestedValue, - ContextValue::String(suggestion), - ); + if let Some(suggestion) = suggestion { + err = err.insert_context_unchecked( + ContextKind::SuggestedValue, + ContextValue::String(suggestion), + ); + } } + err } @@ -335,9 +370,11 @@ impl Error { usage: StyledStr, ) -> Self { let suggestion = format!("{} -- {}", name, subcmd); - Self::new(ErrorKind::InvalidSubcommand) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)), ( ContextKind::SuggestedSubcommand, @@ -348,16 +385,24 @@ impl Error { ContextValue::String(suggestion), ), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn unrecognized_subcommand(cmd: &Command, subcmd: String, usage: StyledStr) -> Self { - Self::new(ErrorKind::InvalidSubcommand) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn missing_required_argument( @@ -365,27 +410,43 @@ impl Error { required: Vec, usage: StyledStr, ) -> Self { - Self::new(ErrorKind::MissingRequiredArgument) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::MissingRequiredArgument).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::Strings(required)), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn missing_subcommand(cmd: &Command, name: String, usage: StyledStr) -> Self { - Self::new(ErrorKind::MissingSubcommand) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::MissingSubcommand).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidSubcommand, ContextValue::String(name)), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn invalid_utf8(cmd: &Command, usage: StyledStr) -> Self { - Self::new(ErrorKind::InvalidUtf8) - .with_cmd(cmd) - .extend_context_unchecked([(ContextKind::Usage, ContextValue::StyledStr(usage))]) + let mut err = Self::new(ErrorKind::InvalidUtf8).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err + .extend_context_unchecked([(ContextKind::Usage, ContextValue::StyledStr(usage))]); + } + + err } pub(crate) fn too_many_values( @@ -394,13 +455,18 @@ impl Error { arg: String, usage: StyledStr, ) -> Self { - Self::new(ErrorKind::TooManyValues) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::TooManyValues).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(val)), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn too_few_values( @@ -410,9 +476,11 @@ impl Error { curr_vals: usize, usage: StyledStr, ) -> Self { - Self::new(ErrorKind::TooFewValues) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::TooFewValues).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), ( ContextKind::MinValues, @@ -423,7 +491,10 @@ impl Error { ContextValue::Number(curr_vals as isize), ), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn value_validation( @@ -431,12 +502,17 @@ impl Error { val: String, err: Box, ) -> Self { - Self::new(ErrorKind::ValueValidation) - .set_source(err) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::ValueValidation).set_source(err); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::InvalidValue, ContextValue::String(val)), - ]) + ]); + } + + err } pub(crate) fn wrong_number_of_values( @@ -446,9 +522,11 @@ impl Error { curr_vals: usize, usage: StyledStr, ) -> Self { - Self::new(ErrorKind::WrongNumberOfValues) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::WrongNumberOfValues).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), ( ContextKind::ExpectedNumValues, @@ -459,7 +537,10 @@ impl Error { ContextValue::Number(curr_vals as isize), ), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } pub(crate) fn unknown_argument( @@ -468,35 +549,44 @@ impl Error { did_you_mean: Option<(String, Option)>, usage: StyledStr, ) -> Self { - let mut err = Self::new(ErrorKind::UnknownArgument) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::Usage, ContextValue::StyledStr(usage)), ]); - if let Some((flag, sub)) = did_you_mean { - err = err.insert_context_unchecked( - ContextKind::SuggestedArg, - ContextValue::String(format!("--{}", flag)), - ); - if let Some(sub) = sub { + if let Some((flag, sub)) = did_you_mean { err = err.insert_context_unchecked( - ContextKind::SuggestedSubcommand, - ContextValue::String(sub), + ContextKind::SuggestedArg, + ContextValue::String(format!("--{}", flag)), ); + if let Some(sub) = sub { + err = err.insert_context_unchecked( + ContextKind::SuggestedSubcommand, + ContextValue::String(sub), + ); + } } } + err } pub(crate) fn unnecessary_double_dash(cmd: &Command, arg: String, usage: StyledStr) -> Self { - Self::new(ErrorKind::UnknownArgument) - .with_cmd(cmd) - .extend_context_unchecked([ + let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); + + #[cfg(feature = "error-context")] + { + err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), (ContextKind::TrailingArg, ContextValue::Bool(true)), (ContextKind::Usage, ContextValue::StyledStr(usage)), - ]) + ]); + } + + err } fn formatted(&self) -> Cow<'_, StyledStr> { diff --git a/tests/builder/conflicts.rs b/tests/builder/conflicts.rs index c3b062f3..44393ef4 100644 --- a/tests/builder/conflicts.rs +++ b/tests/builder/conflicts.rs @@ -1,7 +1,8 @@ -use super::utils; - use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command}; +#[cfg(feature = "error-context")] +use super::utils; + #[test] fn flag_conflict() { let result = Command::new("flag_conflict") @@ -271,6 +272,7 @@ fn get_arg_conflicts_with_group() { } #[test] +#[cfg(feature = "error-context")] fn conflict_output() { static CONFLICT_ERR: &str = "\ error: The argument '--flag...' cannot be used with '-F' @@ -289,6 +291,7 @@ For more information try '--help' } #[test] +#[cfg(feature = "error-context")] fn conflict_output_rev() { static CONFLICT_ERR_REV: &str = "\ error: The argument '-F' cannot be used with '--flag...' @@ -307,6 +310,7 @@ For more information try '--help' } #[test] +#[cfg(feature = "error-context")] fn conflict_output_with_required() { static CONFLICT_ERR: &str = "\ error: The argument '--flag...' cannot be used with '-F' @@ -325,6 +329,7 @@ For more information try '--help' } #[test] +#[cfg(feature = "error-context")] fn conflict_output_rev_with_required() { static CONFLICT_ERR_REV: &str = "\ error: The argument '-F' cannot be used with '--flag...' @@ -343,6 +348,7 @@ For more information try '--help' } #[test] +#[cfg(feature = "error-context")] fn conflict_output_three_conflicting() { static CONFLICT_ERR_THREE: &str = "\ error: The argument '--one' cannot be used with: @@ -382,6 +388,7 @@ For more information try '--help' } #[test] +#[cfg(feature = "error-context")] fn two_conflicting_arguments() { let a = Command::new("two_conflicting_arguments") .arg( @@ -409,6 +416,7 @@ fn two_conflicting_arguments() { } #[test] +#[cfg(feature = "error-context")] fn three_conflicting_arguments() { let a = Command::new("three_conflicting_arguments") .arg( diff --git a/tests/builder/default_missing_vals.rs b/tests/builder/default_missing_vals.rs index cf67d85c..5a3d2d29 100644 --- a/tests/builder/default_missing_vals.rs +++ b/tests/builder/default_missing_vals.rs @@ -260,6 +260,7 @@ fn delimited_missing_value() { #[cfg(debug_assertions)] #[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]'"] fn default_missing_values_are_possible_values() { use clap::{Arg, Command}; @@ -275,6 +276,7 @@ fn default_missing_values_are_possible_values() { #[cfg(debug_assertions)] #[test] +#[cfg(feature = "error-context")] #[should_panic = "Argument `arg`'s default_missing_value=\"value\" failed validation: error: Invalid value \"value\" for '[arg]"] fn default_missing_values_are_valid() { use clap::{Arg, Command}; diff --git a/tests/builder/default_vals.rs b/tests/builder/default_vals.rs index 0cd67630..e39ae76f 100644 --- a/tests/builder/default_vals.rs +++ b/tests/builder/default_vals.rs @@ -1,9 +1,13 @@ use std::ffi::OsStr; use std::ffi::OsString; -use super::utils; 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] fn opts() { @@ -30,6 +34,7 @@ fn default_has_index() { } #[test] +#[cfg(feature = "error-context")] fn opt_without_value_fail() { let r = Command::new("df") .arg( @@ -694,6 +699,7 @@ fn multiple_defaults_override() { } #[test] +#[cfg(feature = "error-context")] fn default_vals_donnot_show_in_smart_usage() { let cmd = Command::new("bug") .arg( @@ -773,6 +779,7 @@ fn required_args_with_default_values() { #[cfg(debug_assertions)] #[test] +#[cfg(feature = "error-context")] #[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() { use clap::{Arg, Command}; @@ -788,6 +795,7 @@ fn default_values_are_possible_values() { #[cfg(debug_assertions)] #[test] +#[cfg(feature = "error-context")] #[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"] fn invalid_default_values() { use clap::{Arg, Command}; @@ -817,6 +825,7 @@ fn valid_delimited_default_values() { #[cfg(debug_assertions)] #[test] +#[cfg(feature = "error-context")] #[should_panic = "Argument `arg`'s default_value=\"one\" failed validation: error: Invalid value \"one\" for '[arg]"] fn invalid_delimited_default_values() { use clap::{Arg, Command}; diff --git a/tests/builder/double_require.rs b/tests/builder/double_require.rs index d191ac98..7c617b42 100644 --- a/tests/builder/double_require.rs +++ b/tests/builder/double_require.rs @@ -10,24 +10,6 @@ Options: -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 { Command::new("prog") .arg( @@ -74,13 +56,32 @@ fn help_text() { } #[test] +#[cfg(feature = "error-context")] 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"]); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!(err.kind(), ErrorKind::MissingRequiredArgument); 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"]); assert!(res.is_err()); let err = res.unwrap_err(); diff --git a/tests/builder/empty_values.rs b/tests/builder/empty_values.rs index 38ffb293..02aa7cf2 100644 --- a/tests/builder/empty_values.rs +++ b/tests/builder/empty_values.rs @@ -1,7 +1,8 @@ -use super::utils; - use clap::{error::ErrorKind, Arg, ArgAction, Command}; +#[cfg(feature = "error-context")] +use super::utils; + #[test] fn empty_values() { let m = Command::new("config") @@ -102,6 +103,7 @@ fn no_empty_values_without_equals() { } #[test] +#[cfg(feature = "error-context")] fn no_empty_values_without_equals_but_requires_equals() { let cmd = Command::new("config").arg( Arg::new("config") diff --git a/tests/builder/error.rs b/tests/builder/error.rs index 64998b09..bbdfa735 100644 --- a/tests/builder/error.rs +++ b/tests/builder/error.rs @@ -97,6 +97,7 @@ Options: } #[test] +#[cfg(feature = "error-context")] fn raw_prints_help() { let cmd = Command::new("test"); let res = cmd @@ -130,6 +131,7 @@ error: Found an argument which wasn't expected or isn't valid in this context } #[test] +#[cfg(feature = "error-context")] fn rich_formats_validation_error() { let cmd = Command::new("test"); let res = cmd.try_get_matches_from(["test", "unused"]); @@ -147,6 +149,7 @@ For more information try '--help' } #[test] +#[cfg(feature = "error-context")] fn raw_formats_validation_error() { let cmd = Command::new("test"); let res = cmd diff --git a/tests/builder/flags.rs b/tests/builder/flags.rs index e92d9757..0f4d316f 100644 --- a/tests/builder/flags.rs +++ b/tests/builder/flags.rs @@ -1,15 +1,7 @@ -use super::utils; use clap::{arg, Arg, ArgAction, Command}; -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' -"; +#[cfg(feature = "error-context")] +use super::utils; #[test] fn flag_using_short() { @@ -143,7 +135,18 @@ fn multiple_flags_in_single() { } #[test] +#[cfg(feature = "error-context")] 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") .arg(Arg::new("filename")) .arg(Arg::new("a-flag").long("a-flag").action(ArgAction::SetTrue)); @@ -176,6 +179,7 @@ fn issue_1284_argument_in_flag_style() { } #[test] +#[cfg(feature = "error-context")] fn issue_2308_multiple_dashes() { static MULTIPLE_DASHES: &str = "\ error: Found argument '-----' which wasn't expected, or isn't valid in this context diff --git a/tests/builder/groups.rs b/tests/builder/groups.rs index 99ac9fec..085996b6 100644 --- a/tests/builder/groups.rs +++ b/tests/builder/groups.rs @@ -1,30 +1,6 @@ -use super::utils; - use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgGroup, Command, Id}; -static REQ_GROUP_USAGE: &str = "error: The following required arguments were not provided: - - -Usage: clap-test - -For more information try '--help' -"; - -static REQ_GROUP_CONFLICT_USAGE: &str = "\ -error: The argument '--delete' cannot be used with '[base]' - -Usage: clap-test - -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' -"; +use super::utils; #[test] fn required_group_missing_arg() { @@ -150,7 +126,16 @@ fn empty_group() { } #[test] +#[cfg(feature = "error-context")] fn req_group_usage_string() { + static REQ_GROUP_USAGE: &str = "error: The following required arguments were not provided: + + +Usage: clap-test + +For more information try '--help' +"; + let cmd = Command::new("req_group") .arg(arg!([base] "Base commit")) .arg(arg!( @@ -166,7 +151,16 @@ fn req_group_usage_string() { } #[test] +#[cfg(feature = "error-context")] 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 + +For more information try '--help' +"; + let cmd = Command::new("req_group") .arg(arg!([base] "Base commit").conflicts_with("delete")) .arg(arg!( @@ -187,7 +181,16 @@ fn req_group_with_conflict_usage_string() { } #[test] +#[cfg(feature = "error-context")] 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") .arg(arg!(-a --all "All").conflicts_with("delete")) .arg(arg!( diff --git a/tests/builder/help.rs b/tests/builder/help.rs index fdb94799..39e4e60c 100644 --- a/tests/builder/help.rs +++ b/tests/builder/help.rs @@ -1,7 +1,7 @@ -use super::utils; - use clap::{arg, builder::PossibleValue, error::ErrorKind, Arg, ArgAction, ArgGroup, Command}; +use super::utils; + fn setup() -> Command { Command::new("test") .author("Kevin K.") @@ -52,6 +52,7 @@ fn help_subcommand() { } #[test] +#[cfg(feature = "error-context")] fn help_multi_subcommand_error() { let cmd = Command::new("ctest").subcommand( Command::new("subcmd").subcommand( diff --git a/tests/builder/opts.rs b/tests/builder/opts.rs index 8c7a836e..2771306f 100644 --- a/tests/builder/opts.rs +++ b/tests/builder/opts.rs @@ -1,32 +1,7 @@ -use super::utils; - use clap::{arg, error::ErrorKind, Arg, ArgAction, ArgMatches, Command}; -#[cfg(feature = "suggestions")] -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 ... [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' -"; +#[cfg(feature = "error-context")] +use super::utils; #[test] fn require_equals_fail() { @@ -44,6 +19,7 @@ fn require_equals_fail() { } #[test] +#[cfg(feature = "error-context")] fn require_equals_fail_message() { static NO_EQUALS: &str = "error: Equal sign is needed when assigning values to '--config='. @@ -468,7 +444,20 @@ fn leading_hyphen_with_only_pos_follows() { #[test] #[cfg(feature = "suggestions")] +#[cfg(feature = "error-context")] 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 ... [positional] [positional2] [positional3]... + +For more information try '--help' +"; + 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] #[cfg(feature = "suggestions")] +#[cfg(feature = "error-context")] 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") .arg( Arg::new("files-with-matches") diff --git a/tests/builder/possible_values.rs b/tests/builder/possible_values.rs index 3bca2ffd..17ab980a 100644 --- a/tests/builder/possible_values.rs +++ b/tests/builder/possible_values.rs @@ -1,42 +1,7 @@ -use super::utils; - use clap::{builder::PossibleValue, error::ErrorKind, Arg, ArgAction, Command}; -#[cfg(feature = "suggestions")] -static PV_ERROR: &str = "\ -error: \"slo\" isn't a valid value for '-O