1 | const CLEARV: u8 = 0b0000_0000; |
2 | const BOLD: u8 = 0b0000_0001; |
3 | const UNDERLINE: u8 = 0b0000_0010; |
4 | const REVERSED: u8 = 0b0000_0100; |
5 | const ITALIC: u8 = 0b0000_1000; |
6 | const BLINK: u8 = 0b0001_0000; |
7 | const HIDDEN: u8 = 0b0010_0000; |
8 | const DIMMED: u8 = 0b0100_0000; |
9 | const STRIKETHROUGH: u8 = 0b1000_0000; |
10 | |
11 | static 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 | |
22 | pub static CLEAR: Style = Style(CLEARV); |
23 | |
24 | /// A combinatorial style such as bold, italics, dimmed, etc. |
25 | #[derive (Clone, Copy, PartialEq, Eq, Debug)] |
26 | pub struct Style(u8); |
27 | |
28 | #[derive (Clone, Copy, PartialEq, Eq, Debug)] |
29 | #[allow (missing_docs)] |
30 | pub enum Styles { |
31 | Clear, |
32 | Bold, |
33 | Dimmed, |
34 | Underline, |
35 | Reversed, |
36 | Italic, |
37 | Blink, |
38 | Hidden, |
39 | Strikethrough, |
40 | } |
41 | |
42 | impl 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 | |
89 | impl 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)] |
119 | mod 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 | |