use std::io::IsTerminal;
use std::sync::atomic::{AtomicU8, Ordering};
const COLOR_OFF: u8 = 0;
const COLOR_ON: u8 = 1;
const COLOR_UNINITIALIZED: u8 = 2;
static COLOR_STATE: AtomicU8 = AtomicU8::new(COLOR_UNINITIALIZED);
pub fn init(color: bool) {
COLOR_STATE.store(if color { COLOR_ON } else { COLOR_OFF }, Ordering::Release);
}
fn auto_detect() -> bool {
if std::env::var_os("NO_COLOR").is_some() {
return false;
}
std::io::stdout().is_terminal()
}
fn enabled() -> bool {
match COLOR_STATE.load(Ordering::Acquire) {
COLOR_OFF => false,
COLOR_ON => true,
_ => {
let detected = if auto_detect() { COLOR_ON } else { COLOR_OFF };
match COLOR_STATE.compare_exchange(
COLOR_UNINITIALIZED,
detected,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => detected == COLOR_ON,
Err(current) => current == COLOR_ON,
}
}
}
}
pub fn success(msg: &str) -> String {
if enabled() {
format!("\x1b[38;5;83m\u{2714}\x1b[0m {msg}")
} else {
format!("[ok] {msg}")
}
}
pub fn fail(msg: &str) -> String {
if enabled() {
format!("\x1b[38;5;196m\u{2718}\x1b[0m {msg}")
} else {
format!("[error] {msg}")
}
}
pub fn step(msg: &str) -> String {
if enabled() {
format!("\x1b[38;5;45m\u{276f}\x1b[0m {msg}")
} else {
format!("> {msg}")
}
}
pub fn warn(msg: &str) -> String {
if enabled() {
format!("\x1b[38;5;208m\u{26a0}\x1b[0m {msg}")
} else {
format!("[warn] {msg}")
}
}
pub fn bold(s: &str) -> String {
if enabled() {
format!("\x1b[1m{s}\x1b[0m")
} else {
s.to_string()
}
}
pub fn dim(s: &str) -> String {
if enabled() {
format!("\x1b[2m{s}\x1b[0m")
} else {
s.to_string()
}
}
pub fn color(code: u8, s: &str) -> String {
if enabled() {
format!("\x1b[38;5;{code}m{s}\x1b[0m")
} else {
s.to_string()
}
}
pub fn human_size(bytes: u64) -> String {
if bytes < 1024 {
format!("{bytes} B")
} else if bytes < 1024 * 1024 {
format!("{:.2} KiB", bytes as f64 / 1024.0)
} else {
format!("{:.2} MiB", bytes as f64 / (1024.0 * 1024.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn forced_off_strips_ansi() {
init(false);
let s = success("ok");
assert!(
!s.contains("\x1b["),
"expected no ANSI when disabled, got {s:?}"
);
assert!(s.starts_with("[ok]"));
}
#[test]
fn human_size_chooses_units() {
init(false);
assert_eq!(human_size(0), "0 B");
assert_eq!(human_size(1024), "1.00 KiB");
assert_eq!(human_size(2_500_000), "2.38 MiB");
}
}