From 98b3905ed198a773fde2bf2349d607eab7ed1d9f Mon Sep 17 00:00:00 2001 From: Amos Wenger Date: Fri, 12 Dec 2025 08:43:33 +0100 Subject: [PATCH] Strip ANSI when terminal lacks color --- Cargo.lock | 35 ++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/main.rs | 54 +++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4ab2e8..8f3cddc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,8 @@ dependencies = [ "log", "owo-colors", "serde_json", + "strip-ansi-escapes", + "supports-color", "toml_edit", ] @@ -84,6 +86,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "itoa" version = "1.0.15" @@ -185,6 +193,24 @@ dependencies = [ "serde_core", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + [[package]] name = "syn" version = "2.0.111" @@ -259,6 +285,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.7.14" diff --git a/Cargo.toml b/Cargo.toml index 623237d..ea07756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,5 +21,7 @@ log = { version = "0.4.29", features = ["std"] } owo-colors = "4.2.3" serde_json = "1.0.132" toml_edit = "0.23" +supports-color = "3.0.2" +strip-ansi-escapes = "0.2.1" [dev-dependencies] diff --git a/src/main.rs b/src/main.rs index 7127f58..bed2f5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use owo_colors::{OwoColorize, Style}; use std::os::unix::fs::PermissionsExt; use std::sync::mpsc; use std::{ + borrow::Cow, ffi::OsStr, fs, io::{self, BufRead, BufReader, Write}, @@ -11,10 +12,31 @@ use std::{ process::{Command, Stdio}, time::{Duration, Instant}, }; +use supports_color::{self, Stream as ColorStream}; use toml_edit::{Array, DocumentMut, Item, Table, Value, value}; mod readme; +fn terminal_supports_color(stream: ColorStream) -> bool { + supports_color::on_cached(stream).is_some() +} + +fn maybe_strip_bytes<'a>(data: &'a [u8], stream: ColorStream) -> Cow<'a, [u8]> { + if terminal_supports_color(stream) { + Cow::Borrowed(data) + } else { + Cow::Owned(strip_ansi_escapes::strip(data)) + } +} + +fn maybe_strip_str<'a>(line: &'a str, stream: ColorStream) -> Cow<'a, str> { + if terminal_supports_color(stream) { + Cow::Borrowed(line) + } else { + Cow::Owned(strip_ansi_escapes::strip_str(line)) + } +} + fn apply_color_env(cmd: &mut Command) { cmd.env("FORCE_COLOR", "1"); cmd.env("CARGO_TERM_COLOR", "always"); @@ -766,12 +788,14 @@ fn enqueue_rustfmt_jobs(sender: std::sync::mpsc::Sender, staged_files: &Sta ); if !output.status.success() { + let stderr_clean = maybe_strip_bytes(&output.stderr, ColorStream::Stderr); + let stdout_clean = maybe_strip_bytes(&output.stdout, ColorStream::Stdout); error!( "{} {}: rustfmt failed\n{}\n{}", "❌".red(), path.display().to_string().blue(), - String::from_utf8_lossy(&output.stderr).dimmed(), - String::from_utf8_lossy(&output.stdout).dimmed() + String::from_utf8_lossy(&stderr_clean).dimmed(), + String::from_utf8_lossy(&stdout_clean).dimmed() ); continue; } @@ -1051,15 +1075,13 @@ fn install_cargo_shear() { exit_with_command_failure(&binstall_command, &[], binstall_output, None); } -fn print_stream(label: &str, data: &[u8]) { +fn print_stream(label: &str, data: &[u8], stream: ColorStream) { if data.is_empty() { println!(" {}: ", label); } else { - println!( - " {}:\n{}", - label, - String::from_utf8_lossy(data).trim_end() - ); + let cleaned = maybe_strip_bytes(data, stream); + let text = String::from_utf8_lossy(&cleaned); + println!(" {}:\n{}", label, text.trim_end()); } } @@ -1083,8 +1105,8 @@ fn exit_with_command_failure( Some(code) => println!(" exit code: {}", code), None => println!(" exit code: terminated by signal"), } - print_stream("stdout", &output.stdout); - print_stream("stderr", &output.stderr); + print_stream("stdout", &output.stdout, ColorStream::Stdout); + print_stream("stderr", &output.stderr, ColorStream::Stderr); if let Some(action) = hint { action(); } @@ -1179,13 +1201,13 @@ fn run_command_with_streaming( // Process has finished, collect remaining output while let Ok(line) = stdout_rx.try_recv() { if streaming { - println!("{}", line); + println!("{}", maybe_strip_str(&line, ColorStream::Stdout)); } stdout_buffer.push(line); } while let Ok(line) = stderr_rx.try_recv() { if streaming { - eprintln!("{}", line); + eprintln!("{}", maybe_strip_str(&line, ColorStream::Stderr)); } stderr_buffer.push(line); } @@ -1223,10 +1245,10 @@ fn run_command_with_streaming( ); // Flush buffered output for line in &stdout_buffer { - println!("{}", line); + println!("{}", maybe_strip_str(line, ColorStream::Stdout)); } for line in &stderr_buffer { - eprintln!("{}", line); + eprintln!("{}", maybe_strip_str(line, ColorStream::Stderr)); } } @@ -1234,14 +1256,14 @@ fn run_command_with_streaming( let mut got_output = false; while let Ok(line) = stdout_rx.try_recv() { if streaming { - println!("{}", line); + println!("{}", maybe_strip_str(&line, ColorStream::Stdout)); } stdout_buffer.push(line); got_output = true; } while let Ok(line) = stderr_rx.try_recv() { if streaming { - eprintln!("{}", line); + eprintln!("{}", maybe_strip_str(&line, ColorStream::Stderr)); } stderr_buffer.push(line); got_output = true;