use std::fmt::Display;
use std::io::{IsTerminal, Write};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ColorMode {
#[default]
Auto,
Always,
Never,
}
impl ColorMode {
pub fn enabled(self, is_terminal: bool) -> bool {
match self {
Self::Auto => is_terminal && std::env::var_os("NO_COLOR").is_none(),
Self::Always => true,
Self::Never => false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tone {
Title,
Muted,
Info,
Success,
Warning,
Error,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SymbolTheme {
#[default]
Unicode,
Ascii,
}
#[derive(Debug, Clone, Copy)]
pub struct Console {
color: bool,
theme: SymbolTheme,
}
impl Console {
pub fn new(color: ColorMode, is_terminal: bool) -> Self {
let enabled = color.enabled(is_terminal);
Self {
color: enabled,
theme: if enabled {
SymbolTheme::Unicode
} else {
SymbolTheme::Ascii
},
}
}
pub fn stdout(color: ColorMode) -> Self {
Self::new(color, std::io::stdout().is_terminal())
}
pub fn stderr(color: ColorMode) -> Self {
Self::new(color, std::io::stderr().is_terminal())
}
pub fn with_theme(mut self, theme: SymbolTheme) -> Self {
self.theme = theme;
self
}
pub fn color_enabled(self) -> bool {
self.color
}
pub fn symbol_theme(self) -> SymbolTheme {
self.theme
}
pub fn write_paint(
self,
tone: Tone,
value: impl Display,
writer: &mut (impl Write + ?Sized),
) -> std::io::Result<()> {
if !self.color {
write!(writer, "{value}")
} else {
let style = match tone {
Tone::Title => "\x1b[1;96m",
Tone::Muted => "\x1b[2m",
Tone::Info => "\x1b[36m",
Tone::Success => "\x1b[1;92m",
Tone::Warning => "\x1b[1;93m",
Tone::Error => "\x1b[1;91m",
};
write!(writer, "{style}{value}\x1b[0m")
}
}
pub fn paint(self, tone: Tone, value: impl Display) -> String {
let mut buf = Vec::new();
let _ = self.write_paint(tone, &value, &mut buf);
String::from_utf8(buf).unwrap_or_else(|_| value.to_string())
}
}