1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
use regex::Regex;
/// Manage conversion of a user provided string
/// defining foreground and background colors into
/// a string with TTY colors
///
use std::result::Result;
use crossterm::{
Attribute::{self, *},
Color::{self, *},
};
use termimad::CompoundStyle;
use crate::{errors::InvalidSkinError, skin};
/// read a color from a string.
/// It may be either
/// - "none"
/// - one of the few known color name. Example: "darkred"
/// - grayscale with level in [0,24[. Example: "grey(5)"
/// - an Ansi code. Example "ansi(106)"
/// - RGB. Example: "rgb(25, 100, 0)"
/// This function needs a lowercase string (assuming lowercasing
/// has be done before, to ensure case-insensitive parsing)
fn parse_color(s: &str) -> Result<Option<Color>, InvalidSkinError> {
if let Some(c) = regex!(r"^ansi\((?P<value>\d+)\)$").captures(&s) {
let value: &str = c.name("value").unwrap().as_str();
let value = value.parse();
if let Ok(value) = value {
return Ok(skin::ansi(value)); // all ANSI values are ok
} else {
return Err(InvalidSkinError::InvalidColor { raw: s.to_owned() });
}
}
if let Some(c) = regex!(r"^gr[ae]y(?:scale)?\((?P<level>\d+)\)$").captures(&s) {
let level: &str = c.name("level").unwrap().as_str();
let level = level.parse();
if let Ok(level) = level {
if level > 23 {
return Err(InvalidSkinError::InvalidGreyLevel { level });
}
return Ok(skin::gray(level));
} else {
return Err(InvalidSkinError::InvalidColor { raw: s.to_owned() });
}
}
if let Some(c) = regex!(r"^rgb\((?P<r>\d+),\s*(?P<g>\d+),\s*(?P<b>\d+)\)$").captures(&s) {
let r = c.name("r").unwrap().as_str().parse();
let g = c.name("g").unwrap().as_str().parse();
let b = c.name("b").unwrap().as_str().parse();
if let (Ok(r), Ok(g), Ok(b)) = (r, g, b) {
return Ok(skin::rgb(r, g, b));
} else {
return Err(InvalidSkinError::InvalidColor { raw: s.to_owned() });
}
}
match s {
"black" => Ok(skin::rgb(0, 0, 0)), // crossterm black isn't black
"blue" => Ok(Some(Blue)),
"cyan" => Ok(Some(Cyan)),
"darkblue" => Ok(Some(DarkBlue)),
"darkcyan" => Ok(Some(DarkCyan)),
"darkgreen" => Ok(Some(DarkGreen)),
"darkmagenta" => Ok(Some(DarkMagenta)),
"darkred" => Ok(Some(DarkRed)),
"green" => Ok(Some(Green)),
"grey" => Ok(Some(Grey)),
"magenta" => Ok(Some(Magenta)),
"red" => Ok(Some(Red)),
"yellow" => Ok(Some(Yellow)),
"darkyellow" => Ok(Some(DarkYellow)),
"white" => Ok(Some(White)),
"none" => Ok(None),
_ => Err(InvalidSkinError::InvalidColor { raw: s.to_owned() }),
}
}
///
fn parse_attribute(s: &str) -> Result<Attribute, InvalidSkinError> {
match s {
"bold" => Ok(Bold),
"crossedout" => Ok(CrossedOut),
"dim" => Ok(Dim), // does it do anything ?
"italic" => Ok(Italic),
"underlined" => Ok(Underlined),
"overlined" => Ok(OverLined),
_ => Err(InvalidSkinError::InvalidAttribute { raw: s.to_owned() }),
}
}
/// parse a sequence of space separated style attributes
fn parse_attributes(s: &str) -> Result<Vec<Attribute>, InvalidSkinError> {
s.split_whitespace().map(|t| parse_attribute(t)).collect()
}
pub fn parse_object_style(s: &str) -> Result<CompoundStyle, InvalidSkinError> {
let s = s.to_ascii_lowercase();
let parts_rex = regex!(
r"(?x)
^
(?P<fg>\w+(\([\d,\s]+\))?)
\s+
(?P<bg>\w+(\([\d,\s]+\))?)
(?P<attributes>.*)
$
"
);
if let Some(c) = parts_rex.captures(&s) {
debug!("match for {:?}", s);
let fg_color = parse_color(c.name("fg").unwrap().as_str())?;
let bg_color = parse_color(c.name("bg").unwrap().as_str())?;
let attrs = parse_attributes(c.name("attributes").unwrap().as_str())?;
Ok(CompoundStyle::new(
fg_color,
bg_color,
attrs,
))
} else {
debug!("NO match for {:?}", s);
Err(InvalidSkinError::InvalidStyle {
style: s.to_owned(),
})
}
}
|