Auto merge of #12751 - epage:color, r=arlosi

refactor: Switch from termcolor to anstream

### What does this PR try to resolve?

`anstream` asks the question "what if you took `fwdansi` and removed `termcolor` underneath it.  It wraps output streams, adapting ANSI escape codes to what is needed
- Pass through if its supported
- Strip if its not
- Adapt to wincon API if needed

Benefits
- Lower boilerplate: we can use `write!` with styled text rather than the back-and-forth between colors and writing that termcolor needs
- Allows richer styling as `Shell` can accept styled messages and adapt them as needed

Side effects
- We'll now respect [NO_COLOR](https://no-color.org/), [CLICOLOR_FORCE](https://bixense.com/clicolors/), and [CLICOLOR](3a22aaa5e8)

Fixes #12627

### How should we test and review this PR?

This is broken up by commits for easier browsing.

However, as there aren't really tests for colored output, this needs hand inspection to verify

### Additional information

This allowed us to remove the need for stripping ansi escape codes completely.  Even if it didn't, it exposes its internal stripping API for reuse, saving on a dependency and being significantly faster.
This commit is contained in:
bors 2023-09-29 19:29:17 +00:00
commit 59596f0f31
8 changed files with 105 additions and 294 deletions

101
Cargo.lock generated
View File

@ -25,9 +25,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.5.0"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
checksum = "83d7b3983a025adeb201ef26a5564ebd1641ea9851f6282aee4940f745a3c07c"
dependencies = [
"anstyle",
"anstyle-parse",
@ -39,9 +39,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
@ -61,21 +61,11 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11c3d1411f1f4c8a7b177caec3c71b51290f9e8ad9f99124fd3fe9aa96e56834"
dependencies = [
"anstyle",
"termcolor",
]
[[package]]
name = "anstyle-wincon"
version = "2.1.0"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
dependencies = [
"anstyle",
"windows-sys",
@ -93,12 +83,6 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -252,8 +236,8 @@ dependencies = [
name = "cargo"
version = "0.75.0"
dependencies = [
"anstream",
"anstyle",
"anstyle-termcolor",
"anyhow",
"base64",
"bytesize",
@ -272,7 +256,6 @@ dependencies = [
"curl-sys",
"filetime",
"flate2",
"fwdansi",
"git2",
"git2-curl",
"gix",
@ -310,11 +293,9 @@ dependencies = [
"sha1",
"shell-escape",
"snapbox",
"strip-ansi-escapes",
"syn 2.0.29",
"tar",
"tempfile",
"termcolor",
"time",
"toml",
"toml_edit",
@ -400,6 +381,8 @@ version = "0.1.0"
name = "cargo-test-support"
version = "0.1.0"
dependencies = [
"anstream",
"anstyle",
"anyhow",
"cargo-test-macro",
"cargo-util",
@ -414,7 +397,6 @@ dependencies = [
"serde_json",
"snapbox",
"tar",
"termcolor",
"time",
"toml",
"url",
@ -505,18 +487,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.4"
version = "4.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.4.4"
version = "4.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
dependencies = [
"anstream",
"anstyle",
@ -998,16 +980,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fwdansi"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c1f5787fe85505d1f7777268db5103d80a7a374d2316a7ce262e57baf8f208"
dependencies = [
"memchr",
"termcolor",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@ -3071,9 +3043,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "snapbox"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad90eb3a2e3a8031d636d45bd4832751aefd58a291b553f7305a2bacae21aff3"
checksum = "7b439536a42c43be148b610c7f7f968fb79a457254910a9cb20900da73cd3271"
dependencies = [
"anstream",
"anstyle",
@ -3090,9 +3062,9 @@ dependencies = [
[[package]]
name = "snapbox-macros"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95f4ffd811b87da98d0e48285134b7847954bd76e843bb794a893b47ca3ee325"
checksum = "ed1559baff8a696add3322b9be3e940d433e7bb4e38d79017205fd37ff28b28e"
dependencies = [
"anstream",
]
@ -3117,15 +3089,6 @@ dependencies = [
"der",
]
[[package]]
name = "strip-ansi-escapes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
dependencies = [
"vte",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -3195,15 +3158,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
@ -3583,27 +3537,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vte"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
dependencies = [
"arrayvec",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wait-timeout"
version = "0.2.0"

View File

@ -16,8 +16,8 @@ edition = "2021"
license = "MIT OR Apache-2.0"
[workspace.dependencies]
anstyle = "1.0.3"
anstyle-termcolor = "1.1.0"
anstream = "0.6.3"
anstyle = "1.0.4"
anyhow = "1.0.75"
base64 = "0.21.3"
bytesize = "1.3"
@ -31,7 +31,7 @@ cargo-test-macro = { path = "crates/cargo-test-macro" }
cargo-test-support = { path = "crates/cargo-test-support" }
cargo-util = { version = "0.2.6", path = "crates/cargo-util" }
cargo_metadata = "0.17.0"
clap = "4.4.4"
clap = "4.4.6"
color-print = "0.3.4"
core-foundation = { version = "0.9.3", features = ["mac_os_10_7_support"] }
crates-io = { version = "0.39.0", path = "crates/crates-io" }
@ -40,7 +40,6 @@ curl = "0.4.44"
curl-sys = "0.4.66"
filetime = "0.2.22"
flate2 = { version = "1.0.27", default-features = false, features = ["zlib"] }
fwdansi = "1.1.0"
git2 = "0.18.0"
git2-curl = "0.19.0"
gix = { version = "0.54.1", default-features = false, features = ["blocking-http-transport-curl", "progress-tree", "revision"] }
@ -86,12 +85,10 @@ serde_json = "1.0.105"
sha1 = "0.10.5"
sha2 = "0.10.7"
shell-escape = "0.1.5"
snapbox = { version = "0.4.12", features = ["diff", "path"] }
strip-ansi-escapes = "0.1.1"
snapbox = { version = "0.4.13", features = ["diff", "path"] }
syn = { version = "2.0.29", features = ["extra-traits", "full"] }
tar = { version = "0.4.40", default-features = false }
tempfile = "3.8.0"
termcolor = "1.2.0"
thiserror = "1.0.47"
time = { version = "0.3", features = ["parsing", "formatting", "serde"] }
toml = "0.7.6"
@ -123,8 +120,8 @@ name = "cargo"
path = "src/cargo/lib.rs"
[dependencies]
anstream.workspace = true
anstyle.workspace = true
anstyle-termcolor.workspace = true
anyhow.workspace = true
base64.workspace = true
bytesize.workspace = true
@ -175,11 +172,9 @@ serde_ignored.workspace = true
serde_json = { workspace = true, features = ["raw_value"] }
sha1.workspace = true
shell-escape.workspace = true
strip-ansi-escapes.workspace = true
syn.workspace = true
tar.workspace = true
tempfile.workspace = true
termcolor.workspace = true
time.workspace = true
toml.workspace = true
toml_edit.workspace = true
@ -194,9 +189,6 @@ walkdir.workspace = true
[target.'cfg(not(windows))'.dependencies]
openssl = { workspace = true, optional = true }
[target.'cfg(windows)'.dependencies]
fwdansi.workspace = true
[target.'cfg(windows)'.dependencies.windows-sys]
workspace = true
features = [

View File

@ -10,6 +10,8 @@ publish = false
doctest = false
[dependencies]
anstream.workspace = true
anstyle.workspace = true
anyhow.workspace = true
cargo-test-macro.workspace = true
cargo-util.workspace = true
@ -24,7 +26,6 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
snapbox.workspace = true
tar.workspace = true
termcolor.workspace = true
time.workspace = true
toml.workspace = true
url.workspace = true

View File

@ -7,7 +7,6 @@
use std::fmt;
use std::io::Write;
use termcolor::{Ansi, Color, ColorSpec, NoColor, WriteColor};
/// A single line change to be applied to the original.
#[derive(Debug, Eq, PartialEq)]
@ -111,42 +110,35 @@ where
}
pub fn render_colored_changes<T: fmt::Display>(changes: &[Change<T>]) -> String {
// termcolor is not very ergonomic, but I don't want to bring in another dependency.
let mut red = ColorSpec::new();
red.set_fg(Some(Color::Red));
let mut green = ColorSpec::new();
green.set_fg(Some(Color::Green));
let mut dim = ColorSpec::new();
dim.set_dimmed(true);
let mut v = Vec::new();
let mut result: Box<dyn WriteColor> = if crate::is_ci() {
// anstyle is not very ergonomic, but I don't want to bring in another dependency.
let red = anstyle::AnsiColor::Red.on_default().render();
let green = anstyle::AnsiColor::Green.on_default().render();
let dim = (anstyle::Style::new() | anstyle::Effects::DIMMED).render();
let bold = (anstyle::Style::new() | anstyle::Effects::BOLD).render();
let reset = anstyle::Reset.render();
let choice = if crate::is_ci() {
// Don't use color on CI. Even though GitHub can display colors, it
// makes reading the raw logs more difficult.
Box::new(NoColor::new(&mut v))
anstream::ColorChoice::Never
} else {
Box::new(Ansi::new(&mut v))
anstream::AutoStream::choice(&std::io::stdout())
};
let mut buffer = anstream::AutoStream::new(Vec::new(), choice);
for change in changes {
let (nums, sign, color, text) = match change {
Change::Add(i, s) => (format!(" {:<4} ", i), '+', &green, s),
Change::Remove(i, s) => (format!("{:<4} ", i), '-', &red, s),
Change::Keep(x, y, s) => (format!("{:<4}{:<4} ", x, y), ' ', &dim, s),
Change::Add(i, s) => (format!(" {:<4} ", i), '+', green, s),
Change::Remove(i, s) => (format!("{:<4} ", i), '-', red, s),
Change::Keep(x, y, s) => (format!("{:<4}{:<4} ", x, y), ' ', dim, s),
};
result.set_color(&dim).unwrap();
write!(result, "{}", nums).unwrap();
let mut bold = color.clone();
bold.set_bold(true);
result.set_color(&bold).unwrap();
write!(result, "{}", sign).unwrap();
result.reset().unwrap();
result.set_color(&color).unwrap();
write!(result, "{}", text).unwrap();
result.reset().unwrap();
writeln!(result).unwrap();
write!(
buffer,
"{dim}{nums}{reset}{bold}{sign}{reset}{color}{text}{reset}"
)
.unwrap();
}
drop(result);
String::from_utf8(v).unwrap()
String::from_utf8(buffer.into_inner()).unwrap()
}
#[cfg(test)]

View File

@ -44,7 +44,7 @@ fn report_future_incompatibilities(config: &Config, args: &ArgMatches) -> CliRes
.value_of_u32("id")?
.unwrap_or_else(|| reports.last_id());
let krate = args.get_one::<String>("package").map(String::as_str);
let report = reports.get_report(id, config, krate)?;
let report = reports.get_report(id, krate)?;
drop_println!(config, "{}", REPORT_PREAMBLE);
drop(config.shell().print_ansi_stdout(report.as_bytes()));
Ok(())

View File

@ -37,7 +37,7 @@ use crate::core::compiler::BuildContext;
use crate::core::{Dependency, PackageId, Workspace};
use crate::sources::source::QueryKind;
use crate::sources::SourceConfigMap;
use crate::util::{iter_join, CargoResult, Config};
use crate::util::{iter_join, CargoResult};
use anyhow::{bail, format_err, Context};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
@ -224,12 +224,8 @@ impl OnDiskReports {
self.reports.last().map(|r| r.id).unwrap()
}
pub fn get_report(
&self,
id: u32,
config: &Config,
package: Option<&str>,
) -> CargoResult<String> {
/// Returns an ANSI-styled report
pub fn get_report(&self, id: u32, package: Option<&str>) -> CargoResult<String> {
let report = self.reports.iter().find(|r| r.id == id).ok_or_else(|| {
let available = iter_join(self.reports.iter().map(|r| r.id.to_string()), ", ");
format_err!(
@ -267,15 +263,6 @@ impl OnDiskReports {
};
to_display += &package_report;
let shell = config.shell();
let to_display = if shell.err_supports_color() && shell.out_supports_color() {
to_display
} else {
strip_ansi_escapes::strip(&to_display)
.map(|v| String::from_utf8(v).expect("utf8"))
.expect("strip should never fail")
};
Ok(to_display)
}
}

View File

@ -202,7 +202,6 @@ fn compile<'cfg>(
&unit.target,
cx.files().message_cache_path(unit),
cx.bcx.build_config.message_format,
cx.bcx.config.shell().err_supports_color(),
unit.show_warnings(bcx.config),
);
// Need to link targets on both the dirty and fresh.
@ -1416,8 +1415,6 @@ fn envify(s: &str) -> String {
struct OutputOptions {
/// What format we're emitting from Cargo itself.
format: MessageFormat,
/// Whether or not to display messages in color.
color: bool,
/// Where to write the JSON messages to support playback later if the unit
/// is fresh. The file is created lazily so that in the normal case, lots
/// of empty files are not created. If this is None, the output will not
@ -1439,14 +1436,12 @@ struct OutputOptions {
impl OutputOptions {
fn new(cx: &Context<'_, '_>, unit: &Unit) -> OutputOptions {
let color = cx.bcx.config.shell().err_supports_color();
let path = cx.files().message_cache_path(unit);
// Remove old cache, ignore ENOENT, which is the common case.
drop(fs::remove_file(&path));
let cache_cell = Some((path, LazyCell::new()));
OutputOptions {
format: cx.bcx.build_config.message_format,
color,
cache_cell,
show_diagnostics: true,
warnings_seen: 0,
@ -1586,15 +1581,7 @@ fn on_stderr_line_inner(
if msg.rendered.ends_with('\n') {
msg.rendered.pop();
}
let rendered = if options.color {
msg.rendered
} else {
// Strip only fails if the Writer fails, which is Cursor
// on a Vec, which should never fail.
strip_ansi_escapes::strip(&msg.rendered)
.map(|v| String::from_utf8(v).expect("utf8"))
.expect("strip should never fail")
};
let rendered = msg.rendered;
if options.show_diagnostics {
let machine_applicable: bool = msg
.children
@ -1625,9 +1612,7 @@ fn on_stderr_line_inner(
other: std::collections::BTreeMap<String, serde_json::Value>,
}
if let Ok(mut error) = serde_json::from_str::<CompilerMessage>(compiler_message.get()) {
error.rendered = strip_ansi_escapes::strip(&error.rendered)
.map(|v| String::from_utf8(v).expect("utf8"))
.unwrap_or(error.rendered);
error.rendered = anstream::adapter::strip_str(&error.rendered).to_string();
let new_line = serde_json::to_string(&error)?;
let new_msg: Box<serde_json::value::RawValue> = serde_json::from_str(&new_line)?;
compiler_message = new_msg;
@ -1699,13 +1684,11 @@ fn replay_output_cache(
target: &Target,
path: PathBuf,
format: MessageFormat,
color: bool,
show_diagnostics: bool,
) -> Work {
let target = target.clone();
let mut options = OutputOptions {
format,
color,
cache_cell: None,
show_diagnostics,
warnings_seen: 0,

View File

@ -2,9 +2,8 @@ use std::fmt;
use std::io::prelude::*;
use std::io::IsTerminal;
use anstream::AutoStream;
use anstyle::Style;
use anstyle_termcolor::to_termcolor_spec;
use termcolor::{self, BufferWriter, StandardStream, WriteColor};
use crate::util::errors::CargoResult;
use crate::util::style::*;
@ -79,17 +78,11 @@ impl fmt::Debug for Shell {
/// A `Write`able object, either with or without color support
enum ShellOut {
/// A plain write object without color support
Write(Box<dyn Write>),
Write(AutoStream<Box<dyn Write>>),
/// Color-enabled stdio, with information on whether color should be used
///
/// The separate buffered fields are used for buffered writing to the
/// corresponding stream. The non-buffered fields should be used when you
/// do not want content to be buffered.
Stream {
stdout: StandardStream,
buffered_stdout: BufferWriter,
stderr: StandardStream,
buffered_stderr: BufferWriter,
stdout: AutoStream<std::io::Stdout>,
stderr: AutoStream<std::io::Stderr>,
stderr_tty: bool,
color_choice: ColorChoice,
},
@ -111,15 +104,13 @@ impl Shell {
/// output.
pub fn new() -> Shell {
let auto_clr = ColorChoice::CargoAuto;
let stdout_choice = auto_clr.to_termcolor_color_choice(Stream::Stdout);
let stderr_choice = auto_clr.to_termcolor_color_choice(Stream::Stderr);
let stdout_choice = auto_clr.to_anstream_color_choice();
let stderr_choice = auto_clr.to_anstream_color_choice();
Shell {
output: ShellOut::Stream {
stdout: StandardStream::stdout(stdout_choice),
buffered_stdout: BufferWriter::stdout(stdout_choice),
stderr: StandardStream::stderr(stderr_choice),
buffered_stderr: BufferWriter::stderr(stderr_choice),
color_choice: ColorChoice::CargoAuto,
stdout: AutoStream::new(std::io::stdout(), stdout_choice),
stderr: AutoStream::new(std::io::stderr(), stderr_choice),
color_choice: auto_clr,
stderr_tty: std::io::stderr().is_terminal(),
},
verbosity: Verbosity::Verbose,
@ -130,7 +121,7 @@ impl Shell {
/// Creates a shell from a plain writable object, with no color, and max verbosity.
pub fn from_write(out: Box<dyn Write>) -> Shell {
Shell {
output: ShellOut::Write(out),
output: ShellOut::Write(AutoStream::never(out)), // strip all formatting on write
verbosity: Verbosity::Verbose,
needs_clear: false,
}
@ -297,9 +288,7 @@ impl Shell {
pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
if let ShellOut::Stream {
ref mut stdout,
ref mut buffered_stdout,
ref mut stderr,
ref mut buffered_stderr,
ref mut color_choice,
..
} = self.output
@ -317,12 +306,10 @@ impl Shell {
),
};
*color_choice = cfg;
let stdout_choice = cfg.to_termcolor_color_choice(Stream::Stdout);
let stderr_choice = cfg.to_termcolor_color_choice(Stream::Stderr);
*stdout = StandardStream::stdout(stdout_choice);
*buffered_stdout = BufferWriter::stdout(stdout_choice);
*stderr = StandardStream::stderr(stderr_choice);
*buffered_stderr = BufferWriter::stderr(stderr_choice);
let stdout_choice = cfg.to_anstream_color_choice();
let stderr_choice = cfg.to_anstream_color_choice();
*stdout = AutoStream::new(std::io::stdout(), stdout_choice);
*stderr = AutoStream::new(std::io::stderr(), stderr_choice);
}
Ok(())
}
@ -342,14 +329,14 @@ impl Shell {
pub fn err_supports_color(&self) -> bool {
match &self.output {
ShellOut::Write(_) => false,
ShellOut::Stream { stderr, .. } => stderr.supports_color(),
ShellOut::Stream { stderr, .. } => supports_color(stderr.current_choice()),
}
}
pub fn out_supports_color(&self) -> bool {
match &self.output {
ShellOut::Write(_) => false,
ShellOut::Stream { stdout, .. } => stdout.supports_color(),
ShellOut::Stream { stdout, .. } => supports_color(stdout.current_choice()),
}
}
@ -372,13 +359,6 @@ impl Shell {
if self.needs_clear {
self.err_erase_line();
}
#[cfg(windows)]
{
if let ShellOut::Stream { stderr, .. } = &mut self.output {
::fwdansi::write_ansi(stderr, message)?;
return Ok(());
}
}
self.err().write_all(message)?;
Ok(())
}
@ -388,13 +368,6 @@ impl Shell {
if self.needs_clear {
self.err_erase_line();
}
#[cfg(windows)]
{
if let ShellOut::Stream { stdout, .. } = &mut self.output {
::fwdansi::write_ansi(stdout, message)?;
return Ok(());
}
}
self.out().write_all(message)?;
Ok(())
}
@ -425,82 +398,43 @@ impl ShellOut {
style: &Style,
justified: bool,
) -> CargoResult<()> {
match *self {
ShellOut::Stream {
ref mut buffered_stderr,
..
} => {
let mut buffer = buffered_stderr.buffer();
buffer.reset()?;
buffer.set_color(&to_termcolor_spec(*style))?;
if justified {
write!(buffer, "{:>12}", status)?;
} else {
write!(buffer, "{}", status)?;
buffer.set_color(termcolor::ColorSpec::new().set_bold(true))?;
write!(buffer, ":")?;
}
buffer.reset()?;
match message {
Some(message) => writeln!(buffer, " {}", message)?,
None => write!(buffer, " ")?,
}
buffered_stderr.print(&buffer)?;
}
ShellOut::Write(ref mut w) => {
if justified {
write!(w, "{:>12}", status)?;
} else {
write!(w, "{}:", status)?;
}
match message {
Some(message) => writeln!(w, " {}", message)?,
None => write!(w, " ")?,
}
}
let style = style.render();
let bold = (anstyle::Style::new() | anstyle::Effects::BOLD).render();
let reset = anstyle::Reset.render();
let mut buffer = Vec::new();
if justified {
write!(&mut buffer, "{style}{status:>12}{reset}")?;
} else {
write!(&mut buffer, "{style}{status}{reset}{bold}:{reset}")?;
}
match message {
Some(message) => writeln!(buffer, " {message}")?,
None => write!(buffer, " ")?,
}
self.stderr().write_all(&buffer)?;
Ok(())
}
/// Write a styled fragment
fn write_stdout(&mut self, fragment: impl fmt::Display, color: &Style) -> CargoResult<()> {
match *self {
ShellOut::Stream {
ref mut buffered_stdout,
..
} => {
let mut buffer = buffered_stdout.buffer();
buffer.reset()?;
buffer.set_color(&to_termcolor_spec(*color))?;
write!(buffer, "{}", fragment)?;
buffer.reset()?;
buffered_stdout.print(&buffer)?;
}
ShellOut::Write(ref mut w) => {
write!(w, "{}", fragment)?;
}
}
fn write_stdout(&mut self, fragment: impl fmt::Display, style: &Style) -> CargoResult<()> {
let style = style.render();
let reset = anstyle::Reset.render();
let mut buffer = Vec::new();
write!(buffer, "{style}{}{reset}", fragment)?;
self.stdout().write_all(&buffer)?;
Ok(())
}
/// Write a styled fragment
fn write_stderr(&mut self, fragment: impl fmt::Display, color: &Style) -> CargoResult<()> {
match *self {
ShellOut::Stream {
ref mut buffered_stderr,
..
} => {
let mut buffer = buffered_stderr.buffer();
buffer.reset()?;
buffer.set_color(&to_termcolor_spec(*color))?;
write!(buffer, "{}", fragment)?;
buffer.reset()?;
buffered_stderr.print(&buffer)?;
}
ShellOut::Write(ref mut w) => {
write!(w, "{}", fragment)?;
}
}
fn write_stderr(&mut self, fragment: impl fmt::Display, style: &Style) -> CargoResult<()> {
let style = style.render();
let reset = anstyle::Reset.render();
let mut buffer = Vec::new();
write!(buffer, "{style}{}{reset}", fragment)?;
self.stderr().write_all(&buffer)?;
Ok(())
}
@ -522,33 +456,22 @@ impl ShellOut {
}
impl ColorChoice {
/// Converts our color choice to termcolor's version.
fn to_termcolor_color_choice(self, stream: Stream) -> termcolor::ColorChoice {
/// Converts our color choice to anstream's version.
fn to_anstream_color_choice(self) -> anstream::ColorChoice {
match self {
ColorChoice::Always => termcolor::ColorChoice::Always,
ColorChoice::Never => termcolor::ColorChoice::Never,
ColorChoice::CargoAuto => {
if stream.is_terminal() {
termcolor::ColorChoice::Auto
} else {
termcolor::ColorChoice::Never
}
}
ColorChoice::Always => anstream::ColorChoice::Always,
ColorChoice::Never => anstream::ColorChoice::Never,
ColorChoice::CargoAuto => anstream::ColorChoice::Auto,
}
}
}
enum Stream {
Stdout,
Stderr,
}
impl Stream {
fn is_terminal(self) -> bool {
match self {
Self::Stdout => std::io::stdout().is_terminal(),
Self::Stderr => std::io::stderr().is_terminal(),
}
fn supports_color(choice: anstream::ColorChoice) -> bool {
match choice {
anstream::ColorChoice::Always
| anstream::ColorChoice::AlwaysAnsi
| anstream::ColorChoice::Auto => true,
anstream::ColorChoice::Never => false,
}
}