use std::sync::OnceLock;
const RESET: &str = "\x1b[0m";
const BOLD: &str = "\x1b[1m";
const DIM: &str = "\x1b[2m";
const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const BLUE: &str = "\x1b[34m";
const MAGENTA: &str = "\x1b[35m";
pub fn enabled() -> bool {
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| {
if std::env::var_os("NO_COLOR").is_some() {
return false;
}
if std::env::var("TERM").map(|t| t == "dumb").unwrap_or(false) {
return false;
}
unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 }
})
}
static FORCED_OFF: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn turn_off() {
FORCED_OFF.store(true, std::sync::atomic::Ordering::SeqCst);
}
fn active() -> bool {
!FORCED_OFF.load(std::sync::atomic::Ordering::SeqCst) && enabled()
}
fn wrap(codes: &str, text: &str) -> String {
if active() {
format!("{codes}{text}{RESET}")
} else {
text.to_string()
}
}
pub fn heading(text: &str) -> String {
wrap(BOLD, text)
}
pub fn faint(text: &str) -> String {
wrap(DIM, text)
}
pub fn state(name: &str, text: &str) -> String {
match state_codes(name) {
Some(codes) => wrap(codes, text),
None => text.to_string(),
}
}
fn state_codes(name: &str) -> Option<&'static str> {
Some(match name {
"running" => GREEN,
"starting" => GREEN,
"queued" => YELLOW,
"completed" => DIM,
"failed" => RED,
"oom" => RED,
"timeout" => MAGENTA,
"killed" => MAGENTA,
"cancelled" => DIM,
"skipped" => BLUE,
"expired" => BLUE,
_ => return None,
})
}
pub fn warning(text: &str) -> String {
wrap(RED, text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_command_with_no_terminal_writes_plain_text() {
assert_eq!(heading("ID"), "ID");
assert_eq!(faint("done"), "done");
assert_eq!(state("running", "running"), "running");
assert!(!warning("careful").contains('\x1b'));
}
#[test]
fn an_unknown_state_gives_the_text_back() {
assert_eq!(state("something-else", "text"), "text");
assert_eq!(state_codes("something-else"), None);
}
#[test]
fn every_state_that_qex_writes_has_a_colour() {
for name in [
"running",
"starting",
"queued",
"completed",
"failed",
"oom",
"timeout",
"killed",
"cancelled",
"skipped",
"expired",
] {
assert!(
state_codes(name).is_some(),
"the state `{name}` has no colour, so `qex top` writes it as plain text"
);
}
assert_eq!(
state_codes("expired"),
state_codes("skipped"),
"a job that expired and a job that was skipped both never ran"
);
assert_ne!(
state_codes("expired"),
state_codes("failed"),
"a job that expired never ran, and a job that failed did run"
);
assert_ne!(
state_codes("expired"),
state_codes("completed"),
"a job that expired gave no result"
);
}
}