From 3672663319d2f5a8880703d7dbfed360036c8180 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Aug 2022 10:41:51 -0500 Subject: [PATCH 1/4] refactor(error): Delay colorizing until the end This is prep for creating a formatter trait to select alternative implementations for errors. --- src/builder/command.rs | 11 ++++----- src/error/mod.rs | 54 +++++++++++++++++++++++++---------------- src/parser/parser.rs | 15 ++++++------ src/parser/validator.rs | 3 +-- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/builder/command.rs b/src/builder/command.rs index 8f50bd74c56..4d81e93ff32 100644 --- a/src/builder/command.rs +++ b/src/builder/command.rs @@ -4390,12 +4390,11 @@ impl Command { self.disp_ord.unwrap_or(999) } - pub(crate) fn write_help_err(&self, mut use_long: bool, stream: Stream) -> Colorizer { + pub(crate) fn write_help_err(&self, mut use_long: bool) -> StyledStr { debug!( - "Command::write_help_err: {}, use_long={:?}, stream={:?}", + "Command::write_help_err: {}, use_long={:?}", self.get_display_name().unwrap_or_else(|| self.get_name()), use_long && self.use_long_help(), - stream ); use_long = use_long && self.use_long_help(); @@ -4404,14 +4403,14 @@ impl Command { let mut styled = StyledStr::new(); Help::new(&mut styled, self, &usage, use_long).write_help(); - Colorizer::new(stream, self.color_help()).with_content(styled) + styled } - pub(crate) fn write_version_err(&self, use_long: bool) -> Colorizer { + pub(crate) fn write_version_err(&self, use_long: bool) -> StyledStr { let msg = self._render_version(use_long); let mut styled = StyledStr::new(); styled.none(msg); - Colorizer::new(Stream::Stdout, self.color_help()).with_content(styled) + styled } pub(crate) fn use_long_help(&self) -> bool { diff --git a/src/error/mod.rs b/src/error/mod.rs index e73e047b0c5..36bd38095e2 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -48,6 +48,7 @@ struct ErrorInner { source: Option>, help_flag: Option<&'static str>, color_when: ColorChoice, + color_help_when: ColorChoice, backtrace: Option, } @@ -132,7 +133,14 @@ impl Error { /// }; /// ``` pub fn print(&self) -> io::Result<()> { - self.formatted().print() + let style = self.formatted(); + let color_when = if self.kind() == ErrorKind::DisplayHelp { + self.inner.color_help_when + } else { + self.inner.color_when + }; + let c = Colorizer::new(self.stream(), color_when).with_content(style.into_owned()); + c.print() } fn new(kind: ErrorKind) -> Self { @@ -144,18 +152,20 @@ impl Error { source: None, help_flag: None, color_when: ColorChoice::Never, + color_help_when: ColorChoice::Never, backtrace: Backtrace::new(), }), } } #[inline(never)] - fn for_app(kind: ErrorKind, cmd: &Command, colorizer: Colorizer) -> Self { - Self::new(kind).set_message(colorizer).with_cmd(cmd) + fn for_app(kind: ErrorKind, cmd: &Command, styled: StyledStr) -> Self { + Self::new(kind).set_message(styled).with_cmd(cmd) } pub(crate) fn with_cmd(self, cmd: &Command) -> Self { self.set_color(cmd.get_color()) + .set_colored_help(cmd.color_help()) .set_help_flag(get_help_flag(cmd)) } @@ -174,6 +184,11 @@ impl Error { self } + pub(crate) fn set_colored_help(mut self, color_help_when: ColorChoice) -> Self { + self.inner.color_help_when = color_help_when; + self + } + pub(crate) fn set_help_flag(mut self, help_flag: Option<&'static str>) -> Self { self.inner.help_flag = help_flag; self @@ -208,20 +223,20 @@ impl Error { .find_map(|(k, v)| (*k == kind).then(|| v)) } - pub(crate) fn display_help(cmd: &Command, colorizer: Colorizer) -> Self { - Self::for_app(ErrorKind::DisplayHelp, cmd, colorizer) + pub(crate) fn display_help(cmd: &Command, styled: StyledStr) -> Self { + Self::for_app(ErrorKind::DisplayHelp, cmd, styled) } - pub(crate) fn display_help_error(cmd: &Command, colorizer: Colorizer) -> Self { + pub(crate) fn display_help_error(cmd: &Command, styled: StyledStr) -> Self { Self::for_app( ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand, cmd, - colorizer, + styled, ) } - pub(crate) fn display_version(cmd: &Command, colorizer: Colorizer) -> Self { - Self::for_app(ErrorKind::DisplayVersion, cmd, colorizer) + pub(crate) fn display_version(cmd: &Command, styled: StyledStr) -> Self { + Self::for_app(ErrorKind::DisplayVersion, cmd, styled) } pub(crate) fn argument_conflict( @@ -450,7 +465,7 @@ impl Error { ]) } - fn formatted(&self) -> Cow<'_, Colorizer> { + fn formatted(&self) -> Cow<'_, StyledStr> { if let Some(message) = self.inner.message.as_ref() { message.formatted() } else { @@ -474,8 +489,7 @@ impl Error { try_help(&mut styled, self.inner.help_flag); - let c = Colorizer::new(self.stream(), self.inner.color_when).with_content(styled); - Cow::Owned(c) + Cow::Owned(styled) } } @@ -857,7 +871,7 @@ fn escape(s: impl AsRef) -> String { #[derive(Clone, Debug)] pub(crate) enum Message { Raw(String), - Formatted(Colorizer), + Formatted(StyledStr), } impl Message { @@ -873,24 +887,22 @@ impl Message { put_usage(&mut styled, usage); try_help(&mut styled, get_help_flag(cmd)); - let c = Colorizer::new(Stream::Stderr, cmd.get_color()).with_content(styled); - *self = Self::Formatted(c); + *self = Self::Formatted(styled); } Message::Formatted(_) => {} } } - fn formatted(&self) -> Cow { + fn formatted(&self) -> Cow { match self { Message::Raw(s) => { let mut styled = StyledStr::new(); start_error(&mut styled); styled.none(s); - let c = Colorizer::new(Stream::Stderr, ColorChoice::Never).with_content(styled); - Cow::Owned(c) + Cow::Owned(styled) } - Message::Formatted(c) => Cow::Borrowed(c), + Message::Formatted(s) => Cow::Borrowed(s), } } } @@ -901,8 +913,8 @@ impl From for Message { } } -impl From for Message { - fn from(inner: Colorizer) -> Self { +impl From for Message { + fn from(inner: StyledStr) -> Self { Self::Formatted(inner) } } diff --git a/src/parser/parser.rs b/src/parser/parser.rs index f5ed3ba20b7..99f91bad35f 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -14,7 +14,6 @@ use crate::builder::{Arg, Command}; use crate::error::Error as ClapError; use crate::error::Result as ClapResult; use crate::mkeymap::KeyType; -use crate::output::fmt::Stream; use crate::output::Usage; use crate::parser::features::suggestions; use crate::parser::AnyValue; @@ -622,7 +621,7 @@ impl<'cmd> Parser<'cmd> { }; let parser = Parser::new(sc); - Err(parser.help_err(true, Stream::Stdout)) + Err(parser.help_err(true)) } fn is_new_arg(&self, next: &clap_lex::ParsedArg<'_>, current_positional: &Arg) -> bool { @@ -1236,7 +1235,7 @@ impl<'cmd> Parser<'cmd> { None => true, }; debug!("Help: use_long={}", use_long); - Err(self.help_err(use_long, Stream::Stdout)) + Err(self.help_err(use_long)) } ArgAction::Version => { debug_assert_eq!(raw_vals, Vec::::new()); @@ -1529,14 +1528,14 @@ impl<'cmd> Parser<'cmd> { ) } - fn help_err(&self, use_long: bool, stream: Stream) -> ClapError { - let c = self.cmd.write_help_err(use_long, stream); - ClapError::display_help(self.cmd, c) + fn help_err(&self, use_long: bool) -> ClapError { + let styled = self.cmd.write_help_err(use_long); + ClapError::display_help(self.cmd, styled) } fn version_err(&self, use_long: bool) -> ClapError { - let c = self.cmd.write_version_err(use_long); - ClapError::display_version(self.cmd, c) + let styled = self.cmd.write_version_err(use_long); + ClapError::display_version(self.cmd, styled) } } diff --git a/src/parser/validator.rs b/src/parser/validator.rs index e945abf5c70..118165a1331 100644 --- a/src/parser/validator.rs +++ b/src/parser/validator.rs @@ -1,7 +1,6 @@ // Internal use crate::builder::{Arg, ArgPredicate, Command, PossibleValue}; use crate::error::{Error, Result as ClapResult}; -use crate::output::fmt::Stream; use crate::output::Usage; use crate::parser::{ArgMatcher, ParseState}; use crate::util::ChildGraph; @@ -58,7 +57,7 @@ impl<'cmd> Validator<'cmd> { .filter(|arg_id| matcher.check_explicit(arg_id, &ArgPredicate::IsPresent)) .count(); if num_user_values == 0 { - let message = self.cmd.write_help_err(false, Stream::Stderr); + let message = self.cmd.write_help_err(false); return Err(Error::display_help_error(self.cmd, message)); } } From ba5a4bc6d49ca8d3d919270ffd0c861dbcd9f33f Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Aug 2022 11:10:03 -0500 Subject: [PATCH 2/4] refactor(error): Switch Vec to FlatMap Looks like this dropped 2 KiB --- src/error/context.rs | 2 +- src/error/kind.rs | 2 +- src/error/mod.rs | 14 +++++------ src/util/flat_map.rs | 55 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/error/context.rs b/src/error/context.rs index 4b2223c1731..37a84b017b6 100644 --- a/src/error/context.rs +++ b/src/error/context.rs @@ -1,5 +1,5 @@ /// Semantics for a piece of error information -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ContextKind { /// The cause of the error diff --git a/src/error/kind.rs b/src/error/kind.rs index 754d0df3b9d..77bbad4456b 100644 --- a/src/error/kind.rs +++ b/src/error/kind.rs @@ -1,5 +1,5 @@ /// Command line argument parser kind of error -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ErrorKind { /// Occurs when an [`Arg`][crate::Arg] has a set of possible values, diff --git a/src/error/mod.rs b/src/error/mod.rs index 36bd38095e2..2556ce1de88 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -15,6 +15,7 @@ use crate::builder::StyledStr; use crate::output::fmt::Colorizer; use crate::output::fmt::Stream; use crate::parser::features::suggestions; +use crate::util::FlatMap; use crate::util::{color::ColorChoice, safe_exit, SUCCESS_CODE, USAGE_CODE}; use crate::Command; @@ -43,7 +44,7 @@ pub struct Error { #[derive(Debug)] struct ErrorInner { kind: ErrorKind, - context: Vec<(ContextKind, ContextValue)>, + context: FlatMap, message: Option, source: Option>, help_flag: Option<&'static str>, @@ -147,7 +148,7 @@ impl Error { Self { inner: Box::new(ErrorInner { kind, - context: Vec::new(), + context: FlatMap::new(), message: None, source: None, help_flag: None, @@ -201,7 +202,7 @@ impl Error { kind: ContextKind, value: ContextValue, ) -> Self { - self.inner.context.push((kind, value)); + self.inner.context.insert_unchecked(kind, value); self } @@ -211,16 +212,13 @@ impl Error { mut self, context: [(ContextKind, ContextValue); N], ) -> Self { - self.inner.context.extend(context); + self.inner.context.extend_unchecked(context); self } #[inline(never)] fn get_context(&self, kind: ContextKind) -> Option<&ContextValue> { - self.inner - .context - .iter() - .find_map(|(k, v)| (*k == kind).then(|| v)) + self.inner.context.get(&kind) } pub(crate) fn display_help(cmd: &Command, styled: StyledStr) -> Self { diff --git a/src/util/flat_map.rs b/src/util/flat_map.rs index 9b709d29c48..d8f85bb2b3c 100644 --- a/src/util/flat_map.rs +++ b/src/util/flat_map.rs @@ -22,9 +22,19 @@ impl FlatMap { } } + self.insert_unchecked(key, value); + None + } + + pub(crate) fn insert_unchecked(&mut self, key: K, value: V) { self.keys.push(key); self.values.push(value); - None + } + + pub(crate) fn extend_unchecked(&mut self, iter: impl IntoIterator) { + for (key, value) in iter { + self.insert_unchecked(key, value); + } } pub fn contains_key(&self, key: &Q) -> bool @@ -97,6 +107,13 @@ impl FlatMap { self.keys.iter() } + pub fn iter(&self) -> Iter { + Iter { + keys: self.keys.iter(), + values: self.values.iter(), + } + } + pub fn iter_mut(&mut self) -> IterMut { IterMut { keys: self.keys.iter_mut(), @@ -153,6 +170,42 @@ pub struct OccupiedEntry<'a, K: 'a, V: 'a> { index: usize, } +pub struct Iter<'a, K: 'a, V: 'a> { + keys: std::slice::Iter<'a, K>, + values: std::slice::Iter<'a, V>, +} + +impl<'a, K, V> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option<(&'a K, &'a V)> { + match self.keys.next() { + Some(k) => { + let v = self.values.next().unwrap(); + Some((k, v)) + } + None => None, + } + } + fn size_hint(&self) -> (usize, Option) { + self.keys.size_hint() + } +} + +impl<'a, K, V> DoubleEndedIterator for Iter<'a, K, V> { + fn next_back(&mut self) -> Option<(&'a K, &'a V)> { + match self.keys.next_back() { + Some(k) => { + let v = self.values.next_back().unwrap(); + Some((k, v)) + } + None => None, + } + } +} + +impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> {} + pub struct IterMut<'a, K: 'a, V: 'a> { keys: std::slice::IterMut<'a, K>, values: std::slice::IterMut<'a, V>, From d16a531e547f7d2e7a52dfe16e28644467b501c8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Aug 2022 11:12:13 -0500 Subject: [PATCH 3/4] feat(error): Expose context lookup --- src/error/mod.rs | 67 ++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/error/mod.rs b/src/error/mod.rs index 2556ce1de88..37f7b907fdf 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -87,6 +87,12 @@ impl Error { self.inner.context.iter().map(|(k, v)| (*k, v)) } + /// Lookup a piece of context + #[inline(never)] + pub fn get(&self, kind: ContextKind) -> Option<&ContextValue> { + self.inner.context.get(&kind) + } + /// Should the message be written to `stdout` or not? #[inline] pub fn use_stderr(&self) -> bool { @@ -216,11 +222,6 @@ impl Error { self } - #[inline(never)] - fn get_context(&self, kind: ContextKind) -> Option<&ContextValue> { - self.inner.context.get(&kind) - } - pub(crate) fn display_help(cmd: &Command, styled: StyledStr) -> Self { Self::for_app(ErrorKind::DisplayHelp, cmd, styled) } @@ -480,7 +481,7 @@ impl Error { } } - let usage = self.get_context(ContextKind::Usage); + let usage = self.get(ContextKind::Usage); if let Some(ContextValue::String(usage)) = usage { put_usage(&mut styled, usage); } @@ -495,8 +496,8 @@ impl Error { fn write_dynamic_context(&self, styled: &mut StyledStr) -> bool { match self.kind() { ErrorKind::ArgumentConflict => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); - let prior_arg = self.get_context(ContextKind::PriorArg); + let invalid_arg = self.get(ContextKind::InvalidArg); + let prior_arg = self.get(ContextKind::PriorArg); if let (Some(ContextValue::String(invalid_arg)), Some(prior_arg)) = (invalid_arg, prior_arg) { @@ -527,7 +528,7 @@ impl Error { } } ErrorKind::NoEquals => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); + let invalid_arg = self.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { styled.none("Equal sign is needed when assigning values to '"); styled.warning(invalid_arg); @@ -538,8 +539,8 @@ impl Error { } } ErrorKind::InvalidValue => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); - let invalid_value = self.get_context(ContextKind::InvalidValue); + let invalid_arg = self.get(ContextKind::InvalidArg); + let invalid_value = self.get(ContextKind::InvalidValue); if let ( Some(ContextValue::String(invalid_arg)), Some(ContextValue::String(invalid_value)), @@ -556,7 +557,7 @@ impl Error { styled.none("'"); } - let possible_values = self.get_context(ContextKind::ValidValue); + let possible_values = self.get(ContextKind::ValidValue); if let Some(ContextValue::Strings(possible_values)) = possible_values { if !possible_values.is_empty() { styled.none("\n\t[possible values: "); @@ -571,7 +572,7 @@ impl Error { } } - let suggestion = self.get_context(ContextKind::SuggestedValue); + let suggestion = self.get(ContextKind::SuggestedValue); if let Some(ContextValue::String(suggestion)) = suggestion { styled.none("\n\n\tDid you mean "); styled.good(quote(suggestion)); @@ -583,20 +584,20 @@ impl Error { } } ErrorKind::InvalidSubcommand => { - let invalid_sub = self.get_context(ContextKind::InvalidSubcommand); + let invalid_sub = self.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { styled.none("The subcommand '"); styled.warning(invalid_sub); styled.none("' wasn't recognized"); - let valid_sub = self.get_context(ContextKind::SuggestedSubcommand); + let valid_sub = self.get(ContextKind::SuggestedSubcommand); if let Some(ContextValue::String(valid_sub)) = valid_sub { styled.none("\n\n\tDid you mean "); styled.good(valid_sub); styled.none("?"); } - let suggestion = self.get_context(ContextKind::SuggestedCommand); + let suggestion = self.get(ContextKind::SuggestedCommand); if let Some(ContextValue::String(suggestion)) = suggestion { styled.none( "\n\nIf you believe you received this message in error, try re-running with '", @@ -610,7 +611,7 @@ impl Error { } } ErrorKind::MissingRequiredArgument => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); + let invalid_arg = self.get(ContextKind::InvalidArg); if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg { styled.none("The following required arguments were not provided:"); for v in invalid_arg { @@ -623,7 +624,7 @@ impl Error { } } ErrorKind::MissingSubcommand => { - let invalid_sub = self.get_context(ContextKind::InvalidSubcommand); + let invalid_sub = self.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { styled.none("'"); styled.warning(invalid_sub); @@ -635,8 +636,8 @@ impl Error { } ErrorKind::InvalidUtf8 => false, ErrorKind::TooManyValues => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); - let invalid_value = self.get_context(ContextKind::InvalidValue); + let invalid_arg = self.get(ContextKind::InvalidArg); + let invalid_value = self.get(ContextKind::InvalidValue); if let ( Some(ContextValue::String(invalid_arg)), Some(ContextValue::String(invalid_value)), @@ -653,9 +654,9 @@ impl Error { } } ErrorKind::TooFewValues => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); - let actual_num_values = self.get_context(ContextKind::ActualNumValues); - let min_values = self.get_context(ContextKind::MinValues); + let invalid_arg = self.get(ContextKind::InvalidArg); + let actual_num_values = self.get(ContextKind::ActualNumValues); + let min_values = self.get(ContextKind::MinValues); if let ( Some(ContextValue::String(invalid_arg)), Some(ContextValue::Number(actual_num_values)), @@ -676,8 +677,8 @@ impl Error { } } ErrorKind::ValueValidation => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); - let invalid_value = self.get_context(ContextKind::InvalidValue); + let invalid_arg = self.get(ContextKind::InvalidArg); + let invalid_value = self.get(ContextKind::InvalidValue); if let ( Some(ContextValue::String(invalid_arg)), Some(ContextValue::String(invalid_value)), @@ -699,9 +700,9 @@ impl Error { } } ErrorKind::WrongNumberOfValues => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); - let actual_num_values = self.get_context(ContextKind::ActualNumValues); - let num_values = self.get_context(ContextKind::ExpectedNumValues); + let invalid_arg = self.get(ContextKind::InvalidArg); + let actual_num_values = self.get(ContextKind::ActualNumValues); + let num_values = self.get(ContextKind::ExpectedNumValues); if let ( Some(ContextValue::String(invalid_arg)), Some(ContextValue::Number(actual_num_values)), @@ -722,14 +723,14 @@ impl Error { } } ErrorKind::UnknownArgument => { - let invalid_arg = self.get_context(ContextKind::InvalidArg); + let invalid_arg = self.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { styled.none("Found argument '"); styled.warning(invalid_arg.to_string()); styled.none("' which wasn't expected, or isn't valid in this context"); - let valid_sub = self.get_context(ContextKind::SuggestedSubcommand); - let valid_arg = self.get_context(ContextKind::SuggestedArg); + let valid_sub = self.get(ContextKind::SuggestedSubcommand); + let valid_arg = self.get(ContextKind::SuggestedArg); match (valid_sub, valid_arg) { ( Some(ContextValue::String(valid_sub)), @@ -750,7 +751,7 @@ impl Error { (_, _) => {} } - let invalid_arg = self.get_context(ContextKind::InvalidArg); + let invalid_arg = self.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { if invalid_arg.starts_with('-') { styled.none(format!( @@ -759,7 +760,7 @@ impl Error { )); } - let trailing_arg = self.get_context(ContextKind::TrailingArg); + let trailing_arg = self.get(ContextKind::TrailingArg); if trailing_arg == Some(&ContextValue::Bool(true)) { styled.none(format!( "\n\n\tIf you tried to supply `{}` as a subcommand, remove the '--' before it.", From ef5f9f956ac1375532f28f679ce91c0560c2bbeb Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 24 Aug 2022 12:26:56 -0500 Subject: [PATCH 4/4] perf(error): Allow custmizing formatting For now, there isn't much a custom implementation can do. Going from `Rich` to `Null` drops about 6 KiB from the binary This is a part of #1365 and #1384 --- CHANGELOG.md | 1 + src/builder/mod.rs | 2 +- src/builder/styled_str.rs | 6 +- src/error/context.rs | 41 ++++ src/error/format.rs | 448 ++++++++++++++++++++++++++++++++++++++ src/error/mod.rs | 423 ++++------------------------------- tests/builder/error.rs | 99 ++++++++- 7 files changed, 638 insertions(+), 382 deletions(-) create mode 100644 src/error/format.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9421322e96e..42b5410335e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ MSRV is now 1.60.0 - `Arg::num_args` now accepts ranges, allowing setting both the minimum and maximum number of values per occurrence - Added `TypedValueParser::map` to make it easier to reuse existing value parsers +- *(error)* `Error::apply` for changing the formatter for dropping binary size - *(help)* Show `PossibleValue::help` in long help (`--help`) (#3312) ### Fixes diff --git a/src/builder/mod.rs b/src/builder/mod.rs index b717d598f98..a450cbee408 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -33,6 +33,7 @@ pub use possible_value::PossibleValue; pub use range::ValueRange; pub use resettable::IntoResettable; pub use resettable::Resettable; +pub use styled_str::StyledStr; pub use value_hint::ValueHint; pub use value_parser::_AutoValueParser; pub use value_parser::via_prelude; @@ -55,7 +56,6 @@ pub use value_parser::_AnonymousValueParser; #[allow(unused_imports)] pub(crate) use self::str::Inner as StrInner; -pub(crate) use self::styled_str::StyledStr; pub(crate) use action::CountType; pub(crate) use arg::render_arg_val; pub(crate) use arg_settings::{ArgFlags, ArgSettings}; diff --git a/src/builder/styled_str.rs b/src/builder/styled_str.rs index ad2a697bb90..afe2545a3ff 100644 --- a/src/builder/styled_str.rs +++ b/src/builder/styled_str.rs @@ -1,10 +1,12 @@ +/// Terminal-styling container #[derive(Clone, Default, Debug, PartialEq, Eq)] -pub(crate) struct StyledStr { +pub struct StyledStr { pieces: Vec<(Option