From 1040114162165084dfaf34394c45f3a5c8355685 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 16 Mar 2023 06:28:57 -0500 Subject: [PATCH 1/8] feat(help): Respect CLICOLOR, CLICOLOR_FORCE We might have respected `NO_COLOR` before via `termcolor`. See #4722 --- Cargo.lock | 12 ++--- clap_builder/Cargo.toml | 15 ++---- clap_builder/src/builder/styled_str.rs | 63 ++++++++++---------------- clap_builder/src/output/fmt.rs | 35 ++++++-------- 4 files changed, 48 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e3ead759c2..2866e5b7d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,9 +25,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0982309face56a044e935a18bbffcddeb1ce72e69a3ecc3bafb56d4e959f37" +checksum = "1be94522db49757a6f9266e8ac18a59704e3e1c770c2e8173a39f9f8161a163a" dependencies = [ "anstyle", "anstyle-parse", @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453bc2a7b261f8c4d1ce5b2c6c222d648d00988d30315e4911fbddc4ddf8983c" +checksum = "1ba0b55c2201aa802adb684e7963ce2c3191675629e7df899774331e3ac747cf" [[package]] name = "anstyle-parse" @@ -203,18 +203,18 @@ dependencies = [ name = "clap_builder" version = "4.1.14" dependencies = [ + "anstream", + "anstyle", "backtrace", "bitflags", "clap_lex 0.4.0", "humantime", - "is-terminal", "once_cell", "rustversion", "shlex", "snapbox", "static_assertions", "strsim", - "termcolor", "terminal_size", "trybuild", "trycmd", diff --git a/clap_builder/Cargo.toml b/clap_builder/Cargo.toml index 6c83fb598f5..2c245acb4d5 100644 --- a/clap_builder/Cargo.toml +++ b/clap_builder/Cargo.toml @@ -30,20 +30,13 @@ dependent-version = "upgrade" tag-name = "v{{version}}" [features] -default = [ - "std", - "color", - "help", - "usage", - "error-context", - "suggestions", -] +default = ["std", "color", "help", "usage", "error-context", "suggestions"] debug = ["dep:backtrace"] # Enables debug messages unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string", "unstable-replace"] # for docs.rs # Used in default std = [] # support for no_std in a backwards-compatible way -color = ["dep:is-terminal", "dep:termcolor"] +color = ["dep:anstyle", "dep:anstream"] help = [] usage = [] error-context = [] @@ -70,8 +63,8 @@ clap_lex = { path = "../clap_lex", version = "0.4.0" } bitflags = "1.2.0" unicase = { version = "2.6.0", optional = true } strsim = { version = "0.10.0", optional = true } -is-terminal = { version = "0.4.1", optional = true } -termcolor = { version = "1.1.1", optional = true } +anstream = { version = "0.2.4", optional = true } +anstyle = { version = "0.3.4", features = ["std"], optional = true } terminal_size = { version = "0.2.1", optional = true } backtrace = { version = "0.3.67", optional = true } unicode-width = { version = "0.1.9", optional = true } diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index d36329dee3f..dde3057b384 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -196,40 +196,12 @@ impl StyledStr { } #[cfg(feature = "color")] - pub(crate) fn write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()> { - use std::io::Write; - use termcolor::WriteColor; - + pub(crate) fn write_colored(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()> { for (style, content) in &self.pieces { - let mut color = termcolor::ColorSpec::new(); - match style { - Some(Style::Header) => { - color.set_bold(true); - color.set_underline(true); - } - Some(Style::Literal) => { - color.set_bold(true); - } - Some(Style::Placeholder) => {} - Some(Style::Good) => { - color.set_fg(Some(termcolor::Color::Green)); - } - Some(Style::Warning) => { - color.set_fg(Some(termcolor::Color::Yellow)); - } - Some(Style::Error) => { - color.set_fg(Some(termcolor::Color::Red)); - color.set_bold(true); - } - Some(Style::Hint) => { - color.set_dimmed(true); - } - None => {} - } - - ok!(buffer.set_color(&color)); + let style = style.map(|s| s.as_style()).unwrap_or_default(); + ok!(style.write_to(buffer)); ok!(buffer.write_all(content.as_bytes())); - ok!(buffer.reset()); + ok!(style.write_reset_to(buffer)); } Ok(()) @@ -310,14 +282,12 @@ struct AnsiDisplay<'s> { #[cfg(feature = "color")] impl std::fmt::Display for AnsiDisplay<'_> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut buffer = termcolor::Buffer::ansi(); - ok!(self - .styled - .write_colored(&mut buffer) - .map_err(|_| std::fmt::Error)); - let buffer = buffer.into_inner(); - let buffer = ok!(String::from_utf8(buffer).map_err(|_| std::fmt::Error)); - ok!(std::fmt::Display::fmt(&buffer, f)); + for (style, content) in &self.styled.pieces { + let style = style.map(|s| s.as_style()).unwrap_or_default(); + ok!(style.render().fmt(f)); + ok!(content.fmt(f)); + ok!(style.render_reset().fmt(f)); + } Ok(()) } @@ -346,4 +316,17 @@ impl Style { Self::Hint => 6, } } + + #[cfg(feature = "color")] + fn as_style(&self) -> anstyle::Style { + 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(), + } + } } diff --git a/clap_builder/src/output/fmt.rs b/clap_builder/src/output/fmt.rs index b5d0153f11a..93137adb248 100644 --- a/clap_builder/src/output/fmt.rs +++ b/clap_builder/src/output/fmt.rs @@ -34,22 +34,26 @@ impl Colorizer { impl Colorizer { #[cfg(feature = "color")] pub(crate) fn print(&self) -> std::io::Result<()> { - use termcolor::{BufferWriter, ColorChoice as DepColorChoice}; - let color_when = match self.color_when { - ColorChoice::Always => DepColorChoice::Always, - ColorChoice::Auto if is_a_tty(self.stream) => DepColorChoice::Auto, - _ => DepColorChoice::Never, + ColorChoice::Always => anstream::ColorChoice::Always, + ColorChoice::Auto => anstream::ColorChoice::Auto, + ColorChoice::Never => anstream::ColorChoice::Never, }; - let writer = match self.stream { - Stream::Stderr => BufferWriter::stderr(color_when), - Stream::Stdout => BufferWriter::stdout(color_when), + let mut stdout; + let mut stderr; + let writer: &mut dyn std::io::Write = match self.stream { + Stream::Stderr => { + stderr = anstream::AutoStream::new(std::io::stderr().lock(), color_when); + &mut stderr + } + Stream::Stdout => { + stdout = anstream::AutoStream::new(std::io::stdout().lock(), color_when); + &mut stdout + } }; - let mut buffer = writer.buffer(); - ok!(self.content.write_colored(&mut buffer)); - writer.print(&buffer) + self.content.write_colored(writer) } #[cfg(not(feature = "color"))] @@ -79,12 +83,3 @@ impl std::fmt::Display for Colorizer { self.content.fmt(f) } } - -#[cfg(feature = "color")] -fn is_a_tty(stream: Stream) -> bool { - use is_terminal::IsTerminal; - match stream { - Stream::Stdout => std::io::stdout().is_terminal(), - Stream::Stderr => std::io::stderr().is_terminal(), - } -} From bdbfe6470f60c7163f4337ffccc2d9382da5dfc8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 16 Mar 2023 10:17:56 -0500 Subject: [PATCH 2/8] refactor(help): Track style via ANSI codes --- clap_builder/src/builder/arg.rs | 2 +- clap_builder/src/builder/styled_str.rs | 183 +++++++---------------- clap_builder/src/error/format.rs | 10 +- clap_builder/src/error/mod.rs | 2 +- clap_builder/src/output/fmt.rs | 8 +- clap_builder/src/output/help.rs | 12 +- clap_builder/src/output/help_template.rs | 37 ++--- clap_builder/src/output/usage.rs | 10 +- 8 files changed, 82 insertions(+), 182 deletions(-) diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs index 577d968267c..574e6fbaf2f 100644 --- a/clap_builder/src/builder/arg.rs +++ b/clap_builder/src/builder/arg.rs @@ -4280,7 +4280,7 @@ impl Arg { styled.literal("-"); styled.literal(s); } - styled.extend(self.stylize_arg_suffix(required).into_iter()); + styled.push_styled(&self.stylize_arg_suffix(required)); styled } diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs index dde3057b384..fac37e66140 100644 --- a/clap_builder/src/builder/styled_str.rs +++ b/clap_builder/src/builder/styled_str.rs @@ -2,27 +2,13 @@ /// /// For now, this is the same as a [`Str`][crate::builder::Str]. This exists to reserve space in /// the API for exposing terminal styling. -#[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct StyledStr { - #[cfg(feature = "color")] - pieces: Vec<(Option