#![doc = include_str!("../README.md")]
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, punctuated::Punctuated};
#[proc_macro_derive(TermStatus, attributes(display, style))]
pub fn generate_format(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::ItemEnum);
let ident = input.ident.clone();
let mut max_len: usize = 0;
let mut match_arms: Vec<syn::Arm> = Vec::new();
for variant in &input.variants {
let name = variant.ident.to_string();
let mut display = name.clone();
let mut garbage = 0;
for attr in &variant.attrs {
match attr.style {
syn::AttrStyle::Inner(_) => panic!("Expeced outer attribute, got inner attribute"),
syn::AttrStyle::Outer => match &attr.meta {
syn::Meta::Path(_) => panic!(
r#"Expected attribute of form `#[style(blue, bold)]` or `#[display = "Other Label"]`, got one of form `#[unknown]`"#
),
syn::Meta::List(l) => {
if l.path.leading_colon.is_some() {
panic!("Unexpected leading `::`");
};
let attr_name = l.path.segments.first().unwrap().ident.to_string();
if l.path.segments.len() > 1 || attr_name != "style" {
panic!("Expected 'style', got '{}'", attr_name);
};
let args: Punctuated<syn::Ident, syn::Token![,]> = l
.parse_args_with(Punctuated::parse_terminated)
.expect("Invalid attribute syntax");
for arg in args {
let style_name = arg.to_string();
let ansi = match style_name.as_str() {
"black" => "\x1b[30m",
"red" => "\x1b[31m",
"green" => "\x1b[32m",
"yellow" => "\x1b[33m",
"blue" => "\x1b[34m",
"magenta" => "\x1b[35m",
"cyan" => "\x1b[36m",
"white" => "\x1b[37m",
"on_black" => "\x1b[40m",
"on_red" => "\x1b[41m",
"on_green" => "\x1b[42m",
"on_yellow" => "\x1b[43m",
"on_blue" => "\x1b[44m",
"on_magenta" => "\x1b[45m",
"on_cyan" => "\x1b[46m",
"on_white" => "\x1b[47m",
"bold" => "\x1b[1m",
"dim" => "\x1b[2m",
"italic" => "\x1b[3m",
"underlined" => "\x1b[4m",
"blink" => "\x1b[5m",
"blinkfast" => "\x1b[6m",
"reverse" => "\x1b[7m",
"hidden" => "\x1b[8m",
"strikethrough" => "\x1b[9m",
&_ => panic!("Unknown style identifier"),
};
let mut styled = String::from(ansi);
let reset = "\x1b[0m";
garbage += styled.chars().count() + reset.chars().count();
styled.push_str(&display);
styled.push_str(reset);
display = styled;
}
}
syn::Meta::NameValue(nv) => {
if nv.path.leading_colon.is_some() {
panic!("Unexpected leading `::`");
};
let attr_name = nv.path.segments.first().unwrap().ident.to_string();
if nv.path.segments.len() > 1 || attr_name != "display" {
panic!("Expected 'display', got '{}'", attr_name);
};
match &nv.value {
syn::Expr::Lit(syn::ExprLit {
attrs: _,
lit: syn::Lit::Str(s),
}) => {
display = s.value();
}
_ => panic!("Expected string literal"),
};
}
},
};
}
let length = display.chars().count() - garbage;
if length > max_len {
max_len = length;
};
let variant_ident = &variant.ident;
match_arms.push(parse_quote! {
#ident::#variant_ident => (#display, #length)
})
}
quote! {
impl std::fmt::Display for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (name, real_len) = match self { #(#match_arms),* };
let pad = " ".repeat(#max_len - real_len);
let mut padded = std::string::String::new();
padded.push_str(pad.as_str());
padded.push_str(name);
write!(f, "{}", padded)
}
}
}
.into()
}