use colored::Colorize;
use std::collections::VecDeque;
pub trait ColorCode {
fn parse_color_code(&self) -> String;
}
impl ColorCode for &str {
fn parse_color_code(&self) -> String {
parse(self)
}
}
impl ColorCode for String {
fn parse_color_code(&self) -> String {
parse(self)
}
}
pub fn parse(text: impl AsRef<str>) -> String {
let text = text.as_ref();
let lines: Vec<&str> = text.lines().collect();
let mut result = String::new();
let mut content_indent = 0;
for line in lines {
let trimmed_line = line.trim();
let mut line_result = String::new();
if line.trim_start().starts_with('#') {
let chars: Vec<char> = line.trim_start().chars().collect();
let mut level = 0;
while level < chars.len() && level < 7 && chars[level] == '#' {
level += 1;
}
let effective_level = if level > 6 { 6 } else { level };
let mut content_start = level;
while content_start < chars.len() && chars[content_start].is_whitespace() {
content_start += 1;
}
let heading_content: String = if content_start < chars.len() {
chars[content_start..].iter().collect()
} else {
String::new()
};
let processed_content = process_line(&heading_content);
let formatted_heading = format!("\x1b[1m\x1b[47m\x1b[30m {processed_content} \x1b[0m");
let heading_indent = if effective_level > 0 {
effective_level - 1
} else {
0
};
let indent = " ".repeat(heading_indent);
line_result.push_str(&indent);
line_result.push_str(&formatted_heading);
content_indent = effective_level;
} else if !trimmed_line.is_empty() {
let processed_line = process_line_with_quote(trimmed_line);
let indent = " ".repeat(content_indent);
line_result.push_str(&indent);
line_result.push_str(&processed_line);
} else {
line_result.push(' ');
}
if !line_result.is_empty() {
result.push_str(&line_result);
result.push('\n');
}
}
if result.ends_with('\n') {
result.pop();
}
result
}
fn process_line_with_quote(line: &str) -> String {
let chars: Vec<char> = line.chars().collect();
if !chars.is_empty() && chars[0] == '>' {
if chars.len() > 1 && chars[1] == '\\' {
return process_line(line);
}
let gray_bg_space = "\x1b[48;5;242m\x1b[38;5;242m \x1b[0m";
let rest_of_line = if chars.len() > 1 {
chars[1..].iter().collect::<String>()
} else {
String::new()
};
let processed_rest = process_line(&rest_of_line);
format!("{gray_bg_space}{processed_rest}")
} else {
process_line(line)
}
}
fn process_line(line: &str) -> String {
let mut result = String::new();
let mut color_stack: VecDeque<String> = VecDeque::new();
let chars: Vec<char> = line.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '\\' && i + 1 < chars.len() {
let escaped_char = chars[i + 1];
if matches!(escaped_char, '*' | '<' | '>' | '`' | '_') {
let mut escaped_text = escaped_char.to_string();
apply_color_stack(&mut escaped_text, &color_stack);
result.push_str(&escaped_text);
i += 2;
continue;
}
}
if i + 1 < chars.len()
&& chars[i] == '['
&& chars[i + 1] == '['
&& let Some(end) = find_tag_end(&chars, i)
{
let tag_content: String = chars[i + 2..end].iter().collect();
if tag_content == "/" {
color_stack.pop_back();
} else {
color_stack.push_back(tag_content.clone());
}
i = end + 2;
continue;
}
if i + 1 < chars.len()
&& chars[i] == '*'
&& chars[i + 1] == '*'
&& let Some(end) = find_matching(&chars, i + 2, "**")
{
let bold_text: String = chars[i + 2..end].iter().collect();
let mut formatted_text = bold_text.bold().to_string();
apply_color_stack(&mut formatted_text, &color_stack);
result.push_str(&formatted_text);
i = end + 2;
continue;
}
if chars[i] == '*'
&& let Some(end) = find_matching(&chars, i + 1, "*")
{
let italic_text: String = chars[i + 1..end].iter().collect();
let mut formatted_text = italic_text.italic().to_string();
apply_color_stack(&mut formatted_text, &color_stack);
result.push_str(&formatted_text);
i = end + 1;
continue;
}
if chars[i] == '_'
&& let Some(end) = find_matching(&chars, i + 1, "_")
{
let underline_text: String = chars[i + 1..end].iter().collect();
let mut formatted_text = format!("\x1b[4m{underline_text}\x1b[0m");
apply_color_stack(&mut formatted_text, &color_stack);
result.push_str(&formatted_text);
i = end + 1;
continue;
}
if chars[i] == '<'
&& let Some(end) = find_matching(&chars, i + 1, ">")
{
let angle_text: String = chars[i..=end].iter().collect();
let mut formatted_text = angle_text.cyan().to_string();
apply_color_stack(&mut formatted_text, &color_stack);
result.push_str(&formatted_text);
i = end + 1;
continue;
}
if chars[i] == '`'
&& let Some(end) = find_matching(&chars, i + 1, "`")
{
let code_text: String = chars[i..=end].iter().collect();
let mut formatted_text = code_text.green().to_string();
apply_color_stack(&mut formatted_text, &color_stack);
result.push_str(&formatted_text);
i = end + 1;
continue;
}
let mut current_char = chars[i].to_string();
apply_color_stack(&mut current_char, &color_stack);
result.push_str(¤t_char);
i += 1;
}
result
}
fn find_matching(chars: &[char], start: usize, delimiter: &str) -> Option<usize> {
let delim_chars: Vec<char> = delimiter.chars().collect();
let delim_len = delim_chars.len();
let mut j = start;
while j < chars.len() {
if delim_len == 1 {
if chars[j] == delim_chars[0] {
return Some(j);
}
} else if j + 1 < chars.len()
&& chars[j] == delim_chars[0]
&& chars[j + 1] == delim_chars[1]
{
return Some(j);
}
j += 1;
}
None
}
fn find_tag_end(chars: &[char], start: usize) -> Option<usize> {
let mut j = start + 2;
while j + 1 < chars.len() {
if chars[j] == ']' && chars[j + 1] == ']' {
return Some(j);
}
j += 1;
}
None
}
fn apply_color_stack(text: &mut String, color_stack: &VecDeque<String>) {
let mut result = text.clone();
for color in color_stack.iter().rev() {
result = apply_color(&result, color);
}
*text = result;
}
fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String {
let text = text.as_ref();
let color_name = color_name.as_ref();
match color_name {
"black" => text.black().to_string(),
"red" => text.red().to_string(),
"green" => text.green().to_string(),
"yellow" => text.yellow().to_string(),
"blue" => text.blue().to_string(),
"magenta" => text.magenta().to_string(),
"cyan" => text.cyan().to_string(),
"white" | "b_white" | "bright_gray" | "bright_grey" | "b_gray" | "b_grey" => {
text.white().to_string()
}
"bright_black" | "b_black" | "gray" | "grey" => text.bright_black().to_string(),
"bright_red" | "b_red" => text.bright_red().to_string(),
"bright_green" | "b_green" => text.bright_green().to_string(),
"bright_yellow" | "b_yellow" => text.bright_yellow().to_string(),
"bright_blue" | "b_blue" => text.bright_blue().to_string(),
"bright_magenta" | "b_magenta" => text.bright_magenta().to_string(),
"bright_cyan" | "b_cyan" => text.bright_cyan().to_string(),
"bright_white" => text.bright_white().to_string(),
_ => text.to_string(),
}
}