use std::io::IsTerminal;
use std::sync::atomic::{AtomicBool, Ordering};
static COLOR_ENABLED: AtomicBool = AtomicBool::new(true);
pub fn init() {
COLOR_ENABLED.store(std::io::stdout().is_terminal(), Ordering::Relaxed);
}
fn enabled() -> bool {
COLOR_ENABLED.load(Ordering::Relaxed)
}
fn wrap(code: &str, text: &str) -> String {
if enabled() {
format!("\x1b[{code}m{text}\x1b[0m")
} else {
text.to_string()
}
}
pub fn short(text: &str) -> String {
wrap("91", text)
} pub fn mid(text: &str) -> String {
wrap("93", text)
} pub fn long(text: &str) -> String {
wrap("92", text)
}
pub fn dim(text: &str) -> String {
wrap("2", text)
}
pub fn bold(text: &str) -> String {
wrap("1", text)
}
pub fn cyan(text: &str) -> String {
wrap("96", text)
}
pub fn tier_color(tier: &str, text: &str) -> String {
match tier {
"short" => short(text),
"mid" => mid(text),
"long" => long(text),
_ => text.to_string(),
}
}
pub fn priority_bar(p: i32) -> String {
let filled = usize::try_from(p.clamp(1, 10)).expect("i32 as usize");
let empty = 10 - filled;
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
if p >= 8 {
wrap("92", &bar)
} else if p >= 5 {
wrap("93", &bar)
} else {
wrap("91", &bar)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn with_color_off<F: FnOnce()>(f: F) {
let _guard = TEST_LOCK.lock().unwrap();
COLOR_ENABLED.store(false, Ordering::Relaxed);
f();
COLOR_ENABLED.store(true, Ordering::Relaxed);
}
#[test]
fn tier_colors_no_ansi() {
with_color_off(|| {
assert_eq!(short("test"), "test");
assert_eq!(mid("test"), "test");
assert_eq!(long("test"), "test");
});
}
#[test]
fn semantic_colors_no_ansi() {
with_color_off(|| {
assert_eq!(dim("test"), "test");
assert_eq!(bold("test"), "test");
assert_eq!(cyan("test"), "test");
});
}
#[test]
fn tier_color_dispatch() {
with_color_off(|| {
assert_eq!(tier_color("short", "x"), "x");
assert_eq!(tier_color("mid", "x"), "x");
assert_eq!(tier_color("long", "x"), "x");
assert_eq!(tier_color("unknown", "x"), "x");
});
}
#[test]
fn priority_bar_length() {
with_color_off(|| {
let bar = priority_bar(5);
assert!(bar.contains("█"));
assert!(bar.contains("░"));
});
}
#[test]
fn priority_bar_clamps() {
with_color_off(|| {
let bar_min = priority_bar(0); let bar_max = priority_bar(15); assert!(bar_min.contains("░"));
assert!(!bar_max.contains("░")); });
}
#[test]
fn wrap_with_color_enabled() {
let _guard = TEST_LOCK.lock().unwrap();
COLOR_ENABLED.store(true, Ordering::Relaxed);
let result = wrap("91", "red");
assert!(result.contains("\x1b[91m"));
assert!(result.contains("\x1b[0m"));
assert!(result.contains("red"));
}
}