config/
color.rs

1use crate::errors::InvalidColorError;
2
3/// Represents a color.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5#[allow(clippy::exhaustive_enums)]
6pub enum Color {
7	/// The default terminal color.
8	Default,
9	/// The standard white color.
10	LightWhite,
11	/// The standard black color.
12	LightBlack,
13	/// The standard blue color.
14	LightBlue,
15	/// The standard cyan color.
16	LightCyan,
17	/// The standard green color.
18	LightGreen,
19	/// The standard magenta color.
20	LightMagenta,
21	/// The standard red color.
22	LightRed,
23	/// The standard yellow color.
24	LightYellow,
25	/// The standard grey color.
26	LightGrey,
27	/// The dimmed white color.
28	DarkWhite,
29	/// The dimmed black color.
30	DarkBlack,
31	/// The dimmed blue color.
32	DarkBlue,
33	/// The dimmed cyan color.
34	DarkCyan,
35	/// The dimmed green color.
36	DarkGreen,
37	/// The dimmed magenta color.
38	DarkMagenta,
39	/// The dimmed red color.
40	DarkRed,
41	/// The dimmed yellow color.
42	DarkYellow,
43	/// The dimmed grey color.
44	DarkGrey,
45	/// An ANSI indexed color.
46	Index(u8),
47	/// A RGB color triple.
48	Rgb {
49		/// The red amount of the triple.
50		red: u8,
51		/// The green amount of the triple.
52		green: u8,
53		/// The blue amount of the triple.
54		blue: u8,
55	},
56}
57
58impl TryFrom<&str> for Color {
59	type Error = InvalidColorError;
60
61	#[allow(clippy::unwrap_in_result)]
62	#[inline]
63	fn try_from(s: &str) -> Result<Self, Self::Error> {
64		match s {
65			"black" | "light black" => Ok(Self::LightBlack),
66			"blue" | "light blue" => Ok(Self::LightBlue),
67			"cyan" | "light cyan" => Ok(Self::LightCyan),
68			"green" | "light green" => Ok(Self::LightGreen),
69			"magenta" | "light magenta" => Ok(Self::LightMagenta),
70			"red" | "light red" => Ok(Self::LightRed),
71			"white" | "light white" => Ok(Self::LightWhite),
72			"yellow" | "light yellow" => Ok(Self::LightYellow),
73			"grey" | "light grey" => Ok(Self::LightGrey),
74			"dark black" => Ok(Self::DarkBlack),
75			"dark blue" => Ok(Self::DarkBlue),
76			"dark cyan" => Ok(Self::DarkCyan),
77			"dark green" => Ok(Self::DarkGreen),
78			"dark magenta" => Ok(Self::DarkMagenta),
79			"dark red" => Ok(Self::DarkRed),
80			"dark white" => Ok(Self::DarkWhite),
81			"dark yellow" => Ok(Self::DarkYellow),
82			"dark grey" => Ok(Self::DarkGrey),
83			"transparent" | "-1" => Ok(Self::Default),
84			_ => {
85				let matches: Vec<&str> = s.split(',').collect();
86
87				match matches.len() {
88					1 => {
89						let color_index = s.parse::<u8>();
90						match color_index {
91							Ok(i) if (0..=255).contains(&i) => Ok(Self::Index(i)),
92							_ => Err(InvalidColorError::Indexed {}),
93						}
94					},
95					3 => {
96						let red = matches[0].parse::<i16>().unwrap_or(-1);
97						let green = matches[1].parse::<i16>().unwrap_or(-1);
98						let blue = matches[2].parse::<i16>().unwrap_or(-1);
99
100						if !(0..=255).contains(&red) {
101							return Err(InvalidColorError::Red {});
102						}
103
104						if !(0..=255).contains(&green) {
105							return Err(InvalidColorError::Green {});
106						}
107
108						if !(0..=255).contains(&blue) {
109							return Err(InvalidColorError::Blue {});
110						}
111
112						Ok(Self::Rgb {
113							red: red.try_into().unwrap(),
114							green: green.try_into().unwrap(),
115							blue: blue.try_into().unwrap(),
116						})
117					},
118					_ => Err(InvalidColorError::Invalid {}),
119				}
120			},
121		}
122	}
123}
124
125#[cfg(test)]
126mod tests {
127	use claim::assert_ok_eq;
128	use rstest::rstest;
129	use testutils::assert_err_eq;
130
131	use super::*;
132
133	#[rstest]
134	#[case::named_black("black", Color::LightBlack)]
135	#[case::named_light_black("light black", Color::LightBlack)]
136	#[case::named_dark_black("dark black", Color::DarkBlack)]
137	#[case::named_blue("blue", Color::LightBlue)]
138	#[case::named_light_blue("light blue", Color::LightBlue)]
139	#[case::named_dark_blue("dark blue", Color::DarkBlue)]
140	#[case::named_cyan("cyan", Color::LightCyan)]
141	#[case::named_light_cyan("light cyan", Color::LightCyan)]
142	#[case::named_dark_cyan("dark cyan", Color::DarkCyan)]
143	#[case::named_green("green", Color::LightGreen)]
144	#[case::named_light_green("light green", Color::LightGreen)]
145	#[case::named_dark_green("dark green", Color::DarkGreen)]
146	#[case::named_magenta("magenta", Color::LightMagenta)]
147	#[case::named_light_magenta("light magenta", Color::LightMagenta)]
148	#[case::named_dark_magenta("dark magenta", Color::DarkMagenta)]
149	#[case::named_red("red", Color::LightRed)]
150	#[case::named_light_red("light red", Color::LightRed)]
151	#[case::named_dark_red("dark red", Color::DarkRed)]
152	#[case::named_white("white", Color::LightWhite)]
153	#[case::named_yellow("yellow", Color::LightYellow)]
154	#[case::named_light_yellow("light yellow", Color::LightYellow)]
155	#[case::named_dark_yellow("dark yellow", Color::DarkYellow)]
156	#[case::index_0("0", Color::Index(0))]
157	#[case::index_255("255", Color::Index(255))]
158	#[case::rgb("100,101,102", Color::Rgb {
159		red: 100,
160		green: 101,
161		blue: 102
162	})]
163	fn try_from(#[case] color_string: &str, #[case] expected: Color) {
164		assert_ok_eq!(Color::try_from(color_string), expected);
165	}
166
167	#[rstest]
168	#[case::non_number_red("red,0,0", InvalidColorError::Red {})]
169	#[case::rgb_non_number_green("0,green,0", InvalidColorError::Green {})]
170	#[case::rgb_non_number_blue("0,0,blue", InvalidColorError::Blue {})]
171	#[case::rgb_non_number_red_lower_limit("-1,0,0", InvalidColorError::Red {})]
172	#[case::rgb_non_number_green_lower_limit("0,-1,0", InvalidColorError::Green {})]
173	#[case::rgb_non_number_blue_lower_limit("0,0,-1", InvalidColorError::Blue {})]
174	#[case::rgb_non_number_red_upper_limit("256,0,0", InvalidColorError::Red {})]
175	#[case::rgb_non_number_green_upper_limit("0,256,0", InvalidColorError::Green {})]
176	#[case::rgb_non_number_blue_upper_limit("0,0,256", InvalidColorError::Blue {})]
177	#[case::index_upper_limit("256", InvalidColorError::Indexed {})]
178	#[case::index_lower_limit("-2", InvalidColorError::Indexed {})]
179	#[case::str_single_value("invalid", InvalidColorError::Indexed {})]
180	#[case::str_multiple_value("invalid,invalid", InvalidColorError::Invalid {})]
181	fn color_try_from_fail(#[case] color_string: &str, #[case] expected: InvalidColorError) {
182		assert_err_eq!(Color::try_from(color_string), expected);
183	}
184}