hyper_scripter/
color.rs

1// I find ALL solutions on ctate.io not suitable. Let's hand-roll our own...
2// * colored: too many heap allocate
3// * ansi_term: unmaintained
4// * nu_ansi_term: too few people using
5
6use std::fmt::{Display, Error as FmtError, Formatter, Result as FmtResult};
7
8#[derive(Debug, Clone, Copy)]
9pub enum Color {
10    Black,
11    Red,
12    Green,
13    Yellow,
14    Blue,
15    Magenta,
16    Cyan,
17    White,
18    BrightBlack,
19    BrightRed,
20    BrightGreen,
21    BrightYellow,
22    BrightBlue,
23    BrightMagenta,
24    BrightCyan,
25    BrightWhite,
26}
27impl Color {
28    pub fn from(s: &str) -> Self {
29        match s {
30            "black" => Color::Black,
31            "red" => Color::Red,
32            "green" => Color::Green,
33            "yellow" => Color::Yellow,
34            "blue" => Color::Blue,
35            "magenta" => Color::Magenta,
36            "cyan" => Color::Cyan,
37            "white" => Color::White,
38            "bright black" => Color::BrightBlack,
39            "bright red" => Color::BrightRed,
40            "bright green" => Color::BrightGreen,
41            "bright yellow" => Color::BrightYellow,
42            "bright blue" => Color::BrightBlue,
43            "bright magenta" => Color::BrightMagenta,
44            "bright cyan" => Color::BrightCyan,
45            "bright white" => Color::BrightWhite,
46
47            _ => {
48                let ret = Color::White;
49                log::warn!("錯誤的顏色 {},改用 {:?}", s, ret);
50                ret
51            }
52        }
53    }
54}
55
56const BOLD: u8 = 0;
57const DIMMED: u8 = 1;
58const ITALIC: u8 = 2;
59const UNDERLINE: u8 = 3;
60
61#[derive(Default, Debug, Clone, Copy)]
62struct Style {
63    color: Option<Color>,
64    style_map: u8,
65}
66
67impl Style {
68    fn is_plain(&self) -> bool {
69        let Self { color, style_map } = self;
70        color.is_none() && *style_map == 0
71    }
72}
73
74pub struct StyleObj<T> {
75    obj: T,
76    style: Style,
77}
78
79impl<T> StyleObj<T> {
80    pub fn done(&self) -> () {
81        ()
82    }
83    pub fn bold(&mut self) -> &mut Self {
84        self.style.style_map |= 1 << BOLD;
85        self
86    }
87    pub fn dimmed(&mut self) -> &mut Self {
88        self.style.style_map |= 1 << DIMMED;
89        self
90    }
91    pub fn italic(&mut self) -> &mut Self {
92        self.style.style_map |= 1 << ITALIC;
93        self
94    }
95    pub fn underline(&mut self) -> &mut Self {
96        self.style.style_map |= 1 << UNDERLINE;
97        self
98    }
99    pub fn color(&mut self, color: Color) -> &mut Self {
100        self.style.color = Some(color);
101        self
102    }
103}
104
105pub trait Stylize<T> {
106    fn stylize(self) -> StyleObj<T>;
107}
108
109impl<T: Display> Stylize<T> for T {
110    fn stylize(self) -> StyleObj<T> {
111        StyleObj {
112            obj: self,
113            style: Default::default(),
114        }
115    }
116}
117
118fn fmt_stylemap(f: &mut Formatter<'_>, style_map: u8) -> Result<bool, FmtError> {
119    let mut first = true;
120
121    let mut my_write = |s: &'static str| -> FmtResult {
122        if !first {
123            write!(f, ";")?;
124        } else {
125            first = false;
126        }
127        write!(f, "{}", s)
128    };
129
130    if style_map & 1 << BOLD != 0 {
131        my_write("1")?;
132    }
133    if style_map & 1 << DIMMED != 0 {
134        my_write("2")?;
135    }
136    if style_map & 1 << ITALIC != 0 {
137        my_write("3")?;
138    }
139    if style_map & 1 << UNDERLINE != 0 {
140        my_write("4")?;
141    }
142
143    Ok(first)
144}
145fn fmt_color(f: &mut Formatter, color: Color) -> FmtResult {
146    let s = match color {
147        Color::Black => "30",
148        Color::Red => "31",
149        Color::Green => "32",
150        Color::Yellow => "33",
151        Color::Blue => "34",
152        Color::Magenta => "35",
153        Color::Cyan => "36",
154        Color::White => "37",
155        Color::BrightBlack => "90",
156        Color::BrightRed => "91",
157        Color::BrightGreen => "92",
158        Color::BrightYellow => "93",
159        Color::BrightBlue => "94",
160        Color::BrightMagenta => "95",
161        Color::BrightCyan => "96",
162        Color::BrightWhite => "97",
163    };
164    write!(f, "{}", s)
165}
166
167impl<T: Display> Display for StyleObj<T> {
168    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
169        if self.style.is_plain() {
170            return write!(f, "{}", self.obj);
171        }
172
173        write!(f, "\x1B[")?;
174        let first = fmt_stylemap(f, self.style.style_map)?;
175
176        if let Some(color) = self.style.color {
177            if !first {
178                write!(f, ";")?;
179            }
180            fmt_color(f, color)?;
181        }
182
183        write!(f, "m")?;
184        write!(f, "{}", self.obj)?;
185        write!(f, "\x1B[0m")
186    }
187}