use std::io::IsTerminal;
use std::sync::atomic::{AtomicU8, Ordering};
static COLOR_OVERRIDE: AtomicU8 = AtomicU8::new(0);
pub fn set_color_override(value: Option<bool>) {
let code = match value {
None => 0,
Some(false) => 1,
Some(true) => 2,
};
COLOR_OVERRIDE.store(code, Ordering::Relaxed);
}
pub fn color_enabled_with(env: &dyn Fn(&str) -> Option<String>, stream_is_tty: bool) -> bool {
if env("NO_COLOR").is_some_and(|v| !v.is_empty()) {
return false;
}
if env("TERM").as_deref() == Some("dumb") {
return false;
}
stream_is_tty
}
fn color_enabled_for(stream_is_tty: bool) -> bool {
match COLOR_OVERRIDE.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => color_enabled_with(&|k| std::env::var(k).ok(), stream_is_tty),
}
}
pub fn color_enabled() -> bool {
color_enabled_for(std::io::stdout().is_terminal())
}
pub fn color_enabled_stderr() -> bool {
color_enabled_for(std::io::stderr().is_terminal())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color_enabled_rules() {
let env = |m: &'static [(&'static str, &'static str)]| {
move |k: &str| m.iter().find(|(n, _)| *n == k).map(|(_, v)| v.to_string())
};
assert!(color_enabled_with(&env(&[]), true));
assert!(!color_enabled_with(&env(&[("NO_COLOR", "1")]), true));
assert!(color_enabled_with(&env(&[("NO_COLOR", "")]), true));
assert!(!color_enabled_with(&env(&[("TERM", "dumb")]), true));
assert!(!color_enabled_with(&env(&[]), false));
}
#[test]
fn test_color_override_beats_a_tty_and_none_restores() {
set_color_override(Some(false));
assert!(!color_enabled_for(true));
set_color_override(None);
let plain = color_enabled_with(&|k| std::env::var(k).ok(), true);
assert_eq!(color_enabled_for(true), plain);
}
}