use std::io::{IsTerminal, Write};
use std::sync::OnceLock;
pub fn status(msg: &str) {
emit(Tone::Status, msg);
tracing::info!("{msg}");
}
pub fn info(msg: &str) {
emit(Tone::Info, msg);
tracing::info!("{msg}");
}
pub fn warn(msg: &str) {
emit(Tone::Warn, msg);
tracing::warn!("{msg}");
}
pub fn header(msg: &str) {
eprintln!();
emit(Tone::Header, msg);
tracing::info!("{msg}");
}
pub fn error(msg: &str) {
emit(Tone::Error, msg);
tracing::error!("{msg}");
}
#[derive(Clone, Copy)]
enum Tone {
Status,
Info,
Warn,
Header,
Error,
}
fn emit(tone: Tone, msg: &str) {
let ansi = ansi_enabled();
let mut stderr = std::io::stderr().lock();
match tone {
Tone::Status => {
if ansi {
let _ = writeln!(stderr, "\x1b[2m{msg}\x1b[0m");
} else {
let _ = writeln!(stderr, "{msg}");
}
}
Tone::Info => {
let _ = writeln!(stderr, "{msg}");
}
Tone::Warn => {
if ansi {
let _ = writeln!(stderr, "\x1b[33mwarning:\x1b[0m {msg}");
} else {
let _ = writeln!(stderr, "warning: {msg}");
}
}
Tone::Header => {
if ansi {
let _ = writeln!(stderr, "\x1b[1m{msg}\x1b[0m");
} else {
let _ = writeln!(stderr, "{msg}");
}
}
Tone::Error => {
if ansi {
let _ = writeln!(stderr, "\x1b[31merror:\x1b[0m {msg}");
} else {
let _ = writeln!(stderr, "error: {msg}");
}
}
}
}
fn ansi_enabled() -> bool {
static CACHED: OnceLock<bool> = OnceLock::new();
*CACHED.get_or_init(|| {
if std::env::var_os("NO_COLOR").is_some() {
return false;
}
std::io::stderr().is_terminal()
})
}