use std::io::IsTerminal;
use crate::types::ColorMode;
pub const RED: &str = "\x1b[31m";
pub const GREEN: &str = "\x1b[32m";
pub const YELLOW: &str = "\x1b[33m";
pub const BLUE: &str = "\x1b[34m";
pub const MAGENTA: &str = "\x1b[35m";
pub const CYAN: &str = "\x1b[36m";
pub const WHITE: &str = "\x1b[37m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const RESET: &str = "\x1b[0m";
pub const BRIGHT_RED: &str = "\x1b[91m";
pub const BRIGHT_GREEN: &str = "\x1b[92m";
pub const BRIGHT_YELLOW: &str = "\x1b[93m";
pub const BRIGHT_CYAN: &str = "\x1b[96m";
pub fn is_enabled(mode: ColorMode) -> bool {
match mode {
ColorMode::Always => true,
ColorMode::Never => false,
ColorMode::Auto => std::io::stdout().is_terminal(),
}
}
#[derive(Clone, Copy)]
pub struct Painter {
enabled: bool,
}
impl Painter {
pub fn new(mode: ColorMode) -> Self {
Self {
enabled: is_enabled(mode),
}
}
pub fn enabled(&self) -> bool {
self.enabled
}
pub fn error(&self, s: &str) -> String {
self.wrap(s, RED)
}
pub fn warn(&self, s: &str) -> String {
self.wrap(s, YELLOW)
}
pub fn ok(&self, s: &str) -> String {
self.wrap(s, GREEN)
}
pub fn info(&self, s: &str) -> String {
self.wrap(s, BLUE)
}
pub fn path(&self, s: &str) -> String {
self.wrap(s, CYAN)
}
pub fn header(&self, s: &str) -> String {
self.wrap(s, BOLD)
}
pub fn dim(&self, s: &str) -> String {
self.wrap(s, DIM)
}
pub fn symbol(&self, s: &str) -> String {
self.wrap(s, MAGENTA)
}
pub fn number(&self, n: impl std::fmt::Display) -> String {
self.wrap(&n.to_string(), BRIGHT_CYAN)
}
pub fn status_ok(&self, msg: &str) -> String {
format!("{} {}", self.ok("[OK]"), msg)
}
pub fn status_warn(&self, msg: &str) -> String {
format!("{} {}", self.warn("[WARN]"), msg)
}
pub fn status_error(&self, msg: &str) -> String {
format!("{} {}", self.error("[ERROR]"), msg)
}
pub fn status_info(&self, msg: &str) -> String {
format!("{} {}", self.info("[INFO]"), msg)
}
pub fn critical(&self, s: &str) -> String {
self.wrap(s, BRIGHT_RED)
}
pub fn high(&self, s: &str) -> String {
self.wrap(s, RED)
}
pub fn medium(&self, s: &str) -> String {
self.wrap(s, YELLOW)
}
pub fn low(&self, s: &str) -> String {
self.wrap(s, DIM)
}
pub fn wrap(&self, s: &str, code: &str) -> String {
if self.enabled {
format!("{code}{s}{RESET}")
} else {
s.to_string()
}
}
pub fn wrap_both(&self, s: &str, code1: &str, code2: &str) -> String {
if self.enabled {
format!("{code1}{code2}{s}{RESET}")
} else {
s.to_string()
}
}
}
pub fn paint(s: &str, code: &str, enabled: bool) -> String {
if enabled {
format!("{code}{s}{RESET}")
} else {
s.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_painter_disabled() {
let p = Painter { enabled: false };
assert_eq!(p.error("test"), "test");
assert_eq!(p.ok("test"), "test");
assert_eq!(p.path("test"), "test");
}
#[test]
fn test_painter_enabled() {
let p = Painter { enabled: true };
assert_eq!(p.error("test"), "\x1b[31mtest\x1b[0m");
assert_eq!(p.ok("test"), "\x1b[32mtest\x1b[0m");
assert_eq!(p.path("test"), "\x1b[36mtest\x1b[0m");
}
#[test]
fn test_status_prefixes() {
let p = Painter { enabled: true };
assert!(p.status_ok("done").contains("[OK]"));
assert!(p.status_warn("caution").contains("[WARN]"));
assert!(p.status_error("failed").contains("[ERROR]"));
}
#[test]
fn test_color_mode_detection() {
assert!(is_enabled(ColorMode::Always));
assert!(!is_enabled(ColorMode::Never));
}
}