use std::{
fmt::{self, Display, Write},
io::IsTerminal,
};
#[derive(Copy, Clone, Debug)]
pub enum Format {
Reset,
Bold,
Faint,
#[allow(dead_code)]
Italic,
#[allow(dead_code)]
Underline,
#[allow(dead_code)]
Hex(&'static str),
#[allow(dead_code)]
None,
}
impl Display for Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Format::*;
match self {
Reset => write_escape_code(f, "0")?,
Bold => write_escape_code(f, "1")?,
Faint => write_escape_code(f, "2")?,
Italic => write_escape_code(f, "3")?,
Underline => write_escape_code(f, "4")?,
Hex(h) => hex(f, h)?,
None => {}
}
Ok(())
}
}
#[allow(dead_code)]
pub fn hex<W: Write>(w: W, hex: &str) -> fmt::Result {
if let Some((r, g, b)) = hex_to_ansi(hex) {
write_escape_code(w, format!("38;2;{r};{g};{b}"))?;
}
Ok(())
}
fn hex_to_ansi(mut hex: &str) -> Option<(u32, u32, u32)> {
if hex.starts_with('#') {
hex = &hex[1..];
}
u32::from_str_radix(hex, 16)
.ok()
.map(|val| ((val >> 16) & 255, (val >> 8) & 255, val & 255))
}
fn write_escape_code<S: Display, W: Write>(mut w: W, code: S) -> fmt::Result {
if std::env::var("NO_COLOR")
.unwrap_or("".to_string())
.is_empty()
&& std::io::stdout().is_terminal()
{
write!(w, "\x1b[{code}m")?;
}
Ok(())
}