1#![warn(missing_docs)]
31
32#[cfg(test)]
33extern crate rspec;
34
35mod color;
36pub mod control;
37mod error;
38mod style;
39
40pub use self::customcolors::CustomColor;
41
42pub mod customcolors;
44
45pub use color::*;
46
47use std::{
48 borrow::Cow,
49 error::Error,
50 fmt,
51 ops::{Deref, DerefMut},
52};
53
54pub use style::{Style, Styles};
55
56#[derive(Clone, Debug, Default, PartialEq, Eq)]
123#[non_exhaustive]
124pub struct ColoredString {
125 pub input: String,
127
128 pub fgcolor: Option<Color>,
130
131 pub bgcolor: Option<Color>,
134
135 pub style: style::Style,
138}
139
140#[allow(missing_docs)]
145pub trait Colorize: Sized {
146 fn black(self) -> ColoredString {
148 self.color(Color::Black)
149 }
150 fn red(self) -> ColoredString {
151 self.color(Color::Red)
152 }
153 fn green(self) -> ColoredString {
154 self.color(Color::Green)
155 }
156 fn yellow(self) -> ColoredString {
157 self.color(Color::Yellow)
158 }
159 fn blue(self) -> ColoredString {
160 self.color(Color::Blue)
161 }
162 fn magenta(self) -> ColoredString {
163 self.color(Color::Magenta)
164 }
165 fn purple(self) -> ColoredString {
166 self.color(Color::Magenta)
167 }
168 fn cyan(self) -> ColoredString {
169 self.color(Color::Cyan)
170 }
171 fn white(self) -> ColoredString {
172 self.color(Color::White)
173 }
174 fn bright_black(self) -> ColoredString {
175 self.color(Color::BrightBlack)
176 }
177 fn bright_red(self) -> ColoredString {
178 self.color(Color::BrightRed)
179 }
180 fn bright_green(self) -> ColoredString {
181 self.color(Color::BrightGreen)
182 }
183 fn bright_yellow(self) -> ColoredString {
184 self.color(Color::BrightYellow)
185 }
186 fn bright_blue(self) -> ColoredString {
187 self.color(Color::BrightBlue)
188 }
189 fn bright_magenta(self) -> ColoredString {
190 self.color(Color::BrightMagenta)
191 }
192 fn bright_purple(self) -> ColoredString {
193 self.color(Color::BrightMagenta)
194 }
195 fn bright_cyan(self) -> ColoredString {
196 self.color(Color::BrightCyan)
197 }
198 fn bright_white(self) -> ColoredString {
199 self.color(Color::BrightWhite)
200 }
201 fn truecolor(self, r: u8, g: u8, b: u8) -> ColoredString {
202 self.color(Color::TrueColor { r, g, b })
203 }
204 fn custom_color<C: Into<CustomColor>>(self, color: C) -> ColoredString {
205 let color = color.into();
206
207 self.color(Color::TrueColor {
208 r: color.r,
209 g: color.g,
210 b: color.b,
211 })
212 }
213 fn ansi_color<T: Into<u8>>(self, color: T) -> ColoredString {
214 self.color(Color::AnsiColor(color.into()))
215 }
216 fn color<C: Into<Color>>(self, color: C) -> ColoredString;
217
218 fn on_black(self) -> ColoredString {
220 self.on_color(Color::Black)
221 }
222 fn on_red(self) -> ColoredString {
223 self.on_color(Color::Red)
224 }
225 fn on_green(self) -> ColoredString {
226 self.on_color(Color::Green)
227 }
228 fn on_yellow(self) -> ColoredString {
229 self.on_color(Color::Yellow)
230 }
231 fn on_blue(self) -> ColoredString {
232 self.on_color(Color::Blue)
233 }
234 fn on_magenta(self) -> ColoredString {
235 self.on_color(Color::Magenta)
236 }
237 fn on_purple(self) -> ColoredString {
238 self.on_color(Color::Magenta)
239 }
240 fn on_cyan(self) -> ColoredString {
241 self.on_color(Color::Cyan)
242 }
243 fn on_white(self) -> ColoredString {
244 self.on_color(Color::White)
245 }
246 fn on_bright_black(self) -> ColoredString {
247 self.on_color(Color::BrightBlack)
248 }
249 fn on_bright_red(self) -> ColoredString {
250 self.on_color(Color::BrightRed)
251 }
252 fn on_bright_green(self) -> ColoredString {
253 self.on_color(Color::BrightGreen)
254 }
255 fn on_bright_yellow(self) -> ColoredString {
256 self.on_color(Color::BrightYellow)
257 }
258 fn on_bright_blue(self) -> ColoredString {
259 self.on_color(Color::BrightBlue)
260 }
261 fn on_bright_magenta(self) -> ColoredString {
262 self.on_color(Color::BrightMagenta)
263 }
264 fn on_bright_purple(self) -> ColoredString {
265 self.on_color(Color::BrightMagenta)
266 }
267 fn on_bright_cyan(self) -> ColoredString {
268 self.on_color(Color::BrightCyan)
269 }
270 fn on_bright_white(self) -> ColoredString {
271 self.on_color(Color::BrightWhite)
272 }
273 fn on_truecolor(self, r: u8, g: u8, b: u8) -> ColoredString {
274 self.on_color(Color::TrueColor { r, g, b })
275 }
276 fn on_custom_color<C: Into<CustomColor>>(self, color: C) -> ColoredString {
277 let color = color.into();
278
279 self.on_color(Color::TrueColor {
280 r: color.r,
281 g: color.g,
282 b: color.b,
283 })
284 }
285 fn on_ansi_color<T: Into<u8>>(self, color: T) -> ColoredString {
286 self.on_color(Color::AnsiColor(color.into()))
287 }
288 fn on_color<C: Into<Color>>(self, color: C) -> ColoredString;
289
290 fn clear(self) -> ColoredString;
292 fn normal(self) -> ColoredString;
293 fn bold(self) -> ColoredString;
294 fn dimmed(self) -> ColoredString;
295 fn italic(self) -> ColoredString;
296 fn underline(self) -> ColoredString;
297 fn blink(self) -> ColoredString;
298 #[deprecated(since = "1.5.2", note = "Users should use reversed instead")]
299 fn reverse(self) -> ColoredString;
300 fn reversed(self) -> ColoredString;
301 fn hidden(self) -> ColoredString;
302 fn strikethrough(self) -> ColoredString;
303}
304
305impl ColoredString {
306 #[deprecated(note = "Deprecated due to the exposing of the fgcolor struct field.")]
316 #[must_use]
317 pub fn fgcolor(&self) -> Option<Color> {
318 self.fgcolor.as_ref().copied()
319 }
320
321 #[deprecated(note = "Deprecated due to the exposing of the bgcolor struct field.")]
331 #[must_use]
332 pub fn bgcolor(&self) -> Option<Color> {
333 self.bgcolor.as_ref().copied()
334 }
335
336 #[deprecated(note = "Deprecated due to the exposing of the style struct field.")]
346 #[must_use]
347 pub fn style(&self) -> style::Style {
348 self.style
349 }
350
351 pub fn clear_fgcolor(&mut self) {
354 self.fgcolor = None;
355 }
356
357 pub fn clear_bgcolor(&mut self) {
359 self.bgcolor = None;
360 }
361
362 pub fn clear_style(&mut self) {
365 self.style = Style::default();
366 }
367
368 #[must_use]
378 pub fn is_plain(&self) -> bool {
379 self.bgcolor.is_none() && self.fgcolor.is_none() && self.style == style::CLEAR
380 }
381
382 #[cfg(not(feature = "no-color"))]
383 fn has_colors() -> bool {
384 control::SHOULD_COLORIZE.should_colorize()
385 }
386
387 #[cfg(feature = "no-color")]
388 const fn has_colors() -> bool {
389 false
390 }
391
392 fn compute_style(&self) -> String {
393 if !Self::has_colors() || self.is_plain() {
394 return String::new();
395 }
396
397 let mut res = String::from("\x1B[");
398 let mut has_written = if self.style == style::CLEAR {
399 false
400 } else {
401 res.push_str(&self.style.to_str());
402 true
403 };
404
405 if let Some(bgcolor) = &self.bgcolor {
406 if has_written {
407 res.push(';');
408 }
409
410 res.push_str(&bgcolor.to_bg_str());
411 has_written = true;
412 }
413
414 if let Some(fgcolor) = &self.fgcolor {
415 if has_written {
416 res.push(';');
417 }
418
419 res.push_str(&fgcolor.to_fg_str());
420 }
421
422 res.push('m');
423 res
424 }
425
426 fn escape_inner_reset_sequences(&self) -> Cow<'_, str> {
427 if !Self::has_colors() || self.is_plain() {
428 return self.input.as_str().into();
429 }
430
431 let reset = "\x1B[0m";
433 let style = self.compute_style();
434 let matches: Vec<usize> = self
435 .input
436 .match_indices(reset)
437 .map(|(idx, _)| idx)
438 .collect();
439 if matches.is_empty() {
440 return self.input.as_str().into();
441 }
442
443 let mut input = self.input.clone();
444 input.reserve(matches.len() * style.len());
445
446 for (idx_in_matches, offset) in matches.into_iter().enumerate() {
447 let mut offset = offset + reset.len() + idx_in_matches * style.len();
450
451 for cchar in style.chars() {
452 input.insert(offset, cchar);
453 offset += 1;
454 }
455 }
456
457 input.into()
458 }
459}
460
461impl Deref for ColoredString {
462 type Target = str;
463 fn deref(&self) -> &Self::Target {
464 &self.input
465 }
466}
467
468impl DerefMut for ColoredString {
469 fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
470 &mut self.input
471 }
472}
473
474impl From<String> for ColoredString {
475 fn from(s: String) -> Self {
476 Self {
477 input: s,
478 ..Self::default()
479 }
480 }
481}
482
483impl<'a> From<&'a str> for ColoredString {
484 fn from(s: &'a str) -> Self {
485 Self {
486 input: String::from(s),
487 ..Self::default()
488 }
489 }
490}
491
492impl Colorize for ColoredString {
493 fn color<S: Into<Color>>(mut self, color: S) -> ColoredString {
494 self.fgcolor = Some(color.into());
495 self
496 }
497 fn on_color<S: Into<Color>>(mut self, color: S) -> ColoredString {
498 self.bgcolor = Some(color.into());
499 self
500 }
501
502 fn clear(self) -> ColoredString {
503 Self {
504 input: self.input,
505 ..Self::default()
506 }
507 }
508 fn normal(self) -> ColoredString {
509 self.clear()
510 }
511 fn bold(mut self) -> ColoredString {
512 self.style.add(style::Styles::Bold);
513 self
514 }
515 fn dimmed(mut self) -> ColoredString {
516 self.style.add(style::Styles::Dimmed);
517 self
518 }
519 fn italic(mut self) -> ColoredString {
520 self.style.add(style::Styles::Italic);
521 self
522 }
523 fn underline(mut self) -> ColoredString {
524 self.style.add(style::Styles::Underline);
525 self
526 }
527 fn blink(mut self) -> ColoredString {
528 self.style.add(style::Styles::Blink);
529 self
530 }
531 fn reverse(self) -> ColoredString {
532 self.reversed()
533 }
534 fn reversed(mut self) -> ColoredString {
535 self.style.add(style::Styles::Reversed);
536 self
537 }
538 fn hidden(mut self) -> ColoredString {
539 self.style.add(style::Styles::Hidden);
540 self
541 }
542 fn strikethrough(mut self) -> ColoredString {
543 self.style.add(style::Styles::Strikethrough);
544 self
545 }
546}
547
548impl Colorize for &str {
549 fn color<S: Into<Color>>(self, color: S) -> ColoredString {
550 ColoredString {
551 fgcolor: Some(color.into()),
552 input: String::from(self),
553 ..ColoredString::default()
554 }
555 }
556
557 fn on_color<S: Into<Color>>(self, color: S) -> ColoredString {
558 ColoredString {
559 bgcolor: Some(color.into()),
560 input: String::from(self),
561 ..ColoredString::default()
562 }
563 }
564
565 fn clear(self) -> ColoredString {
566 ColoredString {
567 input: String::from(self),
568 style: style::CLEAR,
569 ..ColoredString::default()
570 }
571 }
572 fn normal(self) -> ColoredString {
573 self.clear()
574 }
575 fn bold(self) -> ColoredString {
576 ColoredString::from(self).bold()
577 }
578 fn dimmed(self) -> ColoredString {
579 ColoredString::from(self).dimmed()
580 }
581 fn italic(self) -> ColoredString {
582 ColoredString::from(self).italic()
583 }
584 fn underline(self) -> ColoredString {
585 ColoredString::from(self).underline()
586 }
587 fn blink(self) -> ColoredString {
588 ColoredString::from(self).blink()
589 }
590 fn reverse(self) -> ColoredString {
591 self.reversed()
592 }
593 fn reversed(self) -> ColoredString {
594 ColoredString::from(self).reversed()
595 }
596 fn hidden(self) -> ColoredString {
597 ColoredString::from(self).hidden()
598 }
599 fn strikethrough(self) -> ColoredString {
600 ColoredString::from(self).strikethrough()
601 }
602}
603
604impl fmt::Display for ColoredString {
605 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
606 if !Self::has_colors() || self.is_plain() {
607 return <String as fmt::Display>::fmt(&self.input, f);
608 }
609
610 let escaped_input = self.escape_inner_reset_sequences();
612
613 f.write_str(&self.compute_style())?;
614 escaped_input.fmt(f)?;
615 f.write_str("\x1B[0m")?;
616 Ok(())
617 }
618}
619
620impl From<ColoredString> for Box<dyn Error> {
621 fn from(cs: ColoredString) -> Self {
622 Box::from(error::ColoredStringError(cs))
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 use std::{error::Error, fmt::Write};
630
631 #[test]
632 fn formatting() {
633 assert!(format!("{:40}", "".blue()).len() >= 40);
635 assert_eq!(
637 format!("{:1.1}", "toto".blue()).len(),
638 format!("{:1.1}", "1".blue()).len()
639 );
640 }
641
642 #[test]
643 fn it_works() -> Result<(), Box<dyn Error>> {
644 let mut buf = String::new();
645 let toto = "toto";
646 writeln!(&mut buf, "{}", toto.red())?;
647 writeln!(&mut buf, "{}", String::from(toto).red())?;
648 writeln!(&mut buf, "{}", toto.blue())?;
649
650 writeln!(&mut buf, "blue style ****")?;
651 writeln!(&mut buf, "{}", toto.bold())?;
652 writeln!(&mut buf, "{}", "yeah ! Red bold !".red().bold())?;
653 writeln!(&mut buf, "{}", "yeah ! Yellow bold !".bold().yellow())?;
654 writeln!(&mut buf, "{}", toto.bold().blue())?;
655 writeln!(&mut buf, "{}", toto.blue().bold())?;
656 writeln!(&mut buf, "{}", toto.blue().bold().underline())?;
657 writeln!(&mut buf, "{}", toto.blue().italic())?;
658 writeln!(&mut buf, "******")?;
659 writeln!(&mut buf, "test clearing")?;
660 writeln!(&mut buf, "{}", "red cleared".red().clear())?;
661 writeln!(&mut buf, "{}", "bold cyan cleared".bold().cyan().clear())?;
662 writeln!(&mut buf, "******")?;
663 writeln!(&mut buf, "Bg tests")?;
664 writeln!(&mut buf, "{}", toto.green().on_blue())?;
665 writeln!(&mut buf, "{}", toto.on_magenta().yellow())?;
666 writeln!(&mut buf, "{}", toto.purple().on_yellow())?;
667 writeln!(&mut buf, "{}", toto.magenta().on_white())?;
668 writeln!(&mut buf, "{}", toto.cyan().on_green())?;
669 writeln!(&mut buf, "{}", toto.black().on_white())?;
670 writeln!(&mut buf, "******")?;
671 writeln!(&mut buf, "{}", toto.green())?;
672 writeln!(&mut buf, "{}", toto.yellow())?;
673 writeln!(&mut buf, "{}", toto.purple())?;
674 writeln!(&mut buf, "{}", toto.magenta())?;
675 writeln!(&mut buf, "{}", toto.cyan())?;
676 writeln!(&mut buf, "{}", toto.white())?;
677 writeln!(&mut buf, "{}", toto.white().red().blue().green())?;
678 writeln!(&mut buf, "{}", toto.truecolor(255, 0, 0))?;
679 writeln!(&mut buf, "{}", toto.truecolor(255, 255, 0))?;
680 writeln!(&mut buf, "{}", toto.on_truecolor(0, 80, 80))?;
681 writeln!(&mut buf, "{}", toto.custom_color((255, 255, 0)))?;
682 writeln!(&mut buf, "{}", toto.on_custom_color((0, 80, 80)))?;
683 #[cfg(feature = "no-color")]
684 insta::assert_snapshot!("it_works_no_color", buf);
685 #[cfg(not(feature = "no-color"))]
686 insta::assert_snapshot!("it_works", buf);
687 Ok(())
688 }
689
690 #[test]
691 fn compute_style_empty_string() {
692 assert_eq!("", "".clear().compute_style());
693 }
694
695 #[cfg_attr(feature = "no-color", ignore)]
696 #[test]
697 fn compute_style_simple_fg_blue() {
698 let blue = "\x1B[34m";
699
700 assert_eq!(blue, "".blue().compute_style());
701 }
702
703 #[cfg_attr(feature = "no-color", ignore)]
704 #[test]
705 fn compute_style_simple_bg_blue() {
706 let on_blue = "\x1B[44m";
707
708 assert_eq!(on_blue, "".on_blue().compute_style());
709 }
710
711 #[cfg_attr(feature = "no-color", ignore)]
712 #[test]
713 fn compute_style_blue_on_blue() {
714 let blue_on_blue = "\x1B[44;34m";
715
716 assert_eq!(blue_on_blue, "".blue().on_blue().compute_style());
717 }
718
719 #[cfg_attr(feature = "no-color", ignore)]
720 #[test]
721 fn compute_style_simple_fg_bright_blue() {
722 let blue = "\x1B[94m";
723
724 assert_eq!(blue, "".bright_blue().compute_style());
725 }
726
727 #[cfg_attr(feature = "no-color", ignore)]
728 #[test]
729 fn compute_style_simple_bg_bright_blue() {
730 let on_blue = "\x1B[104m";
731
732 assert_eq!(on_blue, "".on_bright_blue().compute_style());
733 }
734
735 #[cfg_attr(feature = "no-color", ignore)]
736 #[test]
737 fn compute_style_bright_blue_on_bright_blue() {
738 let blue_on_blue = "\x1B[104;94m";
739
740 assert_eq!(
741 blue_on_blue,
742 "".bright_blue().on_bright_blue().compute_style()
743 );
744 }
745
746 #[cfg_attr(feature = "no-color", ignore)]
747 #[test]
748 fn compute_style_simple_bold() {
749 let bold = "\x1B[1m";
750
751 assert_eq!(bold, "".bold().compute_style());
752 }
753
754 #[cfg_attr(feature = "no-color", ignore)]
755 #[test]
756 fn compute_style_blue_bold() {
757 let blue_bold = "\x1B[1;34m";
758
759 assert_eq!(blue_bold, "".blue().bold().compute_style());
760 }
761
762 #[cfg_attr(feature = "no-color", ignore)]
763 #[test]
764 fn compute_style_blue_bold_on_blue() {
765 let blue_bold_on_blue = "\x1B[1;44;34m";
766
767 assert_eq!(
768 blue_bold_on_blue,
769 "".blue().bold().on_blue().compute_style()
770 );
771 }
772
773 #[test]
774 fn escape_reset_sequence_spec_should_do_nothing_on_empty_strings() {
775 let style = ColoredString::default();
776 let expected = String::new();
777
778 let output = style.escape_inner_reset_sequences();
779
780 assert_eq!(expected, output);
781 }
782
783 #[test]
784 fn escape_reset_sequence_spec_should_do_nothing_on_string_with_no_reset() {
785 let style = ColoredString {
786 input: String::from("hello world !"),
787 ..ColoredString::default()
788 };
789
790 let expected = String::from("hello world !");
791 let output = style.escape_inner_reset_sequences();
792
793 assert_eq!(expected, output);
794 }
795
796 #[cfg_attr(feature = "no-color", ignore)]
797 #[test]
798 fn escape_reset_sequence_spec_should_replace_inner_reset_sequence_with_current_style() {
799 let input = format!("start {} end", String::from("hello world !").red());
800 let style = input.blue();
801
802 let output = style.escape_inner_reset_sequences();
803 let blue = "\x1B[34m";
804 let red = "\x1B[31m";
805 let reset = "\x1B[0m";
806 let expected = format!("start {red}hello world !{reset}{blue} end");
807 assert_eq!(expected, output);
808 }
809
810 #[cfg_attr(feature = "no-color", ignore)]
811 #[test]
812 fn escape_reset_sequence_spec_should_replace_multiple_inner_reset_sequences_with_current_style()
813 {
814 let italic_str = String::from("yo").italic();
815 let input = format!("start 1:{italic_str} 2:{italic_str} 3:{italic_str} end");
816 let style = input.blue();
817
818 let output = style.escape_inner_reset_sequences();
819 let blue = "\x1B[34m";
820 let italic = "\x1B[3m";
821 let reset = "\x1B[0m";
822 let expected = format!(
823 "start 1:{italic}yo{reset}{blue} 2:{italic}yo{reset}{blue} 3:{italic}yo{reset}{blue} end"
824 );
825
826 println!("first: {expected}\nsecond: {output}");
827
828 assert_eq!(expected, output);
829 }
830
831 #[test]
832 fn color_fn() {
833 assert_eq!("blue".blue(), "blue".color("blue"));
834 }
835
836 #[test]
837 fn on_color_fn() {
838 assert_eq!("blue".on_blue(), "blue".on_color("blue"));
839 }
840
841 #[test]
842 fn bright_color_fn() {
843 assert_eq!("blue".bright_blue(), "blue".color("bright blue"));
844 }
845
846 #[test]
847 fn on_bright_color_fn() {
848 assert_eq!("blue".on_bright_blue(), "blue".on_color("bright blue"));
849 }
850
851 #[test]
852 fn exposing_tests() {
853 #![allow(deprecated)]
854
855 let cstring = "".red();
856 assert_eq!(cstring.fgcolor(), Some(Color::Red));
857 assert_eq!(cstring.bgcolor(), None);
858
859 let cstring = cstring.clear();
860 assert_eq!(cstring.fgcolor(), None);
861 assert_eq!(cstring.bgcolor(), None);
862
863 let cstring = cstring.blue().on_bright_yellow();
864 assert_eq!(cstring.fgcolor(), Some(Color::Blue));
865 assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow));
866
867 let cstring = cstring.bold().italic();
868 assert_eq!(cstring.fgcolor(), Some(Color::Blue));
869 assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow));
870 assert!(cstring.style().contains(Styles::Bold));
871 assert!(cstring.style().contains(Styles::Italic));
872 assert!(!cstring.style().contains(Styles::Dimmed));
873 }
874}