use std::collections::HashMap;
use super::{Color, TextBuffer};
use regex::Regex;
use crate::text_processing::{OptTextStyle, Processable, ProcessedChar, TextProcessor};
#[derive(Default, Debug, Clone)]
pub struct Parser {
colors: HashMap<String, Color>,
}
impl Parser {
pub fn new() -> Parser {
Parser {
colors: HashMap::<String, Color>::new(),
}
}
pub fn add_color<T: Into<String>>(&mut self, color_str: T, color: Color) {
self.colors.insert(color_str.into(), color);
}
pub fn write<T: Into<String>>(&self, text_buffer: &mut TextBuffer, text: T) {
text_buffer.write_processed(&self.parse(text));
}
pub fn parse<T: Into<String>>(&self, text: T) -> Vec<ProcessedChar> {
let text = text.into();
self.process(vec![text.into()])
}
#[cfg(test)]
pub(crate) fn get_color(&self, color: &str) -> Option<&Color> {
self.colors.get(color)
}
}
impl TextProcessor for Parser {
fn process(&self, processables: Vec<Processable>) -> Vec<ProcessedChar> {
let mut fg_stack = Vec::new();
let mut bg_stack = Vec::new();
let mut shakiness_stack = Vec::new();
let mut current_style = OptTextStyle {
fg_color: None,
bg_color: None,
shakiness: None,
};
let regex = Regex::new(r"\[(/)?((fg|bg|shake)(=([A-z]+|\d+(\.\d+)?))?)\]").unwrap();
let mut parsed = Vec::new();
for processable in processables {
match processable {
Processable::ToProcess(text) => {
let mut parts = regex.split(&text);
for capture in regex.captures_iter(&text) {
parsed.push(ParsedText {
text: parts.next().unwrap().to_owned(),
style: current_style.clone(),
});
if let Some(target) = capture.get(3) {
if capture.get(1).is_some() {
if target.as_str() == "shake" {
current_style.shakiness = shakiness_stack.pop();
} else if target.as_str() == "fg" {
current_style.fg_color = fg_stack.pop();
} else if target.as_str() == "bg" {
current_style.bg_color = bg_stack.pop();
}
}
if let Some(value) = capture.get(5) {
if target.as_str() == "shake" {
let value = match value.as_str().parse::<f32>() {
Ok(val) => val,
Err(e) => panic!("Failed to parse shake-number: {}", e),
};
if let Some(shakiness) = current_style.shakiness {
shakiness_stack.push(shakiness);
}
current_style.shakiness = Some(value);
} else if let Some(color) = self.colors.get(value.as_str()) {
if target.as_str() == "fg" {
if let Some(fg) = current_style.fg_color {
fg_stack.push(fg);
}
current_style.fg_color = Some(*color);
} else {
if let Some(bg) = current_style.bg_color {
bg_stack.push(bg);
}
current_style.bg_color = Some(*color);
}
}
}
}
}
if let Some(last_part) = parts.next() {
parsed.push(ParsedText {
text: last_part.to_owned(),
style: current_style.clone(),
});
}
}
Processable::NoProcess(text) => {
parsed.push(ParsedText {
text: text,
style: current_style.clone(),
});
}
}
}
let mut list = Vec::new();
for text in parsed {
for character in text.text.chars() {
list.push(ProcessedChar {
character: character,
style: text.style.clone(),
});
}
}
list
}
}
#[derive(Clone, Debug)]
struct ParsedText {
pub text: String,
pub style: OptTextStyle,
}