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