use proc_macro::TokenStream;
use quote::ToTokens;
use syn;
const CLOWNCOPTERIZE_PREFIX: &str = "clowntown";
const CLOWNCOPTERIZE_FLAG: &str = "clowncopterize";
struct ParsableNamedField {
pub field: syn::Field,
}
impl syn::parse::Parse for ParsableNamedField {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result<Self> {
let field = syn::Field::parse_named(input)?;
Ok(ParsableNamedField { field })
}
}
#[proc_macro_attribute]
pub fn clowncopterize(_attr: TokenStream, item: TokenStream) -> TokenStream {
let item_struct: syn::ItemStruct = syn::parse_macro_input!(item);
let out = clowncopterize_struct(item_struct);
proc_macro::TokenStream::from(out.to_token_stream())
}
fn clowncopterize_struct(mut ast: syn::ItemStruct) -> syn::ItemStruct {
let mut is_clown = false;
if let syn::Fields::Named(ref mut fields) = ast.fields {
fields.named =
syn::punctuated::Punctuated::from_iter(fields.named.iter_mut().map(|field| {
match &field.ty {
syn::Type::Path(type_path) => {
if type_path.path.is_ident("bool") {
if let Some(ref ident) = field.ident {
if ident.to_string().starts_with(CLOWNCOPTERIZE_PREFIX) {
is_clown = true;
return clowncopterize_field(field);
}
}
}
}
_ => {}
};
field.clone()
}));
if is_clown {
let punctuated_fields: syn::punctuated::Punctuated<ParsableNamedField, syn::Token![,]> = syn::parse_quote! {
#[arg(long)]
clowncopterize: bool,
};
for punctuated_field in punctuated_fields {
fields.named.push(punctuated_field.field);
}
}
}
ast
}
fn clowncopterize_field(ast: &mut syn::Field) -> syn::Field {
for attr in ast.attrs.iter_mut() {
if attr.path().is_ident("arg") {
let meta = attr.meta.require_list().unwrap();
let mut tokens = meta.tokens.clone();
let ext = quote::quote! {
, default_value_if(#CLOWNCOPTERIZE_FLAG, "true", "true")
};
tokens.extend(ext);
attr.meta = syn::Meta::List(syn::MetaList {
path: meta.path.clone(),
delimiter: meta.delimiter.clone(),
tokens: tokens,
});
}
}
ast.clone()
}