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 pub fgcolor: Option<Color>,
129 pub bgcolor: Option<Color>,
132 pub style: style::Style,
135}
136
137#[allow(missing_docs)]
142pub trait Colorize {
143 fn black(self) -> ColoredString
145 where
146 Self: Sized,
147 {
148 self.color(Color::Black)
149 }
150 fn red(self) -> ColoredString
151 where
152 Self: Sized,
153 {
154 self.color(Color::Red)
155 }
156 fn green(self) -> ColoredString
157 where
158 Self: Sized,
159 {
160 self.color(Color::Green)
161 }
162 fn yellow(self) -> ColoredString
163 where
164 Self: Sized,
165 {
166 self.color(Color::Yellow)
167 }
168 fn blue(self) -> ColoredString
169 where
170 Self: Sized,
171 {
172 self.color(Color::Blue)
173 }
174 fn magenta(self) -> ColoredString
175 where
176 Self: Sized,
177 {
178 self.color(Color::Magenta)
179 }
180 fn purple(self) -> ColoredString
181 where
182 Self: Sized,
183 {
184 self.color(Color::Magenta)
185 }
186 fn cyan(self) -> ColoredString
187 where
188 Self: Sized,
189 {
190 self.color(Color::Cyan)
191 }
192 fn white(self) -> ColoredString
193 where
194 Self: Sized,
195 {
196 self.color(Color::White)
197 }
198 fn bright_black(self) -> ColoredString
199 where
200 Self: Sized,
201 {
202 self.color(Color::BrightBlack)
203 }
204 fn bright_red(self) -> ColoredString
205 where
206 Self: Sized,
207 {
208 self.color(Color::BrightRed)
209 }
210 fn bright_green(self) -> ColoredString
211 where
212 Self: Sized,
213 {
214 self.color(Color::BrightGreen)
215 }
216 fn bright_yellow(self) -> ColoredString
217 where
218 Self: Sized,
219 {
220 self.color(Color::BrightYellow)
221 }
222 fn bright_blue(self) -> ColoredString
223 where
224 Self: Sized,
225 {
226 self.color(Color::BrightBlue)
227 }
228 fn bright_magenta(self) -> ColoredString
229 where
230 Self: Sized,
231 {
232 self.color(Color::BrightMagenta)
233 }
234 fn bright_purple(self) -> ColoredString
235 where
236 Self: Sized,
237 {
238 self.color(Color::BrightMagenta)
239 }
240 fn bright_cyan(self) -> ColoredString
241 where
242 Self: Sized,
243 {
244 self.color(Color::BrightCyan)
245 }
246 fn bright_white(self) -> ColoredString
247 where
248 Self: Sized,
249 {
250 self.color(Color::BrightWhite)
251 }
252 fn truecolor(self, r: u8, g: u8, b: u8) -> ColoredString
253 where
254 Self: Sized,
255 {
256 self.color(Color::TrueColor { r, g, b })
257 }
258 fn custom_color<T>(self, color: T) -> ColoredString
259 where
260 Self: Sized,
261 T: Into<CustomColor>,
262 {
263 let color = color.into();
264
265 self.color(Color::TrueColor {
266 r: color.r,
267 g: color.g,
268 b: color.b,
269 })
270 }
271 fn color<S: Into<Color>>(self, color: S) -> ColoredString;
272 fn on_black(self) -> ColoredString
274 where
275 Self: Sized,
276 {
277 self.on_color(Color::Black)
278 }
279 fn on_red(self) -> ColoredString
280 where
281 Self: Sized,
282 {
283 self.on_color(Color::Red)
284 }
285 fn on_green(self) -> ColoredString
286 where
287 Self: Sized,
288 {
289 self.on_color(Color::Green)
290 }
291 fn on_yellow(self) -> ColoredString
292 where
293 Self: Sized,
294 {
295 self.on_color(Color::Yellow)
296 }
297 fn on_blue(self) -> ColoredString
298 where
299 Self: Sized,
300 {
301 self.on_color(Color::Blue)
302 }
303 fn on_magenta(self) -> ColoredString
304 where
305 Self: Sized,
306 {
307 self.on_color(Color::Magenta)
308 }
309 fn on_purple(self) -> ColoredString
310 where
311 Self: Sized,
312 {
313 self.on_color(Color::Magenta)
314 }
315 fn on_cyan(self) -> ColoredString
316 where
317 Self: Sized,
318 {
319 self.on_color(Color::Cyan)
320 }
321 fn on_white(self) -> ColoredString
322 where
323 Self: Sized,
324 {
325 self.on_color(Color::White)
326 }
327 fn on_bright_black(self) -> ColoredString
328 where
329 Self: Sized,
330 {
331 self.on_color(Color::BrightBlack)
332 }
333 fn on_bright_red(self) -> ColoredString
334 where
335 Self: Sized,
336 {
337 self.on_color(Color::BrightRed)
338 }
339 fn on_bright_green(self) -> ColoredString
340 where
341 Self: Sized,
342 {
343 self.on_color(Color::BrightGreen)
344 }
345 fn on_bright_yellow(self) -> ColoredString
346 where
347 Self: Sized,
348 {
349 self.on_color(Color::BrightYellow)
350 }
351 fn on_bright_blue(self) -> ColoredString
352 where
353 Self: Sized,
354 {
355 self.on_color(Color::BrightBlue)
356 }
357 fn on_bright_magenta(self) -> ColoredString
358 where
359 Self: Sized,
360 {
361 self.on_color(Color::BrightMagenta)
362 }
363 fn on_bright_purple(self) -> ColoredString
364 where
365 Self: Sized,
366 {
367 self.on_color(Color::BrightMagenta)
368 }
369 fn on_bright_cyan(self) -> ColoredString
370 where
371 Self: Sized,
372 {
373 self.on_color(Color::BrightCyan)
374 }
375 fn on_bright_white(self) -> ColoredString
376 where
377 Self: Sized,
378 {
379 self.on_color(Color::BrightWhite)
380 }
381 fn on_truecolor(self, r: u8, g: u8, b: u8) -> ColoredString
382 where
383 Self: Sized,
384 {
385 self.on_color(Color::TrueColor { r, g, b })
386 }
387 fn on_custom_color<T>(self, color: T) -> ColoredString
388 where
389 Self: Sized,
390 T: Into<CustomColor>,
391 {
392 let color = color.into();
393
394 self.on_color(Color::TrueColor {
395 r: color.r,
396 g: color.g,
397 b: color.b,
398 })
399 }
400 fn on_color<S: Into<Color>>(self, color: S) -> ColoredString;
401 fn clear(self) -> ColoredString;
403 fn normal(self) -> ColoredString;
404 fn bold(self) -> ColoredString;
405 fn dimmed(self) -> ColoredString;
406 fn italic(self) -> ColoredString;
407 fn underline(self) -> ColoredString;
408 fn blink(self) -> ColoredString;
409 #[deprecated(since = "1.5.2", note = "Users should use reversed instead")]
410 fn reverse(self) -> ColoredString;
411 fn reversed(self) -> ColoredString;
412 fn hidden(self) -> ColoredString;
413 fn strikethrough(self) -> ColoredString;
414}
415
416impl ColoredString {
417 #[deprecated(note = "Deprecated due to the exposing of the fgcolor struct field.")]
427 pub fn fgcolor(&self) -> Option<Color> {
428 self.fgcolor.as_ref().copied()
429 }
430
431 #[deprecated(note = "Deprecated due to the exposing of the bgcolor struct field.")]
441 pub fn bgcolor(&self) -> Option<Color> {
442 self.bgcolor.as_ref().copied()
443 }
444
445 #[deprecated(note = "Deprecated due to the exposing of the style struct field.")]
455 pub fn style(&self) -> style::Style {
456 self.style
457 }
458
459 pub fn clear_fgcolor(&mut self) {
462 self.fgcolor = None;
463 }
464
465 pub fn clear_bgcolor(&mut self) {
467 self.bgcolor = None;
468 }
469
470 pub fn clear_style(&mut self) {
473 self.style = Style::default();
474 }
475
476 pub fn is_plain(&self) -> bool {
486 self.bgcolor.is_none() && self.fgcolor.is_none() && self.style == style::CLEAR
487 }
488
489 #[cfg(not(feature = "no-color"))]
490 fn has_colors() -> bool {
491 control::SHOULD_COLORIZE.should_colorize()
492 }
493
494 #[cfg(feature = "no-color")]
495 fn has_colors() -> bool {
496 false
497 }
498
499 fn compute_style(&self) -> String {
500 if !ColoredString::has_colors() || self.is_plain() {
501 return String::new();
502 }
503
504 let mut res = String::from("\x1B[");
505 let mut has_wrote = if self.style != style::CLEAR {
506 res.push_str(&self.style.to_str());
507 true
508 } else {
509 false
510 };
511
512 if let Some(ref bgcolor) = self.bgcolor {
513 if has_wrote {
514 res.push(';');
515 }
516
517 res.push_str(&bgcolor.to_bg_str());
518 has_wrote = true;
519 }
520
521 if let Some(ref fgcolor) = self.fgcolor {
522 if has_wrote {
523 res.push(';');
524 }
525
526 res.push_str(&fgcolor.to_fg_str());
527 }
528
529 res.push('m');
530 res
531 }
532
533 fn escape_inner_reset_sequences(&self) -> Cow<str> {
534 if !ColoredString::has_colors() || self.is_plain() {
535 return self.input.as_str().into();
536 }
537
538 let reset = "\x1B[0m";
540 let style = self.compute_style();
541 let matches: Vec<usize> = self
542 .input
543 .match_indices(reset)
544 .map(|(idx, _)| idx)
545 .collect();
546 if matches.is_empty() {
547 return self.input.as_str().into();
548 }
549
550 let mut input = self.input.clone();
551 input.reserve(matches.len() * style.len());
552
553 for (idx_in_matches, offset) in matches.into_iter().enumerate() {
554 let mut offset = offset + reset.len() + idx_in_matches * style.len();
557
558 for cchar in style.chars() {
559 input.insert(offset, cchar);
560 offset += 1;
561 }
562 }
563
564 input.into()
565 }
566}
567
568impl Deref for ColoredString {
569 type Target = str;
570 fn deref(&self) -> &Self::Target {
571 &self.input
572 }
573}
574
575impl DerefMut for ColoredString {
576 fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
577 &mut self.input
578 }
579}
580
581impl From<String> for ColoredString {
582 fn from(s: String) -> Self {
583 ColoredString {
584 input: s,
585 ..ColoredString::default()
586 }
587 }
588}
589
590impl<'a> From<&'a str> for ColoredString {
591 fn from(s: &'a str) -> Self {
592 ColoredString {
593 input: String::from(s),
594 ..ColoredString::default()
595 }
596 }
597}
598
599impl Colorize for ColoredString {
600 fn color<S: Into<Color>>(mut self, color: S) -> ColoredString {
601 self.fgcolor = Some(color.into());
602 self
603 }
604 fn on_color<S: Into<Color>>(mut self, color: S) -> ColoredString {
605 self.bgcolor = Some(color.into());
606 self
607 }
608
609 fn clear(self) -> ColoredString {
610 ColoredString {
611 input: self.input,
612 ..ColoredString::default()
613 }
614 }
615 fn normal(self) -> ColoredString {
616 self.clear()
617 }
618 fn bold(mut self) -> ColoredString {
619 self.style.add(style::Styles::Bold);
620 self
621 }
622 fn dimmed(mut self) -> ColoredString {
623 self.style.add(style::Styles::Dimmed);
624 self
625 }
626 fn italic(mut self) -> ColoredString {
627 self.style.add(style::Styles::Italic);
628 self
629 }
630 fn underline(mut self) -> ColoredString {
631 self.style.add(style::Styles::Underline);
632 self
633 }
634 fn blink(mut self) -> ColoredString {
635 self.style.add(style::Styles::Blink);
636 self
637 }
638 fn reverse(self) -> ColoredString {
639 self.reversed()
640 }
641 fn reversed(mut self) -> ColoredString {
642 self.style.add(style::Styles::Reversed);
643 self
644 }
645 fn hidden(mut self) -> ColoredString {
646 self.style.add(style::Styles::Hidden);
647 self
648 }
649 fn strikethrough(mut self) -> ColoredString {
650 self.style.add(style::Styles::Strikethrough);
651 self
652 }
653}
654
655impl Colorize for &str {
656 fn color<S: Into<Color>>(self, color: S) -> ColoredString {
657 ColoredString {
658 fgcolor: Some(color.into()),
659 input: String::from(self),
660 ..ColoredString::default()
661 }
662 }
663
664 fn on_color<S: Into<Color>>(self, color: S) -> ColoredString {
665 ColoredString {
666 bgcolor: Some(color.into()),
667 input: String::from(self),
668 ..ColoredString::default()
669 }
670 }
671
672 fn clear(self) -> ColoredString {
673 ColoredString {
674 input: String::from(self),
675 style: style::CLEAR,
676 ..ColoredString::default()
677 }
678 }
679 fn normal(self) -> ColoredString {
680 self.clear()
681 }
682 fn bold(self) -> ColoredString {
683 ColoredString::from(self).bold()
684 }
685 fn dimmed(self) -> ColoredString {
686 ColoredString::from(self).dimmed()
687 }
688 fn italic(self) -> ColoredString {
689 ColoredString::from(self).italic()
690 }
691 fn underline(self) -> ColoredString {
692 ColoredString::from(self).underline()
693 }
694 fn blink(self) -> ColoredString {
695 ColoredString::from(self).blink()
696 }
697 fn reverse(self) -> ColoredString {
698 self.reversed()
699 }
700 fn reversed(self) -> ColoredString {
701 ColoredString::from(self).reversed()
702 }
703 fn hidden(self) -> ColoredString {
704 ColoredString::from(self).hidden()
705 }
706 fn strikethrough(self) -> ColoredString {
707 ColoredString::from(self).strikethrough()
708 }
709}
710
711impl fmt::Display for ColoredString {
712 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
713 if !ColoredString::has_colors() || self.is_plain() {
714 return <String as fmt::Display>::fmt(&self.input, f);
715 }
716
717 let escaped_input = self.escape_inner_reset_sequences();
719
720 f.write_str(&self.compute_style())?;
721 escaped_input.fmt(f)?;
722 f.write_str("\x1B[0m")?;
723 Ok(())
724 }
725}
726
727impl From<ColoredString> for Box<dyn Error> {
728 fn from(cs: ColoredString) -> Box<dyn Error> {
729 Box::from(error::ColoredStringError(cs))
730 }
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736 use std::{error::Error, fmt::Write};
737
738 #[test]
739 fn formatting() {
740 assert!(format!("{:40}", "".blue()).len() >= 40);
742 assert_eq!(
744 format!("{:1.1}", "toto".blue()).len(),
745 format!("{:1.1}", "1".blue()).len()
746 )
747 }
748
749 #[test]
750 fn it_works() -> Result<(), Box<dyn Error>> {
751 let mut buf = String::new();
752 let toto = "toto";
753 writeln!(&mut buf, "{}", toto.red())?;
754 writeln!(&mut buf, "{}", String::from(toto).red())?;
755 writeln!(&mut buf, "{}", toto.blue())?;
756
757 writeln!(&mut buf, "blue style ****")?;
758 writeln!(&mut buf, "{}", toto.bold())?;
759 writeln!(&mut buf, "{}", "yeah ! Red bold !".red().bold())?;
760 writeln!(&mut buf, "{}", "yeah ! Yellow bold !".bold().yellow())?;
761 writeln!(&mut buf, "{}", toto.bold().blue())?;
762 writeln!(&mut buf, "{}", toto.blue().bold())?;
763 writeln!(&mut buf, "{}", toto.blue().bold().underline())?;
764 writeln!(&mut buf, "{}", toto.blue().italic())?;
765 writeln!(&mut buf, "******")?;
766 writeln!(&mut buf, "test clearing")?;
767 writeln!(&mut buf, "{}", "red cleared".red().clear())?;
768 writeln!(&mut buf, "{}", "bold cyan cleared".bold().cyan().clear())?;
769 writeln!(&mut buf, "******")?;
770 writeln!(&mut buf, "Bg tests")?;
771 writeln!(&mut buf, "{}", toto.green().on_blue())?;
772 writeln!(&mut buf, "{}", toto.on_magenta().yellow())?;
773 writeln!(&mut buf, "{}", toto.purple().on_yellow())?;
774 writeln!(&mut buf, "{}", toto.magenta().on_white())?;
775 writeln!(&mut buf, "{}", toto.cyan().on_green())?;
776 writeln!(&mut buf, "{}", toto.black().on_white())?;
777 writeln!(&mut buf, "******")?;
778 writeln!(&mut buf, "{}", toto.green())?;
779 writeln!(&mut buf, "{}", toto.yellow())?;
780 writeln!(&mut buf, "{}", toto.purple())?;
781 writeln!(&mut buf, "{}", toto.magenta())?;
782 writeln!(&mut buf, "{}", toto.cyan())?;
783 writeln!(&mut buf, "{}", toto.white())?;
784 writeln!(&mut buf, "{}", toto.white().red().blue().green())?;
785 writeln!(&mut buf, "{}", toto.truecolor(255, 0, 0))?;
786 writeln!(&mut buf, "{}", toto.truecolor(255, 255, 0))?;
787 writeln!(&mut buf, "{}", toto.on_truecolor(0, 80, 80))?;
788 writeln!(&mut buf, "{}", toto.custom_color((255, 255, 0)))?;
789 writeln!(&mut buf, "{}", toto.on_custom_color((0, 80, 80)))?;
790 #[cfg(feature = "no-color")]
791 insta::assert_snapshot!("it_works_no_color", buf);
792 #[cfg(not(feature = "no-color"))]
793 insta::assert_snapshot!("it_works", buf);
794 Ok(())
795 }
796
797 #[test]
798 fn compute_style_empty_string() {
799 assert_eq!("", "".clear().compute_style());
800 }
801
802 #[cfg_attr(feature = "no-color", ignore)]
803 #[test]
804 fn compute_style_simple_fg_blue() {
805 let blue = "\x1B[34m";
806
807 assert_eq!(blue, "".blue().compute_style());
808 }
809
810 #[cfg_attr(feature = "no-color", ignore)]
811 #[test]
812 fn compute_style_simple_bg_blue() {
813 let on_blue = "\x1B[44m";
814
815 assert_eq!(on_blue, "".on_blue().compute_style());
816 }
817
818 #[cfg_attr(feature = "no-color", ignore)]
819 #[test]
820 fn compute_style_blue_on_blue() {
821 let blue_on_blue = "\x1B[44;34m";
822
823 assert_eq!(blue_on_blue, "".blue().on_blue().compute_style());
824 }
825
826 #[cfg_attr(feature = "no-color", ignore)]
827 #[test]
828 fn compute_style_simple_fg_bright_blue() {
829 let blue = "\x1B[94m";
830
831 assert_eq!(blue, "".bright_blue().compute_style());
832 }
833
834 #[cfg_attr(feature = "no-color", ignore)]
835 #[test]
836 fn compute_style_simple_bg_bright_blue() {
837 let on_blue = "\x1B[104m";
838
839 assert_eq!(on_blue, "".on_bright_blue().compute_style());
840 }
841
842 #[cfg_attr(feature = "no-color", ignore)]
843 #[test]
844 fn compute_style_bright_blue_on_bright_blue() {
845 let blue_on_blue = "\x1B[104;94m";
846
847 assert_eq!(
848 blue_on_blue,
849 "".bright_blue().on_bright_blue().compute_style()
850 );
851 }
852
853 #[cfg_attr(feature = "no-color", ignore)]
854 #[test]
855 fn compute_style_simple_bold() {
856 let bold = "\x1B[1m";
857
858 assert_eq!(bold, "".bold().compute_style());
859 }
860
861 #[cfg_attr(feature = "no-color", ignore)]
862 #[test]
863 fn compute_style_blue_bold() {
864 let blue_bold = "\x1B[1;34m";
865
866 assert_eq!(blue_bold, "".blue().bold().compute_style());
867 }
868
869 #[cfg_attr(feature = "no-color", ignore)]
870 #[test]
871 fn compute_style_blue_bold_on_blue() {
872 let blue_bold_on_blue = "\x1B[1;44;34m";
873
874 assert_eq!(
875 blue_bold_on_blue,
876 "".blue().bold().on_blue().compute_style()
877 );
878 }
879
880 #[test]
881 fn escape_reset_sequence_spec_should_do_nothing_on_empty_strings() {
882 let style = ColoredString::default();
883 let expected = String::new();
884
885 let output = style.escape_inner_reset_sequences();
886
887 assert_eq!(expected, output);
888 }
889
890 #[test]
891 fn escape_reset_sequence_spec_should_do_nothing_on_string_with_no_reset() {
892 let style = ColoredString {
893 input: String::from("hello world !"),
894 ..ColoredString::default()
895 };
896
897 let expected = String::from("hello world !");
898 let output = style.escape_inner_reset_sequences();
899
900 assert_eq!(expected, output);
901 }
902
903 #[cfg_attr(feature = "no-color", ignore)]
904 #[test]
905 fn escape_reset_sequence_spec_should_replace_inner_reset_sequence_with_current_style() {
906 let input = format!("start {} end", String::from("hello world !").red());
907 let style = input.blue();
908
909 let output = style.escape_inner_reset_sequences();
910 let blue = "\x1B[34m";
911 let red = "\x1B[31m";
912 let reset = "\x1B[0m";
913 let expected = format!("start {}hello world !{}{} end", red, reset, blue);
914 assert_eq!(expected, output);
915 }
916
917 #[cfg_attr(feature = "no-color", ignore)]
918 #[test]
919 fn escape_reset_sequence_spec_should_replace_multiple_inner_reset_sequences_with_current_style()
920 {
921 let italic_str = String::from("yo").italic();
922 let input = format!(
923 "start 1:{} 2:{} 3:{} end",
924 italic_str, italic_str, italic_str
925 );
926 let style = input.blue();
927
928 let output = style.escape_inner_reset_sequences();
929 let blue = "\x1B[34m";
930 let italic = "\x1B[3m";
931 let reset = "\x1B[0m";
932 let expected = format!(
933 "start 1:{}yo{}{} 2:{}yo{}{} 3:{}yo{}{} end",
934 italic, reset, blue, italic, reset, blue, italic, reset, blue
935 );
936
937 println!("first: {}\nsecond: {}", expected, output);
938
939 assert_eq!(expected, output);
940 }
941
942 #[test]
943 fn color_fn() {
944 assert_eq!("blue".blue(), "blue".color("blue"));
945 }
946
947 #[test]
948 fn on_color_fn() {
949 assert_eq!("blue".on_blue(), "blue".on_color("blue"));
950 }
951
952 #[test]
953 fn bright_color_fn() {
954 assert_eq!("blue".bright_blue(), "blue".color("bright blue"));
955 }
956
957 #[test]
958 fn on_bright_color_fn() {
959 assert_eq!("blue".on_bright_blue(), "blue".on_color("bright blue"));
960 }
961
962 #[test]
963 fn exposing_tests() {
964 #![allow(deprecated)]
965
966 let cstring = "".red();
967 assert_eq!(cstring.fgcolor(), Some(Color::Red));
968 assert_eq!(cstring.bgcolor(), None);
969
970 let cstring = cstring.clear();
971 assert_eq!(cstring.fgcolor(), None);
972 assert_eq!(cstring.bgcolor(), None);
973
974 let cstring = cstring.blue().on_bright_yellow();
975 assert_eq!(cstring.fgcolor(), Some(Color::Blue));
976 assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow));
977
978 let cstring = cstring.bold().italic();
979 assert_eq!(cstring.fgcolor(), Some(Color::Blue));
980 assert_eq!(cstring.bgcolor(), Some(Color::BrightYellow));
981 assert!(cstring.style().contains(Styles::Bold));
982 assert!(cstring.style().contains(Styles::Italic));
983 assert!(!cstring.style().contains(Styles::Dimmed));
984 }
985}