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