From c8f5afe9b535b5bf767c1fc30370238e558055dc Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 13 Apr 2023 02:47:57 -0500 Subject: [PATCH 01/13] refactor: Make anstyle always available --- clap_builder/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clap_builder/Cargo.toml b/clap_builder/Cargo.toml index 1bd8cbfad91..4ab9f08b5ae 100644 --- a/clap_builder/Cargo.toml +++ b/clap_builder/Cargo.toml @@ -35,8 +35,8 @@ debug = ["dep:backtrace"] # Enables debug messages unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string"] # for docs.rs # Used in default -std = [] # support for no_std in a backwards-compatible way -color = ["dep:anstyle", "dep:anstream"] +std = ["anstyle/std"] # support for no_std in a backwards-compatible way +color = ["dep:anstream"] help = [] usage = [] error-context = [] @@ -62,7 +62,7 @@ bitflags = "1.2.0" unicase = { version = "2.6.0", optional = true } strsim = { version = "0.10.0", optional = true } anstream = { version = "0.3.0", optional = true } -anstyle = { version = "1.0.0", features = ["std"], optional = true } +anstyle = "1.0.0" terminal_size = { version = "0.2.1", optional = true } backtrace = { version = "0.3.67", optional = true } unicode-width = { version = "0.1.9", optional = true } From ab61cd6aafdc3be72e70e09d721e67b0b83ba3ad Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 13 Apr 2023 03:08:09 -0500 Subject: [PATCH 02/13] refactor(help): Push color knowledge out a layer --- clap_builder/src/builder/arg.rs | 25 +++++----- clap_builder/src/builder/mod.rs | 1 + clap_builder/src/builder/styled_str.rs | 58 +++++++--------------- clap_builder/src/error/format.rs | 63 ++++++++++++------------ clap_builder/src/error/mod.rs | 25 +++++----- clap_builder/src/macros.rs | 4 +- clap_builder/src/output/help_template.rs | 15 +++--- clap_builder/src/output/usage.rs | 43 ++++++++-------- 8 files changed, 110 insertions(+), 124 deletions(-) diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index 399ac66dea0..2d00f12fc33 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -16,6 +16,7 @@ use crate::builder::IntoResettable; use crate::builder::OsStr; use crate::builder::PossibleValue; use crate::builder::Str; +use crate::builder::Style; use crate::builder::StyledStr; use crate::builder::ValueRange; use crate::util::AnyValueId; @@ -4275,11 +4276,13 @@ impl Arg { let mut styled = StyledStr::new(); // Write the name such --long or -l if let Some(l) = self.get_long() { - styled.literal("--"); - styled.literal(l); + styled.stylize(Style::Literal, "--"); + styled.stylize(Style::Literal, l); } else if let Some(s) = self.get_short() { - styled.literal("-"); - styled.literal(s); + styled.stylize(Style::Literal, "-"); + let mut b = [0; 4]; + let s = s.encode_utf8(&mut b); + styled.stylize(Style::Literal, s); } styled.push_styled(&self.stylize_arg_suffix(required)); styled @@ -4294,26 +4297,26 @@ impl Arg { if self.is_require_equals_set() { if is_optional_val { need_closing_bracket = true; - styled.placeholder("[="); + styled.stylize(Style::Placeholder, "[="); } else { - styled.literal("="); + styled.stylize(Style::Literal, "="); } } else if is_optional_val { need_closing_bracket = true; - styled.placeholder(" ["); + styled.stylize(Style::Placeholder, " ["); } else { - styled.placeholder(" "); + styled.stylize(Style::Placeholder, " "); } } if self.is_takes_value_set() || self.is_positional() { let required = required.unwrap_or_else(|| self.is_required_set()); let arg_val = self.render_arg_val(required); - styled.placeholder(arg_val); + styled.stylize(Style::Placeholder, &arg_val); } else if matches!(*self.get_action(), ArgAction::Count) { - styled.placeholder("..."); + styled.stylize(Style::Placeholder, "..."); } if need_closing_bracket { - styled.placeholder("]"); + styled.stylize(Style::Placeholder, "]"); } styled diff --git a/clap_builder/src/builder/mod.rs b/clap_builder/src/builder/mod.rs index 76331694d67..f356e7259a4 100644 --- a/clap_builder/src/builder/mod.rs +++ b/clap_builder/src/builder/mod.rs @@ -59,3 +59,4 @@ pub use value_parser::_AnonymousValueParser; pub(crate) use self::str::Inner as StrInner; pub(crate) use action::CountType; pub(crate) use arg_settings::{ArgFlags, ArgSettings}; +pub(crate) use styled_str::Style; diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index 94c838aaabc..0df8df23627 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -33,35 +33,19 @@ impl StyledStr { self.0.as_str() } - pub(crate) fn header(&mut self, msg: impl Into) { - self.stylize(Style::Header, msg.into()); - } - - pub(crate) fn literal(&mut self, msg: impl Into) { - self.stylize(Style::Literal, msg.into()); - } - - pub(crate) fn placeholder(&mut self, msg: impl Into) { - self.stylize(Style::Placeholder, msg.into()); - } - - #[cfg_attr(not(feature = "error-context"), allow(dead_code))] - pub(crate) fn good(&mut self, msg: impl Into) { - self.stylize(Style::Good, msg.into()); - } - - #[cfg_attr(not(feature = "error-context"), allow(dead_code))] - pub(crate) fn warning(&mut self, msg: impl Into) { - self.stylize(Style::Warning, msg.into()); - } + #[cfg(feature = "color")] + pub(crate) fn stylize(&mut self, style: Style, msg: &str) { + if !msg.is_empty() { + use std::fmt::Write as _; - pub(crate) fn error(&mut self, msg: impl Into) { - self.stylize(Style::Error, msg.into()); + let style = style.as_style(); + let _ = write!(self.0, "{}{}{}", style.render(), msg, style.render_reset()); + } } - #[allow(dead_code)] - pub(crate) fn hint(&mut self, msg: impl Into) { - self.stylize(Style::Hint, msg.into()); + #[cfg(not(feature = "color"))] + pub(crate) fn stylize(&mut self, _style: Style, msg: &str) { + self.0.push_str(msg); } pub(crate) fn none(&mut self, msg: impl Into) { @@ -122,21 +106,6 @@ impl StyledStr { self.0 = new; } - #[cfg(feature = "color")] - fn stylize(&mut self, style: Style, msg: String) { - if !msg.is_empty() { - use std::fmt::Write as _; - - let style = style.as_style(); - let _ = write!(self.0, "{}{}{}", style.render(), msg, style.render_reset()); - } - } - - #[cfg(not(feature = "color"))] - fn stylize(&mut self, _style: Style, msg: String) { - self.0.push_str(&msg); - } - #[inline(never)] #[cfg(feature = "help")] pub(crate) fn display_width(&self) -> usize { @@ -240,12 +209,19 @@ impl std::fmt::Display for StyledStr { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum Style { + #[allow(dead_code)] Header, + #[allow(dead_code)] Literal, + #[allow(dead_code)] Placeholder, + #[allow(dead_code)] Good, + #[allow(dead_code)] Warning, + #[allow(dead_code)] Error, + #[allow(dead_code)] Hint, } diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs index 5ada0abe566..646cee09b80 100644 --- a/clap_builder/src/error/format.rs +++ b/clap_builder/src/error/format.rs @@ -4,6 +4,7 @@ #![cfg_attr(not(feature = "error-context"), allow(unused_imports))] use crate::builder::Command; +use crate::builder::Style; use crate::builder::StyledStr; #[cfg(feature = "error-context")] use crate::error::ContextKind; @@ -99,7 +100,7 @@ impl ErrorFormatter for RichFormatter { for suggestion in suggestions { styled.none("\n"); styled.none(TAB); - styled.good("tip: "); + styled.stylize(Style::Good, "tip: "); styled.push_styled(suggestion); } } @@ -116,7 +117,7 @@ impl ErrorFormatter for RichFormatter { } fn start_error(styled: &mut StyledStr) { - styled.error("error:"); + styled.stylize(Style::Error, "error:"); styled.none(" "); } @@ -132,11 +133,11 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> { if ContextValue::String(invalid_arg.clone()) == *prior_arg { styled.none("the argument '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); styled.none("' cannot be used multiple times"); } else { styled.none("the argument '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); styled.none("' cannot be used with"); match prior_arg { @@ -145,12 +146,12 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> for v in values { styled.none("\n"); styled.none(TAB); - styled.warning(&**v); + styled.stylize(Style::Warning, &**v); } } ContextValue::String(value) => { styled.none(" '"); - styled.warning(value); + styled.stylize(Style::Warning, value); styled.none("'"); } _ => { @@ -167,7 +168,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_arg = error.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); + styled.stylize(Style::Warning, invalid_arg); styled.none("'"); true } else { @@ -184,13 +185,13 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> { if invalid_value.is_empty() { styled.none("a value is required for '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); styled.none("' but none was supplied"); } else { styled.none("invalid value '"); styled.none(invalid_value); styled.none("' for '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); styled.none("'"); } @@ -202,10 +203,10 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> styled.none("[possible values: "); if let Some((last, elements)) = possible_values.split_last() { for v in elements { - styled.good(escape(v)); + styled.stylize(Style::Good, &escape(v)); styled.none(", "); } - styled.good(escape(last)); + styled.stylize(Style::Good, &escape(last)); } styled.none("]"); } @@ -219,7 +220,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_sub = error.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { styled.none("unrecognized subcommand '"); - styled.warning(invalid_sub); + styled.stylize(Style::Warning, invalid_sub); styled.none("'"); true } else { @@ -233,7 +234,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> for v in invalid_arg { styled.none("\n"); styled.none(TAB); - styled.good(&**v); + styled.stylize(Style::Good, &**v); } true } else { @@ -244,7 +245,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_sub = error.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { styled.none("'"); - styled.warning(invalid_sub); + styled.stylize(Style::Warning, invalid_sub); styled.none("' requires a subcommand but one was not provided"); let possible_values = error.get(ContextKind::ValidSubcommand); @@ -255,10 +256,10 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> styled.none("[subcommands: "); if let Some((last, elements)) = possible_values.split_last() { for v in elements { - styled.good(escape(v)); + styled.stylize(Style::Good, &escape(v)); styled.none(", "); } - styled.good(escape(last)); + styled.stylize(Style::Good, &escape(last)); } styled.none("]"); } @@ -279,9 +280,9 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, invalid_value) { styled.none("unexpected value '"); - styled.warning(invalid_value); + styled.stylize(Style::Warning, invalid_value); styled.none("' for '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); styled.none("' found; no more were expected"); true } else { @@ -299,11 +300,11 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, actual_num_values, min_values) { let were_provided = singular_or_plural(*actual_num_values as usize); - styled.warning(min_values.to_string()); + styled.stylize(Style::Warning, &min_values.to_string()); styled.none(" more values required by '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); styled.none("'; only "); - styled.warning(actual_num_values.to_string()); + styled.stylize(Style::Warning, &actual_num_values.to_string()); styled.none(were_provided); true } else { @@ -319,9 +320,9 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, invalid_value) { styled.none("invalid value '"); - styled.warning(invalid_value); + styled.stylize(Style::Warning, invalid_value); styled.none("' for '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); if let Some(source) = error.inner.source.as_deref() { styled.none("': "); styled.none(source.to_string()); @@ -344,11 +345,11 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, actual_num_values, num_values) { let were_provided = singular_or_plural(*actual_num_values as usize); - styled.warning(num_values.to_string()); + styled.stylize(Style::Warning, &num_values.to_string()); styled.none(" values required for '"); - styled.warning(invalid_arg); + styled.stylize(Style::Warning, invalid_arg); styled.none("' but "); - styled.warning(actual_num_values.to_string()); + styled.stylize(Style::Warning, &actual_num_values.to_string()); styled.none(were_provided); true } else { @@ -359,7 +360,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_arg = error.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { styled.none("unexpected argument '"); - styled.warning(invalid_arg.to_string()); + styled.stylize(Style::Warning, invalid_arg); styled.none("' found"); true } else { @@ -418,7 +419,7 @@ pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> { fn try_help(styled: &mut StyledStr, help: Option<&str>) { if let Some(help) = help { styled.none("\n\nFor more information, try '"); - styled.literal(help.to_owned()); + styled.stylize(Style::Literal, help); styled.none("'.\n"); } else { styled.none("\n"); @@ -428,12 +429,12 @@ fn try_help(styled: &mut StyledStr, help: Option<&str>) { #[cfg(feature = "error-context")] fn did_you_mean(styled: &mut StyledStr, context: &str, valid: &ContextValue) { styled.none(TAB); - styled.good("tip:"); + styled.stylize(Style::Good, "tip:"); if let ContextValue::String(valid) = valid { styled.none(" a similar "); styled.none(context); styled.none(" exists: '"); - styled.good(valid); + styled.stylize(Style::Good, valid); styled.none("'"); } else if let ContextValue::Strings(valid) = valid { if valid.len() == 1 { @@ -450,7 +451,7 @@ fn did_you_mean(styled: &mut StyledStr, context: &str, valid: &ContextValue) { styled.none(", "); } styled.none("'"); - styled.good(valid); + styled.stylize(Style::Good, valid); styled.none("'"); } } diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index 52f17bd5649..c4aea163ffa 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -17,6 +17,7 @@ use std::{ }; // Internal +use crate::builder::Style; use crate::builder::StyledStr; use crate::output::fmt::Colorizer; use crate::output::fmt::Stream; @@ -440,11 +441,11 @@ impl Error { { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("to pass '"); - styled_suggestion.warning(&subcmd); + styled_suggestion.stylize(Style::Warning, &subcmd); styled_suggestion.none("' as a value, use '"); - styled_suggestion.good(name); - styled_suggestion.good(" -- "); - styled_suggestion.good(&subcmd); + styled_suggestion.stylize(Style::Good, &name); + styled_suggestion.stylize(Style::Good, " -- "); + styled_suggestion.stylize(Style::Good, &subcmd); styled_suggestion.none("'"); err = err.extend_context_unchecked([ @@ -669,10 +670,10 @@ impl Error { if suggested_trailing_arg { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("to pass '"); - styled_suggestion.warning(&arg); + styled_suggestion.stylize(Style::Warning, &arg); styled_suggestion.none("' as a value, use '"); - styled_suggestion.good("-- "); - styled_suggestion.good(&arg); + styled_suggestion.stylize(Style::Good, "-- "); + styled_suggestion.stylize(Style::Good, &arg); styled_suggestion.none("'"); suggestions.push(styled_suggestion); } @@ -687,10 +688,10 @@ impl Error { Some((flag, Some(sub))) => { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("'"); - styled_suggestion.good(sub); + styled_suggestion.stylize(Style::Good, &sub); styled_suggestion.none(" "); - styled_suggestion.good("--"); - styled_suggestion.good(flag); + styled_suggestion.stylize(Style::Good, "--"); + styled_suggestion.stylize(Style::Good, &flag); styled_suggestion.none("' exists"); suggestions.push(styled_suggestion); } @@ -724,9 +725,9 @@ impl Error { { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("subcommand '"); - styled_suggestion.good(&arg); + styled_suggestion.stylize(Style::Good, &arg); styled_suggestion.none("' exists; to use it, remove the '"); - styled_suggestion.warning("--"); + styled_suggestion.stylize(Style::Warning, "--"); styled_suggestion.none("' before it"); err = err.extend_context_unchecked([ diff --git a/clap_builder/src/macros.rs b/clap_builder/src/macros.rs index 82a8811984f..152a388b102 100644 --- a/clap_builder/src/macros.rs +++ b/clap_builder/src/macros.rs @@ -631,8 +631,8 @@ macro_rules! debug { let prefix = format!("[{:>w$}] \t", module_path!(), w = 28); let body = format!($($arg)*); let mut styled = $crate::builder::StyledStr::new(); - styled.hint(prefix); - styled.hint(body); + styled.stylize($crate::builder::Style::Hint, &prefix); + styled.stylize($crate::builder::Style::Hint, &body); styled.none("\n"); let color = $crate::output::fmt::Colorizer::new($crate::output::fmt::Stream::Stderr, $crate::ColorChoice::Auto).with_content(styled); let _ = color.print(); diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index cea9a7687bb..2585f342d0a 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -6,6 +6,7 @@ use std::usize; // Internal use crate::builder::PossibleValue; use crate::builder::Str; +use crate::builder::Style; use crate::builder::StyledStr; use crate::builder::{Arg, Command}; use crate::output::display_width; @@ -801,12 +802,12 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { spec_vals.join(connector) } - fn header>(&mut self, msg: T) { - self.writer.header(msg); + fn header>(&mut self, msg: T) { + self.writer.stylize(Style::Header, msg.as_ref()); } - fn literal>(&mut self, msg: T) { - self.writer.literal(msg); + fn literal>(&mut self, msg: T) { + self.writer.stylize(Style::Literal, msg.as_ref()); } fn none>(&mut self, msg: T) { @@ -835,14 +836,14 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { .filter(|subcommand| should_show_subcommand(subcommand)) { let mut styled = StyledStr::new(); - styled.literal(subcommand.get_name()); + styled.stylize(Style::Literal, subcommand.get_name()); if let Some(short) = subcommand.get_short_flag() { styled.none(", "); - styled.literal(format!("-{short}")); + styled.stylize(Style::Literal, &format!("-{short}")); } if let Some(long) = subcommand.get_long_flag() { styled.none(", "); - styled.literal(format!("--{long}")); + styled.stylize(Style::Literal, &format!("--{long}")); } longest = longest.max(styled.display_width()); ord_v.push((subcommand.get_display_order(), styled, subcommand)); diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index c49ca2cb063..d3ac941d6e7 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -4,6 +4,7 @@ #![cfg_attr(not(feature = "usage"), allow(dead_code))] // Internal +use crate::builder::Style; use crate::builder::StyledStr; use crate::builder::{ArgPredicate, Command}; use crate::parser::ArgMatcher; @@ -38,7 +39,7 @@ impl<'cmd> Usage<'cmd> { let usage = some!(self.create_usage_no_title(used)); let mut styled = StyledStr::new(); - styled.header("Usage:"); + styled.stylize(Style::Header, "Usage:"); styled.none(" "); styled.push_styled(&usage); Some(styled) @@ -78,10 +79,10 @@ impl<'cmd> Usage<'cmd> { .get_usage_name() .or_else(|| self.cmd.get_bin_name()) .unwrap_or_else(|| self.cmd.get_name()); - styled.literal(name); + styled.stylize(Style::Literal, name); if self.needs_options_tag() { - styled.placeholder(" [OPTIONS]"); + styled.stylize(Style::Placeholder, " [OPTIONS]"); } self.write_args(&[], !incl_reqs, &mut styled); @@ -101,21 +102,21 @@ impl<'cmd> Usage<'cmd> { styled.none(" "); if self.cmd.is_args_conflicts_with_subcommands_set() { // Short-circuit full usage creation since no args will be relevant - styled.literal(name); + styled.stylize(Style::Literal, name); } else { styled.push_styled(&self.create_help_usage(false)); } - styled.placeholder(" <"); - styled.placeholder(placeholder); - styled.placeholder(">"); + styled.stylize(Style::Placeholder, " <"); + styled.stylize(Style::Placeholder, placeholder); + styled.stylize(Style::Placeholder, ">"); } else if self.cmd.is_subcommand_required_set() { - styled.placeholder(" <"); - styled.placeholder(placeholder); - styled.placeholder(">"); + styled.stylize(Style::Placeholder, " <"); + styled.stylize(Style::Placeholder, placeholder); + styled.stylize(Style::Placeholder, ">"); } else { - styled.placeholder(" ["); - styled.placeholder(placeholder); - styled.placeholder("]"); + styled.stylize(Style::Placeholder, " ["); + styled.stylize(Style::Placeholder, placeholder); + styled.stylize(Style::Placeholder, "]"); } } styled.trim(); @@ -129,7 +130,8 @@ impl<'cmd> Usage<'cmd> { debug!("Usage::create_smart_usage"); let mut styled = StyledStr::new(); - styled.literal( + styled.stylize( + Style::Literal, self.cmd .get_usage_name() .or_else(|| self.cmd.get_bin_name()) @@ -139,13 +141,14 @@ impl<'cmd> Usage<'cmd> { self.write_args(used, false, &mut styled); if self.cmd.is_subcommand_required_set() { - styled.placeholder(" <"); - styled.placeholder( + styled.stylize(Style::Placeholder, " <"); + styled.stylize( + Style::Placeholder, self.cmd .get_subcommand_value_name() .unwrap_or(DEFAULT_SUB_VALUE_NAME), ); - styled.placeholder(">"); + styled.stylize(Style::Placeholder, ">"); } styled } @@ -279,7 +282,7 @@ impl<'cmd> Usage<'cmd> { if pos.is_last_set() { let styled = required_positionals[index].take().unwrap(); let mut new = StyledStr::new(); - new.literal("-- "); + new.stylize(Style::Literal, "-- "); new.push_styled(&styled); required_positionals[index] = Some(new); } @@ -287,9 +290,9 @@ impl<'cmd> Usage<'cmd> { let mut styled; if pos.is_last_set() { styled = StyledStr::new(); - styled.literal("[-- "); + styled.stylize(Style::Literal, "[-- "); styled.push_styled(&pos.stylized(Some(true))); - styled.literal("]"); + styled.stylize(Style::Literal, "]"); } else { styled = pos.stylized(Some(false)); } From 9416f3a34745c7d1b7680b1bbd1a24e6d628b2b7 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 13 Apr 2023 03:26:49 -0500 Subject: [PATCH 03/13] refactor(help): Use anstyle in the caller --- clap_builder/src/builder/arg.rs | 22 ++++----- clap_builder/src/builder/styled_str.rs | 8 ++- clap_builder/src/error/format.rs | 62 ++++++++++++------------ clap_builder/src/error/mod.rs | 24 ++++----- clap_builder/src/macros.rs | 4 +- clap_builder/src/output/help_template.rs | 10 ++-- clap_builder/src/output/usage.rs | 40 +++++++-------- 7 files changed, 84 insertions(+), 86 deletions(-) diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index 2d00f12fc33..bcf00752b93 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -4276,13 +4276,13 @@ impl Arg { let mut styled = StyledStr::new(); // Write the name such --long or -l if let Some(l) = self.get_long() { - styled.stylize(Style::Literal, "--"); - styled.stylize(Style::Literal, l); + styled.stylize(Style::Literal.as_style(), "--"); + styled.stylize(Style::Literal.as_style(), l); } else if let Some(s) = self.get_short() { - styled.stylize(Style::Literal, "-"); + styled.stylize(Style::Literal.as_style(), "-"); let mut b = [0; 4]; let s = s.encode_utf8(&mut b); - styled.stylize(Style::Literal, s); + styled.stylize(Style::Literal.as_style(), s); } styled.push_styled(&self.stylize_arg_suffix(required)); styled @@ -4297,26 +4297,26 @@ impl Arg { if self.is_require_equals_set() { if is_optional_val { need_closing_bracket = true; - styled.stylize(Style::Placeholder, "[="); + styled.stylize(Style::Placeholder.as_style(), "[="); } else { - styled.stylize(Style::Literal, "="); + styled.stylize(Style::Literal.as_style(), "="); } } else if is_optional_val { need_closing_bracket = true; - styled.stylize(Style::Placeholder, " ["); + styled.stylize(Style::Placeholder.as_style(), " ["); } else { - styled.stylize(Style::Placeholder, " "); + styled.stylize(Style::Placeholder.as_style(), " "); } } if self.is_takes_value_set() || self.is_positional() { let required = required.unwrap_or_else(|| self.is_required_set()); let arg_val = self.render_arg_val(required); - styled.stylize(Style::Placeholder, &arg_val); + styled.stylize(Style::Placeholder.as_style(), &arg_val); } else if matches!(*self.get_action(), ArgAction::Count) { - styled.stylize(Style::Placeholder, "..."); + styled.stylize(Style::Placeholder.as_style(), "..."); } if need_closing_bracket { - styled.stylize(Style::Placeholder, "]"); + styled.stylize(Style::Placeholder.as_style(), "]"); } styled diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index 0df8df23627..fd98f56e21d 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -34,17 +34,16 @@ impl StyledStr { } #[cfg(feature = "color")] - pub(crate) fn stylize(&mut self, style: Style, msg: &str) { + pub(crate) fn stylize(&mut self, style: anstyle::Style, msg: &str) { if !msg.is_empty() { use std::fmt::Write as _; - let style = style.as_style(); let _ = write!(self.0, "{}{}{}", style.render(), msg, style.render_reset()); } } #[cfg(not(feature = "color"))] - pub(crate) fn stylize(&mut self, _style: Style, msg: &str) { + pub(crate) fn stylize(&mut self, _style: anstyle::Style, msg: &str) { self.0.push_str(msg); } @@ -226,8 +225,7 @@ pub(crate) enum Style { } impl Style { - #[cfg(feature = "color")] - fn as_style(&self) -> anstyle::Style { + pub(crate) fn as_style(&self) -> anstyle::Style { match self { Style::Header => (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).into(), Style::Literal => anstyle::Effects::BOLD.into(), diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs index 646cee09b80..40f49315f07 100644 --- a/clap_builder/src/error/format.rs +++ b/clap_builder/src/error/format.rs @@ -100,7 +100,7 @@ impl ErrorFormatter for RichFormatter { for suggestion in suggestions { styled.none("\n"); styled.none(TAB); - styled.stylize(Style::Good, "tip: "); + styled.stylize(Style::Good.as_style(), "tip: "); styled.push_styled(suggestion); } } @@ -117,7 +117,7 @@ impl ErrorFormatter for RichFormatter { } fn start_error(styled: &mut StyledStr) { - styled.stylize(Style::Error, "error:"); + styled.stylize(Style::Error.as_style(), "error:"); styled.none(" "); } @@ -133,11 +133,11 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> { if ContextValue::String(invalid_arg.clone()) == *prior_arg { styled.none("the argument '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("' cannot be used multiple times"); } else { styled.none("the argument '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("' cannot be used with"); match prior_arg { @@ -146,12 +146,12 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> for v in values { styled.none("\n"); styled.none(TAB); - styled.stylize(Style::Warning, &**v); + styled.stylize(Style::Warning.as_style(), &**v); } } ContextValue::String(value) => { styled.none(" '"); - styled.stylize(Style::Warning, value); + styled.stylize(Style::Warning.as_style(), value); styled.none("'"); } _ => { @@ -168,7 +168,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_arg = error.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { styled.none("equal sign is needed when assigning values to '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("'"); true } else { @@ -185,13 +185,13 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> { if invalid_value.is_empty() { styled.none("a value is required for '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("' but none was supplied"); } else { styled.none("invalid value '"); styled.none(invalid_value); styled.none("' for '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("'"); } @@ -203,10 +203,10 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> styled.none("[possible values: "); if let Some((last, elements)) = possible_values.split_last() { for v in elements { - styled.stylize(Style::Good, &escape(v)); + styled.stylize(Style::Good.as_style(), &escape(v)); styled.none(", "); } - styled.stylize(Style::Good, &escape(last)); + styled.stylize(Style::Good.as_style(), &escape(last)); } styled.none("]"); } @@ -220,7 +220,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_sub = error.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { styled.none("unrecognized subcommand '"); - styled.stylize(Style::Warning, invalid_sub); + styled.stylize(Style::Warning.as_style(), invalid_sub); styled.none("'"); true } else { @@ -234,7 +234,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> for v in invalid_arg { styled.none("\n"); styled.none(TAB); - styled.stylize(Style::Good, &**v); + styled.stylize(Style::Good.as_style(), &**v); } true } else { @@ -245,7 +245,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_sub = error.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { styled.none("'"); - styled.stylize(Style::Warning, invalid_sub); + styled.stylize(Style::Warning.as_style(), invalid_sub); styled.none("' requires a subcommand but one was not provided"); let possible_values = error.get(ContextKind::ValidSubcommand); @@ -256,10 +256,10 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> styled.none("[subcommands: "); if let Some((last, elements)) = possible_values.split_last() { for v in elements { - styled.stylize(Style::Good, &escape(v)); + styled.stylize(Style::Good.as_style(), &escape(v)); styled.none(", "); } - styled.stylize(Style::Good, &escape(last)); + styled.stylize(Style::Good.as_style(), &escape(last)); } styled.none("]"); } @@ -280,9 +280,9 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, invalid_value) { styled.none("unexpected value '"); - styled.stylize(Style::Warning, invalid_value); + styled.stylize(Style::Warning.as_style(), invalid_value); styled.none("' for '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("' found; no more were expected"); true } else { @@ -300,11 +300,11 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, actual_num_values, min_values) { let were_provided = singular_or_plural(*actual_num_values as usize); - styled.stylize(Style::Warning, &min_values.to_string()); + styled.stylize(Style::Warning.as_style(), &min_values.to_string()); styled.none(" more values required by '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("'; only "); - styled.stylize(Style::Warning, &actual_num_values.to_string()); + styled.stylize(Style::Warning.as_style(), &actual_num_values.to_string()); styled.none(were_provided); true } else { @@ -320,9 +320,9 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, invalid_value) { styled.none("invalid value '"); - styled.stylize(Style::Warning, invalid_value); + styled.stylize(Style::Warning.as_style(), invalid_value); styled.none("' for '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); if let Some(source) = error.inner.source.as_deref() { styled.none("': "); styled.none(source.to_string()); @@ -345,11 +345,11 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, actual_num_values, num_values) { let were_provided = singular_or_plural(*actual_num_values as usize); - styled.stylize(Style::Warning, &num_values.to_string()); + styled.stylize(Style::Warning.as_style(), &num_values.to_string()); styled.none(" values required for '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("' but "); - styled.stylize(Style::Warning, &actual_num_values.to_string()); + styled.stylize(Style::Warning.as_style(), &actual_num_values.to_string()); styled.none(were_provided); true } else { @@ -360,7 +360,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> let invalid_arg = error.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { styled.none("unexpected argument '"); - styled.stylize(Style::Warning, invalid_arg); + styled.stylize(Style::Warning.as_style(), invalid_arg); styled.none("' found"); true } else { @@ -419,7 +419,7 @@ pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> { fn try_help(styled: &mut StyledStr, help: Option<&str>) { if let Some(help) = help { styled.none("\n\nFor more information, try '"); - styled.stylize(Style::Literal, help); + styled.stylize(Style::Literal.as_style(), help); styled.none("'.\n"); } else { styled.none("\n"); @@ -429,12 +429,12 @@ fn try_help(styled: &mut StyledStr, help: Option<&str>) { #[cfg(feature = "error-context")] fn did_you_mean(styled: &mut StyledStr, context: &str, valid: &ContextValue) { styled.none(TAB); - styled.stylize(Style::Good, "tip:"); + styled.stylize(Style::Good.as_style(), "tip:"); if let ContextValue::String(valid) = valid { styled.none(" a similar "); styled.none(context); styled.none(" exists: '"); - styled.stylize(Style::Good, valid); + styled.stylize(Style::Good.as_style(), valid); styled.none("'"); } else if let ContextValue::Strings(valid) = valid { if valid.len() == 1 { @@ -451,7 +451,7 @@ fn did_you_mean(styled: &mut StyledStr, context: &str, valid: &ContextValue) { styled.none(", "); } styled.none("'"); - styled.stylize(Style::Good, valid); + styled.stylize(Style::Good.as_style(), valid); styled.none("'"); } } diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index c4aea163ffa..8aabf4cfaed 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -441,11 +441,11 @@ impl Error { { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("to pass '"); - styled_suggestion.stylize(Style::Warning, &subcmd); + styled_suggestion.stylize(Style::Warning.as_style(), &subcmd); styled_suggestion.none("' as a value, use '"); - styled_suggestion.stylize(Style::Good, &name); - styled_suggestion.stylize(Style::Good, " -- "); - styled_suggestion.stylize(Style::Good, &subcmd); + styled_suggestion.stylize(Style::Good.as_style(), &name); + styled_suggestion.stylize(Style::Good.as_style(), " -- "); + styled_suggestion.stylize(Style::Good.as_style(), &subcmd); styled_suggestion.none("'"); err = err.extend_context_unchecked([ @@ -670,10 +670,10 @@ impl Error { if suggested_trailing_arg { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("to pass '"); - styled_suggestion.stylize(Style::Warning, &arg); + styled_suggestion.stylize(Style::Warning.as_style(), &arg); styled_suggestion.none("' as a value, use '"); - styled_suggestion.stylize(Style::Good, "-- "); - styled_suggestion.stylize(Style::Good, &arg); + styled_suggestion.stylize(Style::Good.as_style(), "-- "); + styled_suggestion.stylize(Style::Good.as_style(), &arg); styled_suggestion.none("'"); suggestions.push(styled_suggestion); } @@ -688,10 +688,10 @@ impl Error { Some((flag, Some(sub))) => { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("'"); - styled_suggestion.stylize(Style::Good, &sub); + styled_suggestion.stylize(Style::Good.as_style(), &sub); styled_suggestion.none(" "); - styled_suggestion.stylize(Style::Good, "--"); - styled_suggestion.stylize(Style::Good, &flag); + styled_suggestion.stylize(Style::Good.as_style(), "--"); + styled_suggestion.stylize(Style::Good.as_style(), &flag); styled_suggestion.none("' exists"); suggestions.push(styled_suggestion); } @@ -725,9 +725,9 @@ impl Error { { let mut styled_suggestion = StyledStr::new(); styled_suggestion.none("subcommand '"); - styled_suggestion.stylize(Style::Good, &arg); + styled_suggestion.stylize(Style::Good.as_style(), &arg); styled_suggestion.none("' exists; to use it, remove the '"); - styled_suggestion.stylize(Style::Warning, "--"); + styled_suggestion.stylize(Style::Warning.as_style(), "--"); styled_suggestion.none("' before it"); err = err.extend_context_unchecked([ diff --git a/clap_builder/src/macros.rs b/clap_builder/src/macros.rs index 152a388b102..a02d313313f 100644 --- a/clap_builder/src/macros.rs +++ b/clap_builder/src/macros.rs @@ -631,8 +631,8 @@ macro_rules! debug { let prefix = format!("[{:>w$}] \t", module_path!(), w = 28); let body = format!($($arg)*); let mut styled = $crate::builder::StyledStr::new(); - styled.stylize($crate::builder::Style::Hint, &prefix); - styled.stylize($crate::builder::Style::Hint, &body); + styled.stylize($crate::builder::Style::Hint.as_style(), &prefix); + styled.stylize($crate::builder::Style::Hint.as_style(), &body); styled.none("\n"); let color = $crate::output::fmt::Colorizer::new($crate::output::fmt::Stream::Stderr, $crate::ColorChoice::Auto).with_content(styled); let _ = color.print(); diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index 2585f342d0a..a9587bd4e6b 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -803,11 +803,11 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { } fn header>(&mut self, msg: T) { - self.writer.stylize(Style::Header, msg.as_ref()); + self.writer.stylize(Style::Header.as_style(), msg.as_ref()); } fn literal>(&mut self, msg: T) { - self.writer.stylize(Style::Literal, msg.as_ref()); + self.writer.stylize(Style::Literal.as_style(), msg.as_ref()); } fn none>(&mut self, msg: T) { @@ -836,14 +836,14 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { .filter(|subcommand| should_show_subcommand(subcommand)) { let mut styled = StyledStr::new(); - styled.stylize(Style::Literal, subcommand.get_name()); + styled.stylize(Style::Literal.as_style(), subcommand.get_name()); if let Some(short) = subcommand.get_short_flag() { styled.none(", "); - styled.stylize(Style::Literal, &format!("-{short}")); + styled.stylize(Style::Literal.as_style(), &format!("-{short}")); } if let Some(long) = subcommand.get_long_flag() { styled.none(", "); - styled.stylize(Style::Literal, &format!("--{long}")); + styled.stylize(Style::Literal.as_style(), &format!("--{long}")); } longest = longest.max(styled.display_width()); ord_v.push((subcommand.get_display_order(), styled, subcommand)); diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index d3ac941d6e7..df226caa5d3 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -39,7 +39,7 @@ impl<'cmd> Usage<'cmd> { let usage = some!(self.create_usage_no_title(used)); let mut styled = StyledStr::new(); - styled.stylize(Style::Header, "Usage:"); + styled.stylize(Style::Header.as_style(), "Usage:"); styled.none(" "); styled.push_styled(&usage); Some(styled) @@ -79,10 +79,10 @@ impl<'cmd> Usage<'cmd> { .get_usage_name() .or_else(|| self.cmd.get_bin_name()) .unwrap_or_else(|| self.cmd.get_name()); - styled.stylize(Style::Literal, name); + styled.stylize(Style::Literal.as_style(), name); if self.needs_options_tag() { - styled.stylize(Style::Placeholder, " [OPTIONS]"); + styled.stylize(Style::Placeholder.as_style(), " [OPTIONS]"); } self.write_args(&[], !incl_reqs, &mut styled); @@ -102,21 +102,21 @@ impl<'cmd> Usage<'cmd> { styled.none(" "); if self.cmd.is_args_conflicts_with_subcommands_set() { // Short-circuit full usage creation since no args will be relevant - styled.stylize(Style::Literal, name); + styled.stylize(Style::Literal.as_style(), name); } else { styled.push_styled(&self.create_help_usage(false)); } - styled.stylize(Style::Placeholder, " <"); - styled.stylize(Style::Placeholder, placeholder); - styled.stylize(Style::Placeholder, ">"); + styled.stylize(Style::Placeholder.as_style(), " <"); + styled.stylize(Style::Placeholder.as_style(), placeholder); + styled.stylize(Style::Placeholder.as_style(), ">"); } else if self.cmd.is_subcommand_required_set() { - styled.stylize(Style::Placeholder, " <"); - styled.stylize(Style::Placeholder, placeholder); - styled.stylize(Style::Placeholder, ">"); + styled.stylize(Style::Placeholder.as_style(), " <"); + styled.stylize(Style::Placeholder.as_style(), placeholder); + styled.stylize(Style::Placeholder.as_style(), ">"); } else { - styled.stylize(Style::Placeholder, " ["); - styled.stylize(Style::Placeholder, placeholder); - styled.stylize(Style::Placeholder, "]"); + styled.stylize(Style::Placeholder.as_style(), " ["); + styled.stylize(Style::Placeholder.as_style(), placeholder); + styled.stylize(Style::Placeholder.as_style(), "]"); } } styled.trim(); @@ -131,7 +131,7 @@ impl<'cmd> Usage<'cmd> { let mut styled = StyledStr::new(); styled.stylize( - Style::Literal, + Style::Literal.as_style(), self.cmd .get_usage_name() .or_else(|| self.cmd.get_bin_name()) @@ -141,14 +141,14 @@ impl<'cmd> Usage<'cmd> { self.write_args(used, false, &mut styled); if self.cmd.is_subcommand_required_set() { - styled.stylize(Style::Placeholder, " <"); + styled.stylize(Style::Placeholder.as_style(), " <"); styled.stylize( - Style::Placeholder, + Style::Placeholder.as_style(), self.cmd .get_subcommand_value_name() .unwrap_or(DEFAULT_SUB_VALUE_NAME), ); - styled.stylize(Style::Placeholder, ">"); + styled.stylize(Style::Placeholder.as_style(), ">"); } styled } @@ -282,7 +282,7 @@ impl<'cmd> Usage<'cmd> { if pos.is_last_set() { let styled = required_positionals[index].take().unwrap(); let mut new = StyledStr::new(); - new.stylize(Style::Literal, "-- "); + new.stylize(Style::Literal.as_style(), "-- "); new.push_styled(&styled); required_positionals[index] = Some(new); } @@ -290,9 +290,9 @@ impl<'cmd> Usage<'cmd> { let mut styled; if pos.is_last_set() { styled = StyledStr::new(); - styled.stylize(Style::Literal, "[-- "); + styled.stylize(Style::Literal.as_style(), "[-- "); styled.push_styled(&pos.stylized(Some(true))); - styled.stylize(Style::Literal, "]"); + styled.stylize(Style::Literal.as_style(), "]"); } else { styled = pos.stylized(Some(false)); } From 72515d14b1fe318f78ee189d5efffc622c01a23c Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 14 Apr 2023 08:56:08 -0500 Subject: [PATCH 04/13] refactor(help): Style in the caller Random fixes along the way - In one case the space after `tip:` was "colored" (won't matter until themeing is available) - One error case didn't color invalid values - Changed the args associated with invalid values to be `literal` rather than `warning` - Changed the required value count to be `good` - Changed the arg for invalid value counts to be `literal` (the actual count is `warning`) --- clap_builder/src/builder/arg.rs | 53 ++-- clap_builder/src/builder/command.rs | 6 +- clap_builder/src/builder/styled_str.rs | 27 +- clap_builder/src/error/format.rs | 327 ++++++++++++++--------- clap_builder/src/error/mod.rs | 63 +++-- clap_builder/src/lib.rs | 4 + clap_builder/src/macros.rs | 9 +- clap_builder/src/output/help.rs | 2 +- clap_builder/src/output/help_template.rs | 227 ++++++++++------ clap_builder/src/output/usage.rs | 115 +++++--- 10 files changed, 525 insertions(+), 308 deletions(-) diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index bcf00752b93..8ca7512529f 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -4273,50 +4273,73 @@ impl Arg { } pub(crate) fn stylized(&self, required: Option) -> StyledStr { + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); + let mut styled = StyledStr::new(); // Write the name such --long or -l if let Some(l) = self.get_long() { - styled.stylize(Style::Literal.as_style(), "--"); - styled.stylize(Style::Literal.as_style(), l); + let _ = write!( + styled, + "{}--{l}{}", + literal.render(), + literal.render_reset() + ); } else if let Some(s) = self.get_short() { - styled.stylize(Style::Literal.as_style(), "-"); - let mut b = [0; 4]; - let s = s.encode_utf8(&mut b); - styled.stylize(Style::Literal.as_style(), s); + let _ = write!(styled, "{}-{s}{}", literal.render(), literal.render_reset()); } styled.push_styled(&self.stylize_arg_suffix(required)); styled } pub(crate) fn stylize_arg_suffix(&self, required: Option) -> StyledStr { + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); + let placeholder = Style::Placeholder.as_style(); let mut styled = StyledStr::new(); let mut need_closing_bracket = false; if self.is_takes_value_set() && !self.is_positional() { let is_optional_val = self.get_min_vals() == 0; - if self.is_require_equals_set() { + let (style, start) = if self.is_require_equals_set() { if is_optional_val { need_closing_bracket = true; - styled.stylize(Style::Placeholder.as_style(), "[="); + (placeholder, "[=") } else { - styled.stylize(Style::Literal.as_style(), "="); + (literal, "=") } } else if is_optional_val { need_closing_bracket = true; - styled.stylize(Style::Placeholder.as_style(), " ["); + (placeholder, " [") } else { - styled.stylize(Style::Placeholder.as_style(), " "); - } + (placeholder, " ") + }; + let _ = write!(styled, "{}{start}{}", style.render(), style.render_reset()); } if self.is_takes_value_set() || self.is_positional() { let required = required.unwrap_or_else(|| self.is_required_set()); let arg_val = self.render_arg_val(required); - styled.stylize(Style::Placeholder.as_style(), &arg_val); + let _ = write!( + styled, + "{}{arg_val}{}", + placeholder.render(), + placeholder.render_reset() + ); } else if matches!(*self.get_action(), ArgAction::Count) { - styled.stylize(Style::Placeholder.as_style(), "..."); + let _ = write!( + styled, + "{}...{}", + placeholder.render(), + placeholder.render_reset() + ); } if need_closing_bracket { - styled.stylize(Style::Placeholder.as_style(), "]"); + let _ = write!( + styled, + "{}]{}", + placeholder.render(), + placeholder.render_reset() + ); } styled diff --git a/clap_builder/src/builder/command.rs b/clap_builder/src/builder/command.rs index a17b51fb1e3..590a2692900 100644 --- a/clap_builder/src/builder/command.rs +++ b/clap_builder/src/builder/command.rs @@ -4321,9 +4321,9 @@ impl Command { .collect::>() .join("|"); let mut styled = StyledStr::new(); - styled.none("<"); - styled.none(g_string); - styled.none(">"); + styled.push_str("<"); + styled.push_string(g_string); + styled.push_str(">"); styled } } diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index fd98f56e21d..3958be6c13c 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -33,24 +33,16 @@ impl StyledStr { self.0.as_str() } - #[cfg(feature = "color")] - pub(crate) fn stylize(&mut self, style: anstyle::Style, msg: &str) { - if !msg.is_empty() { - use std::fmt::Write as _; - - let _ = write!(self.0, "{}{}{}", style.render(), msg, style.render_reset()); - } + /// May allow the compiler to consolidate the `Drop`s for `msg`, reducing code size compared to + /// `styled.push_str(&msg)` + pub(crate) fn push_string(&mut self, msg: String) { + self.0.push_str(&msg); } - #[cfg(not(feature = "color"))] - pub(crate) fn stylize(&mut self, _style: anstyle::Style, msg: &str) { + pub(crate) fn push_str(&mut self, msg: &str) { self.0.push_str(msg); } - pub(crate) fn none(&mut self, msg: impl Into) { - self.0.push_str(&msg.into()); - } - pub(crate) fn trim(&mut self) { self.0 = self.0.trim().to_owned() } @@ -162,7 +154,7 @@ impl From for StyledStr { impl From<&'_ std::string::String> for StyledStr { fn from(name: &'_ std::string::String) -> Self { let mut styled = StyledStr::new(); - styled.none(name); + styled.push_str(name); styled } } @@ -170,7 +162,7 @@ impl From<&'_ std::string::String> for StyledStr { impl From<&'static str> for StyledStr { fn from(name: &'static str) -> Self { let mut styled = StyledStr::new(); - styled.none(name); + styled.push_str(name); styled } } @@ -226,6 +218,7 @@ pub(crate) enum Style { impl Style { pub(crate) fn as_style(&self) -> anstyle::Style { + #[cfg(feature = "color")] match self { Style::Header => (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).into(), Style::Literal => anstyle::Effects::BOLD.into(), @@ -235,5 +228,9 @@ impl Style { Style::Error => anstyle::AnsiColor::Red.on_default() | anstyle::Effects::BOLD, Style::Hint => anstyle::Effects::DIMMED.into(), } + #[cfg(not(feature = "color"))] + { + anstyle::Style::new() + } } } diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs index 40f49315f07..0928d2078b9 100644 --- a/clap_builder/src/error/format.rs +++ b/clap_builder/src/error/format.rs @@ -30,16 +30,18 @@ pub struct KindFormatter; impl ErrorFormatter for KindFormatter { fn format_error(error: &crate::error::Error) -> StyledStr { + use std::fmt::Write as _; + let mut styled = StyledStr::new(); start_error(&mut styled); if let Some(msg) = error.kind().as_str() { - styled.none(msg.to_owned()); + styled.push_str(msg); } else if let Some(source) = error.inner.source.as_ref() { - styled.none(source.to_string()); + let _ = write!(styled, "{}", source); } else { - styled.none("unknown cause"); + styled.push_str("unknown cause"); } - styled.none("\n"); + styled.push_str("\n"); styled } } @@ -54,40 +56,43 @@ pub struct RichFormatter; #[cfg(feature = "error-context")] impl ErrorFormatter for RichFormatter { fn format_error(error: &crate::error::Error) -> StyledStr { + use std::fmt::Write as _; + let good = Style::Good.as_style(); + let mut styled = StyledStr::new(); start_error(&mut styled); if !write_dynamic_context(error, &mut styled) { if let Some(msg) = error.kind().as_str() { - styled.none(msg.to_owned()); + styled.push_str(msg); } else if let Some(source) = error.inner.source.as_ref() { - styled.none(source.to_string()); + let _ = write!(styled, "{}", source); } else { - styled.none("unknown cause"); + styled.push_str("unknown cause"); } } let mut suggested = false; if let Some(valid) = error.get(ContextKind::SuggestedSubcommand) { - styled.none("\n"); + styled.push_str("\n"); if !suggested { - styled.none("\n"); + styled.push_str("\n"); suggested = true; } did_you_mean(&mut styled, "subcommand", valid); } if let Some(valid) = error.get(ContextKind::SuggestedArg) { - styled.none("\n"); + styled.push_str("\n"); if !suggested { - styled.none("\n"); + styled.push_str("\n"); suggested = true; } did_you_mean(&mut styled, "argument", valid); } if let Some(valid) = error.get(ContextKind::SuggestedValue) { - styled.none("\n"); + styled.push_str("\n"); if !suggested { - styled.none("\n"); + styled.push_str("\n"); suggested = true; } did_you_mean(&mut styled, "value", valid); @@ -95,12 +100,15 @@ impl ErrorFormatter for RichFormatter { let suggestions = error.get(ContextKind::Suggested); if let Some(ContextValue::StyledStrs(suggestions)) = suggestions { if !suggested { - styled.none("\n"); + styled.push_str("\n"); } for suggestion in suggestions { - styled.none("\n"); - styled.none(TAB); - styled.stylize(Style::Good.as_style(), "tip: "); + let _ = write!( + styled, + "\n{TAB}{}tip:{} ", + good.render(), + good.render_reset() + ); styled.push_styled(suggestion); } } @@ -117,13 +125,19 @@ impl ErrorFormatter for RichFormatter { } fn start_error(styled: &mut StyledStr) { - styled.stylize(Style::Error.as_style(), "error:"); - styled.none(" "); + use std::fmt::Write as _; + let error = Style::Error.as_style(); + let _ = write!(styled, "{}error:{} ", error.render(), error.render_reset()); } #[must_use] #[cfg(feature = "error-context")] fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> bool { + use std::fmt::Write as _; + let good = Style::Good.as_style(); + let warning = Style::Warning.as_style(); + let literal = Style::Literal.as_style(); + match error.kind() { ErrorKind::ArgumentConflict => { let invalid_arg = error.get(ContextKind::InvalidArg); @@ -132,30 +146,42 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> (invalid_arg, prior_arg) { if ContextValue::String(invalid_arg.clone()) == *prior_arg { - styled.none("the argument '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("' cannot be used multiple times"); + let _ = write!( + styled, + "the argument '{}{invalid_arg}{}' cannot be used multiple times", + warning.render(), + warning.render_reset() + ); } else { - styled.none("the argument '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("' cannot be used with"); + let _ = write!( + styled, + "the argument '{}{invalid_arg}{}' cannot be used with", + warning.render(), + warning.render_reset() + ); match prior_arg { ContextValue::Strings(values) => { - styled.none(":"); + styled.push_str(":"); for v in values { - styled.none("\n"); - styled.none(TAB); - styled.stylize(Style::Warning.as_style(), &**v); + let _ = write!( + styled, + "\n{TAB}{}{v}{}", + warning.render(), + warning.render_reset() + ); } } ContextValue::String(value) => { - styled.none(" '"); - styled.stylize(Style::Warning.as_style(), value); - styled.none("'"); + let _ = write!( + styled, + " '{}{value}{}'", + warning.render(), + warning.render_reset() + ); } _ => { - styled.none(" one or more of the other specified arguments"); + styled.push_str(" one or more of the other specified arguments"); } } } @@ -167,9 +193,12 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ErrorKind::NoEquals => { let invalid_arg = error.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { - styled.none("equal sign is needed when assigning values to '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("'"); + let _ = write!( + styled, + "equal sign is needed when assigning values to '{}{invalid_arg}{}'", + warning.render(), + warning.render_reset() + ); true } else { false @@ -184,31 +213,46 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, invalid_value) { if invalid_value.is_empty() { - styled.none("a value is required for '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("' but none was supplied"); + let _ = write!( + styled, + "a value is required for '{}{invalid_arg}{}' but none was supplied", + warning.render(), + warning.render_reset() + ); } else { - styled.none("invalid value '"); - styled.none(invalid_value); - styled.none("' for '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("'"); + let _ = write!( + styled, + "invalid value '{}{invalid_value}{}' for '{}{invalid_arg}{}'", + warning.render(), + warning.render_reset(), + literal.render(), + literal.render_reset() + ); } let possible_values = error.get(ContextKind::ValidValue); if let Some(ContextValue::Strings(possible_values)) = possible_values { if !possible_values.is_empty() { - styled.none("\n"); - styled.none(TAB); - styled.none("[possible values: "); + let _ = write!(styled, "\n{TAB}[possible values: "); if let Some((last, elements)) = possible_values.split_last() { for v in elements { - styled.stylize(Style::Good.as_style(), &escape(v)); - styled.none(", "); + let _ = write!( + styled, + "{}{}{}, ", + good.render(), + Escape(v), + good.render_reset() + ); } - styled.stylize(Style::Good.as_style(), &escape(last)); + let _ = write!( + styled, + "{}{}{}", + good.render(), + Escape(last), + good.render_reset() + ); } - styled.none("]"); + styled.push_str("]"); } } true @@ -219,9 +263,12 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ErrorKind::InvalidSubcommand => { let invalid_sub = error.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { - styled.none("unrecognized subcommand '"); - styled.stylize(Style::Warning.as_style(), invalid_sub); - styled.none("'"); + let _ = write!( + styled, + "unrecognized subcommand '{}{invalid_sub}{}'", + warning.render(), + warning.render_reset() + ); true } else { false @@ -230,11 +277,9 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ErrorKind::MissingRequiredArgument => { let invalid_arg = error.get(ContextKind::InvalidArg); if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg { - styled.none("the following required arguments were not provided:"); + styled.push_str("the following required arguments were not provided:"); for v in invalid_arg { - styled.none("\n"); - styled.none(TAB); - styled.stylize(Style::Good.as_style(), &**v); + let _ = write!(styled, "\n{TAB}{}{v}{}", good.render(), good.render_reset()); } true } else { @@ -244,24 +289,36 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ErrorKind::MissingSubcommand => { let invalid_sub = error.get(ContextKind::InvalidSubcommand); if let Some(ContextValue::String(invalid_sub)) = invalid_sub { - styled.none("'"); - styled.stylize(Style::Warning.as_style(), invalid_sub); - styled.none("' requires a subcommand but one was not provided"); + let _ = write!( + styled, + "'{}{invalid_sub}{}' requires a subcommand but one was not provided", + warning.render(), + warning.render_reset() + ); let possible_values = error.get(ContextKind::ValidSubcommand); if let Some(ContextValue::Strings(possible_values)) = possible_values { if !possible_values.is_empty() { - styled.none("\n"); - styled.none(TAB); - styled.none("[subcommands: "); + let _ = write!(styled, "\n{TAB}[subcommands: "); if let Some((last, elements)) = possible_values.split_last() { for v in elements { - styled.stylize(Style::Good.as_style(), &escape(v)); - styled.none(", "); + let _ = write!( + styled, + "{}{}{}, ", + good.render(), + Escape(v), + good.render_reset() + ); } - styled.stylize(Style::Good.as_style(), &escape(last)); + let _ = write!( + styled, + "{}{}{}", + good.render(), + Escape(last), + good.render_reset() + ); } - styled.none("]"); + styled.push_str("]"); } } @@ -279,11 +336,14 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> Some(ContextValue::String(invalid_value)), ) = (invalid_arg, invalid_value) { - styled.none("unexpected value '"); - styled.stylize(Style::Warning.as_style(), invalid_value); - styled.none("' for '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("' found; no more were expected"); + let _ = write!( + styled, + "unexpected value '{}{invalid_value}{}' for '{}{invalid_arg}{}' found; no more were expected", + warning.render(), + warning.render_reset(), + literal.render(), + literal.render_reset(), + ); true } else { false @@ -300,12 +360,16 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, actual_num_values, min_values) { let were_provided = singular_or_plural(*actual_num_values as usize); - styled.stylize(Style::Warning.as_style(), &min_values.to_string()); - styled.none(" more values required by '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("'; only "); - styled.stylize(Style::Warning.as_style(), &actual_num_values.to_string()); - styled.none(were_provided); + let _ = write!( + styled, + "{}{min_values}{} more values required by '{}{invalid_arg}{}'; only {}{actual_num_values}{}{were_provided}", + good.render(), + good.render_reset(), + literal.render(), + literal.render_reset(), + warning.render(), + warning.render_reset(), + ); true } else { false @@ -319,15 +383,16 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> Some(ContextValue::String(invalid_value)), ) = (invalid_arg, invalid_value) { - styled.none("invalid value '"); - styled.stylize(Style::Warning.as_style(), invalid_value); - styled.none("' for '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); + let _ = write!( + styled, + "invalid value '{}{invalid_value}{}' for '{}{invalid_arg}{}'", + warning.render(), + warning.render_reset(), + literal.render(), + literal.render_reset(), + ); if let Some(source) = error.inner.source.as_deref() { - styled.none("': "); - styled.none(source.to_string()); - } else { - styled.none("'"); + let _ = write!(styled, ": {}", source); } true } else { @@ -345,12 +410,16 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ) = (invalid_arg, actual_num_values, num_values) { let were_provided = singular_or_plural(*actual_num_values as usize); - styled.stylize(Style::Warning.as_style(), &num_values.to_string()); - styled.none(" values required for '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("' but "); - styled.stylize(Style::Warning.as_style(), &actual_num_values.to_string()); - styled.none(were_provided); + let _ = write!( + styled, + "{}{num_values}{} values required for '{}{invalid_arg}{}' but {}{actual_num_values}{}{were_provided}", + good.render(), + good.render_reset(), + literal.render(), + literal.render_reset(), + warning.render(), + warning.render_reset(), + ); true } else { false @@ -359,9 +428,12 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> ErrorKind::UnknownArgument => { let invalid_arg = error.get(ContextKind::InvalidArg); if let Some(ContextValue::String(invalid_arg)) = invalid_arg { - styled.none("unexpected argument '"); - styled.stylize(Style::Warning.as_style(), invalid_arg); - styled.none("' found"); + let _ = write!( + styled, + "unexpected argument '{}{invalid_arg}{}' found", + warning.render(), + warning.render_reset(), + ); true } else { false @@ -382,7 +454,7 @@ pub(crate) fn format_error_message( ) -> StyledStr { let mut styled = StyledStr::new(); start_error(&mut styled); - styled.none(message); + styled.push_str(message); if let Some(usage) = usage { put_usage(&mut styled, usage); } @@ -402,7 +474,7 @@ fn singular_or_plural(n: usize) -> &'static str { } fn put_usage(styled: &mut StyledStr, usage: &StyledStr) { - styled.none("\n\n"); + styled.push_str("\n\n"); styled.push_styled(usage); } @@ -418,50 +490,55 @@ pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> { fn try_help(styled: &mut StyledStr, help: Option<&str>) { if let Some(help) = help { - styled.none("\n\nFor more information, try '"); - styled.stylize(Style::Literal.as_style(), help); - styled.none("'.\n"); + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); + let _ = write!( + styled, + "\n\nFor more information, try '{}{help}{}'.\n", + literal.render(), + literal.render_reset() + ); } else { - styled.none("\n"); + styled.push_str("\n"); } } #[cfg(feature = "error-context")] fn did_you_mean(styled: &mut StyledStr, context: &str, valid: &ContextValue) { - styled.none(TAB); - styled.stylize(Style::Good.as_style(), "tip:"); + use std::fmt::Write as _; + let good = Style::Good.as_style(); + + let _ = write!(styled, "{TAB}{}tip:{}", good.render(), good.render_reset()); if let ContextValue::String(valid) = valid { - styled.none(" a similar "); - styled.none(context); - styled.none(" exists: '"); - styled.stylize(Style::Good.as_style(), valid); - styled.none("'"); + let _ = write!( + styled, + " a similar {context} exists: '{}{valid}{}'", + good.render(), + good.render_reset() + ); } else if let ContextValue::Strings(valid) = valid { if valid.len() == 1 { - styled.none(" a similar "); - styled.none(context); - styled.none(" exists: "); + let _ = write!(styled, " a similar {context} exists: ",); } else { - styled.none(" some similar "); - styled.none(context); - styled.none("s exist: "); + let _ = write!(styled, " some similar {context}s exist: ",); } for (i, valid) in valid.iter().enumerate() { if i != 0 { - styled.none(", "); + styled.push_str(", "); } - styled.none("'"); - styled.stylize(Style::Good.as_style(), valid); - styled.none("'"); + let _ = write!(styled, "'{}{valid}{}'", good.render(), good.render_reset()); } } } -fn escape(s: impl AsRef) -> String { - let s = s.as_ref(); - if s.contains(char::is_whitespace) { - format!("{s:?}") - } else { - s.to_owned() +struct Escape<'s>(&'s str); + +impl<'s> std::fmt::Display for Escape<'s> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.0.contains(char::is_whitespace) { + std::fmt::Debug::fmt(self.0, f) + } else { + self.0.fmt(f) + } } } diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index 8aabf4cfaed..bb612d2d8c8 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -435,18 +435,22 @@ impl Error { name: String, usage: Option, ) -> Self { + use std::fmt::Write as _; + let warning = Style::Warning.as_style(); + let good = Style::Good.as_style(); let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] { let mut styled_suggestion = StyledStr::new(); - styled_suggestion.none("to pass '"); - styled_suggestion.stylize(Style::Warning.as_style(), &subcmd); - styled_suggestion.none("' as a value, use '"); - styled_suggestion.stylize(Style::Good.as_style(), &name); - styled_suggestion.stylize(Style::Good.as_style(), " -- "); - styled_suggestion.stylize(Style::Good.as_style(), &subcmd); - styled_suggestion.none("'"); + let _ = write!( + styled_suggestion, + "to pass '{}{subcmd}{}' as a value, use '{}{name} -- {subcmd}{}'", + warning.render(), + warning.render_reset(), + good.render(), + good.render_reset() + ); err = err.extend_context_unchecked([ (ContextKind::InvalidSubcommand, ContextValue::String(subcmd)), @@ -662,6 +666,9 @@ impl Error { suggested_trailing_arg: bool, usage: Option, ) -> Self { + use std::fmt::Write as _; + let warning = Style::Warning.as_style(); + let good = Style::Good.as_style(); let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -669,12 +676,14 @@ impl Error { let mut suggestions = vec![]; if suggested_trailing_arg { let mut styled_suggestion = StyledStr::new(); - styled_suggestion.none("to pass '"); - styled_suggestion.stylize(Style::Warning.as_style(), &arg); - styled_suggestion.none("' as a value, use '"); - styled_suggestion.stylize(Style::Good.as_style(), "-- "); - styled_suggestion.stylize(Style::Good.as_style(), &arg); - styled_suggestion.none("'"); + let _ = write!( + styled_suggestion, + "to pass '{}{arg}{}' as a value, use '{}-- {arg}{}'", + warning.render(), + warning.render_reset(), + good.render(), + good.render_reset() + ); suggestions.push(styled_suggestion); } @@ -687,12 +696,12 @@ impl Error { match did_you_mean { Some((flag, Some(sub))) => { let mut styled_suggestion = StyledStr::new(); - styled_suggestion.none("'"); - styled_suggestion.stylize(Style::Good.as_style(), &sub); - styled_suggestion.none(" "); - styled_suggestion.stylize(Style::Good.as_style(), "--"); - styled_suggestion.stylize(Style::Good.as_style(), &flag); - styled_suggestion.none("' exists"); + let _ = write!( + styled_suggestion, + "'{}{sub} --{flag}{}' exists", + good.render(), + good.render_reset() + ); suggestions.push(styled_suggestion); } Some((flag, None)) => { @@ -719,16 +728,22 @@ impl Error { arg: String, usage: Option, ) -> Self { + use std::fmt::Write as _; + let warning = Style::Warning.as_style(); + let good = Style::Good.as_style(); let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] { let mut styled_suggestion = StyledStr::new(); - styled_suggestion.none("subcommand '"); - styled_suggestion.stylize(Style::Good.as_style(), &arg); - styled_suggestion.none("' exists; to use it, remove the '"); - styled_suggestion.stylize(Style::Warning.as_style(), "--"); - styled_suggestion.none("' before it"); + let _ = write!( + styled_suggestion, + "subcommand '{}{arg}{}' exists; to use it, remove the '{}--{}' before it", + good.render(), + good.render_reset(), + warning.render(), + warning.render_reset() + ); err = err.extend_context_unchecked([ (ContextKind::InvalidArg, ContextValue::String(arg)), diff --git a/clap_builder/src/lib.rs b/clap_builder/src/lib.rs index a8c1201e0e4..97c32c141bd 100644 --- a/clap_builder/src/lib.rs +++ b/clap_builder/src/lib.rs @@ -16,6 +16,10 @@ clippy::single_char_pattern )] #![forbid(unsafe_code)] +// Wanting consistency in our calls +#![allow(clippy::write_with_newline)] +// Gets in the way of logging +#![allow(clippy::let_and_return)] // HACK https://github.com/rust-lang/rust-clippy/issues/7290 #![allow(clippy::single_component_path_imports)] #![allow(clippy::branches_sharing_code)] diff --git a/clap_builder/src/macros.rs b/clap_builder/src/macros.rs index a02d313313f..a45be83c9b6 100644 --- a/clap_builder/src/macros.rs +++ b/clap_builder/src/macros.rs @@ -628,12 +628,13 @@ macro_rules! impl_settings { #[cfg(feature = "debug")] macro_rules! debug { ($($arg:tt)*) => ({ - let prefix = format!("[{:>w$}] \t", module_path!(), w = 28); + use std::fmt::Write as _; + let hint = $crate::builder::Style::Hint.as_style(); + + let module_path = module_path!(); let body = format!($($arg)*); let mut styled = $crate::builder::StyledStr::new(); - styled.stylize($crate::builder::Style::Hint.as_style(), &prefix); - styled.stylize($crate::builder::Style::Hint.as_style(), &body); - styled.none("\n"); + let _ = write!(styled, "{}[{module_path:>28}]{body}{}\n", hint.render(), hint.render_reset()); let color = $crate::output::fmt::Colorizer::new($crate::output::fmt::Stream::Stderr, $crate::ColorChoice::Auto).with_content(styled); let _ = color.print(); }) diff --git a/clap_builder/src/output/help.rs b/clap_builder/src/output/help.rs index 4921f5f71f3..410616eb1b8 100644 --- a/clap_builder/src/output/help.rs +++ b/clap_builder/src/output/help.rs @@ -33,5 +33,5 @@ pub(crate) fn write_help(writer: &mut StyledStr, cmd: &Command, usage: &Usage<'_ // Remove any extra lines caused by book keeping writer.trim(); // Ensure there is still a trailing newline - writer.none("\n"); + writer.push_str("\n"); } diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index a9587bd4e6b..8a09e5f8af4 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -127,10 +127,12 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// [`Command::help_template`]: Command::help_template() pub(crate) fn write_templated_help(&mut self, template: &str) { debug!("HelpTemplate::write_templated_help"); + use std::fmt::Write as _; + let header = Style::Header.as_style(); let mut parts = template.split('{'); if let Some(first) = parts.next() { - self.none(first); + self.writer.push_str(first); } for part in parts { if let Some((tag, rest)) = part.split_once('}') { @@ -164,7 +166,12 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self.write_about(true, true); } "usage-heading" => { - self.header("Usage:"); + let _ = write!( + self.writer, + "{}Usage:{}", + header.render(), + header.render_reset() + ); } "usage" => { self.writer.push_styled( @@ -194,7 +201,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self.write_subcommands(self.cmd); } "tab" => { - self.none(TAB); + self.writer.push_str(TAB); } "after-help" => { self.write_after_help(); @@ -203,12 +210,10 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self.write_before_help(); } _ => { - self.none("{"); - self.none(tag); - self.none("}"); + let _ = write!(self.writer, "{{{tag}}}"); } } - self.none(rest); + self.writer.push_str(rest); } } } @@ -228,7 +233,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { .replace("{n}", "\n"), self.term_w, ); - self.none(&display_name); + self.writer.push_string(display_name); } /// Writes binary name of a Parser Object to the wrapped stream. @@ -246,7 +251,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { } else { wrap(&self.cmd.get_name().replace("{n}", "\n"), self.term_w) }; - self.none(&bin_name); + self.writer.push_string(bin_name); } fn write_version(&mut self) { @@ -255,18 +260,18 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { .get_version() .or_else(|| self.cmd.get_long_version()); if let Some(output) = version { - self.none(wrap(output, self.term_w)); + self.writer.push_string(wrap(output, self.term_w)); } } fn write_author(&mut self, before_new_line: bool, after_new_line: bool) { if let Some(author) = self.cmd.get_author() { if before_new_line { - self.none("\n"); + self.writer.push_str("\n"); } - self.none(wrap(author, self.term_w)); + self.writer.push_string(wrap(author, self.term_w)); if after_new_line { - self.none("\n"); + self.writer.push_str("\n"); } } } @@ -279,14 +284,14 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { }; if let Some(output) = about { if before_new_line { - self.none("\n"); + self.writer.push_str("\n"); } let mut output = output.clone(); output.replace_newline_var(); output.wrap(self.term_w); self.writer.push_styled(&output); if after_new_line { - self.none("\n"); + self.writer.push_str("\n"); } } } @@ -305,7 +310,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { output.replace_newline_var(); output.wrap(self.term_w); self.writer.push_styled(&output); - self.none("\n\n"); + self.writer.push_str("\n\n"); } } @@ -319,7 +324,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self.cmd.get_after_help() }; if let Some(output) = after_help { - self.none("\n\n"); + self.writer.push_str("\n\n"); let mut output = output.clone(); output.replace_newline_var(); output.wrap(self.term_w); @@ -334,6 +339,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// including titles of a Parser Object to the wrapped stream. pub(crate) fn write_all_args(&mut self) { debug!("HelpTemplate::write_all_args"); + use std::fmt::Write as _; + let header = Style::Header.as_style(); + let pos = self .cmd .get_positionals() @@ -358,39 +366,52 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { if subcmds { if !first { - self.none("\n\n"); + self.writer.push_str("\n\n"); } first = false; let default_help_heading = Str::from("Commands"); - self.header( - self.cmd - .get_subcommand_help_heading() - .unwrap_or(&default_help_heading), + let help_heading = self + .cmd + .get_subcommand_help_heading() + .unwrap_or(&default_help_heading); + let _ = write!( + self.writer, + "{}{help_heading}:{}\n", + header.render(), + header.render_reset() ); - self.header(":"); - self.none("\n"); self.write_subcommands(self.cmd); } if !pos.is_empty() { if !first { - self.none("\n\n"); + self.writer.push_str("\n\n"); } first = false; // Write positional args if any - self.header("Arguments:"); - self.none("\n"); + let help_heading = "Arguments"; + let _ = write!( + self.writer, + "{}{help_heading}:{}\n", + header.render(), + header.render_reset() + ); self.write_args(&pos, "Arguments", positional_sort_key); } if !non_pos.is_empty() { if !first { - self.none("\n\n"); + self.writer.push_str("\n\n"); } first = false; - self.header("Options:"); - self.none("\n"); + let help_heading = "Options"; + let _ = write!( + self.writer, + "{}{help_heading}:{}\n", + header.render(), + header.render_reset() + ); self.write_args(&non_pos, "Options", option_sort_key); } if !custom_headings.is_empty() { @@ -409,12 +430,15 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { if !args.is_empty() { if !first { - self.none("\n\n"); + self.writer.push_str("\n\n"); } first = false; - self.header(heading); - self.header(":"); - self.none("\n"); + let _ = write!( + self.writer, + "{}{heading}:{}\n", + header.render(), + header.render_reset() + ); self.write_args(&args, heading, option_sort_key); } } @@ -452,9 +476,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { for (i, (_, arg)) in ord_v.iter().enumerate() { if i != 0 { - self.none("\n"); + self.writer.push_str("\n"); if next_line_help && self.use_long { - self.none("\n"); + self.writer.push_str("\n"); } } self.write_arg(arg, next_line_help, longest); @@ -465,7 +489,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { fn write_arg(&mut self, arg: &Arg, next_line_help: bool, longest: usize) { let spec_vals = &self.spec_vals(arg); - self.none(TAB); + self.writer.push_str(TAB); self.short(arg); self.long(arg); self.writer.push_styled(&arg.stylize_arg_suffix(None)); @@ -487,22 +511,37 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// Writes argument's short command to the wrapped stream. fn short(&mut self, arg: &Arg) { debug!("HelpTemplate::short"); + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); if let Some(s) = arg.get_short() { - self.literal(format!("-{s}")); + let _ = write!( + self.writer, + "{}-{s}{}", + literal.render(), + literal.render_reset() + ); } else if arg.get_long().is_some() { - self.none(" "); + self.writer.push_str(" "); } } /// Writes argument's long command to the wrapped stream. fn long(&mut self, arg: &Arg) { debug!("HelpTemplate::long"); + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); + if let Some(long) = arg.get_long() { if arg.get_short().is_some() { - self.none(", "); + self.writer.push_str(", "); } - self.literal(format!("--{long}")); + let _ = write!( + self.writer, + "{}--{long}{}", + literal.render(), + literal.render_reset() + ); } } @@ -514,9 +553,10 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { next_line_help, longest ); - if self.use_long || next_line_help { + let padding = if self.use_long || next_line_help { // long help prints messages on the next line so it doesn't need to align text debug!("HelpTemplate::align_to_about: printing long help so skip alignment"); + 0 } else if !arg.is_positional() { let self_len = display_width(&arg.to_string()); // Since we're writing spaces from the tab point we first need to know if we @@ -534,7 +574,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self_len, spcs ); - self.spaces(spcs); + spcs } else { let self_len = display_width(&arg.to_string()); let padding = TAB_WIDTH; @@ -544,8 +584,10 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self_len, spcs ); - self.spaces(spcs); - } + spcs + }; + + self.write_padding(padding); } /// Writes argument's help to the wrapped stream. @@ -558,13 +600,15 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { longest: usize, ) { debug!("HelpTemplate::help"); + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); // Is help on next line, if so then indent if next_line_help { debug!("HelpTemplate::help: Next Line...{:?}", next_line_help); - self.none("\n"); - self.none(TAB); - self.none(NEXT_LINE_INDENT); + self.writer.push_str("\n"); + self.writer.push_str(TAB); + self.writer.push_str(NEXT_LINE_INDENT); } let spaces = if next_line_help { @@ -586,9 +630,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { } else { " " }; - help.none(sep); + help.push_str(sep); } - help.none(spec_vals); + help.push_str(spec_vals); } let avail_chars = self.term_w.saturating_sub(spaces); debug!( @@ -613,11 +657,6 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { "HelpTemplate::help: Found possible vals...{:?}", possible_vals ); - if !help_is_empty { - self.none("\n\n"); - self.spaces(spaces); - } - self.none("Possible values:"); let longest = possible_vals .iter() .filter_map(|f| f.get_visible_quoted_name().map(|name| display_width(&name))) @@ -642,21 +681,29 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { }; let trailing_indent = self.get_spaces(trailing_indent); + if !help_is_empty { + let _ = write!(self.writer, "\n\n{:spaces$}", ""); + } + self.writer.push_str("Possible values:"); for pv in possible_vals.iter().filter(|pv| !pv.is_hide_set()) { - self.none("\n"); - self.spaces(spaces); - self.none("- "); - self.literal(pv.get_name()); + let name = pv.get_name(); + let _ = write!( + self.writer, + "\n{:spaces$}- {}{name}{}", + "", + literal.render(), + literal.render_reset() + ); if let Some(help) = pv.get_help() { debug!("HelpTemplate::help: Possible Value help"); if possible_value_new_line { - self.none(":\n"); - self.spaces(trailing_indent.len()); + let padding = trailing_indent.len(); + let _ = write!(self.writer, ":\n{:padding$}", ""); } else { - self.none(": "); // To align help messages - self.spaces(longest - display_width(pv.get_name())); + let padding = longest - display_width(pv.get_name()); + let _ = write!(self.writer, ": {:padding$}", ""); } let avail_chars = if self.term_w > trailing_indent.len() { @@ -802,24 +849,13 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { spec_vals.join(connector) } - fn header>(&mut self, msg: T) { - self.writer.stylize(Style::Header.as_style(), msg.as_ref()); - } - - fn literal>(&mut self, msg: T) { - self.writer.stylize(Style::Literal.as_style(), msg.as_ref()); - } - - fn none>(&mut self, msg: T) { - self.writer.none(msg); - } - fn get_spaces(&self, n: usize) -> String { " ".repeat(n) } - fn spaces(&mut self, n: usize) { - self.none(self.get_spaces(n)); + fn write_padding(&mut self, amount: usize) { + use std::fmt::Write as _; + let _ = write!(self.writer, "{:amount$}", ""); } } @@ -828,6 +864,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// Writes help for subcommands of a Parser Object to the wrapped stream. fn write_subcommands(&mut self, cmd: &Command) { debug!("HelpTemplate::write_subcommands"); + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); + // The shortest an arg can legally be is 2 (i.e. '-x') let mut longest = 2; let mut ord_v = Vec::new(); @@ -836,14 +875,28 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { .filter(|subcommand| should_show_subcommand(subcommand)) { let mut styled = StyledStr::new(); - styled.stylize(Style::Literal.as_style(), subcommand.get_name()); + let name = subcommand.get_name(); + let _ = write!( + styled, + "{}{name}{}", + literal.render(), + literal.render_reset() + ); if let Some(short) = subcommand.get_short_flag() { - styled.none(", "); - styled.stylize(Style::Literal.as_style(), &format!("-{short}")); + let _ = write!( + styled, + ", {}-{short}{}", + literal.render(), + literal.render_reset() + ); } if let Some(long) = subcommand.get_long_flag() { - styled.none(", "); - styled.stylize(Style::Literal.as_style(), &format!("--{long}")); + let _ = write!( + styled, + ", {}--{long}{}", + literal.render(), + literal.render_reset() + ); } longest = longest.max(styled.display_width()); ord_v.push((subcommand.get_display_order(), styled, subcommand)); @@ -859,7 +912,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { if first { first = false; } else { - self.none("\n"); + self.writer.push_str("\n"); } self.write_subcommand(sc_str, sc, next_line_help, longest); } @@ -943,12 +996,12 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { /// Writes subcommand to the wrapped stream. fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) { - let width = sc_str.display_width(); - - self.none(TAB); + self.writer.push_str(TAB); self.writer.push_styled(&sc_str); if !next_line_help { - self.spaces(longest + TAB_WIDTH - width); + let width = sc_str.display_width(); + let padding = longest + TAB_WIDTH - width; + self.write_padding(padding); } } } diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index df226caa5d3..ccac830919b 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -38,9 +38,15 @@ impl<'cmd> Usage<'cmd> { debug!("Usage::create_usage_with_title"); let usage = some!(self.create_usage_no_title(used)); + use std::fmt::Write as _; + let header = Style::Header.as_style(); let mut styled = StyledStr::new(); - styled.stylize(Style::Header.as_style(), "Usage:"); - styled.none(" "); + let _ = write!( + styled, + "{}Usage:{} ", + header.render(), + header.render_reset() + ); styled.push_styled(&usage); Some(styled) } @@ -73,16 +79,33 @@ impl<'cmd> Usage<'cmd> { // Creates a usage string for display in help messages (i.e. not for errors) fn create_help_usage(&self, incl_reqs: bool) -> StyledStr { debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs); + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); + let placeholder = Style::Placeholder.as_style(); let mut styled = StyledStr::new(); + let name = self .cmd .get_usage_name() .or_else(|| self.cmd.get_bin_name()) .unwrap_or_else(|| self.cmd.get_name()); - styled.stylize(Style::Literal.as_style(), name); + if !name.is_empty() { + // the trim won't properly remove a leading space due to the formatting + let _ = write!( + styled, + "{}{name}{}", + literal.render(), + literal.render_reset() + ); + } if self.needs_options_tag() { - styled.stylize(Style::Placeholder.as_style(), " [OPTIONS]"); + let _ = write!( + styled, + "{} [OPTIONS]{}", + placeholder.render(), + placeholder.render_reset() + ); } self.write_args(&[], !incl_reqs, &mut styled); @@ -91,32 +114,45 @@ impl<'cmd> Usage<'cmd> { if self.cmd.has_visible_subcommands() && incl_reqs || self.cmd.is_allow_external_subcommands_set() { - let placeholder = self + let value_name = self .cmd .get_subcommand_value_name() .unwrap_or(DEFAULT_SUB_VALUE_NAME); if self.cmd.is_subcommand_negates_reqs_set() || self.cmd.is_args_conflicts_with_subcommands_set() { - styled.none("\n"); - styled.none(" "); + let _ = write!(styled, "\n "); if self.cmd.is_args_conflicts_with_subcommands_set() { // Short-circuit full usage creation since no args will be relevant - styled.stylize(Style::Literal.as_style(), name); + let _ = write!( + styled, + "{}{name}{}", + literal.render(), + literal.render_reset() + ); } else { styled.push_styled(&self.create_help_usage(false)); } - styled.stylize(Style::Placeholder.as_style(), " <"); - styled.stylize(Style::Placeholder.as_style(), placeholder); - styled.stylize(Style::Placeholder.as_style(), ">"); + let _ = write!( + styled, + " {}<{value_name}>{}", + placeholder.render(), + placeholder.render_reset() + ); } else if self.cmd.is_subcommand_required_set() { - styled.stylize(Style::Placeholder.as_style(), " <"); - styled.stylize(Style::Placeholder.as_style(), placeholder); - styled.stylize(Style::Placeholder.as_style(), ">"); + let _ = write!( + styled, + " {}<{value_name}>{}", + placeholder.render(), + placeholder.render_reset() + ); } else { - styled.stylize(Style::Placeholder.as_style(), " ["); - styled.stylize(Style::Placeholder.as_style(), placeholder); - styled.stylize(Style::Placeholder.as_style(), "]"); + let _ = write!( + styled, + " {}[{value_name}]{}", + placeholder.render(), + placeholder.render_reset() + ); } } styled.trim(); @@ -128,27 +164,36 @@ impl<'cmd> Usage<'cmd> { // args, and requirements fn create_smart_usage(&self, used: &[Id]) -> StyledStr { debug!("Usage::create_smart_usage"); + use std::fmt::Write; + let literal = Style::Literal.as_style(); + let placeholder = Style::Placeholder.as_style(); let mut styled = StyledStr::new(); - styled.stylize( - Style::Literal.as_style(), - self.cmd - .get_usage_name() - .or_else(|| self.cmd.get_bin_name()) - .unwrap_or_else(|| self.cmd.get_name()), + let bin_name = self + .cmd + .get_usage_name() + .or_else(|| self.cmd.get_bin_name()) + .unwrap_or_else(|| self.cmd.get_name()); + let _ = write!( + styled, + "{}{bin_name}{}", + literal.render(), + literal.render_reset() ); self.write_args(used, false, &mut styled); if self.cmd.is_subcommand_required_set() { - styled.stylize(Style::Placeholder.as_style(), " <"); - styled.stylize( - Style::Placeholder.as_style(), - self.cmd - .get_subcommand_value_name() - .unwrap_or(DEFAULT_SUB_VALUE_NAME), + let value_name = self + .cmd + .get_subcommand_value_name() + .unwrap_or(DEFAULT_SUB_VALUE_NAME); + let _ = write!( + styled, + " {}<{value_name}>{}", + placeholder.render(), + placeholder.render_reset() ); - styled.stylize(Style::Placeholder.as_style(), ">"); } styled } @@ -192,13 +237,15 @@ impl<'cmd> Usage<'cmd> { // Returns the required args in usage string form by fully unrolling all groups pub(crate) fn write_args(&self, incls: &[Id], force_optional: bool, styled: &mut StyledStr) { for required in self.get_args(incls, force_optional) { - styled.none(" "); + styled.push_str(" "); styled.push_styled(&required); } } pub(crate) fn get_args(&self, incls: &[Id], force_optional: bool) -> Vec { debug!("Usage::get_args: incls={:?}", incls,); + use std::fmt::Write as _; + let literal = Style::Literal.as_style(); let required_owned; let required = if let Some(required) = self.required { @@ -282,7 +329,7 @@ impl<'cmd> Usage<'cmd> { if pos.is_last_set() { let styled = required_positionals[index].take().unwrap(); let mut new = StyledStr::new(); - new.stylize(Style::Literal.as_style(), "-- "); + let _ = write!(new, "{}--{} ", literal.render(), literal.render_reset()); new.push_styled(&styled); required_positionals[index] = Some(new); } @@ -290,9 +337,9 @@ impl<'cmd> Usage<'cmd> { let mut styled; if pos.is_last_set() { styled = StyledStr::new(); - styled.stylize(Style::Literal.as_style(), "[-- "); + let _ = write!(styled, "{}[--{} ", literal.render(), literal.render_reset()); styled.push_styled(&pos.stylized(Some(true))); - styled.stylize(Style::Literal.as_style(), "]"); + let _ = write!(styled, "{}]{}", literal.render(), literal.render_reset()); } else { styled = pos.stylized(Some(false)); } From 3cb90b0b22daa82d256bbfb05aa404126989f5c9 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 13 Apr 2023 00:05:08 -0500 Subject: [PATCH 05/13] refactor(help): Use a stylesheet --- clap_builder/src/builder/arg.rs | 17 ++--- clap_builder/src/builder/command.rs | 7 ++- clap_builder/src/builder/mod.rs | 3 +- clap_builder/src/builder/styled_str.rs | 80 ++++++++++++++++-------- clap_builder/src/error/format.rs | 49 ++++++++------- clap_builder/src/error/mod.rs | 40 ++++++++---- clap_builder/src/macros.rs | 3 +- clap_builder/src/output/help_template.rs | 19 +++--- clap_builder/src/output/usage.rs | 24 +++---- 9 files changed, 153 insertions(+), 89 deletions(-) diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index 8ca7512529f..73ddc271b1b 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -16,8 +16,8 @@ use crate::builder::IntoResettable; use crate::builder::OsStr; use crate::builder::PossibleValue; use crate::builder::Str; -use crate::builder::Style; use crate::builder::StyledStr; +use crate::builder::Styles; use crate::builder::ValueRange; use crate::util::AnyValueId; use crate::ArgAction; @@ -4272,9 +4272,9 @@ impl Arg { } } - pub(crate) fn stylized(&self, required: Option) -> StyledStr { + pub(crate) fn stylized(&self, styles: &Styles, required: Option) -> StyledStr { use std::fmt::Write as _; - let literal = Style::Literal.as_style(); + let literal = &styles.literal; let mut styled = StyledStr::new(); // Write the name such --long or -l @@ -4288,14 +4288,14 @@ impl Arg { } else if let Some(s) = self.get_short() { let _ = write!(styled, "{}-{s}{}", literal.render(), literal.render_reset()); } - styled.push_styled(&self.stylize_arg_suffix(required)); + styled.push_styled(&self.stylize_arg_suffix(styles, required)); styled } - pub(crate) fn stylize_arg_suffix(&self, required: Option) -> StyledStr { + pub(crate) fn stylize_arg_suffix(&self, styles: &Styles, required: Option) -> StyledStr { use std::fmt::Write as _; - let literal = Style::Literal.as_style(); - let placeholder = Style::Placeholder.as_style(); + let literal = &styles.literal; + let placeholder = &styles.placeholder; let mut styled = StyledStr::new(); let mut need_closing_bracket = false; @@ -4427,7 +4427,8 @@ impl Eq for Arg {} impl Display for Arg { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.stylized(None).fmt(f) + let plain = Styles::plain(); + self.stylized(&plain, None).fmt(f) } } diff --git a/clap_builder/src/builder/command.rs b/clap_builder/src/builder/command.rs index 590a2692900..37bed473a0d 100644 --- a/clap_builder/src/builder/command.rs +++ b/clap_builder/src/builder/command.rs @@ -17,6 +17,7 @@ use crate::builder::IntoResettable; use crate::builder::PossibleValue; use crate::builder::Str; use crate::builder::StyledStr; +use crate::builder::Styles; use crate::builder::{Arg, ArgGroup, ArgPredicate}; use crate::error::ErrorKind; use crate::error::Result as ClapResult; @@ -3338,6 +3339,10 @@ impl Command { } } + pub(crate) fn get_styles(&self) -> &Styles { + self.app_ext.get().unwrap_or_default() + } + /// Iterate through the set of subcommands, getting a reference to each. #[inline] pub fn get_subcommands(&self) -> impl Iterator { @@ -4649,7 +4654,7 @@ impl fmt::Display for Command { } } -trait AppTag: crate::builder::ext::Extension {} +pub(crate) trait AppTag: crate::builder::ext::Extension {} #[derive(Default, Copy, Clone, Debug)] struct TermWidth(usize); diff --git a/clap_builder/src/builder/mod.rs b/clap_builder/src/builder/mod.rs index f356e7259a4..4c65e9aaa8d 100644 --- a/clap_builder/src/builder/mod.rs +++ b/clap_builder/src/builder/mod.rs @@ -59,4 +59,5 @@ pub use value_parser::_AnonymousValueParser; pub(crate) use self::str::Inner as StrInner; pub(crate) use action::CountType; pub(crate) use arg_settings::{ArgFlags, ArgSettings}; -pub(crate) use styled_str::Style; +pub(crate) use command::AppTag; +pub(crate) use styled_str::Styles; diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index 3958be6c13c..ba07ae64828 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -198,39 +198,67 @@ impl std::fmt::Display for StyledStr { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum Style { +#[derive(Clone, Debug)] +#[non_exhaustive] +pub(crate) struct Styles { + pub header: anstyle::Style, + pub literal: anstyle::Style, + pub placeholder: anstyle::Style, + pub good: anstyle::Style, + pub warning: anstyle::Style, + pub error: anstyle::Style, #[allow(dead_code)] - Header, - #[allow(dead_code)] - Literal, - #[allow(dead_code)] - Placeholder, - #[allow(dead_code)] - Good, - #[allow(dead_code)] - Warning, - #[allow(dead_code)] - Error, - #[allow(dead_code)] - Hint, + pub hint: anstyle::Style, } -impl Style { - pub(crate) fn as_style(&self) -> anstyle::Style { +impl Styles { + pub const fn plain() -> Self { + Self { + header: anstyle::Style::new(), + literal: anstyle::Style::new(), + placeholder: anstyle::Style::new(), + good: anstyle::Style::new(), + warning: anstyle::Style::new(), + error: anstyle::Style::new(), + hint: anstyle::Style::new(), + } + } + + pub const fn styled() -> Self { #[cfg(feature = "color")] - match self { - Style::Header => (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).into(), - Style::Literal => anstyle::Effects::BOLD.into(), - Style::Placeholder => anstyle::Style::default(), - Style::Good => anstyle::AnsiColor::Green.on_default(), - Style::Warning => anstyle::AnsiColor::Yellow.on_default(), - Style::Error => anstyle::AnsiColor::Red.on_default() | anstyle::Effects::BOLD, - Style::Hint => anstyle::Effects::DIMMED.into(), + { + Self { + header: anstyle::Style::new().bold().underline(), + literal: anstyle::Style::new().bold(), + placeholder: anstyle::Style::new(), + good: anstyle::Style::new() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))), + warning: anstyle::Style::new() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))), + error: anstyle::Style::new() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))) + .bold(), + hint: anstyle::Style::new().dimmed(), + } } #[cfg(not(feature = "color"))] { - anstyle::Style::new() + Self::plain() } } } + +impl super::AppTag for Styles {} + +impl Default for Styles { + fn default() -> Self { + Self::styled() + } +} + +impl Default for &'_ Styles { + fn default() -> Self { + const STYLES: Styles = Styles::styled(); + &STYLES + } +} diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs index 0928d2078b9..a121f357847 100644 --- a/clap_builder/src/error/format.rs +++ b/clap_builder/src/error/format.rs @@ -4,8 +4,8 @@ #![cfg_attr(not(feature = "error-context"), allow(unused_imports))] use crate::builder::Command; -use crate::builder::Style; use crate::builder::StyledStr; +use crate::builder::Styles; #[cfg(feature = "error-context")] use crate::error::ContextKind; #[cfg(feature = "error-context")] @@ -31,9 +31,10 @@ pub struct KindFormatter; impl ErrorFormatter for KindFormatter { fn format_error(error: &crate::error::Error) -> StyledStr { use std::fmt::Write as _; + let styles = &error.inner.styles; let mut styled = StyledStr::new(); - start_error(&mut styled); + start_error(&mut styled, styles); if let Some(msg) = error.kind().as_str() { styled.push_str(msg); } else if let Some(source) = error.inner.source.as_ref() { @@ -57,12 +58,13 @@ pub struct RichFormatter; impl ErrorFormatter for RichFormatter { fn format_error(error: &crate::error::Error) -> StyledStr { use std::fmt::Write as _; - let good = Style::Good.as_style(); + let styles = &error.inner.styles; + let good = &styles.good; let mut styled = StyledStr::new(); - start_error(&mut styled); + start_error(&mut styled, styles); - if !write_dynamic_context(error, &mut styled) { + if !write_dynamic_context(error, &mut styled, styles) { if let Some(msg) = error.kind().as_str() { styled.push_str(msg); } else if let Some(source) = error.inner.source.as_ref() { @@ -79,7 +81,7 @@ impl ErrorFormatter for RichFormatter { styled.push_str("\n"); suggested = true; } - did_you_mean(&mut styled, "subcommand", valid); + did_you_mean(&mut styled, styles, "subcommand", valid); } if let Some(valid) = error.get(ContextKind::SuggestedArg) { styled.push_str("\n"); @@ -87,7 +89,7 @@ impl ErrorFormatter for RichFormatter { styled.push_str("\n"); suggested = true; } - did_you_mean(&mut styled, "argument", valid); + did_you_mean(&mut styled, styles, "argument", valid); } if let Some(valid) = error.get(ContextKind::SuggestedValue) { styled.push_str("\n"); @@ -95,7 +97,7 @@ impl ErrorFormatter for RichFormatter { styled.push_str("\n"); suggested = true; } - did_you_mean(&mut styled, "value", valid); + did_you_mean(&mut styled, styles, "value", valid); } let suggestions = error.get(ContextKind::Suggested); if let Some(ContextValue::StyledStrs(suggestions)) = suggestions { @@ -118,25 +120,29 @@ impl ErrorFormatter for RichFormatter { put_usage(&mut styled, usage); } - try_help(&mut styled, error.inner.help_flag); + try_help(&mut styled, styles, error.inner.help_flag); styled } } -fn start_error(styled: &mut StyledStr) { +fn start_error(styled: &mut StyledStr, styles: &Styles) { use std::fmt::Write as _; - let error = Style::Error.as_style(); + let error = &styles.error; let _ = write!(styled, "{}error:{} ", error.render(), error.render_reset()); } #[must_use] #[cfg(feature = "error-context")] -fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> bool { +fn write_dynamic_context( + error: &crate::error::Error, + styled: &mut StyledStr, + styles: &Styles, +) -> bool { use std::fmt::Write as _; - let good = Style::Good.as_style(); - let warning = Style::Warning.as_style(); - let literal = Style::Literal.as_style(); + let good = styles.good; + let warning = styles.warning; + let literal = styles.literal; match error.kind() { ErrorKind::ArgumentConflict => { @@ -449,17 +455,18 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) -> pub(crate) fn format_error_message( message: &str, + styles: &Styles, cmd: Option<&Command>, usage: Option<&StyledStr>, ) -> StyledStr { let mut styled = StyledStr::new(); - start_error(&mut styled); + start_error(&mut styled, styles); styled.push_str(message); if let Some(usage) = usage { put_usage(&mut styled, usage); } if let Some(cmd) = cmd { - try_help(&mut styled, get_help_flag(cmd)); + try_help(&mut styled, styles, get_help_flag(cmd)); } styled } @@ -488,10 +495,10 @@ pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> { } } -fn try_help(styled: &mut StyledStr, help: Option<&str>) { +fn try_help(styled: &mut StyledStr, styles: &Styles, help: Option<&str>) { if let Some(help) = help { use std::fmt::Write as _; - let literal = Style::Literal.as_style(); + let literal = &styles.literal; let _ = write!( styled, "\n\nFor more information, try '{}{help}{}'.\n", @@ -504,9 +511,9 @@ fn try_help(styled: &mut StyledStr, help: Option<&str>) { } #[cfg(feature = "error-context")] -fn did_you_mean(styled: &mut StyledStr, context: &str, valid: &ContextValue) { +fn did_you_mean(styled: &mut StyledStr, styles: &Styles, context: &str, valid: &ContextValue) { use std::fmt::Write as _; - let good = Style::Good.as_style(); + let good = &styles.good; let _ = write!(styled, "{TAB}{}tip:{}", good.render(), good.render_reset()); if let ContextValue::String(valid) = valid { diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index bb612d2d8c8..a43ce0ff64f 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -17,8 +17,8 @@ use std::{ }; // Internal -use crate::builder::Style; use crate::builder::StyledStr; +use crate::builder::Styles; use crate::output::fmt::Colorizer; use crate::output::fmt::Stream; use crate::parser::features::suggestions; @@ -70,6 +70,7 @@ struct ErrorInner { message: Option, source: Option>, help_flag: Option<&'static str>, + styles: Styles, color_when: ColorChoice, color_help_when: ColorChoice, backtrace: Option, @@ -133,6 +134,7 @@ impl Error { message: None, source: None, help_flag: None, + styles: Styles::plain(), color_when: ColorChoice::Never, color_help_when: ColorChoice::Never, backtrace: Backtrace::new(), @@ -145,7 +147,8 @@ impl Error { /// /// Generally, this is used with [`Error::new`] pub fn with_cmd(self, cmd: &Command) -> Self { - self.set_color(cmd.get_color()) + self.set_styles(cmd.get_styles().clone()) + .set_color(cmd.get_color()) .set_colored_help(cmd.color_help()) .set_help_flag(format::get_help_flag(cmd)) } @@ -296,6 +299,11 @@ impl Error { self } + pub(crate) fn set_styles(mut self, styles: Styles) -> Self { + self.inner.styles = styles; + self + } + pub(crate) fn set_color(mut self, color_when: ColorChoice) -> Self { self.inner.color_when = color_when; self @@ -436,8 +444,9 @@ impl Error { usage: Option, ) -> Self { use std::fmt::Write as _; - let warning = Style::Warning.as_style(); - let good = Style::Good.as_style(); + let styles = cmd.get_styles(); + let warning = &styles.warning; + let good = &styles.good; let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -667,8 +676,9 @@ impl Error { usage: Option, ) -> Self { use std::fmt::Write as _; - let warning = Style::Warning.as_style(); - let good = Style::Good.as_style(); + let styles = cmd.get_styles(); + let warning = &styles.warning; + let good = &styles.good; let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -729,8 +739,9 @@ impl Error { usage: Option, ) -> Self { use std::fmt::Write as _; - let warning = Style::Warning.as_style(); - let good = Style::Good.as_style(); + let styles = cmd.get_styles(); + let warning = &styles.warning; + let good = &styles.good; let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -763,7 +774,7 @@ impl Error { fn formatted(&self) -> Cow<'_, StyledStr> { if let Some(message) = self.inner.message.as_ref() { - message.formatted() + message.formatted(&self.inner.styles) } else { let styled = F::format_error(self); Cow::Owned(styled) @@ -822,7 +833,12 @@ impl Message { let mut message = String::new(); std::mem::swap(s, &mut message); - let styled = format::format_error_message(&message, Some(cmd), usage.as_ref()); + let styled = format::format_error_message( + &message, + cmd.get_styles(), + Some(cmd), + usage.as_ref(), + ); *self = Self::Formatted(styled); } @@ -830,10 +846,10 @@ impl Message { } } - fn formatted(&self) -> Cow { + fn formatted(&self, styles: &Styles) -> Cow { match self { Message::Raw(s) => { - let styled = format::format_error_message(s, None, None); + let styled = format::format_error_message(s, styles, None, None); Cow::Owned(styled) } diff --git a/clap_builder/src/macros.rs b/clap_builder/src/macros.rs index a45be83c9b6..7f001a93f6d 100644 --- a/clap_builder/src/macros.rs +++ b/clap_builder/src/macros.rs @@ -629,7 +629,8 @@ macro_rules! impl_settings { macro_rules! debug { ($($arg:tt)*) => ({ use std::fmt::Write as _; - let hint = $crate::builder::Style::Hint.as_style(); + let styles = $crate::builder::Styles::styled(); + let hint = &styles.hint; let module_path = module_path!(); let body = format!($($arg)*); diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index 8a09e5f8af4..2668edd4495 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -6,8 +6,8 @@ use std::usize; // Internal use crate::builder::PossibleValue; use crate::builder::Str; -use crate::builder::Style; use crate::builder::StyledStr; +use crate::builder::Styles; use crate::builder::{Arg, Command}; use crate::output::display_width; use crate::output::wrap; @@ -75,6 +75,7 @@ const DEFAULT_NO_ARGS_TEMPLATE: &str = "\ pub(crate) struct HelpTemplate<'cmd, 'writer> { writer: &'writer mut StyledStr, cmd: &'cmd Command, + styles: &'cmd Styles, usage: &'cmd Usage<'cmd>, next_line_help: bool, term_w: usize, @@ -113,6 +114,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { HelpTemplate { writer, cmd, + styles: cmd.get_styles(), usage, next_line_help, term_w, @@ -128,7 +130,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { pub(crate) fn write_templated_help(&mut self, template: &str) { debug!("HelpTemplate::write_templated_help"); use std::fmt::Write as _; - let header = Style::Header.as_style(); + let header = &self.styles.header; let mut parts = template.split('{'); if let Some(first) = parts.next() { @@ -340,7 +342,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { pub(crate) fn write_all_args(&mut self) { debug!("HelpTemplate::write_all_args"); use std::fmt::Write as _; - let header = Style::Header.as_style(); + let header = &self.styles.header; let pos = self .cmd @@ -492,7 +494,8 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self.writer.push_str(TAB); self.short(arg); self.long(arg); - self.writer.push_styled(&arg.stylize_arg_suffix(None)); + self.writer + .push_styled(&arg.stylize_arg_suffix(&self.styles, None)); self.align_to_about(arg, next_line_help, longest); let about = if self.use_long { @@ -512,7 +515,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { fn short(&mut self, arg: &Arg) { debug!("HelpTemplate::short"); use std::fmt::Write as _; - let literal = Style::Literal.as_style(); + let literal = &self.styles.literal; if let Some(s) = arg.get_short() { let _ = write!( @@ -530,7 +533,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { fn long(&mut self, arg: &Arg) { debug!("HelpTemplate::long"); use std::fmt::Write as _; - let literal = Style::Literal.as_style(); + let literal = &self.styles.literal; if let Some(long) = arg.get_long() { if arg.get_short().is_some() { @@ -601,7 +604,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { ) { debug!("HelpTemplate::help"); use std::fmt::Write as _; - let literal = Style::Literal.as_style(); + let literal = &self.styles.literal; // Is help on next line, if so then indent if next_line_help { @@ -865,7 +868,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { fn write_subcommands(&mut self, cmd: &Command) { debug!("HelpTemplate::write_subcommands"); use std::fmt::Write as _; - let literal = Style::Literal.as_style(); + let literal = &self.styles.literal; // The shortest an arg can legally be is 2 (i.e. '-x') let mut longest = 2; diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index ccac830919b..ffef25d494c 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -4,8 +4,8 @@ #![cfg_attr(not(feature = "usage"), allow(dead_code))] // Internal -use crate::builder::Style; use crate::builder::StyledStr; +use crate::builder::Styles; use crate::builder::{ArgPredicate, Command}; use crate::parser::ArgMatcher; use crate::util::ChildGraph; @@ -16,6 +16,7 @@ static DEFAULT_SUB_VALUE_NAME: &str = "COMMAND"; pub(crate) struct Usage<'cmd> { cmd: &'cmd Command, + styles: &'cmd Styles, required: Option<&'cmd ChildGraph>, } @@ -23,6 +24,7 @@ impl<'cmd> Usage<'cmd> { pub(crate) fn new(cmd: &'cmd Command) -> Self { Usage { cmd, + styles: cmd.get_styles(), required: None, } } @@ -39,7 +41,7 @@ impl<'cmd> Usage<'cmd> { let usage = some!(self.create_usage_no_title(used)); use std::fmt::Write as _; - let header = Style::Header.as_style(); + let header = &self.styles.header; let mut styled = StyledStr::new(); let _ = write!( styled, @@ -80,8 +82,8 @@ impl<'cmd> Usage<'cmd> { fn create_help_usage(&self, incl_reqs: bool) -> StyledStr { debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs); use std::fmt::Write as _; - let literal = Style::Literal.as_style(); - let placeholder = Style::Placeholder.as_style(); + let literal = &self.styles.literal; + let placeholder = &self.styles.placeholder; let mut styled = StyledStr::new(); let name = self @@ -165,8 +167,8 @@ impl<'cmd> Usage<'cmd> { fn create_smart_usage(&self, used: &[Id]) -> StyledStr { debug!("Usage::create_smart_usage"); use std::fmt::Write; - let literal = Style::Literal.as_style(); - let placeholder = Style::Placeholder.as_style(); + let literal = &self.styles.literal; + let placeholder = &self.styles.placeholder; let mut styled = StyledStr::new(); let bin_name = self @@ -245,7 +247,7 @@ impl<'cmd> Usage<'cmd> { pub(crate) fn get_args(&self, incls: &[Id], force_optional: bool) -> Vec { debug!("Usage::get_args: incls={:?}", incls,); use std::fmt::Write as _; - let literal = Style::Literal.as_style(); + let literal = &self.styles.literal; let required_owned; let required = if let Some(required) = self.required { @@ -297,7 +299,7 @@ impl<'cmd> Usage<'cmd> { continue; } - let stylized = arg.stylized(Some(!force_optional)); + let stylized = arg.stylized(&self.styles, Some(!force_optional)); if let Some(index) = arg.get_index() { let new_len = index + 1; if required_positionals.len() < new_len { @@ -338,10 +340,10 @@ impl<'cmd> Usage<'cmd> { if pos.is_last_set() { styled = StyledStr::new(); let _ = write!(styled, "{}[--{} ", literal.render(), literal.render_reset()); - styled.push_styled(&pos.stylized(Some(true))); + styled.push_styled(&pos.stylized(&self.styles, Some(true))); let _ = write!(styled, "{}]{}", literal.render(), literal.render_reset()); } else { - styled = pos.stylized(Some(false)); + styled = pos.stylized(&self.styles, Some(false)); } required_positionals[index] = Some(styled); } @@ -461,7 +463,7 @@ impl<'cmd> Usage<'cmd> { continue; } - let stylized = arg.stylized(Some(true)); + let stylized = arg.stylized(&self.styles, Some(true)); if let Some(index) = arg.get_index() { if !arg.is_last_set() || incl_last { let new_len = index + 1; From 015f88b21a43210b625fbd42dd7140e5c80121ac Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 14:54:16 -0500 Subject: [PATCH 06/13] feat(help): Allow customizing terminal styling For now, this is behind the `unstable-styles` feature as we verify this is what we want for #3224 --- Cargo.toml | 1 + Makefile | 2 +- clap_builder/Cargo.toml | 3 ++- clap_builder/src/builder/command.rs | 25 +++++++++++++++++++++++++ clap_builder/src/builder/mod.rs | 3 +++ clap_builder/src/builder/styled_str.rs | 13 ++++++++++++- src/_features.rs | 1 + 7 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 80cb6b0fed3..f148ffa737a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ string = ["clap_builder/string"] # Allow runtime generated strings # In-work features unstable-v5 = ["clap_builder/unstable-v5", "clap_derive?/unstable-v5", "deprecated"] +unstable-styles = ["clap_builder/unstable-styles"] [lib] bench = false diff --git a/Makefile b/Makefile index 6f54f577464..1c203cedbc9 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ _FEATURES = minimal default wasm full debug release _FEATURES_minimal = --no-default-features --features "std" _FEATURES_default = _FEATURES_wasm = --no-default-features --features "std help usage error-context suggestions" --features "deprecated derive cargo env unicode string" -_FEATURES_full = --features "deprecated derive cargo env unicode string wrap_help" +_FEATURES_full = --features "deprecated derive cargo env unicode string wrap_help unstable-styles" _FEATURES_next = ${_FEATURES_full} --features unstable-v5 _FEATURES_debug = ${_FEATURES_full} --features debug --features clap_complete/debug _FEATURES_release = ${_FEATURES_full} --release diff --git a/clap_builder/Cargo.toml b/clap_builder/Cargo.toml index 4ab9f08b5ae..91c9833e1b4 100644 --- a/clap_builder/Cargo.toml +++ b/clap_builder/Cargo.toml @@ -32,7 +32,7 @@ tag-name = "v{{version}}" [features] default = ["std", "color", "help", "usage", "error-context", "suggestions"] debug = ["dep:backtrace"] # Enables debug messages -unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string"] # for docs.rs +unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string", "unstable-styles"] # for docs.rs # Used in default std = ["anstyle/std"] # support for no_std in a backwards-compatible way @@ -52,6 +52,7 @@ string = [] # Allow runtime generated strings # In-work features unstable-v5 = ["deprecated"] +unstable-styles = ["color"] [lib] bench = false diff --git a/clap_builder/src/builder/command.rs b/clap_builder/src/builder/command.rs index 37bed473a0d..abd743382e6 100644 --- a/clap_builder/src/builder/command.rs +++ b/clap_builder/src/builder/command.rs @@ -1081,6 +1081,31 @@ impl Command { } } + /// Sets when to color output. + /// + /// **NOTE:** This choice is propagated to all child subcommands. + /// + /// **NOTE:** Default behaviour is [`ColorChoice::Auto`]. + /// + /// # Examples + /// + /// ```no_run + /// # use clap_builder as clap; + /// # use clap::{Command, ColorChoice}; + /// Command::new("myprog") + /// .color(ColorChoice::Never) + /// .get_matches(); + /// ``` + /// [`ColorChoice::Auto`]: crate::ColorChoice::Auto + #[cfg(feature = "color")] + #[inline] + #[must_use] + #[cfg(feature = "unstable-styles")] + pub fn styles(mut self, styles: Styles) -> Self { + self.app_ext.set(styles); + self + } + /// Sets the terminal width at which to wrap help messages. /// /// Using `0` will ignore terminal widths and use source formatting. diff --git a/clap_builder/src/builder/mod.rs b/clap_builder/src/builder/mod.rs index 4c65e9aaa8d..495c8458762 100644 --- a/clap_builder/src/builder/mod.rs +++ b/clap_builder/src/builder/mod.rs @@ -35,6 +35,8 @@ pub use range::ValueRange; pub use resettable::IntoResettable; pub use resettable::Resettable; pub use styled_str::StyledStr; +#[cfg(feature = "unstable-styles")] +pub use styled_str::Styles; pub use value_hint::ValueHint; pub use value_parser::_AutoValueParser; pub use value_parser::via_prelude; @@ -60,4 +62,5 @@ pub(crate) use self::str::Inner as StrInner; pub(crate) use action::CountType; pub(crate) use arg_settings::{ArgFlags, ArgSettings}; pub(crate) use command::AppTag; +#[cfg(not(feature = "unstable-styles"))] pub(crate) use styled_str::Styles; diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index ba07ae64828..4d277815801 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -198,20 +198,30 @@ impl std::fmt::Display for StyledStr { } } +/// Terminal styling definitions #[derive(Clone, Debug)] #[non_exhaustive] -pub(crate) struct Styles { +#[allow(missing_copy_implementations)] // Large enough type that I want an explicit `clone()` for now +pub struct Styles { + /// Heading style, e.g. [`help_heading`][crate::Arg::help_heading] pub header: anstyle::Style, + /// Literal command-line syntax, like `--help` pub literal: anstyle::Style, + /// Descriptions within command-line syntax, like [`value_name`][crate::Arg::value_name] pub placeholder: anstyle::Style, + /// Suggested usage pub good: anstyle::Style, + /// Invalid usage pub warning: anstyle::Style, + /// Error heading pub error: anstyle::Style, + /// Extra details #[allow(dead_code)] pub hint: anstyle::Style, } impl Styles { + /// No terminal styling pub const fn plain() -> Self { Self { header: anstyle::Style::new(), @@ -224,6 +234,7 @@ impl Styles { } } + /// Default terminal styling pub const fn styled() -> Self { #[cfg(feature = "color")] { diff --git a/src/_features.rs b/src/_features.rs index b47ee259c28..27b567c9842 100644 --- a/src/_features.rs +++ b/src/_features.rs @@ -26,3 +26,4 @@ //! **Warning:** These may contain breaking changes between minor releases. //! //! * **unstable-v5**: Preview features which will be stable on the v5.0 release +//! * **unstable-unstable-styles**: Custom theming support for clap From 7cf08e63cd75d6b336493d5828260b7827154e10 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 14:56:36 -0500 Subject: [PATCH 07/13] refactor(help): Remove unused hint --- clap_builder/src/builder/styled_str.rs | 5 ----- clap_builder/src/macros.rs | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index 4d277815801..e9b56fb4c17 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -215,9 +215,6 @@ pub struct Styles { pub warning: anstyle::Style, /// Error heading pub error: anstyle::Style, - /// Extra details - #[allow(dead_code)] - pub hint: anstyle::Style, } impl Styles { @@ -230,7 +227,6 @@ impl Styles { good: anstyle::Style::new(), warning: anstyle::Style::new(), error: anstyle::Style::new(), - hint: anstyle::Style::new(), } } @@ -249,7 +245,6 @@ impl Styles { error: anstyle::Style::new() .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))) .bold(), - hint: anstyle::Style::new().dimmed(), } } #[cfg(not(feature = "color"))] diff --git a/clap_builder/src/macros.rs b/clap_builder/src/macros.rs index 7f001a93f6d..59135e21252 100644 --- a/clap_builder/src/macros.rs +++ b/clap_builder/src/macros.rs @@ -629,8 +629,7 @@ macro_rules! impl_settings { macro_rules! debug { ($($arg:tt)*) => ({ use std::fmt::Write as _; - let styles = $crate::builder::Styles::styled(); - let hint = &styles.hint; + let hint = anstyle::Style::new().dimmed(); let module_path = module_path!(); let body = format!($($arg)*); From 8fd0a93bdc2605d0d1fca59fe7d15f04fda2eaea Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 15:00:31 -0500 Subject: [PATCH 08/13] refactor(help): Clarify style meaning --- clap_builder/src/builder/styled_str.rs | 34 ++++---- clap_builder/src/error/format.rs | 116 ++++++++++++++----------- clap_builder/src/error/mod.rs | 40 ++++----- 3 files changed, 102 insertions(+), 88 deletions(-) diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index e9b56fb4c17..13a81bec87f 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -203,18 +203,18 @@ impl std::fmt::Display for StyledStr { #[non_exhaustive] #[allow(missing_copy_implementations)] // Large enough type that I want an explicit `clone()` for now pub struct Styles { - /// Heading style, e.g. [`help_heading`][crate::Arg::help_heading] + /// General Heading style, e.g. [`help_heading`][crate::Arg::help_heading] pub header: anstyle::Style, - /// Literal command-line syntax, like `--help` - pub literal: anstyle::Style, - /// Descriptions within command-line syntax, like [`value_name`][crate::Arg::value_name] - pub placeholder: anstyle::Style, - /// Suggested usage - pub good: anstyle::Style, - /// Invalid usage - pub warning: anstyle::Style, /// Error heading pub error: anstyle::Style, + /// Literal command-line syntax, e.g. `--help` + pub literal: anstyle::Style, + /// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name] + pub placeholder: anstyle::Style, + /// Highlight suggested usage + pub valid: anstyle::Style, + /// Highlight invalid usage + pub invalid: anstyle::Style, } impl Styles { @@ -222,11 +222,11 @@ impl Styles { pub const fn plain() -> Self { Self { header: anstyle::Style::new(), + error: anstyle::Style::new(), literal: anstyle::Style::new(), placeholder: anstyle::Style::new(), - good: anstyle::Style::new(), - warning: anstyle::Style::new(), - error: anstyle::Style::new(), + valid: anstyle::Style::new(), + invalid: anstyle::Style::new(), } } @@ -236,15 +236,15 @@ impl Styles { { Self { header: anstyle::Style::new().bold().underline(), + error: anstyle::Style::new() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))) + .bold(), literal: anstyle::Style::new().bold(), placeholder: anstyle::Style::new(), - good: anstyle::Style::new() + valid: anstyle::Style::new() .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))), - warning: anstyle::Style::new() + invalid: anstyle::Style::new() .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))), - error: anstyle::Style::new() - .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))) - .bold(), } } #[cfg(not(feature = "color"))] diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs index a121f357847..34356788265 100644 --- a/clap_builder/src/error/format.rs +++ b/clap_builder/src/error/format.rs @@ -59,7 +59,7 @@ impl ErrorFormatter for RichFormatter { fn format_error(error: &crate::error::Error) -> StyledStr { use std::fmt::Write as _; let styles = &error.inner.styles; - let good = &styles.good; + let valid = &styles.valid; let mut styled = StyledStr::new(); start_error(&mut styled, styles); @@ -108,8 +108,8 @@ impl ErrorFormatter for RichFormatter { let _ = write!( styled, "\n{TAB}{}tip:{} ", - good.render(), - good.render_reset() + valid.render(), + valid.render_reset() ); styled.push_styled(suggestion); } @@ -140,8 +140,8 @@ fn write_dynamic_context( styles: &Styles, ) -> bool { use std::fmt::Write as _; - let good = styles.good; - let warning = styles.warning; + let valid = styles.valid; + let invalid = styles.invalid; let literal = styles.literal; match error.kind() { @@ -155,15 +155,15 @@ fn write_dynamic_context( let _ = write!( styled, "the argument '{}{invalid_arg}{}' cannot be used multiple times", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); } else { let _ = write!( styled, "the argument '{}{invalid_arg}{}' cannot be used with", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); match prior_arg { @@ -173,8 +173,8 @@ fn write_dynamic_context( let _ = write!( styled, "\n{TAB}{}{v}{}", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); } } @@ -182,8 +182,8 @@ fn write_dynamic_context( let _ = write!( styled, " '{}{value}{}'", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); } _ => { @@ -202,8 +202,8 @@ fn write_dynamic_context( let _ = write!( styled, "equal sign is needed when assigning values to '{}{invalid_arg}{}'", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); true } else { @@ -222,15 +222,15 @@ fn write_dynamic_context( let _ = write!( styled, "a value is required for '{}{invalid_arg}{}' but none was supplied", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); } else { let _ = write!( styled, "invalid value '{}{invalid_value}{}' for '{}{invalid_arg}{}'", - warning.render(), - warning.render_reset(), + invalid.render(), + invalid.render_reset(), literal.render(), literal.render_reset() ); @@ -245,17 +245,17 @@ fn write_dynamic_context( let _ = write!( styled, "{}{}{}, ", - good.render(), + valid.render(), Escape(v), - good.render_reset() + valid.render_reset() ); } let _ = write!( styled, "{}{}{}", - good.render(), + valid.render(), Escape(last), - good.render_reset() + valid.render_reset() ); } styled.push_str("]"); @@ -272,8 +272,8 @@ fn write_dynamic_context( let _ = write!( styled, "unrecognized subcommand '{}{invalid_sub}{}'", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); true } else { @@ -285,7 +285,12 @@ fn write_dynamic_context( if let Some(ContextValue::Strings(invalid_arg)) = invalid_arg { styled.push_str("the following required arguments were not provided:"); for v in invalid_arg { - let _ = write!(styled, "\n{TAB}{}{v}{}", good.render(), good.render_reset()); + let _ = write!( + styled, + "\n{TAB}{}{v}{}", + valid.render(), + valid.render_reset() + ); } true } else { @@ -298,8 +303,8 @@ fn write_dynamic_context( let _ = write!( styled, "'{}{invalid_sub}{}' requires a subcommand but one was not provided", - warning.render(), - warning.render_reset() + invalid.render(), + invalid.render_reset() ); let possible_values = error.get(ContextKind::ValidSubcommand); @@ -311,17 +316,17 @@ fn write_dynamic_context( let _ = write!( styled, "{}{}{}, ", - good.render(), + valid.render(), Escape(v), - good.render_reset() + valid.render_reset() ); } let _ = write!( styled, "{}{}{}", - good.render(), + valid.render(), Escape(last), - good.render_reset() + valid.render_reset() ); } styled.push_str("]"); @@ -345,8 +350,8 @@ fn write_dynamic_context( let _ = write!( styled, "unexpected value '{}{invalid_value}{}' for '{}{invalid_arg}{}' found; no more were expected", - warning.render(), - warning.render_reset(), + invalid.render(), + invalid.render_reset(), literal.render(), literal.render_reset(), ); @@ -369,12 +374,12 @@ fn write_dynamic_context( let _ = write!( styled, "{}{min_values}{} more values required by '{}{invalid_arg}{}'; only {}{actual_num_values}{}{were_provided}", - good.render(), - good.render_reset(), + valid.render(), + valid.render_reset(), literal.render(), literal.render_reset(), - warning.render(), - warning.render_reset(), + invalid.render(), + invalid.render_reset(), ); true } else { @@ -392,8 +397,8 @@ fn write_dynamic_context( let _ = write!( styled, "invalid value '{}{invalid_value}{}' for '{}{invalid_arg}{}'", - warning.render(), - warning.render_reset(), + invalid.render(), + invalid.render_reset(), literal.render(), literal.render_reset(), ); @@ -419,12 +424,12 @@ fn write_dynamic_context( let _ = write!( styled, "{}{num_values}{} values required for '{}{invalid_arg}{}' but {}{actual_num_values}{}{were_provided}", - good.render(), - good.render_reset(), + valid.render(), + valid.render_reset(), literal.render(), literal.render_reset(), - warning.render(), - warning.render_reset(), + invalid.render(), + invalid.render_reset(), ); true } else { @@ -437,8 +442,8 @@ fn write_dynamic_context( let _ = write!( styled, "unexpected argument '{}{invalid_arg}{}' found", - warning.render(), - warning.render_reset(), + invalid.render(), + invalid.render_reset(), ); true } else { @@ -513,15 +518,19 @@ fn try_help(styled: &mut StyledStr, styles: &Styles, help: Option<&str>) { #[cfg(feature = "error-context")] fn did_you_mean(styled: &mut StyledStr, styles: &Styles, context: &str, valid: &ContextValue) { use std::fmt::Write as _; - let good = &styles.good; - let _ = write!(styled, "{TAB}{}tip:{}", good.render(), good.render_reset()); + let _ = write!( + styled, + "{TAB}{}tip:{}", + styles.valid.render(), + styles.valid.render_reset() + ); if let ContextValue::String(valid) = valid { let _ = write!( styled, " a similar {context} exists: '{}{valid}{}'", - good.render(), - good.render_reset() + styles.valid.render(), + styles.valid.render_reset() ); } else if let ContextValue::Strings(valid) = valid { if valid.len() == 1 { @@ -533,7 +542,12 @@ fn did_you_mean(styled: &mut StyledStr, styles: &Styles, context: &str, valid: & if i != 0 { styled.push_str(", "); } - let _ = write!(styled, "'{}{valid}{}'", good.render(), good.render_reset()); + let _ = write!( + styled, + "'{}{valid}{}'", + styles.valid.render(), + styles.valid.render_reset() + ); } } } diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index a43ce0ff64f..01d379b27ba 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -445,8 +445,8 @@ impl Error { ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); - let warning = &styles.warning; - let good = &styles.good; + let invalid = &styles.invalid; + let valid = &styles.valid; let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -455,10 +455,10 @@ impl Error { let _ = write!( styled_suggestion, "to pass '{}{subcmd}{}' as a value, use '{}{name} -- {subcmd}{}'", - warning.render(), - warning.render_reset(), - good.render(), - good.render_reset() + invalid.render(), + invalid.render_reset(), + valid.render(), + valid.render_reset() ); err = err.extend_context_unchecked([ @@ -677,8 +677,8 @@ impl Error { ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); - let warning = &styles.warning; - let good = &styles.good; + let invalid = &styles.invalid; + let valid = &styles.valid; let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -689,10 +689,10 @@ impl Error { let _ = write!( styled_suggestion, "to pass '{}{arg}{}' as a value, use '{}-- {arg}{}'", - warning.render(), - warning.render_reset(), - good.render(), - good.render_reset() + invalid.render(), + invalid.render_reset(), + valid.render(), + valid.render_reset() ); suggestions.push(styled_suggestion); } @@ -709,8 +709,8 @@ impl Error { let _ = write!( styled_suggestion, "'{}{sub} --{flag}{}' exists", - good.render(), - good.render_reset() + valid.render(), + valid.render_reset() ); suggestions.push(styled_suggestion); } @@ -740,8 +740,8 @@ impl Error { ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); - let warning = &styles.warning; - let good = &styles.good; + let invalid = &styles.invalid; + let valid = &styles.valid; let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -750,10 +750,10 @@ impl Error { let _ = write!( styled_suggestion, "subcommand '{}{arg}{}' exists; to use it, remove the '{}--{}' before it", - good.render(), - good.render_reset(), - warning.render(), - warning.render_reset() + valid.render(), + valid.render_reset(), + invalid.render(), + invalid.render_reset() ); err = err.extend_context_unchecked([ From e10e2ad249254b21847a0c626b9ba48ba30ad855 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 15:03:20 -0500 Subject: [PATCH 09/13] refactor(help): Split out separate usage style Because this doesn't have a full section after it, people might want to style it differently than other headers. --- clap_builder/src/builder/styled_str.rs | 4 ++++ clap_builder/src/output/help_template.rs | 5 ++--- clap_builder/src/output/usage.rs | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index 13a81bec87f..ed72b51693d 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -207,6 +207,8 @@ pub struct Styles { pub header: anstyle::Style, /// Error heading pub error: anstyle::Style, + /// Usage heading + pub usage: anstyle::Style, /// Literal command-line syntax, e.g. `--help` pub literal: anstyle::Style, /// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name] @@ -223,6 +225,7 @@ impl Styles { Self { header: anstyle::Style::new(), error: anstyle::Style::new(), + usage: anstyle::Style::new(), literal: anstyle::Style::new(), placeholder: anstyle::Style::new(), valid: anstyle::Style::new(), @@ -239,6 +242,7 @@ impl Styles { error: anstyle::Style::new() .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))) .bold(), + usage: anstyle::Style::new().bold().underline(), literal: anstyle::Style::new().bold(), placeholder: anstyle::Style::new(), valid: anstyle::Style::new() diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index 2668edd4495..703a1f47f5c 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -130,7 +130,6 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { pub(crate) fn write_templated_help(&mut self, template: &str) { debug!("HelpTemplate::write_templated_help"); use std::fmt::Write as _; - let header = &self.styles.header; let mut parts = template.split('{'); if let Some(first) = parts.next() { @@ -171,8 +170,8 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { let _ = write!( self.writer, "{}Usage:{}", - header.render(), - header.render_reset() + self.styles.usage.render(), + self.styles.usage.render_reset() ); } "usage" => { diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index ffef25d494c..1eb9139d718 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -41,13 +41,12 @@ impl<'cmd> Usage<'cmd> { let usage = some!(self.create_usage_no_title(used)); use std::fmt::Write as _; - let header = &self.styles.header; let mut styled = StyledStr::new(); let _ = write!( styled, "{}Usage:{} ", - header.render(), - header.render_reset() + self.styles.usage.render(), + self.styles.usage.render_reset() ); styled.push_styled(&usage); Some(styled) From 57974bed7b628eb5eacf08facf6fa19666464a71 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 15:16:15 -0500 Subject: [PATCH 10/13] refactor(help): Future proof styling We can add a new style and default it to what it was split out of without a breaking change. --- clap_builder/src/builder/arg.rs | 6 +- clap_builder/src/builder/styled_str.rs | 116 ++++++++++++++++++++--- clap_builder/src/error/format.rs | 24 ++--- clap_builder/src/error/mod.rs | 12 +-- clap_builder/src/output/help_template.rs | 14 +-- clap_builder/src/output/usage.rs | 14 +-- 6 files changed, 136 insertions(+), 50 deletions(-) diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index 73ddc271b1b..ce7e02d8712 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -4274,7 +4274,7 @@ impl Arg { pub(crate) fn stylized(&self, styles: &Styles, required: Option) -> StyledStr { use std::fmt::Write as _; - let literal = &styles.literal; + let literal = styles.get_literal(); let mut styled = StyledStr::new(); // Write the name such --long or -l @@ -4294,8 +4294,8 @@ impl Arg { pub(crate) fn stylize_arg_suffix(&self, styles: &Styles, required: Option) -> StyledStr { use std::fmt::Write as _; - let literal = &styles.literal; - let placeholder = &styles.placeholder; + let literal = styles.get_literal(); + let placeholder = styles.get_placeholder(); let mut styled = StyledStr::new(); let mut need_closing_bracket = false; diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index ed72b51693d..4a30df550bc 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -200,23 +200,15 @@ impl std::fmt::Display for StyledStr { /// Terminal styling definitions #[derive(Clone, Debug)] -#[non_exhaustive] #[allow(missing_copy_implementations)] // Large enough type that I want an explicit `clone()` for now pub struct Styles { - /// General Heading style, e.g. [`help_heading`][crate::Arg::help_heading] - pub header: anstyle::Style, - /// Error heading - pub error: anstyle::Style, - /// Usage heading - pub usage: anstyle::Style, - /// Literal command-line syntax, e.g. `--help` - pub literal: anstyle::Style, - /// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name] - pub placeholder: anstyle::Style, - /// Highlight suggested usage - pub valid: anstyle::Style, - /// Highlight invalid usage - pub invalid: anstyle::Style, + header: anstyle::Style, + error: anstyle::Style, + usage: anstyle::Style, + literal: anstyle::Style, + placeholder: anstyle::Style, + valid: anstyle::Style, + invalid: anstyle::Style, } impl Styles { @@ -256,6 +248,100 @@ impl Styles { Self::plain() } } + + /// General Heading style, e.g. [`help_heading`][crate::Arg::help_heading] + #[inline] + pub const fn header(mut self, style: anstyle::Style) -> Self { + self.header = style; + self + } + + /// Error heading + #[inline] + pub const fn error(mut self, style: anstyle::Style) -> Self { + self.error = style; + self + } + + /// Usage heading + #[inline] + pub const fn usage(mut self, style: anstyle::Style) -> Self { + self.usage = style; + self + } + + /// Literal command-line syntax, e.g. `--help` + #[inline] + pub const fn literal(mut self, style: anstyle::Style) -> Self { + self.literal = style; + self + } + + /// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name] + #[inline] + pub const fn placeholder(mut self, style: anstyle::Style) -> Self { + self.placeholder = style; + self + } + + /// Highlight suggested usage + #[inline] + pub const fn valid(mut self, style: anstyle::Style) -> Self { + self.valid = style; + self + } + + /// Highlight invalid usage + #[inline] + pub const fn invalid(mut self, style: anstyle::Style) -> Self { + self.invalid = style; + self + } +} + +/// Reflection +impl Styles { + /// General Heading style, e.g. [`help_heading`][crate::Arg::help_heading] + #[inline(always)] + pub const fn get_header(&self) -> anstyle::Style { + self.header + } + + /// Error heading + #[inline(always)] + pub const fn get_error(&self) -> anstyle::Style { + self.error + } + + /// Usage heading + #[inline(always)] + pub const fn get_usage(&self) -> anstyle::Style { + self.usage + } + + /// Literal command-line syntax, e.g. `--help` + #[inline(always)] + pub const fn get_literal(&self) -> anstyle::Style { + self.literal + } + + /// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name] + #[inline(always)] + pub const fn get_placeholder(&self) -> anstyle::Style { + self.placeholder + } + + /// Highlight suggested usage + #[inline(always)] + pub const fn get_valid(&self) -> anstyle::Style { + self.valid + } + + /// Highlight invalid usage + #[inline(always)] + pub const fn get_invalid(&self) -> anstyle::Style { + self.invalid + } } impl super::AppTag for Styles {} diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs index 34356788265..b5f0c126459 100644 --- a/clap_builder/src/error/format.rs +++ b/clap_builder/src/error/format.rs @@ -59,7 +59,7 @@ impl ErrorFormatter for RichFormatter { fn format_error(error: &crate::error::Error) -> StyledStr { use std::fmt::Write as _; let styles = &error.inner.styles; - let valid = &styles.valid; + let valid = &styles.get_valid(); let mut styled = StyledStr::new(); start_error(&mut styled, styles); @@ -128,7 +128,7 @@ impl ErrorFormatter for RichFormatter { fn start_error(styled: &mut StyledStr, styles: &Styles) { use std::fmt::Write as _; - let error = &styles.error; + let error = &styles.get_error(); let _ = write!(styled, "{}error:{} ", error.render(), error.render_reset()); } @@ -140,9 +140,9 @@ fn write_dynamic_context( styles: &Styles, ) -> bool { use std::fmt::Write as _; - let valid = styles.valid; - let invalid = styles.invalid; - let literal = styles.literal; + let valid = styles.get_valid(); + let invalid = styles.get_invalid(); + let literal = styles.get_literal(); match error.kind() { ErrorKind::ArgumentConflict => { @@ -503,7 +503,7 @@ pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> { fn try_help(styled: &mut StyledStr, styles: &Styles, help: Option<&str>) { if let Some(help) = help { use std::fmt::Write as _; - let literal = &styles.literal; + let literal = &styles.get_literal(); let _ = write!( styled, "\n\nFor more information, try '{}{help}{}'.\n", @@ -522,15 +522,15 @@ fn did_you_mean(styled: &mut StyledStr, styles: &Styles, context: &str, valid: & let _ = write!( styled, "{TAB}{}tip:{}", - styles.valid.render(), - styles.valid.render_reset() + styles.get_valid().render(), + styles.get_valid().render_reset() ); if let ContextValue::String(valid) = valid { let _ = write!( styled, " a similar {context} exists: '{}{valid}{}'", - styles.valid.render(), - styles.valid.render_reset() + styles.get_valid().render(), + styles.get_valid().render_reset() ); } else if let ContextValue::Strings(valid) = valid { if valid.len() == 1 { @@ -545,8 +545,8 @@ fn did_you_mean(styled: &mut StyledStr, styles: &Styles, context: &str, valid: & let _ = write!( styled, "'{}{valid}{}'", - styles.valid.render(), - styles.valid.render_reset() + styles.get_valid().render(), + styles.get_valid().render_reset() ); } } diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs index 01d379b27ba..cb362966944 100644 --- a/clap_builder/src/error/mod.rs +++ b/clap_builder/src/error/mod.rs @@ -445,8 +445,8 @@ impl Error { ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); - let invalid = &styles.invalid; - let valid = &styles.valid; + let invalid = &styles.get_invalid(); + let valid = &styles.get_valid(); let mut err = Self::new(ErrorKind::InvalidSubcommand).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -677,8 +677,8 @@ impl Error { ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); - let invalid = &styles.invalid; - let valid = &styles.valid; + let invalid = &styles.get_invalid(); + let valid = &styles.get_valid(); let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] @@ -740,8 +740,8 @@ impl Error { ) -> Self { use std::fmt::Write as _; let styles = cmd.get_styles(); - let invalid = &styles.invalid; - let valid = &styles.valid; + let invalid = &styles.get_invalid(); + let valid = &styles.get_valid(); let mut err = Self::new(ErrorKind::UnknownArgument).with_cmd(cmd); #[cfg(feature = "error-context")] diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index 703a1f47f5c..bba7dc42ac8 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -170,8 +170,8 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { let _ = write!( self.writer, "{}Usage:{}", - self.styles.usage.render(), - self.styles.usage.render_reset() + self.styles.get_usage().render(), + self.styles.get_usage().render_reset() ); } "usage" => { @@ -341,7 +341,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { pub(crate) fn write_all_args(&mut self) { debug!("HelpTemplate::write_all_args"); use std::fmt::Write as _; - let header = &self.styles.header; + let header = &self.styles.get_header(); let pos = self .cmd @@ -514,7 +514,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { fn short(&mut self, arg: &Arg) { debug!("HelpTemplate::short"); use std::fmt::Write as _; - let literal = &self.styles.literal; + let literal = &self.styles.get_literal(); if let Some(s) = arg.get_short() { let _ = write!( @@ -532,7 +532,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { fn long(&mut self, arg: &Arg) { debug!("HelpTemplate::long"); use std::fmt::Write as _; - let literal = &self.styles.literal; + let literal = &self.styles.get_literal(); if let Some(long) = arg.get_long() { if arg.get_short().is_some() { @@ -603,7 +603,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { ) { debug!("HelpTemplate::help"); use std::fmt::Write as _; - let literal = &self.styles.literal; + let literal = &self.styles.get_literal(); // Is help on next line, if so then indent if next_line_help { @@ -867,7 +867,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { fn write_subcommands(&mut self, cmd: &Command) { debug!("HelpTemplate::write_subcommands"); use std::fmt::Write as _; - let literal = &self.styles.literal; + let literal = &self.styles.get_literal(); // The shortest an arg can legally be is 2 (i.e. '-x') let mut longest = 2; diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index 1eb9139d718..a476a8fa2a3 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -45,8 +45,8 @@ impl<'cmd> Usage<'cmd> { let _ = write!( styled, "{}Usage:{} ", - self.styles.usage.render(), - self.styles.usage.render_reset() + self.styles.get_usage().render(), + self.styles.get_usage().render_reset() ); styled.push_styled(&usage); Some(styled) @@ -81,8 +81,8 @@ impl<'cmd> Usage<'cmd> { fn create_help_usage(&self, incl_reqs: bool) -> StyledStr { debug!("Usage::create_help_usage; incl_reqs={:?}", incl_reqs); use std::fmt::Write as _; - let literal = &self.styles.literal; - let placeholder = &self.styles.placeholder; + let literal = &self.styles.get_literal(); + let placeholder = &self.styles.get_placeholder(); let mut styled = StyledStr::new(); let name = self @@ -166,8 +166,8 @@ impl<'cmd> Usage<'cmd> { fn create_smart_usage(&self, used: &[Id]) -> StyledStr { debug!("Usage::create_smart_usage"); use std::fmt::Write; - let literal = &self.styles.literal; - let placeholder = &self.styles.placeholder; + let literal = &self.styles.get_literal(); + let placeholder = &self.styles.get_placeholder(); let mut styled = StyledStr::new(); let bin_name = self @@ -246,7 +246,7 @@ impl<'cmd> Usage<'cmd> { pub(crate) fn get_args(&self, incls: &[Id], force_optional: bool) -> Vec { debug!("Usage::get_args: incls={:?}", incls,); use std::fmt::Write as _; - let literal = &self.styles.literal; + let literal = &self.styles.get_literal(); let required_owned; let required = if let Some(required) = self.required { From ec7040e8ba9ddcbdd18f9aa2608e8bae9a83582a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 15:17:20 -0500 Subject: [PATCH 11/13] style: Make clippy happy --- clap_builder/src/output/help_template.rs | 2 +- clap_builder/src/output/usage.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index bba7dc42ac8..9ac31cf87a0 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -494,7 +494,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> { self.short(arg); self.long(arg); self.writer - .push_styled(&arg.stylize_arg_suffix(&self.styles, None)); + .push_styled(&arg.stylize_arg_suffix(self.styles, None)); self.align_to_about(arg, next_line_help, longest); let about = if self.use_long { diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs index a476a8fa2a3..dd99c63c7b7 100644 --- a/clap_builder/src/output/usage.rs +++ b/clap_builder/src/output/usage.rs @@ -298,7 +298,7 @@ impl<'cmd> Usage<'cmd> { continue; } - let stylized = arg.stylized(&self.styles, Some(!force_optional)); + let stylized = arg.stylized(self.styles, Some(!force_optional)); if let Some(index) = arg.get_index() { let new_len = index + 1; if required_positionals.len() < new_len { @@ -339,10 +339,10 @@ impl<'cmd> Usage<'cmd> { if pos.is_last_set() { styled = StyledStr::new(); let _ = write!(styled, "{}[--{} ", literal.render(), literal.render_reset()); - styled.push_styled(&pos.stylized(&self.styles, Some(true))); + styled.push_styled(&pos.stylized(self.styles, Some(true))); let _ = write!(styled, "{}]{}", literal.render(), literal.render_reset()); } else { - styled = pos.stylized(&self.styles, Some(false)); + styled = pos.stylized(self.styles, Some(false)); } required_positionals[index] = Some(styled); } @@ -462,7 +462,7 @@ impl<'cmd> Usage<'cmd> { continue; } - let stylized = arg.stylized(&self.styles, Some(true)); + let stylized = arg.stylized(self.styles, Some(true)); if let Some(index) = arg.get_index() { if !arg.is_last_set() || incl_last { let new_len = index + 1; From 5ca3b2fc5f295cfd5851ea10360dad0afccb57c5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 15:28:20 -0500 Subject: [PATCH 12/13] refactor(help): Shrink binary size --- clap_builder/src/builder/styled_str.rs | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index 4a30df550bc..e4acc63ffba 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -303,44 +303,44 @@ impl Styles { impl Styles { /// General Heading style, e.g. [`help_heading`][crate::Arg::help_heading] #[inline(always)] - pub const fn get_header(&self) -> anstyle::Style { - self.header + pub const fn get_header(&self) -> &anstyle::Style { + &self.header } /// Error heading #[inline(always)] - pub const fn get_error(&self) -> anstyle::Style { - self.error + pub const fn get_error(&self) -> &anstyle::Style { + &self.error } /// Usage heading #[inline(always)] - pub const fn get_usage(&self) -> anstyle::Style { - self.usage + pub const fn get_usage(&self) -> &anstyle::Style { + &self.usage } /// Literal command-line syntax, e.g. `--help` #[inline(always)] - pub const fn get_literal(&self) -> anstyle::Style { - self.literal + pub const fn get_literal(&self) -> &anstyle::Style { + &self.literal } /// Descriptions within command-line syntax, e.g. [`value_name`][crate::Arg::value_name] #[inline(always)] - pub const fn get_placeholder(&self) -> anstyle::Style { - self.placeholder + pub const fn get_placeholder(&self) -> &anstyle::Style { + &self.placeholder } /// Highlight suggested usage #[inline(always)] - pub const fn get_valid(&self) -> anstyle::Style { - self.valid + pub const fn get_valid(&self) -> &anstyle::Style { + &self.valid } /// Highlight invalid usage #[inline(always)] - pub const fn get_invalid(&self) -> anstyle::Style { - self.invalid + pub const fn get_invalid(&self) -> &anstyle::Style { + &self.invalid } } From cbea23e3fcbafdc6387f6799325de622d00c4749 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 18 Apr 2023 15:36:02 -0500 Subject: [PATCH 13/13] style: Make clippy happy --- clap_builder/src/output/help_template.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs index 9ac31cf87a0..86a61169c32 100644 --- a/clap_builder/src/output/help_template.rs +++ b/clap_builder/src/output/help_template.rs @@ -1,3 +1,8 @@ +// HACK: for rust 1.64 (1.68 doesn't need this since this is in lib.rs) +// +// Wanting consistency in our calls +#![allow(clippy::write_with_newline)] + // Std use std::borrow::Cow; use std::cmp;