Skip to main content

recolored/
style.rs

1const CLEARV: u8 = 0b0000_0000;
2const BOLD: u8 = 0b0000_0001;
3const UNDERLINE: u8 = 0b0000_0010;
4const REVERSED: u8 = 0b0000_0100;
5const ITALIC: u8 = 0b0000_1000;
6const BLINK: u8 = 0b0001_0000;
7const HIDDEN: u8 = 0b0010_0000;
8const DIMMED: u8 = 0b0100_0000;
9const STRIKETHROUGH: u8 = 0b1000_0000;
10
11static STYLES: [(u8, Styles); 8] = [
12    (BOLD, Styles::Bold),
13    (DIMMED, Styles::Dimmed),
14    (UNDERLINE, Styles::Underline),
15    (REVERSED, Styles::Reversed),
16    (ITALIC, Styles::Italic),
17    (BLINK, Styles::Blink),
18    (HIDDEN, Styles::Hidden),
19    (STRIKETHROUGH, Styles::Strikethrough),
20];
21
22pub static CLEAR: Style = Style(CLEARV);
23
24/// A combinatorial style such as bold, italics, dimmed, etc.
25#[derive(Clone, Copy, PartialEq, Eq, Debug)]
26pub struct Style(u8);
27
28#[derive(Clone, Copy, PartialEq, Eq, Debug)]
29#[allow(missing_docs)]
30pub enum Styles {
31    Clear,
32    Bold,
33    Dimmed,
34    Underline,
35    Reversed,
36    Italic,
37    Blink,
38    Hidden,
39    Strikethrough,
40}
41
42impl Styles {
43    fn to_str<'a>(self) -> &'a str {
44        match self {
45            Styles::Clear => "", // unreachable, but we don't want to panic
46            Styles::Bold => "1",
47            Styles::Dimmed => "2",
48            Styles::Italic => "3",
49            Styles::Underline => "4",
50            Styles::Blink => "5",
51            Styles::Reversed => "7",
52            Styles::Hidden => "8",
53            Styles::Strikethrough => "9",
54        }
55    }
56
57    fn to_u8(self) -> u8 {
58        match self {
59            Styles::Clear => CLEARV,
60            Styles::Bold => BOLD,
61            Styles::Dimmed => DIMMED,
62            Styles::Italic => ITALIC,
63            Styles::Underline => UNDERLINE,
64            Styles::Blink => BLINK,
65            Styles::Reversed => REVERSED,
66            Styles::Hidden => HIDDEN,
67            Styles::Strikethrough => STRIKETHROUGH,
68        }
69    }
70
71    fn from_u8(u: u8) -> Option<Vec<Styles>> {
72        if u == CLEARV {
73            return None;
74        }
75
76        let res: Vec<Styles> = STYLES
77            .iter()
78            .filter(|&&(ref mask, _)| (0 != (u & mask)))
79            .map(|&(_, value)| value)
80            .collect();
81        if res.is_empty() {
82            None
83        } else {
84            Some(res)
85        }
86    }
87}
88
89impl Style {
90    /// Check if the current style has one of [`Styles`](Styles) switched on.
91    ///
92    /// ```rust
93    /// # use colored::*;
94    /// let colored = "".bold().italic();
95    /// assert_eq!(colored.style().contains(Styles::Bold), true);
96    /// assert_eq!(colored.style().contains(Styles::Italic), true);
97    /// assert_eq!(colored.style().contains(Styles::Dimmed), false);
98    /// ```
99    pub fn contains(&self, style: Styles) -> bool {
100        let s = style.to_u8();
101        self.0 & s == s
102    }
103
104    pub(crate) fn to_str(self) -> String {
105        let styles = Styles::from_u8(self.0).unwrap_or_default();
106        styles
107            .iter()
108            .map(|s| s.to_str())
109            .collect::<Vec<&str>>()
110            .join(";")
111    }
112
113    pub(crate) fn add(&mut self, two: Styles) {
114        self.0 |= two.to_u8();
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    mod u8_to_styles_invalid_is_none {
123        use super::super::CLEARV;
124        use super::super::{Style, Styles};
125
126        #[test]
127        fn empty_is_none() {
128            assert_eq!(None, Styles::from_u8(CLEARV))
129        }
130    }
131
132    mod u8_to_styles_isomorphism {
133        use super::super::Styles;
134        use super::super::{
135            BLINK, BOLD, DIMMED, HIDDEN, ITALIC, REVERSED, STRIKETHROUGH, UNDERLINE,
136        };
137
138        macro_rules! value_isomorph {
139            ($name:ident, $value:expr) => {
140                #[test]
141                fn $name() {
142                    let u = Styles::from_u8($value);
143                    assert!(
144                        u.is_some(),
145                        "{}: Styles::from_u8 -> None",
146                        stringify!($value)
147                    );
148                    let u = u.unwrap();
149                    assert!(
150                        u.len() == 1,
151                        "{}: Styles::from_u8 found {} styles (expected 1)",
152                        stringify!($value),
153                        u.len()
154                    );
155                    assert!(
156                        u[0].to_u8() == $value,
157                        "{}: to_u8() doesn't match its const value",
158                        stringify!($value)
159                    );
160                }
161            };
162        }
163
164        value_isomorph!(bold, BOLD);
165        value_isomorph!(underline, UNDERLINE);
166        value_isomorph!(reversed, REVERSED);
167        value_isomorph!(italic, ITALIC);
168        value_isomorph!(blink, BLINK);
169        value_isomorph!(hidden, HIDDEN);
170        value_isomorph!(dimmed, DIMMED);
171        value_isomorph!(strikethrough, STRIKETHROUGH);
172    }
173
174    mod styles_combine_complex {
175        use super::super::Styles::*;
176        use super::super::{Style, Styles};
177        use super::super::{
178            BLINK, BOLD, DIMMED, HIDDEN, ITALIC, REVERSED, STRIKETHROUGH, UNDERLINE,
179        };
180
181        fn style_from_multiples(styles: &[Styles]) -> Style {
182            let mut res = Style(styles[0].to_u8());
183            for s in &styles[1..] {
184                res = Style(res.0 | s.to_u8());
185            }
186            res
187        }
188
189        macro_rules! test_aggreg {
190            ($styles:expr, $expect:expr) => {{
191                let v = style_from_multiples($styles);
192                let r = Styles::from_u8(v.0).expect("should find styles");
193                assert_eq!(&$expect as &[Styles], &r[..])
194            }};
195        }
196
197        #[test]
198        fn aggreg1() {
199            let styles: &[Styles] = &[Bold, Bold, Bold];
200            test_aggreg!(styles, [Bold])
201        }
202
203        #[test]
204        fn aggreg2() {
205            let styles: &[Styles] = &[Italic, Italic, Bold, Bold];
206            test_aggreg!(styles, [Bold, Italic])
207        }
208
209        #[test]
210        fn aggreg3() {
211            let styles: &[Styles] = &[Bold, Italic, Bold];
212            test_aggreg!(styles, [Bold, Italic])
213        }
214
215        macro_rules! test_combine {
216            ($styles:expr) => {{
217                let v = style_from_multiples($styles);
218                let r = Styles::from_u8(v.0).expect("should find styles");
219                assert_eq!($styles, &r[..])
220            }};
221        }
222
223        #[test]
224        fn two1() {
225            let s: &[Styles] = &[Bold, Underline];
226            test_combine!(s)
227        }
228
229        #[test]
230        fn two2() {
231            let s: &[Styles] = &[Underline, Italic];
232            test_combine!(s)
233        }
234
235        #[test]
236        fn two3() {
237            let s: &[Styles] = &[Bold, Italic];
238            test_combine!(s)
239        }
240
241        #[test]
242        fn three1() {
243            let s: &[Styles] = &[Bold, Underline, Italic];
244            test_combine!(s)
245        }
246
247        #[test]
248        fn three2() {
249            let s: &[Styles] = &[Dimmed, Underline, Italic];
250            test_combine!(s)
251        }
252
253        #[test]
254        fn four() {
255            let s: &[Styles] = &[Dimmed, Underline, Italic, Hidden];
256            test_combine!(s)
257        }
258
259        #[test]
260        fn five() {
261            let s: &[Styles] = &[Dimmed, Underline, Italic, Blink, Hidden];
262            test_combine!(s)
263        }
264
265        #[test]
266        fn six() {
267            let s: &[Styles] = &[Bold, Dimmed, Underline, Italic, Blink, Hidden];
268            test_combine!(s)
269        }
270
271        #[test]
272        fn all() {
273            let s: &[Styles] = &[
274                Bold,
275                Dimmed,
276                Underline,
277                Reversed,
278                Italic,
279                Blink,
280                Hidden,
281                Strikethrough,
282            ];
283            test_combine!(s)
284        }
285    }
286
287    fn test_style_contains() {
288        let mut style = Style(Styles::Bold.to_u8());
289        style.add(Styles::Italic);
290
291        assert_eq!(style.contains(Styles::Bold), true);
292        assert_eq!(style.contains(Styles::Italic), true);
293        assert_eq!(style.contains(Styles::Dimmed), false);
294    }
295}