1use std::fmt;
2
3use paste::paste;
4
5use crate::{
6 style::{Color, Modifier, Style},
7 text::Span,
8};
9
10pub trait Styled {
16 type Item;
17
18 fn style(&self) -> Style;
20
21 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item;
26}
27
28pub(crate) struct ColorDebug {
30 pub kind: ColorDebugKind,
31 pub color: Color,
32}
33
34#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
35pub(crate) enum ColorDebugKind {
36 Foreground,
37 Background,
38 #[cfg(feature = "underline-color")]
39 Underline,
40}
41
42impl fmt::Debug for ColorDebug {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 #[cfg(feature = "underline-color")]
45 let is_underline = self.kind == ColorDebugKind::Underline;
46 #[cfg(not(feature = "underline-color"))]
47 let is_underline = false;
48 if is_underline
49 || matches!(
50 self.color,
51 Color::Reset | Color::Indexed(_) | Color::Rgb(_, _, _)
52 )
53 {
54 match self.kind {
55 ColorDebugKind::Foreground => write!(f, ".fg(")?,
56 ColorDebugKind::Background => write!(f, ".bg(")?,
57 #[cfg(feature = "underline-color")]
58 ColorDebugKind::Underline => write!(f, ".underline_color(")?,
59 }
60 write!(f, "Color::{:?}", self.color)?;
61 write!(f, ")")?;
62 return Ok(());
63 }
64
65 match self.kind {
66 ColorDebugKind::Foreground => write!(f, ".")?,
67 ColorDebugKind::Background => write!(f, ".on_")?,
68 #[cfg(feature = "underline-color")]
70 ColorDebugKind::Underline => {
71 unreachable!("covered by the first part of the if statement")
72 }
73 }
74 match self.color {
75 Color::Black => write!(f, "black")?,
76 Color::Red => write!(f, "red")?,
77 Color::Green => write!(f, "green")?,
78 Color::Yellow => write!(f, "yellow")?,
79 Color::Blue => write!(f, "blue")?,
80 Color::Magenta => write!(f, "magenta")?,
81 Color::Cyan => write!(f, "cyan")?,
82 Color::Gray => write!(f, "gray")?,
83 Color::DarkGray => write!(f, "dark_gray")?,
84 Color::LightRed => write!(f, "light_red")?,
85 Color::LightGreen => write!(f, "light_green")?,
86 Color::LightYellow => write!(f, "light_yellow")?,
87 Color::LightBlue => write!(f, "light_blue")?,
88 Color::LightMagenta => write!(f, "light_magenta")?,
89 Color::LightCyan => write!(f, "light_cyan")?,
90 Color::White => write!(f, "white")?,
91 _ => unreachable!("covered by the first part of the if statement"),
92 }
93 write!(f, "()")
94 }
95}
96
97macro_rules! color {
117 ( $color:ident ) => {
118 paste! {
119 #[doc = "Sets the foreground color to [`" $color "`](Color::" $color:camel ")."]
120 #[must_use = concat!("`", stringify!($color), "` returns the modified style without modifying the original")]
121 fn $color(self) -> T {
122 self.fg(Color::[<$color:camel>])
123 }
124
125 #[doc = "Sets the background color to [`" $color "`](Color::" $color:camel ")."]
126 #[must_use = concat!("`on_", stringify!($color), "` returns the modified style without modifying the original")]
127 fn [<on_ $color>](self) -> T {
128 self.bg(Color::[<$color:camel>])
129 }
130 }
131 };
132}
133
134macro_rules! modifier {
155 ( $modifier:ident ) => {
156 paste! {
157 #[doc = "Adds the [`" $modifier:upper "`](Modifier::" $modifier:upper ") modifier."]
158 #[must_use = concat!("`", stringify!($modifier), "` returns the modified style without modifying the original")]
159 fn [<$modifier>](self) -> T {
160 self.add_modifier(Modifier::[<$modifier:upper>])
161 }
162 }
163
164 paste! {
165 #[doc = "Removes the [`" $modifier:upper "`](Modifier::" $modifier:upper ") modifier."]
166 #[must_use = concat!("`not_", stringify!($modifier), "` returns the modified style without modifying the original")]
167 fn [<not_ $modifier>](self) -> T {
168 self.remove_modifier(Modifier::[<$modifier:upper>])
169 }
170 }
171 };
172}
173
174pub trait Stylize<'a, T>: Sized {
214 #[must_use = "`bg` returns the modified style without modifying the original"]
215 fn bg<C: Into<Color>>(self, color: C) -> T;
216 #[must_use = "`fg` returns the modified style without modifying the original"]
217 fn fg<C: Into<Color>>(self, color: C) -> T;
218 #[must_use = "`reset` returns the modified style without modifying the original"]
219 fn reset(self) -> T;
220 #[must_use = "`add_modifier` returns the modified style without modifying the original"]
221 fn add_modifier(self, modifier: Modifier) -> T;
222 #[must_use = "`remove_modifier` returns the modified style without modifying the original"]
223 fn remove_modifier(self, modifier: Modifier) -> T;
224
225 color!(black);
226 color!(red);
227 color!(green);
228 color!(yellow);
229 color!(blue);
230 color!(magenta);
231 color!(cyan);
232 color!(gray);
233 color!(dark_gray);
234 color!(light_red);
235 color!(light_green);
236 color!(light_yellow);
237 color!(light_blue);
238 color!(light_magenta);
239 color!(light_cyan);
240 color!(white);
241
242 modifier!(bold);
243 modifier!(dim);
244 modifier!(italic);
245 modifier!(underlined);
246 modifier!(slow_blink);
247 modifier!(rapid_blink);
248 modifier!(reversed);
249 modifier!(hidden);
250 modifier!(crossed_out);
251}
252
253impl<'a, T, U> Stylize<'a, T> for U
254where
255 U: Styled<Item = T>,
256{
257 fn bg<C: Into<Color>>(self, color: C) -> T {
258 let style = self.style().bg(color.into());
259 self.set_style(style)
260 }
261
262 fn fg<C: Into<Color>>(self, color: C) -> T {
263 let style = self.style().fg(color.into());
264 self.set_style(style)
265 }
266
267 fn add_modifier(self, modifier: Modifier) -> T {
268 let style = self.style().add_modifier(modifier);
269 self.set_style(style)
270 }
271
272 fn remove_modifier(self, modifier: Modifier) -> T {
273 let style = self.style().remove_modifier(modifier);
274 self.set_style(style)
275 }
276
277 fn reset(self) -> T {
278 self.set_style(Style::reset())
279 }
280}
281
282impl<'a> Styled for &'a str {
283 type Item = Span<'a>;
284
285 fn style(&self) -> Style {
286 Style::default()
287 }
288
289 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
290 Span::styled(self, style)
291 }
292}
293
294impl Styled for String {
295 type Item = Span<'static>;
296
297 fn style(&self) -> Style {
298 Style::default()
299 }
300
301 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
302 Span::styled(self, style)
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use itertools::Itertools;
309 use rstest::rstest;
310
311 use super::*;
312
313 #[test]
314 fn str_styled() {
315 assert_eq!("hello".style(), Style::default());
316 assert_eq!(
317 "hello".set_style(Style::new().cyan()),
318 Span::styled("hello", Style::new().cyan())
319 );
320 assert_eq!("hello".black(), Span::from("hello").black());
321 assert_eq!("hello".red(), Span::from("hello").red());
322 assert_eq!("hello".green(), Span::from("hello").green());
323 assert_eq!("hello".yellow(), Span::from("hello").yellow());
324 assert_eq!("hello".blue(), Span::from("hello").blue());
325 assert_eq!("hello".magenta(), Span::from("hello").magenta());
326 assert_eq!("hello".cyan(), Span::from("hello").cyan());
327 assert_eq!("hello".gray(), Span::from("hello").gray());
328 assert_eq!("hello".dark_gray(), Span::from("hello").dark_gray());
329 assert_eq!("hello".light_red(), Span::from("hello").light_red());
330 assert_eq!("hello".light_green(), Span::from("hello").light_green());
331 assert_eq!("hello".light_yellow(), Span::from("hello").light_yellow());
332 assert_eq!("hello".light_blue(), Span::from("hello").light_blue());
333 assert_eq!("hello".light_magenta(), Span::from("hello").light_magenta());
334 assert_eq!("hello".light_cyan(), Span::from("hello").light_cyan());
335 assert_eq!("hello".white(), Span::from("hello").white());
336
337 assert_eq!("hello".on_black(), Span::from("hello").on_black());
338 assert_eq!("hello".on_red(), Span::from("hello").on_red());
339 assert_eq!("hello".on_green(), Span::from("hello").on_green());
340 assert_eq!("hello".on_yellow(), Span::from("hello").on_yellow());
341 assert_eq!("hello".on_blue(), Span::from("hello").on_blue());
342 assert_eq!("hello".on_magenta(), Span::from("hello").on_magenta());
343 assert_eq!("hello".on_cyan(), Span::from("hello").on_cyan());
344 assert_eq!("hello".on_gray(), Span::from("hello").on_gray());
345 assert_eq!("hello".on_dark_gray(), Span::from("hello").on_dark_gray());
346 assert_eq!("hello".on_light_red(), Span::from("hello").on_light_red());
347 assert_eq!(
348 "hello".on_light_green(),
349 Span::from("hello").on_light_green()
350 );
351 assert_eq!(
352 "hello".on_light_yellow(),
353 Span::from("hello").on_light_yellow()
354 );
355 assert_eq!("hello".on_light_blue(), Span::from("hello").on_light_blue());
356 assert_eq!(
357 "hello".on_light_magenta(),
358 Span::from("hello").on_light_magenta()
359 );
360 assert_eq!("hello".on_light_cyan(), Span::from("hello").on_light_cyan());
361 assert_eq!("hello".on_white(), Span::from("hello").on_white());
362
363 assert_eq!("hello".bold(), Span::from("hello").bold());
364 assert_eq!("hello".dim(), Span::from("hello").dim());
365 assert_eq!("hello".italic(), Span::from("hello").italic());
366 assert_eq!("hello".underlined(), Span::from("hello").underlined());
367 assert_eq!("hello".slow_blink(), Span::from("hello").slow_blink());
368 assert_eq!("hello".rapid_blink(), Span::from("hello").rapid_blink());
369 assert_eq!("hello".reversed(), Span::from("hello").reversed());
370 assert_eq!("hello".hidden(), Span::from("hello").hidden());
371 assert_eq!("hello".crossed_out(), Span::from("hello").crossed_out());
372
373 assert_eq!("hello".not_bold(), Span::from("hello").not_bold());
374 assert_eq!("hello".not_dim(), Span::from("hello").not_dim());
375 assert_eq!("hello".not_italic(), Span::from("hello").not_italic());
376 assert_eq!(
377 "hello".not_underlined(),
378 Span::from("hello").not_underlined()
379 );
380 assert_eq!(
381 "hello".not_slow_blink(),
382 Span::from("hello").not_slow_blink()
383 );
384 assert_eq!(
385 "hello".not_rapid_blink(),
386 Span::from("hello").not_rapid_blink()
387 );
388 assert_eq!("hello".not_reversed(), Span::from("hello").not_reversed());
389 assert_eq!("hello".not_hidden(), Span::from("hello").not_hidden());
390 assert_eq!(
391 "hello".not_crossed_out(),
392 Span::from("hello").not_crossed_out()
393 );
394
395 assert_eq!("hello".reset(), Span::from("hello").reset());
396 }
397
398 #[test]
399 fn string_styled() {
400 let s = String::from("hello");
401 assert_eq!(s.style(), Style::default());
402 assert_eq!(
403 s.clone().set_style(Style::new().cyan()),
404 Span::styled("hello", Style::new().cyan())
405 );
406 assert_eq!(s.clone().black(), Span::from("hello").black());
407 assert_eq!(s.clone().on_black(), Span::from("hello").on_black());
408 assert_eq!(s.clone().bold(), Span::from("hello").bold());
409 assert_eq!(s.clone().not_bold(), Span::from("hello").not_bold());
410 assert_eq!(s.clone().reset(), Span::from("hello").reset());
411 }
412
413 #[test]
414 fn temporary_string_styled() {
415 let s = "hello".to_string().red();
419 assert_eq!(s, Span::from("hello").red());
420
421 let items = [String::from("a"), String::from("b")];
424 let sss = items.iter().map(|s| format!("{s}{s}").red()).collect_vec();
425 assert_eq!(sss, [Span::from("aa").red(), Span::from("bb").red()]);
426 }
427
428 #[test]
429 fn reset() {
430 assert_eq!(
431 "hello".on_cyan().light_red().bold().underlined().reset(),
432 Span::styled("hello", Style::reset())
433 );
434 }
435
436 #[test]
437 fn fg() {
438 let cyan_fg = Style::default().fg(Color::Cyan);
439
440 assert_eq!("hello".cyan(), Span::styled("hello", cyan_fg));
441 }
442
443 #[test]
444 fn bg() {
445 let cyan_bg = Style::default().bg(Color::Cyan);
446
447 assert_eq!("hello".on_cyan(), Span::styled("hello", cyan_bg));
448 }
449
450 #[test]
451 fn color_modifier() {
452 let cyan_bold = Style::default()
453 .fg(Color::Cyan)
454 .add_modifier(Modifier::BOLD);
455
456 assert_eq!("hello".cyan().bold(), Span::styled("hello", cyan_bold));
457 }
458
459 #[test]
460 fn fg_bg() {
461 let cyan_fg_bg = Style::default().bg(Color::Cyan).fg(Color::Cyan);
462
463 assert_eq!("hello".cyan().on_cyan(), Span::styled("hello", cyan_fg_bg));
464 }
465
466 #[test]
467 fn repeated_attributes() {
468 let bg = Style::default().bg(Color::Cyan);
469 let fg = Style::default().fg(Color::Cyan);
470
471 assert_eq!("hello".on_red().on_cyan(), Span::styled("hello", bg));
473 assert_eq!("hello".red().cyan(), Span::styled("hello", fg));
474 }
475
476 #[test]
477 fn all_chained() {
478 let all_modifier_black = Style::default()
479 .bg(Color::Black)
480 .fg(Color::Black)
481 .add_modifier(
482 Modifier::UNDERLINED
483 | Modifier::BOLD
484 | Modifier::DIM
485 | Modifier::SLOW_BLINK
486 | Modifier::REVERSED
487 | Modifier::CROSSED_OUT,
488 );
489 assert_eq!(
490 "hello"
491 .on_black()
492 .black()
493 .bold()
494 .underlined()
495 .dim()
496 .slow_blink()
497 .crossed_out()
498 .reversed(),
499 Span::styled("hello", all_modifier_black)
500 );
501 }
502
503 #[rstest]
504 #[case(ColorDebugKind::Foreground, Color::Black, ".black()")]
505 #[case(ColorDebugKind::Foreground, Color::Red, ".red()")]
506 #[case(ColorDebugKind::Foreground, Color::Green, ".green()")]
507 #[case(ColorDebugKind::Foreground, Color::Yellow, ".yellow()")]
508 #[case(ColorDebugKind::Foreground, Color::Blue, ".blue()")]
509 #[case(ColorDebugKind::Foreground, Color::Magenta, ".magenta()")]
510 #[case(ColorDebugKind::Foreground, Color::Cyan, ".cyan()")]
511 #[case(ColorDebugKind::Foreground, Color::Gray, ".gray()")]
512 #[case(ColorDebugKind::Foreground, Color::DarkGray, ".dark_gray()")]
513 #[case(ColorDebugKind::Foreground, Color::LightRed, ".light_red()")]
514 #[case(ColorDebugKind::Foreground, Color::LightGreen, ".light_green()")]
515 #[case(ColorDebugKind::Foreground, Color::LightYellow, ".light_yellow()")]
516 #[case(ColorDebugKind::Foreground, Color::LightBlue, ".light_blue()")]
517 #[case(ColorDebugKind::Foreground, Color::LightMagenta, ".light_magenta()")]
518 #[case(ColorDebugKind::Foreground, Color::LightCyan, ".light_cyan()")]
519 #[case(ColorDebugKind::Foreground, Color::White, ".white()")]
520 #[case(
521 ColorDebugKind::Foreground,
522 Color::Indexed(10),
523 ".fg(Color::Indexed(10))"
524 )]
525 #[case(
526 ColorDebugKind::Foreground,
527 Color::Rgb(255, 0, 0),
528 ".fg(Color::Rgb(255, 0, 0))"
529 )]
530 #[case(ColorDebugKind::Background, Color::Black, ".on_black()")]
531 #[case(ColorDebugKind::Background, Color::Red, ".on_red()")]
532 #[case(ColorDebugKind::Background, Color::Green, ".on_green()")]
533 #[case(ColorDebugKind::Background, Color::Yellow, ".on_yellow()")]
534 #[case(ColorDebugKind::Background, Color::Blue, ".on_blue()")]
535 #[case(ColorDebugKind::Background, Color::Magenta, ".on_magenta()")]
536 #[case(ColorDebugKind::Background, Color::Cyan, ".on_cyan()")]
537 #[case(ColorDebugKind::Background, Color::Gray, ".on_gray()")]
538 #[case(ColorDebugKind::Background, Color::DarkGray, ".on_dark_gray()")]
539 #[case(ColorDebugKind::Background, Color::LightRed, ".on_light_red()")]
540 #[case(ColorDebugKind::Background, Color::LightGreen, ".on_light_green()")]
541 #[case(ColorDebugKind::Background, Color::LightYellow, ".on_light_yellow()")]
542 #[case(ColorDebugKind::Background, Color::LightBlue, ".on_light_blue()")]
543 #[case(ColorDebugKind::Background, Color::LightMagenta, ".on_light_magenta()")]
544 #[case(ColorDebugKind::Background, Color::LightCyan, ".on_light_cyan()")]
545 #[case(ColorDebugKind::Background, Color::White, ".on_white()")]
546 #[case(
547 ColorDebugKind::Background,
548 Color::Indexed(10),
549 ".bg(Color::Indexed(10))"
550 )]
551 #[case(
552 ColorDebugKind::Background,
553 Color::Rgb(255, 0, 0),
554 ".bg(Color::Rgb(255, 0, 0))"
555 )]
556 #[cfg(feature = "underline-color")]
557 #[case(
558 ColorDebugKind::Underline,
559 Color::Black,
560 ".underline_color(Color::Black)"
561 )]
562 #[cfg(feature = "underline-color")]
563 #[case(ColorDebugKind::Underline, Color::Red, ".underline_color(Color::Red)")]
564 #[cfg(feature = "underline-color")]
565 #[case(
566 ColorDebugKind::Underline,
567 Color::Green,
568 ".underline_color(Color::Green)"
569 )]
570 #[cfg(feature = "underline-color")]
571 #[case(
572 ColorDebugKind::Underline,
573 Color::Yellow,
574 ".underline_color(Color::Yellow)"
575 )]
576 fn stylize_debug(#[case] kind: ColorDebugKind, #[case] color: Color, #[case] expected: &str) {
577 let debug = color.stylize_debug(kind);
578 assert_eq!(format!("{debug:?}"), expected);
579 }
580}