use std::sync::atomic::{AtomicBool, Ordering};
use colored::ColoredString;
use colored::Colorize;
use colored::control;
static QUIET: AtomicBool = AtomicBool::new(false);
pub fn set_quiet(quiet: bool) {
QUIET.store(quiet, Ordering::SeqCst);
}
pub fn is_quiet() -> bool {
QUIET.load(Ordering::SeqCst)
}
pub fn init(no_color_flag: bool) {
if no_color_flag || env_is_set("NO_COLOR") || env_is_set("UPSKILL_NO_COLOR") || term_is_dumb() {
control::set_override(false);
} else if env_is_set("FORCE_COLOR") || env_is_set("CLICOLOR_FORCE") {
control::set_override(true);
}
}
fn env_is_set(name: &str) -> bool {
std::env::var_os(name).is_some_and(|v| !v.is_empty())
}
fn term_is_dumb() -> bool {
std::env::var("TERM").is_ok_and(|t| t == "dumb")
}
pub fn error_label(text: &str) -> ColoredString {
text.red().bold()
}
pub fn warn(text: &str) -> ColoredString {
text.yellow()
}
pub fn success(text: &str) -> ColoredString {
text.green()
}
pub fn name(text: &str) -> ColoredString {
text.bold()
}
pub fn dim(text: &str) -> ColoredString {
text.dimmed()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn with_env<F: FnOnce()>(vars: &[(&str, Option<&str>)], f: F) {
let _lock = ENV_LOCK.lock().unwrap();
let originals: Vec<_> = vars
.iter()
.map(|(k, _)| (*k, std::env::var(k).ok()))
.collect();
for (k, v) in vars {
unsafe {
match v {
Some(val) => std::env::set_var(k, val),
None => std::env::remove_var(k),
}
}
}
f();
for (k, original) in &originals {
unsafe {
match original {
Some(val) => std::env::set_var(k, val),
None => std::env::remove_var(k),
}
}
}
}
fn rendered(text: ColoredString) -> String {
format!("{text}")
}
fn has_ansi(s: &str) -> bool {
s.contains('\x1b')
}
#[test]
fn no_color_flag_disables() {
with_env(
&[
("NO_COLOR", None),
("UPSKILL_NO_COLOR", None),
("TERM", Some("xterm")),
("FORCE_COLOR", None),
("CLICOLOR_FORCE", None),
],
|| {
init(true);
assert!(!has_ansi(&rendered(error_label("error"))));
control::unset_override();
},
);
}
#[test]
fn no_color_env_disables() {
with_env(
&[
("NO_COLOR", Some("1")),
("UPSKILL_NO_COLOR", None),
("TERM", Some("xterm")),
("FORCE_COLOR", None),
("CLICOLOR_FORCE", None),
],
|| {
init(false);
assert!(!has_ansi(&rendered(error_label("error"))));
control::unset_override();
},
);
}
#[test]
fn upskill_no_color_disables() {
with_env(
&[
("NO_COLOR", None),
("UPSKILL_NO_COLOR", Some("1")),
("TERM", Some("xterm")),
("FORCE_COLOR", None),
("CLICOLOR_FORCE", None),
],
|| {
init(false);
assert!(!has_ansi(&rendered(error_label("error"))));
control::unset_override();
},
);
}
#[test]
fn term_dumb_disables() {
with_env(
&[
("NO_COLOR", None),
("UPSKILL_NO_COLOR", None),
("TERM", Some("dumb")),
("FORCE_COLOR", None),
("CLICOLOR_FORCE", None),
],
|| {
init(false);
assert!(!has_ansi(&rendered(error_label("error"))));
control::unset_override();
},
);
}
#[test]
fn force_color_re_enables_when_piped() {
with_env(
&[
("NO_COLOR", None),
("UPSKILL_NO_COLOR", None),
("TERM", Some("xterm")),
("FORCE_COLOR", Some("1")),
("CLICOLOR_FORCE", None),
],
|| {
init(false);
assert!(has_ansi(&rendered(error_label("error"))));
control::unset_override();
},
);
}
#[test]
fn empty_no_color_does_not_disable() {
with_env(
&[
("NO_COLOR", Some("")),
("UPSKILL_NO_COLOR", None),
("TERM", Some("xterm")),
("FORCE_COLOR", Some("1")),
("CLICOLOR_FORCE", None),
],
|| {
init(false);
assert!(has_ansi(&rendered(error_label("error"))));
control::unset_override();
},
);
}
}