use proc_macro::{TokenStream};
use std::{ops::Range, iter::repeat};
#[proc_macro]
pub fn printcol(grammar: TokenStream) -> TokenStream {
format!(r#"::std::print!("{{}}", ::fancy::colorize!({}));"#, grammar.to_string()).parse().unwrap()
}
#[proc_macro]
pub fn eprintcol(grammar: TokenStream) -> TokenStream {
format!(r#"::std::eprint!("{{}}", ::fancy::colorize!({}));"#, grammar.to_string()).parse().unwrap()
}
#[proc_macro]
pub fn printcoln(grammar: TokenStream) -> TokenStream {
format!("::fancy::printcol!({}); ::std::println!()", grammar.to_string()).parse().unwrap()
}
#[proc_macro]
pub fn eprintcoln(grammar: TokenStream) -> TokenStream {
format!("::fancy::eprintcol!({}); ::std::println!()", grammar.to_string()).parse().unwrap()
}
#[proc_macro]
pub fn colorize(grammar: TokenStream) -> TokenStream {
let text = grammar.to_string();
let mut colored = ColorString::new(text.capacity());
let len = text.chars().count();
let mut formats = 0..len;
let mut braces: isize = 0;
let mut escaped = 0;
let mut parsing = (false, 0..0);
let mut prev = '\0';
for (idx, chr) in text.chars().enumerate() {
parsing.0 = braces > 0 && escaped == 0;
if chr != '[' && prev == '[' && escaped > 0 {
for br in repeat('[').take(escaped) { colored.push(br) };
escaped = 0;
}
else if chr != ']' && prev == ']' && escaped > 0 {
for br in repeat(']').take(escaped) { colored.push(br) };
escaped = 0;
}
if chr == '[' && prev == '[' { escaped += 1; braces = 0 }
else if chr == ']' && prev == ']' { escaped += 1; braces = 0 }
else if chr == '[' {
if parsing.0 { panic!("cannot have a opening square brace inside a pattern") }
braces += 1;
parsing.1.start = idx + 1;
}
else if chr == ']' && parsing.0 && braces == 1 {
braces -= 1;
parsing.1.end = idx;
parse(&text[parsing.1.clone()], &mut colored);
}
else if chr == ']' {
}
else if chr == '"' && prev != '\\' && idx != 0 {
formats.start = idx + 1;
break
}
else if chr == '"' && idx == 0 {}
else if !parsing.0 {
colored.push(chr);
}
prev = chr;
}
if braces != 0 {
panic!("braces did not match, you either have to many `[` or to many `]`")
}
colored.raw("\x1b[0m");
let output = colored.view();
if formats.is_empty() {
format!(
"\"{}\"",
output,
).parse().expect("Could not parse output.")
} else {
return format!(
"format!(\"{}\"{})",
output, &text[formats]
).parse().expect("Could not parse output.")
}
}
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" | "r" => 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!("{}", format!("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
}
}