|
37 | 37 | use std::io::prelude::*; |
38 | 38 | use std::{io, fmt}; |
39 | 39 | use std::rc::Rc; |
| 40 | +use std::str::FromStr; |
| 41 | +use std::error::Error; |
40 | 42 | use std::cell::RefCell; |
41 | 43 | use std::time::SystemTime; |
42 | 44 |
|
43 | | -use termcolor::{ColorSpec, ColorChoice, Buffer, BufferWriter, WriteColor}; |
| 45 | +use termcolor::{self, ColorSpec, ColorChoice, Buffer, BufferWriter, WriteColor}; |
44 | 46 | use atty; |
45 | 47 | use humantime::format_rfc3339_seconds; |
46 | 48 |
|
47 | | -pub use termcolor::Color; |
48 | | - |
49 | 49 | /// A formatter to write logs into. |
50 | 50 | /// |
51 | 51 | /// `Formatter` implements the standard [`Write`] trait for writing log records. |
@@ -287,7 +287,7 @@ impl Style { |
287 | 287 | /// }); |
288 | 288 | /// ``` |
289 | 289 | pub fn set_color(&mut self, color: Color) -> &mut Style { |
290 | | - self.spec.set_fg(Some(color)); |
| 290 | + self.spec.set_fg(color.to_termcolor()); |
291 | 291 | self |
292 | 292 | } |
293 | 293 |
|
@@ -366,7 +366,7 @@ impl Style { |
366 | 366 | /// }); |
367 | 367 | /// ``` |
368 | 368 | pub fn set_bg(&mut self, color: Color) -> &mut Style { |
369 | | - self.spec.set_bg(Some(color)); |
| 369 | + self.spec.set_bg(color.to_termcolor()); |
370 | 370 | self |
371 | 371 | } |
372 | 372 |
|
@@ -575,6 +575,144 @@ impl fmt::Display for Timestamp { |
575 | 575 | } |
576 | 576 | } |
577 | 577 |
|
| 578 | +// The `Color` type is copied from https://github.com/BurntSushi/ripgrep/tree/master/termcolor |
| 579 | + |
| 580 | +/// The set of available colors for the terminal foreground/background. |
| 581 | +/// |
| 582 | +/// The `Ansi256` and `Rgb` colors will only output the correct codes when |
| 583 | +/// paired with the `Ansi` `WriteColor` implementation. |
| 584 | +/// |
| 585 | +/// The `Ansi256` and `Rgb` color types are not supported when writing colors |
| 586 | +/// on Windows using the console. If they are used on Windows, then they are |
| 587 | +/// silently ignored and no colors will be emitted. |
| 588 | +/// |
| 589 | +/// This set may expand over time. |
| 590 | +/// |
| 591 | +/// This type has a `FromStr` impl that can parse colors from their human |
| 592 | +/// readable form. The format is as follows: |
| 593 | +/// |
| 594 | +/// 1. Any of the explicitly listed colors in English. They are matched |
| 595 | +/// case insensitively. |
| 596 | +/// 2. A single 8-bit integer, in either decimal or hexadecimal format. |
| 597 | +/// 3. A triple of 8-bit integers separated by a comma, where each integer is |
| 598 | +/// in decimal or hexadecimal format. |
| 599 | +/// |
| 600 | +/// Hexadecimal numbers are written with a `0x` prefix. |
| 601 | +#[allow(missing_docs)] |
| 602 | +#[derive(Clone, Debug, Eq, PartialEq)] |
| 603 | +pub enum Color { |
| 604 | + Black, |
| 605 | + Blue, |
| 606 | + Green, |
| 607 | + Red, |
| 608 | + Cyan, |
| 609 | + Magenta, |
| 610 | + Yellow, |
| 611 | + White, |
| 612 | + Ansi256(u8), |
| 613 | + Rgb(u8, u8, u8), |
| 614 | + #[doc(hidden)] |
| 615 | + __Nonexhaustive, |
| 616 | +} |
| 617 | + |
| 618 | +/// An error from parsing an invalid color specification. |
| 619 | +#[derive(Clone, Debug, Eq, PartialEq)] |
| 620 | +pub struct ParseColorError(ParseColorErrorKind); |
| 621 | + |
| 622 | +#[derive(Clone, Debug, Eq, PartialEq)] |
| 623 | +enum ParseColorErrorKind { |
| 624 | + /// An error originating from `termcolor`. |
| 625 | + TermColor(termcolor::ParseColorError), |
| 626 | + /// An error converting the `termcolor` color to a `env_logger::Color`. |
| 627 | + /// |
| 628 | + /// This variant should only get reached if a user uses a new spec that's |
| 629 | + /// valid for `termcolor`, but not recognised in `env_logger` yet. |
| 630 | + Unrecognized { |
| 631 | + given: String, |
| 632 | + } |
| 633 | +} |
| 634 | + |
| 635 | +impl ParseColorError { |
| 636 | + fn termcolor(err: termcolor::ParseColorError) -> Self { |
| 637 | + ParseColorError(ParseColorErrorKind::TermColor(err)) |
| 638 | + } |
| 639 | + |
| 640 | + fn unrecognized(given: String) -> Self { |
| 641 | + ParseColorError(ParseColorErrorKind::Unrecognized { given }) |
| 642 | + } |
| 643 | + |
| 644 | + /// Return the string that couldn't be parsed as a valid color. |
| 645 | + pub fn invalid(&self) -> &str { |
| 646 | + match self.0 { |
| 647 | + ParseColorErrorKind::TermColor(ref err) => err.invalid(), |
| 648 | + ParseColorErrorKind::Unrecognized { ref given, .. } => given, |
| 649 | + } |
| 650 | + } |
| 651 | +} |
| 652 | + |
| 653 | +impl Error for ParseColorError { |
| 654 | + fn description(&self) -> &str { |
| 655 | + match self.0 { |
| 656 | + ParseColorErrorKind::TermColor(ref err) => err.description(), |
| 657 | + ParseColorErrorKind::Unrecognized { .. } => "unrecognized color value", |
| 658 | + } |
| 659 | + } |
| 660 | +} |
| 661 | + |
| 662 | +impl fmt::Display for ParseColorError { |
| 663 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 664 | + match self.0 { |
| 665 | + ParseColorErrorKind::TermColor(ref err) => fmt::Display::fmt(err, f), |
| 666 | + ParseColorErrorKind::Unrecognized { ref given, .. } => { |
| 667 | + write!(f, "unrecognized color value '{}'", given) |
| 668 | + } |
| 669 | + } |
| 670 | + } |
| 671 | +} |
| 672 | + |
| 673 | +impl Color { |
| 674 | + fn to_termcolor(self) -> Option<termcolor::Color> { |
| 675 | + match self { |
| 676 | + Color::Black => Some(termcolor::Color::Black), |
| 677 | + Color::Blue => Some(termcolor::Color::Blue), |
| 678 | + Color::Green => Some(termcolor::Color::Green), |
| 679 | + Color::Red => Some(termcolor::Color::Red), |
| 680 | + Color::Cyan => Some(termcolor::Color::Cyan), |
| 681 | + Color::Magenta => Some(termcolor::Color::Magenta), |
| 682 | + Color::Yellow => Some(termcolor::Color::Yellow), |
| 683 | + Color::White => Some(termcolor::Color::White), |
| 684 | + Color::Ansi256(value) => Some(termcolor::Color::Ansi256(value)), |
| 685 | + Color::Rgb(r, g, b) => Some(termcolor::Color::Rgb(r, g, b)), |
| 686 | + _ => None, |
| 687 | + } |
| 688 | + } |
| 689 | + |
| 690 | + fn from_termcolor(color: termcolor::Color) -> Option<Color> { |
| 691 | + match color { |
| 692 | + termcolor::Color::Black => Some(Color::Black), |
| 693 | + termcolor::Color::Blue => Some(Color::Blue), |
| 694 | + termcolor::Color::Green => Some(Color::Green), |
| 695 | + termcolor::Color::Red => Some(Color::Red), |
| 696 | + termcolor::Color::Cyan => Some(Color::Cyan), |
| 697 | + termcolor::Color::Magenta => Some(Color::Magenta), |
| 698 | + termcolor::Color::Yellow => Some(Color::Yellow), |
| 699 | + termcolor::Color::White => Some(Color::White), |
| 700 | + termcolor::Color::Ansi256(value) => Some(Color::Ansi256(value)), |
| 701 | + termcolor::Color::Rgb(r, g, b) => Some(Color::Rgb(r, g, b)), |
| 702 | + _ => None, |
| 703 | + } |
| 704 | + } |
| 705 | +} |
| 706 | + |
| 707 | +impl FromStr for Color { |
| 708 | + type Err = ParseColorError; |
| 709 | + |
| 710 | + fn from_str(s: &str) -> Result<Color, ParseColorError> { |
| 711 | + let tc = termcolor::Color::from_str(s).map_err(ParseColorError::termcolor)?; |
| 712 | + Color::from_termcolor(tc).ok_or(ParseColorError::unrecognized(s.to_owned())) |
| 713 | + } |
| 714 | +} |
| 715 | + |
578 | 716 | fn parse_write_style(spec: &str) -> WriteStyle { |
579 | 717 | match spec { |
580 | 718 | "auto" => WriteStyle::Auto, |
@@ -614,4 +752,64 @@ mod tests { |
614 | 752 | assert_eq!(WriteStyle::Auto, parse_write_style(input)); |
615 | 753 | } |
616 | 754 | } |
| 755 | + |
| 756 | + #[test] |
| 757 | + fn parse_color_name_valid() { |
| 758 | + let inputs = vec![ |
| 759 | + "black", |
| 760 | + "blue", |
| 761 | + "green", |
| 762 | + "red", |
| 763 | + "cyan", |
| 764 | + "magenta", |
| 765 | + "yellow", |
| 766 | + "white", |
| 767 | + ]; |
| 768 | + |
| 769 | + for input in inputs { |
| 770 | + assert!(Color::from_str(input).is_ok()); |
| 771 | + } |
| 772 | + } |
| 773 | + |
| 774 | + #[test] |
| 775 | + fn parse_color_ansi_valid() { |
| 776 | + let inputs = vec![ |
| 777 | + "7", |
| 778 | + "32", |
| 779 | + "0xFF", |
| 780 | + ]; |
| 781 | + |
| 782 | + for input in inputs { |
| 783 | + assert!(Color::from_str(input).is_ok()); |
| 784 | + } |
| 785 | + } |
| 786 | + |
| 787 | + #[test] |
| 788 | + fn parse_color_rgb_valid() { |
| 789 | + let inputs = vec![ |
| 790 | + "0,0,0", |
| 791 | + "0,128,255", |
| 792 | + "0x0,0x0,0x0", |
| 793 | + "0x33,0x66,0xFF", |
| 794 | + ]; |
| 795 | + |
| 796 | + for input in inputs { |
| 797 | + assert!(Color::from_str(input).is_ok()); |
| 798 | + } |
| 799 | + } |
| 800 | + |
| 801 | + #[test] |
| 802 | + fn parse_color_invalid() { |
| 803 | + let inputs = vec![ |
| 804 | + "not_a_color", |
| 805 | + "256", |
| 806 | + "0,0", |
| 807 | + "0,0,256", |
| 808 | + ]; |
| 809 | + |
| 810 | + for input in inputs { |
| 811 | + let err = Color::from_str(input).unwrap_err(); |
| 812 | + assert_eq!(input, err.invalid()); |
| 813 | + } |
| 814 | + } |
617 | 815 | } |
0 commit comments