use nom::{ branch::alt, bytes::complete::{tag, take_till, take_till1}, combinator::eof, error::Error, multi::fold_many0, sequence::{pair, tuple}, Err, Parser, }; use proc_macro::TokenStream; use quote::ToTokens; use syn::{parse_macro_input, Lit}; enum AnsiFrag<'l> { Lit(&'l str), Special(&'l str), } use AnsiFrag::Special; #[proc_macro] pub fn ansi(input: TokenStream) -> TokenStream { let raw = match parse_macro_input!(input as Lit) { Lit::Str(lit_str) => lit_str.value(), _ => panic!("Expected a string literal"), }; fn parser(i: &str) -> Result>> { pair( fold_many0( alt(( take_till1(|c| c == '<').map(AnsiFrag::Lit), tuple((tag("<"), take_till(|c| c == '>'), tag(">"))) .map(|t| AnsiFrag::Special(t.1)), )), || "".to_owned(), |a, r| { a + match r { AnsiFrag::Lit(s) => &s, Special(s) if s == "reset" => "\x1b[0m", Special(s) if s == "bold" => "\x1b[1m", Special(s) if s == "under" => "\x1b[4m", Special(s) if s == "strike" => "\x1b[9m", Special(s) if s == "nounder" => "\x1b[24m", Special(s) if s == "black" => "\x1b[30m", Special(s) if s == "red" => "\x1b[31m", Special(s) if s == "green" => "\x1b[32m", Special(s) if s == "yellow" => "\x1b[33m", Special(s) if s == "blue" => "\x1b[34m", Special(s) if s == "magenta" => "\x1b[35m", Special(s) if s == "cyan" => "\x1b[36m", Special(s) if s == "white" => "\x1b[37m", Special(s) if s == "bgblack" => "\x1b[40m", Special(s) if s == "bgred" => "\x1b[41m", Special(s) if s == "bggreen" => "\x1b[42m", Special(s) if s == "bgyellow" => "\x1b[43m", Special(s) if s == "bgblue" => "\x1b[44m", Special(s) if s == "bgmagenta" => "\x1b[45m", Special(s) if s == "bgcyan" => "\x1b[46m", Special(s) if s == "bgwhite" => "\x1b[47m", Special(s) if s == "lt" => "<", Special(r) => panic!("Unknown ansi type {}", r), } }, ), eof, )(i) .map(|(_, (r, _))| r) } TokenStream::from( parser(&raw) .unwrap_or_else(|e| panic!("Bad ansi literal: {}", e)) .into_token_stream(), ) }