use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::ToTokens;
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 clowncopterizer = syn::parse_macro_input!(attr as Clowncopterize);
let item_struct: syn::ItemStruct = syn::parse_macro_input!(item);
let out = clowncopterizer.clowncopterize_struct(item_struct);
proc_macro::TokenStream::from(out.to_token_stream())
}
#[derive(Debug)]
struct Clowncopterize {
clowncopterizer: String,
}
impl Default for Clowncopterize {
fn default() -> Self {
Clowncopterize {
clowncopterizer: CLOWNCOPTERIZE_FLAG.to_string(),
}
}
}
impl syn::parse::Parse for Clowncopterize {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
if input.is_empty() {
return Ok(Clowncopterize {
..Default::default()
});
}
let attr_name = input.parse::<syn::Ident>()?;
assert_eq!(
attr_name, "clowncopterizer",
"Unexpected attribute {}",
attr_name
);
input.parse::<syn::Token![=]>()?;
let attr_value = input.parse::<syn::LitStr>()?;
Ok(Clowncopterize {
clowncopterizer: attr_value.value().replace("-", "_"),
})
}
}
impl Clowncopterize {
fn clowncopterize_struct(&self, 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| {
if let syn::Type::Path(type_path) = &field.ty {
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 self.clowncopterize_field(field);
}
}
}
}
field.clone()
}));
if is_clown {
let clowncopterizer = Ident::new(&self.clowncopterizer, Span::call_site());
let punctuated_fields: syn::punctuated::Punctuated<
ParsableNamedField,
syn::Token![,],
> = syn::parse_quote! {
#[arg(long)]
#clowncopterizer: bool
};
for punctuated_field in punctuated_fields {
fields.named.push(punctuated_field.field);
}
}
}
ast
}
fn clowncopterize_field(&self, 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 clowncopterizer = &self.clowncopterizer;
let ext = quote::quote! {
, default_value_if(#clowncopterizer, "true", "true")
};
tokens.extend(ext);
attr.meta = syn::Meta::List(syn::MetaList {
path: meta.path.clone(),
delimiter: meta.delimiter.clone(),
tokens,
});
}
}
ast.clone()
}
}