diff --git a/src/lib.rs b/src/lib.rs index c93cf7b..077aeae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1799,8 +1799,35 @@ impl ColorSpec { /// 2. A single 8-bit integer, in either decimal or hexadecimal format. /// 3. A triple of 8-bit integers separated by a comma, where each integer is /// in decimal or hexadecimal format. +/// 4. A `#` followed by 6 hexadecimal digits of any case, with each octet +/// representing red, green and blue, respectively. /// /// Hexadecimal numbers are written with a `0x` prefix. +/// +/// ## Example +/// ``` +/// use termcolor::Color; +/// +/// let color = "red".parse::(); +/// assert_eq!(color, Ok(Color::Red)); +/// +/// let color = "32".parse::(); +/// assert_eq!(color, Ok(Color::Ansi256(32))); +/// +/// // Note that also lowercase 0xaa is allowed +/// let color = "0xAA".parse::(); +/// assert_eq!(color, Ok(Color::Ansi256(0xAA))); +/// +/// let color = "0,128,255".parse::(); +/// assert_eq!(color, Ok(Color::Rgb(0, 128, 255))); +/// +/// let color = "0x0,0xFF,0x0".parse::(); +/// assert_eq!(color, Ok(Color::Rgb(0, 0xFF, 0))); +/// +/// // Note that also lowercase #ff6633 is allowed +/// let color = "#FF6633".parse::(); +/// assert_eq!(color, Ok(Color::Rgb(0xFF, 0x66, 0x33))); +/// ``` #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Color { @@ -1866,7 +1893,8 @@ impl Color { // corresponding to one of 256 colors. // // The "rgb" format is a triple of numbers (decimal or hex) delimited - // by a comma corresponding to one of 256^3 colors. + // by a comma corresponding to one of 256^3 colors or #rrggbb string + // in the hex format. fn parse_number(s: &str) -> Option { use std::u8; @@ -1880,7 +1908,22 @@ impl Color { let codes: Vec<&str> = s.split(',').collect(); if codes.len() == 1 { - if let Some(n) = parse_number(&codes[0]) { + if codes[0].starts_with('#') { + use std::u8; + if codes[0].len() == 7 { + if let (Ok(r), Ok(g), Ok(b)) = ( + u8::from_str_radix(&codes[0][1..3], 16), + u8::from_str_radix(&codes[0][3..5], 16), + u8::from_str_radix(&codes[0][5..7], 16), + ) { + return Ok(Color::Rgb(r, g, b)); + } + } + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: s.to_string(), + }) + } else if let Some(n) = parse_number(&codes[0]) { Ok(Color::Ansi256(n)) } else { if s.chars().all(|c| c.is_digit(16)) { @@ -2137,6 +2180,18 @@ mod tests { let color = "0x33,0x66,0xFF".parse::(); assert_eq!(color, Ok(Color::Rgb(0x33, 0x66, 0xFF))); + + let color = "0xCC,0xaa,0xff".parse::(); + assert_eq!(color, Ok(Color::Rgb(0xCC, 0xAA, 0xFF))); + + let color = "0xCC,0xaa,0xff".parse::(); + assert_eq!(color, Ok(Color::Rgb(0xCC, 0xAA, 0xFF))); + + let color = "#FF6633".parse::(); + assert_eq!(color, Ok(Color::Rgb(0xFF, 0x66, 0x33))); + + let color = "#ffCCaa".parse::(); + assert_eq!(color, Ok(Color::Rgb(0xFF, 0xCC, 0xAA))); } #[test] @@ -2149,6 +2204,15 @@ mod tests { given: "0,0,256".to_string(), }) ); + + let color = "0,0,aaa".parse::(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: "0,0,aaa".to_string(), + }) + ); } #[test] @@ -2162,6 +2226,24 @@ mod tests { }) ); + let color = "#ffCC001".parse::(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: "#ffCC001".to_string() + }) + ); + + let color = "#00CC1".parse::(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: "#00CC1".to_string() + }) + ); + let color = "not_a_color".parse::(); assert_eq!( color, @@ -2170,6 +2252,24 @@ mod tests { given: "not_a_color".to_string(), }) ); + + let color = "#00ggee".parse::(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: "#00ggee".to_string(), + }) + ); + + let color = "#00gg😜".parse::(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: "#00gg😜".to_string(), + }) + ); } #[test]