use std::ops::Range;
use std::iter::once;
pub fn colorize(text: String) -> String {
let mut colored = ColorString::new(text.capacity());
let mut range = Range { start: 0, end: 0 };
let mut brackets = 0;
let third = |num| once('\x1b').chain(text.chars()).skip(num);
let mut chrs1 = third(0);
let mut chrs2 = third(1);
let mut chrs3 = third(2);
let mut chrsi = text.chars().enumerate().map(|v| v.0 );
loop {
let idx = match chrsi.next() { Some(v) => v, None => break, };
let last = match chrs1.next() { Some(v) => v, None => break, };
let curr = match chrs2.next() { Some(v) => v, None => break, };
let next = match chrs3.next() { Some(v) => v, None => '\x1b', };
if curr == '[' && last != '[' && next != '[' && brackets == 0 { range.start = idx + 1; brackets += 1 }
else if curr == '[' && last != '[' && next != '[' && brackets != 0 { panic!("At string index {}: Cannot color-format inside a pattern.", idx); }
else if curr == ']' && last != ']' && brackets > 0 {
brackets -= 1;
range.end = idx;
if parse(&text[range.clone()], &mut colored) == false {
panic!("Invalid mode.");
};
}
else if curr == ']' && last != ']' && next != ']' && brackets == 0 { panic!("At string index {}: Unmatched ']' in color format sequence.", idx); }
else if curr == '[' && next == '[' { }
else if curr == ']' && next == ']' { }
else if brackets == 0 && curr != '\x1b' { colored.push(curr) };
}
if brackets > 0 { panic!("Unclosed '[' in color format sequence."); };
colored.raw("\x1b[0m");
colored.view()
}
fn parse(text: &str, buffer: &mut ColorString) -> bool {
let mut view = Range { start: 0, end: text.len() };
if text.chars().nth(0) == Some(':') {
buffer.raw("\x1b[0m");
view.start = 1;
};
let modifiers = (&text[view]).split('|');
for modi in modifiers {
match &modi[..] {
"bold" | "b" => buffer.add("1"),
"dim" | "faint" => buffer.add("2"),
"italic" | "i" => buffer.add("3"),
"underline" | "u" => buffer.add("4"),
"inverse" | "!" => buffer.add("7"),
"hidden" => buffer.add("8"),
"strikethrough" | "s" => buffer.add("9"),
"black" => buffer.add("30"),
"red" => buffer.add("31"),
"green" => buffer.add("32"),
"yellow" => buffer.add("33"),
"blue" => buffer.add("34"),
"magenta" => buffer.add("35"),
"cyan" => buffer.add("36"),
"white" => buffer.add("37"),
"default" | "def" => buffer.add("39"),
"?black" => buffer.add("40"),
"?red" => buffer.add("41"),
"?green" => buffer.add("42"),
"?yellow" => buffer.add("43"),
"?blue" => buffer.add("44"),
"?magenta" => buffer.add("45"),
"?cyan" => buffer.add("46"),
"?white" => buffer.add("47"),
"?default" | "?def" => buffer.add("49"),
"visible" | "vis" => buffer.raw("\x1b[?25l"),
"invisible" | "invis" => buffer.raw("\x1b[?25h"),
"blink" => buffer.raw("\x1b[5m"),
"noblink" => buffer.raw("\x1b[25m"),
seq => {
let isdec = |seq: &str| seq.chars().all(|v| matches!(v, '0'..='9'));
let ishex = |seq: &str| seq.chars().all(|v| matches!(v, '0'..='9' | 'a'..='f' | 'A'..='B'));
if (1..=3).contains(&seq.len()) && isdec(seq) {
buffer.add(&format!("38;5;{}", seq));
}
else if (2..=4).contains(&seq.len()) && &seq[0..=1] == "?" && isdec(&seq[1..]) {
buffer.add(&format!("48;5;{}", seq));
}
else if (4..).contains(&seq.len()) && (&seq[0..=1] == "?" && isdec(&seq[1..])) | isdec(&seq[..]) {
panic!("Invalid ansi color code: {}. Ansi color codes must be in range 0.155.", seq);
}
else if seq.len() == 7 && &seq[0..1] == "#" && ishex(&seq[1..]) {
let red = u8::from_str_radix(&seq[1..=2], 16).unwrap();
let green = u8::from_str_radix(&seq[3..=4], 16).unwrap();
let blue = u8::from_str_radix(&seq[5..=6], 16).unwrap();
buffer.add(&format!("38;2;{};{};{}", red, green, blue));
}
else if seq.len() == 8 && &seq[0..=1] == "?#" && ishex(&seq[2..]) {
let red = u8::from_str_radix(&seq[2..=3], 16).unwrap();
let green = u8::from_str_radix(&seq[4..=5], 16).unwrap();
let blue = u8::from_str_radix(&seq[6..=7], 16).unwrap();
buffer.add(&format!("48;2;{};{};{}", red, green, blue));
}
else if (2..).contains(&seq.len()) && (&seq[0..=1]).contains('#') {
panic!("Invalid hex color code: {}. Hex color codes must be a '#' or '?#' followed by exactly 6 hex-digits.", seq);
}
else if !seq.is_empty() {
panic!("Unknown modifier: {:?}", seq);
}
},
}
}
buffer.next();
true
}
#[derive(Debug)]
struct ColorString {
text: String,
next: String,
}
impl ColorString {
pub(crate) fn new(cap: usize) -> Self {
Self { text: String::with_capacity(cap), next: String::with_capacity(16) }
}
pub(crate) fn add(&mut self, style: &str) {
if !self.next.is_empty() { self.next.push(';'); };
self.next.push_str(&format!("{}", style));
}
pub(crate) fn raw(&mut self, style: &str) {
self.next();
self.text.push_str(style);
}
pub(crate) fn next(&mut self) {
if !self.next.is_empty() {
self.text.push_str(&format!("\x1b[{}m", self.next));
self.next.clear();
};
}
pub(crate) fn push(&mut self, chr: char) {
self.text.push(chr);
}
pub(crate) fn view(self) -> String {
self.text
}
}