summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2023-02-18 01:55:26 -0500
committerGitHub <noreply@github.com>2023-02-18 01:55:26 -0500
commitf89b243589b5d0e0a3dfe75337a66fa1091d0434 (patch)
treeddc401aac04839252a55d1e46a9d246ab2c0366c /src
parentedc61d428c9045982030cf2b54ea20ef8cd5baab (diff)
feature: support 3-char hex colours (#1022)
Diffstat (limited to 'src')
-rw-r--r--src/canvas/canvas_styling/colour_utils.rs172
1 files changed, 124 insertions, 48 deletions
diff --git a/src/canvas/canvas_styling/colour_utils.rs b/src/canvas/canvas_styling/colour_utils.rs
index 89b1b170..2814c580 100644
--- a/src/canvas/canvas_styling/colour_utils.rs
+++ b/src/canvas/canvas_styling/colour_utils.rs
@@ -1,4 +1,7 @@
+use concat_string::concat_string;
+use itertools::Itertools;
use tui::style::{Color, Style};
+use unicode_segmentation::UnicodeSegmentation;
use crate::utils::error;
@@ -10,41 +13,44 @@ pub const HIGHLIGHT_COLOUR: Color = Color::LightBlue;
pub const AVG_COLOUR: Color = Color::Red;
pub const ALL_COLOUR: Color = Color::Green;
+/// Convert a hex string to a colour.
fn convert_hex_to_color(hex: &str) -> error::Result<Color> {
- fn hex_err(hex: &str) -> error::Result<u8> {
- Err(
+ fn hex_component_to_int(hex: &str, first: &str, second: &str) -> error::Result<u8> {
+ u8::from_str_radix(&concat_string!(first, second), 16).map_err(|_| {
error::BottomError::ConfigError(format!(
- "\"{}\" is an invalid hex colour. It must be a valid 7 character hex string of the (ie: \"#112233\")."
- , hex))
- )
+ "\"{hex}\" is an invalid hex color, could not decode."
+ ))
+ })
}
- fn convert_hex_to_rgb(hex: &str) -> error::Result<(u8, u8, u8)> {
- let hex_components: Vec<char> = hex.chars().collect();
+ fn invalid_hex_format(hex: &str) -> error::BottomError {
+ error::BottomError::ConfigError(format!(
+ "\"{hex}\" is an invalid hex color. It must be either a 7 character hex string of the form \"#12ab3c\" or a 3 character hex string of the form \"#1a2\".",
+ ))
+ }
- if hex_components.len() == 7 {
- let mut r_string = hex_components[1].to_string();
- r_string.push(hex_components[2]);
- let mut g_string = hex_components[3].to_string();
- g_string.push(hex_components[4]);
- let mut b_string = hex_components[5].to_string();
- b_string.push(hex_components[6]);
+ if !hex.starts_with('#') {
+ return Err(invalid_hex_format(hex));
+ }
- let r = u8::from_str_radix(&r_string, 16).or_else(|_err| hex_err(hex))?;
- let g = u8::from_str_radix(&g_string, 16).or_else(|_err| hex_err(hex))?;
- let b = u8::from_str_radix(&b_string, 16).or_else(|_err| hex_err(hex))?;
+ let components = hex.graphemes(true).collect_vec();
+ if components.len() == 7 {
+ // A 6-long hex.
+ let r = hex_component_to_int(hex, components[1], components[2])?;
+ let g = hex_component_to_int(hex, components[3], components[4])?;
+ let b = hex_component_to_int(hex, components[5], components[6])?;
- return Ok((r, g, b));
- }
+ Ok(Color::Rgb(r, g, b))
+ } else if components.len() == 4 {
+ // A 3-long hex.
+ let r = hex_component_to_int(hex, components[1], components[1])?;
+ let g = hex_component_to_int(hex, components[2], components[2])?;
+ let b = hex_component_to_int(hex, components[3], components[3])?;
- Err(error::BottomError::ConfigError(format!(
- "\"{}\" is an invalid hex colour. It must be a 7 character string of the form \"#112233\".",
- hex
- )))
+ Ok(Color::Rgb(r, g, b))
+ } else {
+ Err(invalid_hex_format(hex))
}
-
- let rgb = convert_hex_to_rgb(hex)?;
- Ok(Color::Rgb(rgb.0, rgb.1, rgb.2))
}
pub fn str_to_fg(input_val: &str) -> error::Result<Style> {
@@ -58,7 +64,7 @@ pub fn str_to_colour(input_val: &str) -> error::Result<Color> {
} else if input_val.contains(',') {
convert_rgb_to_color(input_val)
} else {
- convert_name_to_color(input_val)
+ convert_name_to_colour(input_val)
}
} else {
Err(error::BottomError::ConfigError(format!(
@@ -72,7 +78,7 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> {
let rgb_list = rgb_str.split(',').collect::<Vec<&str>>();
if rgb_list.len() != 3 {
return Err(error::BottomError::ConfigError(format!(
- "value \"{}\" is an invalid RGB colour. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
+ "value \"{}\" is an invalid RGB colour. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
rgb_str
)));
}
@@ -91,13 +97,13 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> {
Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
} else {
Err(error::BottomError::ConfigError(format!(
- "value \"{}\" contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
+ "value \"{}\" contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
rgb_str
)))
}
}
-fn convert_name_to_color(color_name: &str) -> error::Result<Color> {
+fn convert_name_to_colour(color_name: &str) -> error::Result<Color> {
match color_name.to_lowercase().trim() {
"reset" => Ok(Color::Reset),
"black" => Ok(Color::Black),
@@ -117,7 +123,7 @@ fn convert_name_to_color(color_name: &str) -> error::Result<Color> {
"lightcyan" | "light cyan" => Ok(Color::LightCyan),
"white" => Ok(Color::White),
_ => Err(error::BottomError::ConfigError(format!(
- "\"{}\" is an invalid named colour.
+ "\"{}\" is an invalid named color.
The following are supported strings:
+--------+-------------+---------------------+
@@ -144,49 +150,119 @@ mod test {
use super::*;
#[test]
- fn test_invalid_colours() {
+ fn invalid_colour_names() {
// Test invalid spacing in single word.
- assert!(convert_name_to_color("bl ack").is_err());
+ assert!(convert_name_to_colour("bl ack").is_err());
// Test invalid spacing in dual word.
- assert!(convert_name_to_color("darkg ray").is_err());
+ assert!(convert_name_to_colour("darkg ray").is_err());
// Test completely invalid colour.
- assert!(convert_name_to_color("darkreset").is_err());
+ assert!(convert_name_to_colour("darkreset").is_err());
}
#[test]
- fn test_valid_colours() {
+ fn valid_colour_names() {
// Standard color should work
- assert_eq!(convert_name_to_color("red"), Ok(Color::Red));
+ assert_eq!(convert_name_to_colour("red"), Ok(Color::Red));
// Capitalizing should be fine.
- assert_eq!(convert_name_to_color("RED"), Ok(Color::Red));
+ assert_eq!(convert_name_to_colour("RED"), Ok(Color::Red));
// Spacing shouldn't be an issue now.
- assert_eq!(convert_name_to_color(" red "), Ok(Color::Red));
+ assert_eq!(convert_name_to_colour(" red "), Ok(Color::Red));
// The following are all equivalent.
- assert_eq!(convert_name_to_color("darkgray"), Ok(Color::DarkGray));
- assert_eq!(convert_name_to_color("darkgrey"), Ok(Color::DarkGray));
- assert_eq!(convert_name_to_color("dark grey"), Ok(Color::DarkGray));
- assert_eq!(convert_name_to_color("dark gray"), Ok(Color::DarkGray));
+ assert_eq!(convert_name_to_colour("darkgray"), Ok(Color::DarkGray));
+ assert_eq!(convert_name_to_colour("darkgrey"), Ok(Color::DarkGray));
+ assert_eq!(convert_name_to_colour("dark grey"), Ok(Color::DarkGray));
+ assert_eq!(convert_name_to_colour("dark gray"), Ok(Color::DarkGray));
- assert_eq!(convert_name_to_color("grey"), Ok(Color::Gray));
- assert_eq!(convert_name_to_color("gray"), Ok(Color::Gray));
+ assert_eq!(convert_name_to_colour("grey"), Ok(Color::Gray));
+ assert_eq!(convert_name_to_colour("gray"), Ok(Color::Gray));
// One more test with spacing.
assert_eq!(
- convert_name_to_color(" lightmagenta "),
+ convert_name_to_colour(" lightmagenta "),
Ok(Color::LightMagenta)
);
assert_eq!(
- convert_name_to_color("light magenta"),
+ convert_name_to_colour("light magenta"),
Ok(Color::LightMagenta)
);
assert_eq!(
- convert_name_to_color(" light magenta "),
+ convert_name_to_colour(" light magenta "),
Ok(Color::LightMagenta)
);
}
+
+ #[test]
+ fn valid_hex_colours() {
+ assert_eq!(
+ convert_hex_to_color("#ffffff").unwrap(),
+ Color::Rgb(255, 255, 255)
+ );
+ assert_eq!(
+ convert_hex_to_color("#000000").unwrap(),
+ Color::Rgb(0, 0, 0)
+ );
+ convert_hex_to_color("#111111").unwrap();
+ convert_hex_to_color("#11ff11").unwrap();
+ convert_hex_to_color("#1f1f1f").unwrap();
+ assert_eq!(
+ convert_hex_to_color("#123abc").unwrap(),
+ Color::Rgb(18, 58, 188)
+ );
+
+ assert_eq!(
+ convert_hex_to_color("#fff").unwrap(),
+ Color::Rgb(255, 255, 255)
+ );
+ assert_eq!(convert_hex_to_color("#000").unwrap(), Color::Rgb(0, 0, 0));
+ convert_hex_to_color("#111").unwrap();
+ convert_hex_to_color("#1f1").unwrap();
+ convert_hex_to_color("#f1f").unwrap();
+ convert_hex_to_color("#ff1").unwrap();
+ convert_hex_to_color("#1ab").unwrap();
+ assert_eq!(
+ convert_hex_to_color("#1ab").unwrap(),
+ Color::Rgb(17, 170, 187)
+ );
+ }
+
+ #[test]
+ fn invalid_hex_colours() {
+ assert!(convert_hex_to_color("ffffff").is_err());
+ assert!(convert_hex_to_color("111111").is_err());
+
+ assert!(convert_hex_to_color("fff").is_err());
+ assert!(convert_hex_to_color("111").is_err());
+ assert!(convert_hex_to_color("fffffff").is_err());
+ assert!(convert_hex_to_color("1234567").is_err());
+
+ assert!(convert_hex_to_color("#fffffff").is_err());
+ assert!(convert_hex_to_color("#1234567").is_err());
+ assert!(convert_hex_to_color("#ff").is_err());
+ assert!(convert_hex_to_color("#12").is_err());
+ assert!(convert_hex_to_color("").is_err());
+
+ assert!(convert_hex_to_color("#pppppp").is_err());
+ assert!(convert_hex_to_color("#00000p").is_err());
+ assert!(convert_hex_to_color("#ppp").is_err());
+
+ assert!(convert_hex_to_color("#一").is_err());
+ assert!(convert_hex_to_color("#一二").is_err());
+ assert!(convert_hex_to_color("#一二三").is_err());
+ assert!(convert_hex_to_color("#一二三四").is_err());
+
+ assert!(convert_hex_to_color("#f一f").is_err());
+ assert!(convert_hex_to_color("#ff一11").is_err());
+
+ assert!(convert_hex_to_color("#🇨🇦").is_err());
+ assert!(convert_hex_to_color("#🇨🇦🇨🇦").is_err());
+ assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦").is_err());
+ assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦🇨🇦").is_err());
+
+ assert!(convert_hex_to_color("#हिन्दी").is_err());
+ }
}