use std::io::IsTerminal;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum ColorChoice {
Auto,
Always,
Never,
}
#[derive(Debug, Clone, Copy)]
pub struct Term {
pub stdin_tty: bool,
pub stdout_tty: bool,
pub color: bool,
}
static TERM: OnceLock<Term> = OnceLock::new();
fn env_set(k: &str) -> bool {
std::env::var_os(k).map(|v| !v.is_empty()).unwrap_or(false)
}
pub fn init(choice: ColorChoice) -> Term {
let stderr_tty = std::io::stderr().is_terminal();
let color = match choice {
ColorChoice::Always => true,
ColorChoice::Never => false,
ColorChoice::Auto => {
if env_set("NO_COLOR") {
false
} else if env_set("FORCE_COLOR") || env_set("CLICOLOR_FORCE") {
true
} else {
stderr_tty && std::env::var("TERM").map(|t| t != "dumb").unwrap_or(true)
}
}
};
let stdout_tty = std::io::stdout().is_terminal();
let t = Term {
stdin_tty: std::io::stdin().is_terminal(),
stdout_tty,
color: color && (stdout_tty || choice == ColorChoice::Always),
};
let _ = TERM.set(t);
t
}
pub fn get() -> Term {
*TERM.get().unwrap_or(&Term { stdin_tty: false, stdout_tty: false, color: false })
}
pub fn paint(code: &str, s: &str) -> String {
if get().color {
format!("\x1b[{code}m{s}\x1b[0m")
} else {
s.to_string()
}
}
pub fn dim(s: &str) -> String {
paint("2", s)
}
pub fn red(s: &str) -> String {
paint("31", s)
}
pub fn green(s: &str) -> String {
paint("32", s)
}
pub fn yellow(s: &str) -> String {
paint("33", s)
}
pub fn bold(s: &str) -> String {
paint("1", s)
}