1use parking_lot::Once;
2use std::fmt::{self, Formatter};
3use std::sync::atomic::{AtomicBool, Ordering};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use crate::style::{parse_next_u8, Color};
9
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
16pub enum Colored {
17 ForegroundColor(Color),
19 BackgroundColor(Color),
21 UnderlineColor(Color),
24}
25
26static ANSI_COLOR_DISABLED: AtomicBool = AtomicBool::new(false);
27static INITIALIZER: Once = Once::new();
28
29impl Colored {
30 pub fn parse_ansi(ansi: &str) -> Option<Self> {
50 use Colored::{BackgroundColor, ForegroundColor, UnderlineColor};
51
52 let values = &mut ansi.split(';');
53
54 let output = match parse_next_u8(values)? {
55 38 => return Color::parse_ansi_iter(values).map(ForegroundColor),
56 48 => return Color::parse_ansi_iter(values).map(BackgroundColor),
57 58 => return Color::parse_ansi_iter(values).map(UnderlineColor),
58
59 39 => ForegroundColor(Color::Reset),
60 49 => BackgroundColor(Color::Reset),
61 59 => UnderlineColor(Color::Reset),
62
63 _ => return None,
64 };
65
66 if values.next().is_some() {
67 return None;
68 }
69
70 Some(output)
71 }
72
73 pub fn ansi_color_disabled() -> bool {
76 !std::env::var("NO_COLOR")
77 .unwrap_or("".to_string())
78 .is_empty()
79 }
80
81 pub fn ansi_color_disabled_memoized() -> bool {
82 INITIALIZER.call_once(|| {
83 ANSI_COLOR_DISABLED.store(Self::ansi_color_disabled(), Ordering::SeqCst);
84 });
85
86 ANSI_COLOR_DISABLED.load(Ordering::SeqCst)
87 }
88
89 pub fn set_ansi_color_disabled(val: bool) {
90 _ = Self::ansi_color_disabled_memoized();
92 ANSI_COLOR_DISABLED.store(val, Ordering::SeqCst);
93 }
94}
95
96impl fmt::Display for Colored {
97 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98 let color;
99
100 if Self::ansi_color_disabled_memoized() {
101 return Ok(());
102 }
103
104 match *self {
105 Colored::ForegroundColor(new_color) => {
106 if new_color == Color::Reset {
107 return f.write_str("39");
108 } else {
109 f.write_str("38;")?;
110 color = new_color;
111 }
112 }
113 Colored::BackgroundColor(new_color) => {
114 if new_color == Color::Reset {
115 return f.write_str("49");
116 } else {
117 f.write_str("48;")?;
118 color = new_color;
119 }
120 }
121 Colored::UnderlineColor(new_color) => {
122 if new_color == Color::Reset {
123 return f.write_str("59");
124 } else {
125 f.write_str("58;")?;
126 color = new_color;
127 }
128 }
129 }
130
131 match color {
132 Color::Black => f.write_str("5;0"),
133 Color::DarkGrey => f.write_str("5;8"),
134 Color::Red => f.write_str("5;9"),
135 Color::DarkRed => f.write_str("5;1"),
136 Color::Green => f.write_str("5;10"),
137 Color::DarkGreen => f.write_str("5;2"),
138 Color::Yellow => f.write_str("5;11"),
139 Color::DarkYellow => f.write_str("5;3"),
140 Color::Blue => f.write_str("5;12"),
141 Color::DarkBlue => f.write_str("5;4"),
142 Color::Magenta => f.write_str("5;13"),
143 Color::DarkMagenta => f.write_str("5;5"),
144 Color::Cyan => f.write_str("5;14"),
145 Color::DarkCyan => f.write_str("5;6"),
146 Color::White => f.write_str("5;15"),
147 Color::Grey => f.write_str("5;7"),
148 Color::Rgb { r, g, b } => write!(f, "2;{r};{g};{b}"),
149 Color::AnsiValue(val) => write!(f, "5;{val}"),
150 _ => Ok(()),
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use crate::style::{Color, Colored};
158
159 fn check_format_color(colored: Colored, expected: &str) {
160 Colored::set_ansi_color_disabled(true);
161 assert_eq!(colored.to_string(), "");
162 Colored::set_ansi_color_disabled(false);
163 assert_eq!(colored.to_string(), expected);
164 }
165
166 #[test]
167 fn test_format_fg_color() {
168 let colored = Colored::ForegroundColor(Color::Red);
169 check_format_color(colored, "38;5;9");
170 }
171
172 #[test]
173 fn test_format_bg_color() {
174 let colored = Colored::BackgroundColor(Color::Red);
175 check_format_color(colored, "48;5;9");
176 }
177
178 #[test]
179 fn test_format_reset_fg_color() {
180 let colored = Colored::ForegroundColor(Color::Reset);
181 check_format_color(colored, "39");
182 }
183
184 #[test]
185 fn test_format_reset_bg_color() {
186 let colored = Colored::BackgroundColor(Color::Reset);
187 check_format_color(colored, "49");
188 }
189
190 #[test]
191 fn test_format_fg_rgb_color() {
192 let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 });
193 check_format_color(colored, "48;2;1;2;3");
194 }
195
196 #[test]
197 fn test_format_fg_ansi_color() {
198 let colored = Colored::ForegroundColor(Color::AnsiValue(255));
199 check_format_color(colored, "38;5;255");
200 }
201
202 #[test]
203 fn test_parse_ansi_fg() {
204 test_parse_ansi(Colored::ForegroundColor)
205 }
206
207 #[test]
208 fn test_parse_ansi_bg() {
209 test_parse_ansi(Colored::ForegroundColor)
210 }
211
212 fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) {
214 macro_rules! test {
216 ($color:expr) => {
217 let colored = bg_or_fg($color);
218 assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored));
219 };
220 }
221
222 use Color::*;
223
224 test!(Reset);
225 test!(Black);
226 test!(DarkGrey);
227 test!(Red);
228 test!(DarkRed);
229 test!(Green);
230 test!(DarkGreen);
231 test!(Yellow);
232 test!(DarkYellow);
233 test!(Blue);
234 test!(DarkBlue);
235 test!(Magenta);
236 test!(DarkMagenta);
237 test!(Cyan);
238 test!(DarkCyan);
239 test!(White);
240 test!(Grey);
241
242 for n in 16..=255 {
244 test!(AnsiValue(n));
245 }
246
247 for r in 0..=255 {
248 for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() {
249 for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() {
250 test!(Rgb { r, g, b });
251 }
252 }
253 }
254 }
255
256 #[test]
257 fn test_parse_invalid_ansi_color() {
258 fn test(s: &str) {
260 assert_eq!(Colored::parse_ansi(s), None);
261 }
262 test("");
263 test(";");
264 test(";;");
265 test(";;");
266 test("0");
267 test("1");
268 test("12");
269 test("100");
270 test("100048949345");
271 test("39;");
272 test("49;");
273 test("39;2");
274 test("49;2");
275 test("38");
276 test("38;");
277 test("38;0");
278 test("38;5");
279 test("38;5;0;");
280 test("38;5;0;2");
281 test("38;5;80;");
282 test("38;5;80;2");
283 test("38;5;257");
284 test("38;2");
285 test("38;2;");
286 test("38;2;0");
287 test("38;2;0;2");
288 test("38;2;0;2;257");
289 test("38;2;0;2;25;");
290 test("38;2;0;2;25;3");
291 test("48");
292 test("48;");
293 test("48;0");
294 test("48;5");
295 test("48;5;0;");
296 test("48;5;0;2");
297 test("48;5;80;");
298 test("48;5;80;2");
299 test("48;5;257");
300 test("48;2");
301 test("48;2;");
302 test("48;2;0");
303 test("48;2;0;2");
304 test("48;2;0;2;257");
305 test("48;2;0;2;25;");
306 test("48;2;0;2;25;3");
307 }
308
309 #[test]
310 fn test_no_color() {
311 std::env::set_var("NO_COLOR", "1");
312 assert!(Colored::ansi_color_disabled());
313 std::env::set_var("NO_COLOR", "XXX");
314 assert!(Colored::ansi_color_disabled());
315 std::env::set_var("NO_COLOR", "");
316 assert!(!Colored::ansi_color_disabled());
317 std::env::remove_var("NO_COLOR");
318 assert!(!Colored::ansi_color_disabled());
319 }
320}