use std::io::IsTerminal;
pub const HEADER: &str = "\x1b[1;32m";
pub const KEY: &str = "\x1b[36m";
pub const DIM: &str = "\x1b[2m";
pub const WARN: &str = "\x1b[1;33m";
pub const ERROR: &str = "\x1b[1;31m";
pub const NUM: &str = "\x1b[94m";
pub fn paint(s: &str, code: &str, on: bool) -> String {
if on {
format!("{code}{s}\x1b[0m")
} else {
s.to_string()
}
}
pub fn color_enabled(plain: bool) -> bool {
!plain && std::io::stdout().is_terminal() && std::env::var_os("NO_COLOR").is_none()
}
pub struct Painter {
pub color: bool,
}
impl Painter {
pub fn header(&self, s: &str) -> String {
paint(s, HEADER, self.color)
}
pub fn key(&self, s: &str) -> String {
paint(s, KEY, self.color)
}
pub fn dim(&self, s: &str) -> String {
paint(s, DIM, self.color)
}
pub fn warn(&self, s: &str) -> String {
paint(s, WARN, self.color)
}
pub fn num(&self, s: &str) -> String {
paint(s, NUM, self.color)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paint_on_wraps_with_reset() {
assert_eq!(paint("x", KEY, true), "\x1b[36mx\x1b[0m");
}
#[test]
fn paint_off_is_byte_plain() {
let s = paint("x", KEY, false);
assert_eq!(s, "x");
assert!(!s.contains('\x1b'));
}
#[test]
fn painter_off_leaks_no_ansi() {
let p = Painter { color: false };
for s in [
p.header("h"),
p.key("k"),
p.dim("d"),
p.warn("w"),
p.num("[1]"),
] {
assert!(!s.contains('\x1b'), "leaked ANSI: {s}");
}
}
#[test]
fn painter_on_uses_the_standard_codes() {
let p = Painter { color: true };
assert_eq!(p.header("h"), "\x1b[1;32mh\x1b[0m");
assert_eq!(p.key("k"), "\x1b[36mk\x1b[0m");
assert_eq!(p.dim("d"), "\x1b[2md\x1b[0m");
assert_eq!(p.warn("w"), "\x1b[1;33mw\x1b[0m");
assert_eq!(p.num("[1]"), "\x1b[94m[1]\x1b[0m");
}
}