use proc_macro::TokenStream;
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
Error, Expr, Ident, LitStr, Result, Token,
};
mod colors;
#[allow(dead_code)]
#[derive(Debug)]
struct WithFormatString {
fstring: LitStr,
sep: Token![,],
rest: TokenStream,
}
#[allow(dead_code)]
#[derive(Debug)]
struct ColorizeAll {
ident: Ident,
tok: Token![=>],
rest: TokenStream,
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) struct ColorizeItem {
pub ident: Ident,
pub sep: Token![->],
pub msg: Expr,
}
#[derive(Debug)]
pub(crate) enum Args {
Item(ColorizeItem),
Expr(Expr),
}
impl Parse for WithFormatString {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
fstring: input.parse()?,
sep: input.parse()?,
rest: input.parse::<proc_macro2::TokenStream>()?.into(),
})
}
}
impl Parse for ColorizeAll {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
ident: input.parse()?,
tok: input.parse()?,
rest: input.parse::<proc_macro2::TokenStream>()?.into(),
})
}
}
impl Parse for ColorizeItem {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
ident: input.parse()?,
sep: input.parse()?,
msg: input.parse()?,
})
}
}
impl Parse for Args {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(Ident) && input.peek2(Token![->]) {
input.parse().map(Args::Item)
} else {
input.parse().map(Args::Expr)
}
}
}
fn valid_color_all(tag: &Ident) -> Result<()> {
let str_tag = tag.to_string();
let mut it = str_tag.chars().peekable();
while let Some(t) = it.next() {
match t {
'b' | 'i' | 'u' | 'N' => continue,
'F' => {
if let Some(n) = it.peek() {
match n {
'k' | 'r' | 'g' | 'y' | 'b' | 'm' | 'c' | 'w' => continue,
e => {
return Err(Error::new(
tag.span(),
format!("'F{e}' Invalid foreground option - '{e}'"),
))
}
}
} else {
return Err(Error::new(
tag.span(),
"Forground option must be followed by a valid identifier",
));
}
}
'B' => {
if let Some(n) = it.peek() {
match n {
'k' | 'r' | 'g' | 'y' | 'b' | 'm' | 'c' | 'w' => continue,
e => {
return Err(Error::new(
tag.span(),
format!("'B{e}' Invalid background option - '{e}'"),
))
}
}
} else {
return Err(Error::new(
tag.span(),
"Background option must be followed by a valid identifier",
));
}
}
_ => {
return Err(Error::new(
tag.span(),
format!("Invalid format identifier '{t}'"),
))
}
}
}
Ok(())
}
#[proc_macro]
pub fn colorize(input: TokenStream) -> TokenStream {
let inp = input.clone();
let (fstring, inp) = match syn::parse::<WithFormatString>(inp) {
Ok(r) => (r.fstring, r.rest),
Err(e) => return e.into_compile_error().into(),
};
let (args, id) = match syn::parse::<ColorizeAll>(inp.clone()) {
Ok(r) => {
if let Err(e) = valid_color_all(&r.ident) {
e.into_compile_error();
}
let rem = r.rest;
let a = parse_macro_input!(rem with Punctuated::<Args, Token![,]>::parse_terminated);
(a, Some(r.ident))
}
Err(_) => {
let a = parse_macro_input!(inp with Punctuated::<Args, Token![,]>::parse_terminated);
(a, None)
}
};
let res = match crate::colors::parse_fstring(&fstring, args, id) {
Ok(r) => r,
Err(e) => return e.into_compile_error().into(),
};
res.into()
}