1use crate::regex;
2use std::collections::HashMap;
3use termcolor::{Color, ColorSpec};
4
5pub struct ColorMap {
6 pub dir_color: ColorSpec,
7 pub exec_color: ColorSpec,
8 pub exec_other: ColorSpec,
9 pub link_color: ColorSpec,
10 pub bad_color: ColorSpec,
11 #[cfg(debug_assertions)]
12 pub debug_color: ColorSpec,
13 ext_colors: HashMap<String, ColorSpec>,
14}
15
16impl ColorMap {
17 pub fn new(colors: Option<String>) -> Self {
18 let (std_colors, ext_colors) = Self::load_colors(colors);
19 let dir_color = Self::default_color(&std_colors, "di", Color::Blue, None);
20 let exec_color = Self::default_color(&std_colors, "ex", Color::Green, None);
21 let exec_other = Self::darken_color(&exec_color);
22 let link_color = Self::default_color(&std_colors, "ln", Color::Cyan, None);
23 let bad_color = Self::default_color(&std_colors, "or", Color::Red, Some(Color::Black));
24 #[cfg(debug_assertions)]
25 let debug_color = Self::create_color(Some(Color::Yellow), None, true);
26 Self {
27 dir_color,
28 exec_color,
29 exec_other,
30 link_color,
31 bad_color,
32 #[cfg(debug_assertions)]
33 debug_color,
34 ext_colors,
35 }
36 }
37
38 pub fn find_color(&self, ext: &str) -> Option<&ColorSpec> {
39 let ext = ext.to_lowercase();
40 if ext.starts_with('.') {
41 self.ext_colors.get(&ext[1..])
42 } else {
43 self.ext_colors.get(&ext)
44 }
45 }
46
47 fn load_colors(colors: Option<String>) -> (HashMap<String, ColorSpec>, HashMap<String, ColorSpec>) {
48 let mut std_colors = HashMap::new();
49 let mut ext_colors = HashMap::new();
50 colors.unwrap_or_default()
51 .split(':')
52 .map(Self::parse_item)
53 .flat_map(std::convert::identity)
54 .for_each(|(key, color)| Self::load_into(&mut std_colors, &mut ext_colors, key, color));
55 (std_colors, ext_colors)
56 }
57
58 fn load_into(
59 std_colors: &mut HashMap<String, ColorSpec>,
60 ext_colors: &mut HashMap<String, ColorSpec>,
61 key: String,
62 color: ColorSpec,
63 ) {
64 let ext_regex = regex!(r"^\*\.(\w+)$");
65 let ext = ext_regex.captures(&key)
66 .and_then(|captures| captures.get(1))
67 .map(|matched| matched.as_str());
68 if let Some(ext) = ext {
69 ext_colors.insert(ext.to_lowercase(), color);
70 } else {
71 std_colors.insert(key.to_lowercase(), color);
72 }
73 }
74
75 fn parse_item(item: &str) -> Option<(String, ColorSpec)> {
76 let pair_regex = regex!(r"^(.+)=(.+)$");
77 pair_regex.captures(item)
78 .map(|captures| captures.extract())
79 .and_then(|(_, [key, value])| Self::parse_color(key, value))
80 }
81
82 fn parse_color(key: &str, value: &str) -> Option<(String, ColorSpec)> {
83 let mut color = ColorSpec::new();
84 for item in value.split(';') {
85 let item = item.parse::<usize>().ok()?;
86 match item {
87 1 => { color.set_bold(true); }
88 4 => { color.set_underline(true); }
89 30 => { color.set_fg(Some(Color::Black)); }
90 31 => { color.set_fg(Some(Color::Red)); }
91 32 => { color.set_fg(Some(Color::Green)); }
92 33 => { color.set_fg(Some(Color::Yellow)); }
93 34 => { color.set_fg(Some(Color::Blue)); }
94 35 => { color.set_fg(Some(Color::Magenta)); }
95 36 => { color.set_fg(Some(Color::Cyan)); }
96 37 => { color.set_fg(Some(Color::White)); }
97 40 => { color.set_bg(Some(Color::Black)); }
98 41 => { color.set_bg(Some(Color::Red)); }
99 42 => { color.set_bg(Some(Color::Green)); }
100 43 => { color.set_bg(Some(Color::Yellow)); }
101 44 => { color.set_bg(Some(Color::Blue)); }
102 45 => { color.set_bg(Some(Color::Magenta)); }
103 46 => { color.set_bg(Some(Color::Cyan)); }
104 47 => { color.set_bg(Some(Color::White)); }
105 _ => (),
106 }
107 }
108 Some((key.to_string(), color))
109 }
110
111 fn default_color(
112 colors: &HashMap<String, ColorSpec>,
113 key: &str,
114 fg_color: Color,
115 bg_color: Option<Color>,
116 ) -> ColorSpec {
117 colors.get(key)
118 .map(ColorSpec::clone)
119 .unwrap_or_else(|| Self::create_color(Some(fg_color), bg_color, true))
120 }
121
122 fn create_color(
123 fg_color: Option<Color>,
124 bg_color: Option<Color>,
125 bold: bool,
126 ) -> ColorSpec {
127 let mut color = ColorSpec::new();
128 color.set_fg(fg_color);
129 color.set_bg(bg_color);
130 color.set_bold(bold);
131 color
132 }
133
134 fn darken_color(color: &ColorSpec) -> ColorSpec {
135 let mut color = color.clone();
136 color.set_bold(false);
137 color
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use crate::util::colors::ColorMap;
144 use pretty_assertions::assert_eq;
145 use std::collections::HashMap;
146 use termcolor::Color;
147
148 #[test]
149 fn test_loads_colors_from_environment() {
150 let colors = String::from("DI=34:ex=32:ln=36:or=40;31:*.GZ=01;31:*.png=01;35");
151 let colors = ColorMap::new(Some(colors));
152 let expected = HashMap::from([
153 (String::from("gz"), ColorMap::create_color(Some(Color::Red), None, true)),
154 (String::from("png"), ColorMap::create_color(Some(Color::Magenta), None, true)),
155 ]);
156 assert_eq!(ColorMap::create_color(Some(Color::Blue), None, false), colors.dir_color);
157 assert_eq!(ColorMap::create_color(Some(Color::Green), None, false), colors.exec_color);
158 assert_eq!(ColorMap::create_color(Some(Color::Green), None, false), colors.exec_other);
159 assert_eq!(ColorMap::create_color(Some(Color::Cyan), None, false), colors.link_color);
160 assert_eq!(ColorMap::create_color(Some(Color::Red), Some(Color::Black), false), colors.bad_color);
161 assert_eq!(expected, colors.ext_colors);
162 }
163
164 #[test]
165 fn test_loads_colors_from_default() {
166 let colors = ColorMap::new(None);
167 let expected = HashMap::new();
168 assert_eq!(ColorMap::create_color(Some(Color::Blue), None, true), colors.dir_color);
169 assert_eq!(ColorMap::create_color(Some(Color::Green), None, true), colors.exec_color);
170 assert_eq!(ColorMap::create_color(Some(Color::Green), None, false), colors.exec_other);
171 assert_eq!(ColorMap::create_color(Some(Color::Cyan), None, true), colors.link_color);
172 assert_eq!(ColorMap::create_color(Some(Color::Red), Some(Color::Black), true), colors.bad_color);
173 assert_eq!(expected, colors.ext_colors);
174 }
175
176 #[test]
177 fn test_parses_color_from_invalid_string() {
178 assert_eq!(None, ColorMap::parse_color("key", ""));
179 assert_eq!(None, ColorMap::parse_color("key", "foo"));
180 }
181
182 #[test]
183 fn test_parses_color_from_unexpected_number() {
184 let expected = ColorMap::create_color(None, None, false);
185 assert_eq!(Some((String::from("key"), expected)), ColorMap::parse_color("key", "999"));
186 }
187
188 #[test]
189 fn test_parses_color_from_foreground_string() {
190 let expected1 = ColorMap::create_color(Some(Color::Black), None, false);
191 let expected2 = ColorMap::create_color(Some(Color::Red), None, false);
192 let expected3 = ColorMap::create_color(Some(Color::Green), None, false);
193 let expected4 = ColorMap::create_color(Some(Color::Yellow), None, false);
194 let expected5 = ColorMap::create_color(Some(Color::Blue), None, false);
195 let expected6 = ColorMap::create_color(Some(Color::Magenta), None, false);
196 let expected7 = ColorMap::create_color(Some(Color::Cyan), None, false);
197 let expected8 = ColorMap::create_color(Some(Color::White), None, false);
198 assert_eq!(Some((String::from("key"), expected1)), ColorMap::parse_color("key", "30"));
199 assert_eq!(Some((String::from("key"), expected2)), ColorMap::parse_color("key", "31"));
200 assert_eq!(Some((String::from("key"), expected3)), ColorMap::parse_color("key", "32"));
201 assert_eq!(Some((String::from("key"), expected4)), ColorMap::parse_color("key", "33"));
202 assert_eq!(Some((String::from("key"), expected5)), ColorMap::parse_color("key", "34"));
203 assert_eq!(Some((String::from("key"), expected6)), ColorMap::parse_color("key", "35"));
204 assert_eq!(Some((String::from("key"), expected7)), ColorMap::parse_color("key", "36"));
205 assert_eq!(Some((String::from("key"), expected8)), ColorMap::parse_color("key", "37"));
206 }
207
208 #[test]
209 fn test_parses_color_from_background_string() {
210 let expected1 = ColorMap::create_color(None, Some(Color::Black), false);
211 let expected2 = ColorMap::create_color(None, Some(Color::Red), false);
212 let expected3 = ColorMap::create_color(None, Some(Color::Green), false);
213 let expected4 = ColorMap::create_color(None, Some(Color::Yellow), false);
214 let expected5 = ColorMap::create_color(None, Some(Color::Blue), false);
215 let expected6 = ColorMap::create_color(None, Some(Color::Magenta), false);
216 let expected7 = ColorMap::create_color(None, Some(Color::Cyan), false);
217 let expected8 = ColorMap::create_color(None, Some(Color::White), false);
218 assert_eq!(Some((String::from("key"), expected1)), ColorMap::parse_color("key", "40"));
219 assert_eq!(Some((String::from("key"), expected2)), ColorMap::parse_color("key", "41"));
220 assert_eq!(Some((String::from("key"), expected3)), ColorMap::parse_color("key", "42"));
221 assert_eq!(Some((String::from("key"), expected4)), ColorMap::parse_color("key", "43"));
222 assert_eq!(Some((String::from("key"), expected5)), ColorMap::parse_color("key", "44"));
223 assert_eq!(Some((String::from("key"), expected6)), ColorMap::parse_color("key", "45"));
224 assert_eq!(Some((String::from("key"), expected7)), ColorMap::parse_color("key", "46"));
225 assert_eq!(Some((String::from("key"), expected8)), ColorMap::parse_color("key", "47"));
226 }
227
228 #[test]
229 fn test_parses_color_from_combined_string() {
230 let expected = ColorMap::create_color(Some(Color::Yellow), Some(Color::Blue), true);
231 assert_eq!(Some((String::from("key"), expected)), ColorMap::parse_color("key", "33;44;1"));
232 }
233
234 #[test]
235 fn test_creates_dark_color_from_light_color() {
236 let colors = vec![
237 Color::Black,
238 Color::Red,
239 Color::Green,
240 Color::Yellow,
241 Color::Blue,
242 Color::Magenta,
243 Color::Cyan,
244 Color::White,
245 ];
246 for color in colors {
247 let light = ColorMap::create_color(Some(color), Some(color), true);
248 let dark = ColorMap::create_color(Some(color), Some(color), false);
249 assert_eq!(ColorMap::darken_color(&light), dark);
250 assert_eq!(ColorMap::darken_color(&dark), dark);
251 }
252 }
253}