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