1use termcolor::{Color, ColorSpec, ParseColorError};
2
3pub fn default_color_specs() -> Vec<UserColorSpec> {
11 vec![
12 #[cfg(unix)]
13 "path:fg:magenta".parse().unwrap(),
14 #[cfg(windows)]
15 "path:fg:cyan".parse().unwrap(),
16 "line:fg:green".parse().unwrap(),
17 "match:fg:red".parse().unwrap(),
18 "match:style:bold".parse().unwrap(),
19 ]
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub enum ColorError {
25 UnrecognizedOutType(String),
27 UnrecognizedSpecType(String),
29 UnrecognizedColor(String, String),
31 UnrecognizedStyle(String),
33 InvalidFormat(String),
35}
36
37impl std::error::Error for ColorError {}
38
39impl ColorError {
40 fn from_parse_error(err: ParseColorError) -> ColorError {
41 ColorError::UnrecognizedColor(
42 err.invalid().to_string(),
43 err.to_string(),
44 )
45 }
46}
47
48impl std::fmt::Display for ColorError {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match *self {
51 ColorError::UnrecognizedOutType(ref name) => write!(
52 f,
53 "unrecognized output type '{}'. Choose from: \
54 path, line, column, match, highlight.",
55 name,
56 ),
57 ColorError::UnrecognizedSpecType(ref name) => write!(
58 f,
59 "unrecognized spec type '{}'. Choose from: \
60 fg, bg, style, none.",
61 name,
62 ),
63 ColorError::UnrecognizedColor(_, ref msg) => write!(f, "{}", msg),
64 ColorError::UnrecognizedStyle(ref name) => write!(
65 f,
66 "unrecognized style attribute '{}'. Choose from: \
67 nobold, bold, nointense, intense, nounderline, \
68 underline, noitalic, italic.",
69 name,
70 ),
71 ColorError::InvalidFormat(ref original) => write!(
72 f,
73 "invalid color spec format: '{}'. Valid format is \
74 '(path|line|column|match|highlight):(fg|bg|style):(value)'.",
75 original,
76 ),
77 }
78 }
79}
80
81#[derive(Clone, Debug, Default, Eq, PartialEq)]
88pub struct ColorSpecs {
89 path: ColorSpec,
90 line: ColorSpec,
91 column: ColorSpec,
92 matched: ColorSpec,
93 highlight: ColorSpec,
94}
95
96#[derive(Clone, Debug, Eq, PartialEq)]
152pub struct UserColorSpec {
153 ty: OutType,
154 value: SpecValue,
155}
156
157impl UserColorSpec {
158 pub fn to_color_spec(&self) -> ColorSpec {
163 let mut spec = ColorSpec::default();
164 self.value.merge_into(&mut spec);
165 spec
166 }
167}
168
169#[derive(Clone, Debug, Eq, PartialEq)]
171enum SpecValue {
172 None,
173 Fg(Color),
174 Bg(Color),
175 Style(Style),
176}
177
178#[derive(Clone, Debug, Eq, PartialEq)]
180enum OutType {
181 Path,
182 Line,
183 Column,
184 Match,
185 Highlight,
186}
187
188#[derive(Clone, Debug, Eq, PartialEq)]
190enum SpecType {
191 Fg,
192 Bg,
193 Style,
194 None,
195}
196
197#[derive(Clone, Debug, Eq, PartialEq)]
199enum Style {
200 Bold,
201 NoBold,
202 Intense,
203 NoIntense,
204 Underline,
205 NoUnderline,
206 Italic,
207 NoItalic,
208}
209
210impl ColorSpecs {
211 pub fn new(specs: &[UserColorSpec]) -> ColorSpecs {
214 let mut merged = ColorSpecs::default();
215 for spec in specs {
216 match spec.ty {
217 OutType::Path => spec.merge_into(&mut merged.path),
218 OutType::Line => spec.merge_into(&mut merged.line),
219 OutType::Column => spec.merge_into(&mut merged.column),
220 OutType::Match => spec.merge_into(&mut merged.matched),
221 OutType::Highlight => spec.merge_into(&mut merged.highlight),
222 }
223 }
224 merged
225 }
226
227 pub fn default_with_color() -> ColorSpecs {
233 ColorSpecs::new(&default_color_specs())
234 }
235
236 pub fn path(&self) -> &ColorSpec {
238 &self.path
239 }
240
241 pub fn line(&self) -> &ColorSpec {
243 &self.line
244 }
245
246 pub fn column(&self) -> &ColorSpec {
248 &self.column
249 }
250
251 pub fn matched(&self) -> &ColorSpec {
253 &self.matched
254 }
255
256 pub fn highlight(&self) -> &ColorSpec {
259 &self.highlight
260 }
261}
262
263impl UserColorSpec {
264 fn merge_into(&self, cspec: &mut ColorSpec) {
266 self.value.merge_into(cspec);
267 }
268}
269
270impl SpecValue {
271 fn merge_into(&self, cspec: &mut ColorSpec) {
273 match *self {
274 SpecValue::None => cspec.clear(),
275 SpecValue::Fg(ref color) => {
276 cspec.set_fg(Some(color.clone()));
277 }
278 SpecValue::Bg(ref color) => {
279 cspec.set_bg(Some(color.clone()));
280 }
281 SpecValue::Style(ref style) => match *style {
282 Style::Bold => {
283 cspec.set_bold(true);
284 }
285 Style::NoBold => {
286 cspec.set_bold(false);
287 }
288 Style::Intense => {
289 cspec.set_intense(true);
290 }
291 Style::NoIntense => {
292 cspec.set_intense(false);
293 }
294 Style::Underline => {
295 cspec.set_underline(true);
296 }
297 Style::NoUnderline => {
298 cspec.set_underline(false);
299 }
300 Style::Italic => {
301 cspec.set_italic(true);
302 }
303 Style::NoItalic => {
304 cspec.set_italic(false);
305 }
306 },
307 }
308 }
309}
310
311impl std::str::FromStr for UserColorSpec {
312 type Err = ColorError;
313
314 fn from_str(s: &str) -> Result<UserColorSpec, ColorError> {
315 let pieces: Vec<&str> = s.split(':').collect();
316 if pieces.len() <= 1 || pieces.len() > 3 {
317 return Err(ColorError::InvalidFormat(s.to_string()));
318 }
319 let otype: OutType = pieces[0].parse()?;
320 match pieces[1].parse()? {
321 SpecType::None => {
322 Ok(UserColorSpec { ty: otype, value: SpecValue::None })
323 }
324 SpecType::Style => {
325 if pieces.len() < 3 {
326 return Err(ColorError::InvalidFormat(s.to_string()));
327 }
328 let style: Style = pieces[2].parse()?;
329 Ok(UserColorSpec { ty: otype, value: SpecValue::Style(style) })
330 }
331 SpecType::Fg => {
332 if pieces.len() < 3 {
333 return Err(ColorError::InvalidFormat(s.to_string()));
334 }
335 let color: Color =
336 pieces[2].parse().map_err(ColorError::from_parse_error)?;
337 Ok(UserColorSpec { ty: otype, value: SpecValue::Fg(color) })
338 }
339 SpecType::Bg => {
340 if pieces.len() < 3 {
341 return Err(ColorError::InvalidFormat(s.to_string()));
342 }
343 let color: Color =
344 pieces[2].parse().map_err(ColorError::from_parse_error)?;
345 Ok(UserColorSpec { ty: otype, value: SpecValue::Bg(color) })
346 }
347 }
348 }
349}
350
351impl std::str::FromStr for OutType {
352 type Err = ColorError;
353
354 fn from_str(s: &str) -> Result<OutType, ColorError> {
355 match &*s.to_lowercase() {
356 "path" => Ok(OutType::Path),
357 "line" => Ok(OutType::Line),
358 "column" => Ok(OutType::Column),
359 "match" => Ok(OutType::Match),
360 "highlight" => Ok(OutType::Highlight),
361 _ => Err(ColorError::UnrecognizedOutType(s.to_string())),
362 }
363 }
364}
365
366impl std::str::FromStr for SpecType {
367 type Err = ColorError;
368
369 fn from_str(s: &str) -> Result<SpecType, ColorError> {
370 match &*s.to_lowercase() {
371 "fg" => Ok(SpecType::Fg),
372 "bg" => Ok(SpecType::Bg),
373 "style" => Ok(SpecType::Style),
374 "none" => Ok(SpecType::None),
375 _ => Err(ColorError::UnrecognizedSpecType(s.to_string())),
376 }
377 }
378}
379
380impl std::str::FromStr for Style {
381 type Err = ColorError;
382
383 fn from_str(s: &str) -> Result<Style, ColorError> {
384 match &*s.to_lowercase() {
385 "bold" => Ok(Style::Bold),
386 "nobold" => Ok(Style::NoBold),
387 "intense" => Ok(Style::Intense),
388 "nointense" => Ok(Style::NoIntense),
389 "underline" => Ok(Style::Underline),
390 "nounderline" => Ok(Style::NoUnderline),
391 "italic" => Ok(Style::Italic),
392 "noitalic" => Ok(Style::NoItalic),
393 _ => Err(ColorError::UnrecognizedStyle(s.to_string())),
394 }
395 }
396}