#[derive(Clone, Copy, Debug)]
struct Sgr(u8);
#[allow(dead_code)]
impl Sgr {
const RESET: Sgr = Sgr(0);
const BOLD: Sgr = Sgr(1);
const DIM: Sgr = Sgr(2);
const RED: Sgr = Sgr(31);
const GREEN: Sgr = Sgr(32);
const YELLOW: Sgr = Sgr(33);
const BLUE: Sgr = Sgr(34);
const MAGENTA: Sgr = Sgr(35);
const CYAN: Sgr = Sgr(36);
}
#[derive(Clone, Copy, Debug)]
pub enum Style {
Severity(Severity),
Code,
Caret(Severity),
Location,
Dim,
}
#[derive(Clone, Copy, Debug)]
pub enum Severity {
Error,
Warning,
Note,
Help,
}
#[derive(Clone, Copy, Debug)]
pub struct Palette {
styled: bool,
}
impl Palette {
pub const fn plain() -> Palette {
Palette { styled: false }
}
pub const fn styled() -> Palette {
Palette { styled: true }
}
pub fn from_enabled(enabled: bool) -> Palette {
if enabled {
Palette::styled()
} else {
Palette::plain()
}
}
#[must_use]
pub fn is_styled(self) -> bool {
self.styled
}
#[must_use]
pub fn paint(&self, style: Style, text: &str) -> String {
if !self.styled {
return text.to_string();
}
let codes = self.codes(style);
if codes.is_empty() {
return text.to_string();
}
let mut out = String::with_capacity(text.len() + codes.len() * 4 + 5);
write_codes(&mut out, &codes);
out.push_str(text);
write_codes(&mut out, &[Sgr::RESET]);
out
}
fn codes(&self, style: Style) -> Vec<Sgr> {
match style {
Style::Severity(Severity::Error) => vec![Sgr::BOLD, Sgr::RED],
Style::Severity(Severity::Warning) => vec![Sgr::BOLD, Sgr::YELLOW],
Style::Severity(Severity::Note) => vec![Sgr::BOLD, Sgr::BLUE],
Style::Severity(Severity::Help) => vec![Sgr::BOLD, Sgr::CYAN],
Style::Code => vec![Sgr::BOLD],
Style::Caret(Severity::Error) => vec![Sgr::RED],
Style::Caret(Severity::Warning) => vec![Sgr::YELLOW],
Style::Caret(Severity::Note) => vec![Sgr::BLUE],
Style::Caret(Severity::Help) => vec![Sgr::CYAN],
Style::Location | Style::Dim => vec![Sgr::DIM],
}
}
}
fn write_codes(out: &mut String, codes: &[Sgr]) {
use std::fmt::Write;
out.push_str("\x1b[");
for (i, c) in codes.iter().enumerate() {
if i > 0 {
out.push(';');
}
let _ = write!(out, "{}", c.0);
}
out.push('m');
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_palette_emits_no_ansi() {
let p = Palette::plain();
assert_eq!(p.paint(Style::Severity(Severity::Error), "boom"), "boom");
assert_eq!(p.paint(Style::Code, "[Y001]"), "[Y001]");
}
#[test]
fn styled_palette_wraps_text() {
let p = Palette::styled();
assert_eq!(
p.paint(Style::Severity(Severity::Error), "boom"),
"\x1b[1;31mboom\x1b[0m"
);
assert_eq!(p.paint(Style::Code, "[Y001]"), "\x1b[1m[Y001]\x1b[0m");
assert_eq!(
p.paint(Style::Location, "f.px:1:0"),
"\x1b[2mf.px:1:0\x1b[0m"
);
}
#[test]
fn from_enabled_toggles() {
assert!(Palette::from_enabled(true).is_styled());
assert!(!Palette::from_enabled(false).is_styled());
}
}