1#![warn(missing_docs)]
17
18use std::borrow::Cow;
142
143use regex::Regex;
144use regex::Replacer;
145use regex::Captures;
146use regex::CaptureMatches;
147use lazy_static::lazy_static;
148
149use colored::*;
150
151fn is_truecolor(text: &str) -> bool
153{
154 lazy_static! {
155 static ref RE: Regex = Regex::new(r"^#[ABCDEF\d]{6}$").unwrap();
156 }
157 RE.is_match(text)
158}
159
160fn is_on_truecolor(text: &str) -> bool
162{
163 lazy_static! {
164 static ref RE: Regex = Regex::new(r"^on_#[ABCDEF\d]{6}$").unwrap();
165 }
166 RE.is_match(text)
167}
168
169fn get_styles<R>(text: &str, rep: R) -> Cow<str>
171 where R: Replacer
172{
173 lazy_static! {
174 static ref RE: Regex = Regex::new(r"<([\w\d#+]+?)>((.|\n)*?)</>").unwrap();
175 }
177 RE.replace_all(text, rep)
178}
179
180fn iter_substyles(text: &str) -> CaptureMatches
182{
183 lazy_static! {
184 static ref RE: Regex = Regex::new(r"<\+([\w\d#+]+?)>((.|\n)*?)<->").unwrap();
185 }
187 RE.captures_iter(text)
188}
189
190fn test_truecolor(style: &'_ str, content: &ColoredString) -> Option<ColoredString>
192{
193 if !is_truecolor(style) {
194 return None
195 }
196
197 let red = &style[1..3];
198 let green = &style[3..5];
199 let blue = &style[5..7];
200 let r = u8::from_str_radix(red, 16).expect("invalid color");
201 let g = u8::from_str_radix(green, 16).expect("invalid color");
202 let b = u8::from_str_radix(blue, 16).expect("invalid color");
203 Some(content.clone().truecolor(r, g, b))
204}
205
206fn test_on_truecolor(style: &'_ str, content: &ColoredString) -> Option<ColoredString>
208{
209 if !is_on_truecolor(style) {
210 return None
211 }
212
213 let red = &style[4..6];
214 let green = &style[6..8];
215 let blue = &style[8..10];
216 let r = u8::from_str_radix(red, 16).expect("invalid color");
217 let g = u8::from_str_radix(green, 16).expect("invalid color");
218 let b = u8::from_str_radix(blue, 16).expect("invalid color");
219 Some(content.clone().on_truecolor(r, g, b))
220}
221
222fn test_other(style: &'_ str) -> impl Fn(ColoredString) -> ColoredString + '_
224{
225 move |content: ColoredString| {
226 if let Some(result) = test_truecolor(style, &content) {
227 result
228 } else if let Some(result) = test_on_truecolor(style, &content) {
229 result
230 } else {
231 let colored = format!("<{}>{}</>", style, content);
232 ColoredString::from(colored.as_ref())
233 }
234 }
235}
236
237fn test_style<'a>(style: &'a str) -> Box<dyn Fn(ColoredString) -> ColoredString + 'a>
239{
240 match style.to_lowercase().as_str() {
241
242 "black" => Box::new(&ColoredString::black),
243 "red" => Box::new(&ColoredString::red),
244 "green" => Box::new(&ColoredString::green),
245 "yellow" => Box::new(&ColoredString::yellow),
246 "blue" => Box::new(&ColoredString::blue),
247 "magenta" => Box::new(&ColoredString::magenta),
248 "purple" => Box::new(&ColoredString::purple),
249 "cyan" => Box::new(&ColoredString::cyan),
250 "white" => Box::new(&ColoredString::white),
251 "lblack" => Box::new(&ColoredString::bright_black),
252 "lred" => Box::new(&ColoredString::bright_red),
253 "lgreen" => Box::new(&ColoredString::bright_green),
254 "lyellow" => Box::new(&ColoredString::bright_yellow),
255 "lblue" => Box::new(&ColoredString::bright_blue),
256 "lmagenta" => Box::new(&ColoredString::bright_magenta),
257 "lpurple" => Box::new(&ColoredString::bright_purple),
258 "lcyan" => Box::new(&ColoredString::bright_cyan),
259 "lwhite" => Box::new(&ColoredString::bright_white),
260
261 "on_black" => Box::new(&ColoredString::on_black),
262 "on_red" => Box::new(&ColoredString::on_red),
263 "on_green" => Box::new(&ColoredString::on_green),
264 "on_yellow" => Box::new(&ColoredString::on_yellow),
265 "on_blue" => Box::new(&ColoredString::on_blue),
266 "on_magenta" => Box::new(&ColoredString::on_magenta),
267 "on_purple" => Box::new(&ColoredString::on_purple),
268 "on_cyan" => Box::new(&ColoredString::on_cyan),
269 "on_white" => Box::new(&ColoredString::on_white),
270 "on_lblack" => Box::new(&ColoredString::on_bright_black),
271 "on_lred" => Box::new(&ColoredString::on_bright_red),
272 "on_lgreen" => Box::new(&ColoredString::on_bright_green),
273 "on_lyellow" => Box::new(&ColoredString::on_bright_yellow),
274 "on_lblue" => Box::new(&ColoredString::on_bright_blue),
275 "on_lmagenta" => Box::new(&ColoredString::on_bright_magenta),
276 "on_lpurple" => Box::new(&ColoredString::on_bright_purple),
277 "on_lcyan" => Box::new(&ColoredString::on_bright_cyan),
278 "on_lwhite" => Box::new(&ColoredString::on_bright_white),
279
280 "bold" => Box::new(&ColoredString::bold),
281 "underline" => Box::new(&ColoredString::underline),
282 "italic" => Box::new(&ColoredString::italic),
283 "dimmed" => Box::new(&ColoredString::dimmed),
284 "reverse" => Box::new(&ColoredString::reverse),
285 "reversed" => Box::new(&ColoredString::reversed),
286 "blink" => Box::new(&ColoredString::blink),
287 "hidden" => Box::new(&ColoredString::hidden),
288 "strikethrough" => Box::new(&ColoredString::strikethrough),
289
290 _ => Box::new(test_other(style))
291 }
292}
293
294fn update_with_style(text: ColoredString, colored: &ColoredString) -> ColoredString
296{
297 let mut result = text;
298 if let Some(fgcolor) = colored.fgcolor() {
299 result = result.color(fgcolor);
300 }
301 if let Some(bgcolor) = colored.bgcolor() {
302 result = result.on_color(bgcolor);
303 }
304 if colored.style().contains(Styles::Bold) { result = result.bold(); }
305 if colored.style().contains(Styles::Underline) { result = result.underline(); }
306 if colored.style().contains(Styles::Italic) { result = result.italic(); }
307 if colored.style().contains(Styles::Dimmed) { result = result.dimmed(); }
308 if colored.style().contains(Styles::Reversed) { result = result.reverse(); }
309 if colored.style().contains(Styles::Blink) { result = result.blink(); }
310 if colored.style().contains(Styles::Hidden) { result = result.hidden(); }
311 if colored.style().contains(Styles::Strikethrough) { result = result.strikethrough(); }
312 result
313}
314
315fn set_style_from(text: &str, colored: &ColoredString) -> ColoredString
317{
318 let mut result = ColoredString::from(text);
319 result = update_with_style(result, colored);
320 result
321}
322
323fn add_style_from(colored_from: &ColoredString, colored_to: &ColoredString) -> ColoredString
325{
326 let mut result = set_style_from(colored_to, colored_from);
327 result = update_with_style(result, colored_to);
328 result
329}
330
331
332
333
334
335
336
337
338
339
340
341
342pub fn colored(text: &str) -> ColoredString
362{
363 let updated = get_styles(text, |caps: &Captures| {
364
365 let combined = caps[1].split('+');
367 let mut item = ColoredString::from(&caps[2]);
368 for style in combined {
369 item = test_style(style.trim())(item);
370 }
371
372 let mut items: Vec<ColoredString> = vec![];
373 let mut id_start = 0;
374 let mut id_end = 0;
375
376 for caps in iter_substyles(&item) {
378
379 let range = caps.get(0).unwrap().range();
380 id_end = range.end;
381
382 if id_start != range.start {
384 let text = &item[id_start..range.start];
385 items.push(set_style_from(text, &item));
386 id_start = id_end;
387 }
388
389 if !&caps[2].is_empty() {
391 let combined = caps[1].split('+');
392 let mut subitem = ColoredString::from(&caps[2]);
393 for style in combined {
394 subitem = test_style(style.trim())(subitem);
395 }
396 subitem = add_style_from(&item, &subitem);
397 items.push(subitem.clone());
398 }
399 }
400
401 if id_end != item.len() {
403 let text = &item[id_end..item.len()];
404 items.push(set_style_from(text, &item));
405 }
406
407 let mut res = "".to_owned();
409 for i in items {
410 res = format!("{}{}", res, &i);
411 }
412 res
413 });
414
415 ColoredString::from(updated.as_ref())
416}
417
418pub trait Colored
420{
421 fn colored(self) -> ColoredString;
425}
426
427impl<'a> Colored for &'a str
428{
429 fn colored(self) -> ColoredString
430 {
431 colored(self)
432 }
433}
434
435#[macro_export]
453macro_rules! cformat {
454 () => {
455 String::from("")
456 };
457 ($top:tt) => ({
458 let msg = format!($top);
459 $crate::colored(&msg).to_string()
460 });
461 ($top:tt, $($arg:tt)*) => ({
462 let msg = format!($top, $($arg)*);
463 $crate::colored(&msg).to_string()
464 });
465}
466
467#[macro_export]
485macro_rules! colored {
486 () => {
487 print!()
488 };
489 ($top:tt) => {
490 let msg = format!($top);
491 print!("{}", $crate::colored(&msg));
492 };
493 ($top:tt, $($arg:tt)*) => {
494 let msg = format!($top, $($arg)*);
495 print!("{}", $crate::colored(&msg));
496 };
497}
498
499
500#[macro_export]
518macro_rules! coloredln {
519 () => {
520 println!()
521 };
522 ($top:tt) => {
523 let msg = format!($top);
524 println!("{}", $crate::colored(&msg));
525 };
526 ($top:tt, $($arg:tt)*) => {
527 let msg = format!($top, $($arg)*);
528 println!("{}", $crate::colored(&msg));
529 };
530}