Skip to content

Commit f32b662

Browse files
authored
Wrap termcolor's color api (#75)
1 parent 4d7926c commit f32b662

1 file changed

Lines changed: 203 additions & 5 deletions

File tree

src/fmt.rs

Lines changed: 203 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@
3737
use std::io::prelude::*;
3838
use std::{io, fmt};
3939
use std::rc::Rc;
40+
use std::str::FromStr;
41+
use std::error::Error;
4042
use std::cell::RefCell;
4143
use std::time::SystemTime;
4244

43-
use termcolor::{ColorSpec, ColorChoice, Buffer, BufferWriter, WriteColor};
45+
use termcolor::{self, ColorSpec, ColorChoice, Buffer, BufferWriter, WriteColor};
4446
use atty;
4547
use humantime::format_rfc3339_seconds;
4648

47-
pub use termcolor::Color;
48-
4949
/// A formatter to write logs into.
5050
///
5151
/// `Formatter` implements the standard [`Write`] trait for writing log records.
@@ -287,7 +287,7 @@ impl Style {
287287
/// });
288288
/// ```
289289
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());
291291
self
292292
}
293293

@@ -366,7 +366,7 @@ impl Style {
366366
/// });
367367
/// ```
368368
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());
370370
self
371371
}
372372

@@ -575,6 +575,144 @@ impl fmt::Display for Timestamp {
575575
}
576576
}
577577

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+
578716
fn parse_write_style(spec: &str) -> WriteStyle {
579717
match spec {
580718
"auto" => WriteStyle::Auto,
@@ -614,4 +752,64 @@ mod tests {
614752
assert_eq!(WriteStyle::Auto, parse_write_style(input));
615753
}
616754
}
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+
}
617815
}

0 commit comments

Comments
 (0)