1#![deny(missing_docs)]
13#![cfg_attr(test, deny(warnings))]
14
15extern crate ansi_term;
16use ansi_term::{Color, Style};
17
18#[derive(Debug, PartialEq)]
20pub enum Error {
21 ExtraColor(String, String),
23 UnknownWord(String, String),
25}
26
27impl std::fmt::Display for Error {
28 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
29 match self {
30 Self::ExtraColor(s, word) => {
31 write!(fmt, "Error parsing style \"{}\": extra color \"{}\"", s, word)
32 }
33 Self::UnknownWord(s, word) => {
34 write!(fmt, "Error parsing style \"{}\": unknown word: \"{}\"", s, word)
35 }
36 }
37 }
38}
39
40impl std::error::Error for Error {}
41
42fn parse_color(word: &str) -> Result<Option<Color>, ()> {
43 let color = match word {
44 "normal" => None,
45 "-1" => None,
46 "black" => Some(Color::Black),
47 "red" => Some(Color::Red),
48 "green" => Some(Color::Green),
49 "yellow" => Some(Color::Yellow),
50 "blue" => Some(Color::Blue),
51 "magenta" => Some(Color::Purple),
52 "cyan" => Some(Color::Cyan),
53 "white" => Some(Color::White),
54 _ => {
55 if word.starts_with('#') && word.len() == 7 {
56 if let (Ok(r), Ok(g), Ok(b)) = (u8::from_str_radix(&word[1..3], 16),
57 u8::from_str_radix(&word[3..5], 16),
58 u8::from_str_radix(&word[5..7], 16)) {
59 return Ok(Some(Color::RGB(r, g, b)))
60 }
61 } else if let Ok(n) = u8::from_str_radix(word, 10) {
62 return Ok(Some(Color::Fixed(n)));
63 }
64 return Err(());
65 }
66 };
67 Ok(color)
68}
69
70pub fn parse(s: &str) -> Result<Style, Error> {
73 let mut style = Style::new();
74 let mut colors = 0;
75 let mut bold = false;
76 let mut dim = false;
77 let mut ul = false;
78 let mut blink = false;
79 let mut reverse = false;
80 let mut italic = false;
81 let mut strike = false;
82 for word in s.split_whitespace() {
83 match word.to_lowercase().as_ref() {
84 "nobold"|"no-bold" => { bold = false; }
85 "bold" => { bold = true; }
86 "nodim"|"no-dim" => { dim = false; }
87 "dim" => { dim = true; }
88 "noul"|"no-ul" => { ul = false; }
89 "ul" => { ul = true; }
90 "noblink"|"no-blink" => { blink = false; }
91 "blink" => { blink = true; }
92 "noreverse"|"no-reverse" => { reverse = false; }
93 "reverse" => { reverse = true; }
94 "noitalic"|"no-italic" => { italic = false; }
95 "italic" => { italic = true; }
96 "nostrike"|"no-strike" => { strike = false; }
97 "strike" => { strike = true; }
98 w => {
99 if let Ok(color) = parse_color(w) {
100 if colors == 2 {
101 return Err(Error::ExtraColor(s.to_string(), word.to_string()));
102 } else if let Some(color) = color {
103 if colors == 0 {
104 style = style.fg(color);
105 } else if colors == 1 {
106 style = style.on(color);
107 }
108 }
109 colors += 1;
110 } else {
111 return Err(Error::UnknownWord(s.to_string(), word.to_string()));
112 }
113 }
114 }
115 }
116 if bold { style = style.bold(); }
117 if dim { style = style.dimmed(); }
118 if ul { style = style.underline(); }
119 if blink { style = style.blink(); }
120 if reverse { style = style.reverse(); }
121 if italic { style = style.italic(); }
122 if strike { style = style.strikethrough(); }
123 Ok(style)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use super::Error::*;
130 use ansi_term::Color::*;
131 use ansi_term::Style;
132
133 #[test]
134 fn test_parse_style() {
135 macro_rules! test {
136 ($s:expr => $style:expr) => {
137 assert_eq!(parse($s), Ok($style));
138 };
139 }
140
141 test!("" => Style::new());
142 test!(" " => Style::new());
143 test!("normal" => Style::new());
144 test!("normal normal" => Style::new());
145 test!("-1 normal" => Style::new());
146 test!("red" => Red.normal());
147 test!("red blue" => Red.on(Blue));
148 test!(" red blue " => Red.on(Blue));
149 test!("red\tblue" => Red.on(Blue));
150 test!("red\n blue" => Red.on(Blue));
151 test!("red\r\n blue" => Red.on(Blue));
152 test!("blue red" => Blue.on(Red));
153 test!("yellow green" => Yellow.on(Green));
154 test!("white magenta" => White.on(Purple));
155 test!("black cyan" => Black.on(Cyan));
156 test!("red normal" => Red.normal());
157 test!("normal red" => Style::new().on(Red));
158 test!("0" => Fixed(0).normal());
159 test!("8 3" => Fixed(8).on(Fixed(3)));
160 test!("255" => Fixed(255).normal());
161 test!("255 -1" => Fixed(255).normal());
162 test!("#000000" => RGB(0,0,0).normal());
163 test!("#204060" => RGB(0x20,0x40,0x60).normal());
164
165 test!("bold cyan white" => Cyan.on(White).bold());
166 test!("bold cyan nobold white" => Cyan.on(White));
167 test!("bold cyan reverse white nobold" => Cyan.on(White).reverse());
168 test!("bold cyan ul white dim" => Cyan.on(White).bold().underline().dimmed());
169 test!("ul cyan white no-ul" => Cyan.on(White));
170 test!("italic cyan white" => Cyan.on(White).italic());
171 test!("strike cyan white" => Cyan.on(White).strikethrough());
172 test!("blink #050505 white" => RGB(5,5,5).on(White).blink());
173 }
174
175 #[test]
176 fn test_parse_style_err() {
177 macro_rules! test {
178 ($s:expr => $err:ident $word:expr) => {
179 assert_eq!(parse($s), Err($err($s.to_string(), $word.to_string())));
180 };
181 }
182
183 test!("red blue green" => ExtraColor "green");
184 test!("red blue 123" => ExtraColor "123");
185 test!("123 red blue" => ExtraColor "blue");
186 test!("red blue normal" => ExtraColor "normal");
187 test!("red blue -1" => ExtraColor "-1");
188 test!("yellow green #abcdef" => ExtraColor "#abcdef");
189 test!("#123456 #654321 #abcdef" => ExtraColor "#abcdef");
190 test!("bold red blue green" => ExtraColor "green");
191 test!("red bold blue green" => ExtraColor "green");
192 test!("red blue bold green" => ExtraColor "green");
193 test!("red blue green bold" => ExtraColor "green");
194
195 test!("256" => UnknownWord "256");
196 test!("-2" => UnknownWord "-2");
197 test!("-" => UnknownWord "-");
198 test!("- 1" => UnknownWord "-");
199 test!("123-1" => UnknownWord "123-1");
200 test!("blue1" => UnknownWord "blue1");
201 test!("blue-1" => UnknownWord "blue-1");
202 test!("no" => UnknownWord "no");
203 test!("nou" => UnknownWord "nou");
204 test!("noblue" => UnknownWord "noblue");
205 test!("no#123456" => UnknownWord "no#123456");
206 test!("no-" => UnknownWord "no-");
207 test!("no-u" => UnknownWord "no-u");
208 test!("no-green" => UnknownWord "no-green");
209 test!("no-#123456" => UnknownWord "no-#123456");
210 test!("#" => UnknownWord "#");
211 test!("#12345" => UnknownWord "#12345");
212 test!("#1234567" => UnknownWord "#1234567");
213 test!("#bcdefg" => UnknownWord "#bcdefg");
214 test!("#blue" => UnknownWord "#blue");
215 test!("blue#123456" => UnknownWord "blue#123456");
216 }
217}