named_colour/ext/
red.rs

1//! Extended named colours providing shades collected in enums for the main colour
2//!
3
4use std::{fmt, str::FromStr};
5
6use rgb::Rgb;
7use strum::EnumCount;
8use tinyrand::{RandRange, StdRand};
9
10use crate::Prefix;
11
12use super::ExtendedColour;
13
14/// Shades of red
15#[allow(missing_docs)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumCount)]
17pub enum Red {
18    Maroon,
19    #[allow(clippy::enum_variant_names)]
20    DarkRed,
21    Brown,
22    Firebrick,
23    Crimson,
24    #[allow(clippy::enum_variant_names)]
25    Red,
26    Tomato,
27    Coral,
28    #[allow(clippy::enum_variant_names)]
29    IndianRed,
30    LightCoral,
31    DarkSalmon,
32    Salmon,
33    LightSalmon,
34    #[allow(clippy::enum_variant_names)]
35    OrangeRed,
36    DarkOrange,
37    Orange,
38}
39
40impl fmt::Display for Red {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::Maroon => write!(f, "#800000"),
44            Self::DarkRed => write!(f, "#8B0000"),
45            Self::Brown => write!(f, "#A52A2A"),
46            Self::Firebrick => write!(f, "#B22222"),
47            Self::Crimson => write!(f, "#DC143C"),
48            Self::Red => write!(f, "#FF0000"),
49            Self::Tomato => write!(f, "#FF6347"),
50            Self::Coral => write!(f, "#FF7F50"),
51            Self::IndianRed => write!(f, "#CD5C5C"),
52            Self::LightCoral => write!(f, "#F08080"),
53            Self::DarkSalmon => write!(f, "#E9967A"),
54            Self::Salmon => write!(f, "#FA8072"),
55            Self::LightSalmon => write!(f, "#FFA07A"),
56            Self::OrangeRed => write!(f, "#FF4500"),
57            Self::DarkOrange => write!(f, "#FF8C00"),
58            Self::Orange => write!(f, "#FFA500"),
59        }
60    }
61}
62
63impl Red {
64    /// Display the colour name as an RGB Tuple
65    ///
66    /// ## Example
67    ///
68    ///```
69    /// # use named_colour::ext::Red;
70    /// # fn main() {
71    ///    let rgb_colour = Red::Maroon.to_rgb();
72    ///    let string = rgb_colour.to_string();
73    ///    assert_eq!("rgb(128,0,0)", string);
74    ///
75    ///  # }
76    ///```
77    pub fn to_rgb(&self) -> Rgb<u8> {
78        let colour = self.to_string();
79
80        let r: u8 = u8::from_str_radix(&colour[1..3], 16).unwrap();
81        let g: u8 = u8::from_str_radix(&colour[3..5], 16).unwrap();
82        let b: u8 = u8::from_str_radix(&colour[5..7], 16).unwrap();
83
84        Rgb::new(r, g, b)
85    }
86
87    /// Display the colour name as an RGB Tuple
88    ///
89    /// ## Example
90    ///
91    ///```
92    /// # use named_colour::ext::Red;
93    /// # use named_colour::Prefix;
94    ///     let colour = Red::Maroon;
95    ///
96    ///     assert_eq!("#800000", colour.to_hex_triplet(Prefix::Hash));
97    ///
98    ///```
99    pub fn to_hex_triplet(&self, prefix: Prefix) -> String {
100        let rgb = self.to_rgb();
101
102        let prefix = match prefix {
103            Prefix::Hash => "#",
104            Prefix::None => "",
105        };
106
107        format!("{}{:02X}{:02X}{:02X}", prefix, rgb.r, rgb.g, rgb.b)
108    }
109
110    /// Parse a colour from string
111    ///
112    /// ## Example
113    ///
114    ///```
115    /// # use named_colour::ext::Red;
116    /// # use std::str::FromStr;
117    /// # fn main() {
118    ///    let colour = Red::from_str("Maroon");
119    ///    assert_eq!(Red::Maroon, colour.unwrap());
120    ///
121    ///  # }
122    ///```      
123    ///
124    pub fn parse(name: &str) -> Option<Self> {
125        match name.to_lowercase().as_str() {
126            "#800000" | "800000" | "maroon" => Some(Self::Maroon),
127            "#8b0000" | "8b0000" | "darkred" => Some(Self::DarkRed),
128            "#a52a2a" | "a52a2a" | "brown" => Some(Self::Brown),
129            "#b22222" | "b22222" | "firebrick" => Some(Self::Firebrick),
130            "#dc143c" | "dc143c" | "crimson" => Some(Self::Crimson),
131            "#ff0000" | "ff0000" | "red" => Some(Self::Red),
132            "#ff6347" | "ff6347" | "tomato" => Some(Self::Tomato),
133            "#ff7f50" | "ff7f50" | "coral" => Some(Self::Coral),
134            "#cd5c5c" | "cd5c5c" | "indianred" => Some(Self::IndianRed),
135            "#f08080" | "f08080" | "lightcoral" => Some(Self::LightCoral),
136            "#e9967a" | "e9967a" | "darksalmon" => Some(Self::DarkSalmon),
137            "#fa8072" | "fa8072" | "salmon" => Some(Self::Salmon),
138            "#ffa07a" | "ffa07a" | "lightsalmon" => Some(Self::LightSalmon),
139            "#ff4500" | "ff4500" | "orangered" => Some(Self::OrangeRed),
140            "#ff8c00" | "ff8c00" | "darkorange" => Some(Self::DarkOrange),
141            "#ffa500" | "ffa500" | "orange" => Some(Self::Orange),
142            _ => None,
143        }
144    }
145
146    /// Generate a random colour
147    ///     
148    /// ## Example
149    ///
150    ///```
151    /// # use named_colour::ext::Red;
152    /// # fn main() {
153    ///    let colour = Red::random();
154    ///
155    /// # }
156    /// ```
157    pub fn random() -> Self {
158        let mut rand = StdRand::default();
159
160        match rand.next_range(0..Self::COUNT) {
161            0 => Self::Maroon,
162            1 => Self::DarkRed,
163            2 => Self::Brown,
164            3 => Self::Firebrick,
165            4 => Self::Crimson,
166            5 => Self::Red,
167            6 => Self::Tomato,
168            7 => Self::Coral,
169            8 => Self::IndianRed,
170            9 => Self::LightCoral,
171            10 => Self::DarkSalmon,
172            11 => Self::Salmon,
173            12 => Self::LightSalmon,
174            13 => Self::OrangeRed,
175            14 => Self::DarkOrange,
176            15 => Self::Orange,
177            _ => Self::Red,
178        }
179    }
180}
181
182impl FromStr for Red {
183    type Err = String;
184    fn from_str(s: &str) -> Result<Self, Self::Err> {
185        match Self::parse(s) {
186            Some(colour) => Ok(colour),
187            None => Err(format!("Invalid Colour: {s}")),
188        }
189    }
190}
191
192impl ExtendedColour for Red {}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use rstest::rstest;
198
199    #[rstest]
200    #[case(Red::Maroon, "rgb(128,0,0)")]
201    #[case(Red::DarkRed, "rgb(139,0,0)")]
202    #[case(Red::Brown, "rgb(165,42,42)")]
203    #[case(Red::Firebrick, "rgb(178,34,34)")]
204    #[case(Red::Crimson, "rgb(220,20,60)")]
205    #[case(Red::Red, "rgb(255,0,0)")]
206    #[case(Red::Tomato, "rgb(255,99,71)")]
207    #[case(Red::Coral, "rgb(255,127,80)")]
208    #[case(Red::IndianRed, "rgb(205,92,92)")]
209    #[case(Red::LightCoral, "rgb(240,128,128)")]
210    #[case(Red::DarkSalmon, "rgb(233,150,122)")]
211    #[case(Red::Salmon, "rgb(250,128,114)")]
212    #[case(Red::LightSalmon, "rgb(255,160,122)")]
213    #[case(Red::OrangeRed, "rgb(255,69,0)")]
214    #[case(Red::DarkOrange, "rgb(255,140,0)")]
215    #[case(Red::Orange, "rgb(255,165,0)")]
216    fn test_rgb_string(#[case] colour: Red, #[case] expected: String) {
217        let rgb_colour = colour.to_rgb();
218        let string = rgb_colour.to_string();
219
220        assert_eq!(expected, string);
221    }
222
223    #[rstest]
224    #[case(Red::Maroon, "800000")]
225    #[case(Red::DarkRed, "8B0000")]
226    #[case(Red::Brown, "A52A2A")]
227    #[case(Red::Firebrick, "B22222")]
228    #[case(Red::Crimson, "DC143C")]
229    #[case(Red::Red, "FF0000")]
230    #[case(Red::Tomato, "FF6347")]
231    #[case(Red::Coral, "FF7F50")]
232    #[case(Red::IndianRed, "CD5C5C")]
233    #[case(Red::LightCoral, "F08080")]
234    #[case(Red::DarkSalmon, "E9967A")]
235    #[case(Red::Salmon, "FA8072")]
236    #[case(Red::LightSalmon, "FFA07A")]
237    #[case(Red::OrangeRed, "FF4500")]
238    #[case(Red::DarkOrange, "FF8C00")]
239    #[case(Red::Orange, "FFA500")]
240    fn test_hex_triplet_string(
241        #[case] colour: Red,
242        #[values(Prefix::None, Prefix::Hash)] prefix: Prefix,
243        #[case] expected: String,
244    ) {
245        let prefix_string = match prefix {
246            Prefix::None => "".to_string(),
247            Prefix::Hash => "#".to_string(),
248        };
249
250        let expected = format!("{prefix_string}{expected}");
251
252        let hex_colour = colour.to_hex_triplet(prefix);
253
254        assert_eq!(expected, hex_colour);
255    }
256
257    #[rstest]
258    #[case("#800000", Red::Maroon)]
259    #[case("800000", Red::Maroon)]
260    #[case("maroon", Red::Maroon)]
261    #[case("#8b0000", Red::DarkRed)]
262    #[case("8b0000", Red::DarkRed)]
263    #[case("darkred", Red::DarkRed)]
264    #[case("#a52a2a", Red::Brown)]
265    #[case("a52a2a", Red::Brown)]
266    #[case("brown", Red::Brown)]
267    #[case("#b22222", Red::Firebrick)]
268    #[case("b22222", Red::Firebrick)]
269    #[case("firebrick", Red::Firebrick)]
270    #[case("#dc143c", Red::Crimson)]
271    #[case("dc143c", Red::Crimson)]
272    #[case("crimson", Red::Crimson)]
273    #[case("#ff0000", Red::Red)]
274    #[case("ff0000", Red::Red)]
275    #[case("red", Red::Red)]
276    #[case("#ff6347", Red::Tomato)]
277    #[case("ff6347", Red::Tomato)]
278    #[case("tomato", Red::Tomato)]
279    #[case("#ff7f50", Red::Coral)]
280    #[case("ff7f50", Red::Coral)]
281    #[case("coral", Red::Coral)]
282    #[case("#cd5c5c", Red::IndianRed)]
283    #[case("cd5c5c", Red::IndianRed)]
284    #[case("indianred", Red::IndianRed)]
285    #[case("#f08080", Red::LightCoral)]
286    #[case("f08080", Red::LightCoral)]
287    #[case("lightcoral", Red::LightCoral)]
288    #[case("#e9967a", Red::DarkSalmon)]
289    #[case("e9967a", Red::DarkSalmon)]
290    #[case("darksalmon", Red::DarkSalmon)]
291    #[case("#fa8072", Red::Salmon)]
292    #[case("fa8072", Red::Salmon)]
293    #[case("salmon", Red::Salmon)]
294    #[case("#ffa07a", Red::LightSalmon)]
295    #[case("ffa07a", Red::LightSalmon)]
296    #[case("lightsalmon", Red::LightSalmon)]
297    #[case("#ff4500", Red::OrangeRed)]
298    #[case("ff4500", Red::OrangeRed)]
299    #[case("orangered", Red::OrangeRed)]
300    #[case("#ff8c00", Red::DarkOrange)]
301    #[case("ff8c00", Red::DarkOrange)]
302    #[case("darkorange", Red::DarkOrange)]
303    #[case("#ffa500", Red::Orange)]
304    #[case("ffa500", Red::Orange)]
305    #[case("orange", Red::Orange)]
306    fn test_from_str(#[case] input: &str, #[case] expected: Red) {
307        assert_eq!(expected, Red::from_str(input).unwrap())
308    }
309
310    #[rstest]
311    #[case("#800000", Some(Red::Maroon))]
312    #[case("800000", Some(Red::Maroon))]
313    #[case("maroon", Some(Red::Maroon))]
314    #[case("#8b0000", Some(Red::DarkRed))]
315    #[case("8b0000", Some(Red::DarkRed))]
316    #[case("darkred", Some(Red::DarkRed))]
317    #[case("#a52a2a", Some(Red::Brown))]
318    #[case("a52a2a", Some(Red::Brown))]
319    #[case("brown", Some(Red::Brown))]
320    #[case("#b22222", Some(Red::Firebrick))]
321    #[case("b22222", Some(Red::Firebrick))]
322    #[case("firebrick", Some(Red::Firebrick))]
323    #[case("#dc143c", Some(Red::Crimson))]
324    #[case("dc143c", Some(Red::Crimson))]
325    #[case("crimson", Some(Red::Crimson))]
326    #[case("#ff0000", Some(Red::Red))]
327    #[case("ff0000", Some(Red::Red))]
328    #[case("red", Some(Red::Red))]
329    #[case("#ff6347", Some(Red::Tomato))]
330    #[case("ff6347", Some(Red::Tomato))]
331    #[case("tomato", Some(Red::Tomato))]
332    #[case("#ff7f50", Some(Red::Coral))]
333    #[case("ff7f50", Some(Red::Coral))]
334    #[case("coral", Some(Red::Coral))]
335    #[case("#cd5c5c", Some(Red::IndianRed))]
336    #[case("cd5c5c", Some(Red::IndianRed))]
337    #[case("indianred", Some(Red::IndianRed))]
338    #[case("#f08080", Some(Red::LightCoral))]
339    #[case("f08080", Some(Red::LightCoral))]
340    #[case("lightcoral", Some(Red::LightCoral))]
341    #[case("#e9967a", Some(Red::DarkSalmon))]
342    #[case("e9967a", Some(Red::DarkSalmon))]
343    #[case("darksalmon", Some(Red::DarkSalmon))]
344    #[case("#fa8072", Some(Red::Salmon))]
345    #[case("fa8072", Some(Red::Salmon))]
346    #[case("salmon", Some(Red::Salmon))]
347    #[case("#ffa07a", Some(Red::LightSalmon))]
348    #[case("ffa07a", Some(Red::LightSalmon))]
349    #[case("lightsalmon", Some(Red::LightSalmon))]
350    #[case("#ff4500", Some(Red::OrangeRed))]
351    #[case("ff4500", Some(Red::OrangeRed))]
352    #[case("orangered", Some(Red::OrangeRed))]
353    #[case("#ff8c00", Some(Red::DarkOrange))]
354    #[case("ff8c00", Some(Red::DarkOrange))]
355    #[case("darkorange", Some(Red::DarkOrange))]
356    #[case("#ffa500", Some(Red::Orange))]
357    #[case("ffa500", Some(Red::Orange))]
358    #[case("orange", Some(Red::Orange))]
359    #[case("012345", None)]
360    fn test_name_colour(#[case] input: &str, #[case] expected: Option<Red>) {
361        assert_eq!(expected, Red::name_colour(input))
362    }
363}