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(|&(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::Styles;
124 use super::super::CLEARV;
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
178 fn style_from_multiples(styles: &[Styles]) -> Style {
179 let mut res = Style(styles[0].to_u8());
180 for s in &styles[1..] {
181 res = Style(res.0 | s.to_u8());
182 }
183 res
184 }
185
186 macro_rules! test_aggreg {
187 ($styles:expr, $expect:expr) => {{
188 let v = style_from_multiples($styles);
189 let r = Styles::from_u8(v.0).expect("should find styles");
190 assert_eq!(&$expect as &[Styles], &r[..])
191 }};
192 }
193
194 #[test]
195 fn aggreg1() {
196 let styles: &[Styles] = &[Bold, Bold, Bold];
197 test_aggreg!(styles, [Bold])
198 }
199
200 #[test]
201 fn aggreg2() {
202 let styles: &[Styles] = &[Italic, Italic, Bold, Bold];
203 test_aggreg!(styles, [Bold, Italic])
204 }
205
206 #[test]
207 fn aggreg3() {
208 let styles: &[Styles] = &[Bold, Italic, Bold];
209 test_aggreg!(styles, [Bold, Italic])
210 }
211
212 macro_rules! test_combine {
213 ($styles:expr) => {{
214 let v = style_from_multiples($styles);
215 let r = Styles::from_u8(v.0).expect("should find styles");
216 assert_eq!($styles, &r[..])
217 }};
218 }
219
220 #[test]
221 fn two1() {
222 let s: &[Styles] = &[Bold, Underline];
223 test_combine!(s)
224 }
225
226 #[test]
227 fn two2() {
228 let s: &[Styles] = &[Underline, Italic];
229 test_combine!(s)
230 }
231
232 #[test]
233 fn two3() {
234 let s: &[Styles] = &[Bold, Italic];
235 test_combine!(s)
236 }
237
238 #[test]
239 fn three1() {
240 let s: &[Styles] = &[Bold, Underline, Italic];
241 test_combine!(s)
242 }
243
244 #[test]
245 fn three2() {
246 let s: &[Styles] = &[Dimmed, Underline, Italic];
247 test_combine!(s)
248 }
249
250 #[test]
251 fn four() {
252 let s: &[Styles] = &[Dimmed, Underline, Italic, Hidden];
253 test_combine!(s)
254 }
255
256 #[test]
257 fn five() {
258 let s: &[Styles] = &[Dimmed, Underline, Italic, Blink, Hidden];
259 test_combine!(s)
260 }
261
262 #[test]
263 fn six() {
264 let s: &[Styles] = &[Bold, Dimmed, Underline, Italic, Blink, Hidden];
265 test_combine!(s)
266 }
267
268 #[test]
269 fn all() {
270 let s: &[Styles] = &[
271 Bold,
272 Dimmed,
273 Underline,
274 Reversed,
275 Italic,
276 Blink,
277 Hidden,
278 Strikethrough,
279 ];
280 test_combine!(s)
281 }
282 }
283
284 #[test]
285 fn test_style_contains() {
286 let mut style = Style(Styles::Bold.to_u8());
287 style.add(Styles::Italic);
288
289 assert_eq!(style.contains(Styles::Bold), true);
290 assert_eq!(style.contains(Styles::Italic), true);
291 assert_eq!(style.contains(Styles::Dimmed), false);
292 }
293}
294