use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Style {
fg_color: Option<Color>,
bg_color: Option<Color>,
bold: bool,
dim: bool,
underlined: bool,
italic: bool,
blink: bool,
reverse: bool,
bright: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Color {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
impl Style {
#[inline]
pub const fn new() -> Self {
Style {
fg_color: None,
bg_color: None,
bold: false,
dim: false,
underlined: false,
italic: false,
blink: false,
reverse: false,
bright: false,
}
}
#[inline]
pub const fn black(mut self) -> Self {
self.fg_color = Some(Color::Black);
self
}
#[inline]
pub const fn red(mut self) -> Self {
self.fg_color = Some(Color::Red);
self
}
#[inline]
pub const fn green(mut self) -> Self {
self.fg_color = Some(Color::Green);
self
}
#[inline]
pub const fn yellow(mut self) -> Self {
self.fg_color = Some(Color::Yellow);
self
}
#[inline]
pub const fn blue(mut self) -> Self {
self.fg_color = Some(Color::Blue);
self
}
#[inline]
pub const fn magenta(mut self) -> Self {
self.fg_color = Some(Color::Magenta);
self
}
#[inline]
pub const fn cyan(mut self) -> Self {
self.fg_color = Some(Color::Cyan);
self
}
#[inline]
pub const fn white(mut self) -> Self {
self.fg_color = Some(Color::White);
self
}
#[inline]
pub const fn on_black(mut self) -> Self {
self.bg_color = Some(Color::Black);
self
}
#[inline]
pub const fn on_red(mut self) -> Self {
self.bg_color = Some(Color::Red);
self
}
#[inline]
pub const fn on_green(mut self) -> Self {
self.bg_color = Some(Color::Green);
self
}
#[inline]
pub const fn on_yellow(mut self) -> Self {
self.bg_color = Some(Color::Yellow);
self
}
#[inline]
pub const fn on_blue(mut self) -> Self {
self.bg_color = Some(Color::Blue);
self
}
#[inline]
pub const fn on_magenta(mut self) -> Self {
self.bg_color = Some(Color::Magenta);
self
}
#[inline]
pub const fn on_cyan(mut self) -> Self {
self.bg_color = Some(Color::Cyan);
self
}
#[inline]
pub const fn on_white(mut self) -> Self {
self.bg_color = Some(Color::White);
self
}
#[inline]
pub const fn bold(mut self) -> Self {
self.bold = true;
self
}
#[allow(dead_code)]
#[inline]
pub const fn dim(mut self) -> Self {
self.dim = true;
self
}
#[inline]
pub const fn underlined(mut self) -> Self {
self.underlined = true;
self
}
#[inline]
pub const fn italic(mut self) -> Self {
self.italic = true;
self
}
#[inline]
pub const fn blink(mut self) -> Self {
self.blink = true;
self
}
#[inline]
pub const fn reverse(mut self) -> Self {
self.reverse = true;
self
}
#[inline]
pub const fn bright(mut self) -> Self {
self.bright = true;
self
}
pub fn apply_to<'a>(&self, text: &'a str) -> StyledText<'a> {
StyledText { text, style: *self }
}
fn to_ansi_codes(self) -> String {
if self.is_empty() {
return String::new();
}
let mut codes = Vec::new();
if self.bold {
codes.push("1");
}
if self.dim {
codes.push("2");
}
if self.italic {
codes.push("3");
}
if self.underlined {
codes.push("4");
}
if self.blink {
codes.push("5");
}
if self.reverse {
codes.push("7");
}
if let Some(fg) = self.fg_color {
codes.push(match fg {
Color::Black if self.bright => "90",
Color::Black => "30",
Color::Red if self.bright => "91",
Color::Green if self.bright => "92",
Color::Yellow if self.bright => "93",
Color::Blue if self.bright => "94",
Color::Magenta if self.bright => "95",
Color::Cyan if self.bright => "96",
Color::White if self.bright => "97",
Color::Red => "31",
Color::Green => "32",
Color::Yellow => "33",
Color::Blue => "34",
Color::Magenta => "35",
Color::Cyan => "36",
Color::White => "37",
});
}
if let Some(bg) = self.bg_color {
codes.push(match bg {
Color::Black => "40",
Color::Red => "41",
Color::Green => "42",
Color::Yellow => "43",
Color::Blue => "44",
Color::Magenta => "45",
Color::Cyan => "46",
Color::White => "47",
});
}
if codes.is_empty() {
String::new()
} else {
format!("\x1b[{}m", codes.join(";"))
}
}
const fn is_empty(&self) -> bool {
self.fg_color.is_none()
&& self.bg_color.is_none()
&& !self.bold
&& !self.dim
&& !self.underlined
&& !self.italic
&& !self.blink
&& !self.reverse
}
}
pub struct StyledText<'a> {
text: &'a str,
style: Style,
}
impl<'a> fmt::Display for StyledText<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.style.is_empty() {
write!(f, "{}", self.text)
} else {
write!(f, "{}{}\x1b[0m", self.style.to_ansi_codes(), self.text)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_colors() {
let style = Style::new().red();
assert_eq!(style.to_ansi_codes(), "\x1b[31m");
let style = Style::new().green();
assert_eq!(style.to_ansi_codes(), "\x1b[32m");
let style = Style::new().blue();
assert_eq!(style.to_ansi_codes(), "\x1b[34m");
}
#[test]
fn test_bright_colors() {
let style = Style::new().bright().black();
assert_eq!(style.to_ansi_codes(), "\x1b[90m");
let style = Style::new().bright().red();
assert_eq!(style.to_ansi_codes(), "\x1b[91m");
let style = Style::new().bright().green();
assert_eq!(style.to_ansi_codes(), "\x1b[92m");
}
#[test]
fn test_background_colors() {
let style = Style::new().on_red();
assert_eq!(style.to_ansi_codes(), "\x1b[41m");
let style = Style::new().on_green();
assert_eq!(style.to_ansi_codes(), "\x1b[42m");
}
#[test]
fn test_text_attributes() {
let style = Style::new().bold();
assert_eq!(style.to_ansi_codes(), "\x1b[1m");
let style = Style::new().underlined();
assert_eq!(style.to_ansi_codes(), "\x1b[4m");
let style = Style::new().italic();
assert_eq!(style.to_ansi_codes(), "\x1b[3m");
}
#[test]
fn test_combined_styles() {
let style = Style::new().bold().red();
assert_eq!(style.to_ansi_codes(), "\x1b[1;31m");
let style = Style::new().bold().underlined().green();
assert_eq!(style.to_ansi_codes(), "\x1b[1;4;32m");
let style = Style::new().red().on_blue();
assert_eq!(style.to_ansi_codes(), "\x1b[31;44m");
}
#[test]
fn test_apply_to() {
let style = Style::new().red();
let styled = style.apply_to("hello");
assert_eq!(format!("{}", styled), "\x1b[31mhello\x1b[0m");
}
#[test]
fn test_empty_style() {
let style = Style::new();
assert_eq!(style.to_ansi_codes(), "");
let styled = style.apply_to("hello");
assert_eq!(format!("{}", styled), "hello");
}
}