use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};
static COLOR_NAMES: &[&str] = &[
"black", "red", "green", "yellow", "blue", "purple", "cyan", "white",
];
fn parse_color(c: &str) -> Option<String> {
let n = match c {
"/" => 0,
"b" | "bold" => 1,
"d" | "dim" => 2,
"i" | "italic" => 3,
"u" | "underline" => 4,
"gray" => 90, mut tok => {
let mut n = 30;
if let Some(c) = tok.strip_prefix("on ") {
n += 10;
tok = c;
}
if let Some(c) = tok.strip_prefix("bright ") {
n += 60;
tok = c;
}
n + COLOR_NAMES.iter().position(|&x| x == tok)?
}
};
Some(format!("\x1b[{n}m"))
}
#[proc_macro]
pub fn fmt(input: TokenStream) -> TokenStream {
let mut fmt = String::new();
for tok in input {
let stream = TokenStream::from(tok);
fmt.push_str(&parse_macro_input!(stream as LitStr).value());
}
let map = fmt
.match_indices('<')
.rev()
.map(|(i, _)| i)
.collect::<Vec<usize>>();
for i in map {
if let Some(j) = fmt[i..].find('>') {
if let Some(color) = parse_color(&fmt[i + 1..=i + j - 1]) {
fmt.replace_range(i..=i + j, &color);
}
}
}
TokenStream::from(quote! { #fmt })
}
#[cfg(test)]
mod tests {
macro_rules! test {
($name:ident: $input:expr, $output:expr) => {
#[test]
fn $name() {
assert_eq!(fmt!($input), $output);
}
};
}
test!(simple: "<red>fuming</>", "\x1b[31mfuming\x1b[0m");
test!(on_bright: "<purple>meooooooow <b><red><on bright purple>:3</>", "\x1b[35mmeooooooow \x1b[1m\x1b[31m\x1b[105m:3\x1b[0m");
}