use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Ident, ItemStruct, Token, Type};
use super::{
FIELD_ATTR_CLAP_FLATTEN_COMMAND, FIELD_ATTR_CLAP_FLATTEN_COMMAND_FLATTEN,
FIELD_ATTR_CLAPPEN_COMMAND, FIELD_ATTR_CLAPPEN_COMMAND_APPLY, ProcessItem,
};
use crate::clappen_command::attrs::NestedAttributes;
use crate::{clappen_command, helper};
impl ProcessItem for ItemStruct {
fn process(
&mut self,
default_prefix: String,
struct_prefix: String,
) -> syn::Result<TokenStream> {
let mut nested_macro_uses: Vec<clappen_command::attrs::Attributes> = Vec::new();
let mut nested_macro_calls: Vec<TokenStream> = Vec::new();
let prefix = helper::field_prefix(&default_prefix, &struct_prefix);
if !prefix.is_empty() {
self.ident = helper::prefixed_ident(&prefix, &self.ident.to_string());
}
for field in self.fields.iter_mut() {
let mut command_clap_flatten = false;
let mut clappen_command = false;
let mut clappen_command_attributes: Option<clappen_command::attrs::Attributes> = None;
for attr in &field.attrs {
if attr.path().is_ident(FIELD_ATTR_CLAP_FLATTEN_COMMAND) {
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident(FIELD_ATTR_CLAP_FLATTEN_COMMAND_FLATTEN) {
command_clap_flatten = true;
}
Ok(())
});
}
if attr.path().is_ident(FIELD_ATTR_CLAPPEN_COMMAND) {
clappen_command = true;
let meta: Punctuated<NestedAttributes, Token![,]> =
attr.parse_args_with(Punctuated::parse_terminated)?;
let meta: Vec<NestedAttributes> = meta.into_iter().collect();
let attrs: std::result::Result<clappen_command::attrs::Attributes, ()> =
meta.try_into();
clappen_command_attributes = attrs.ok();
}
}
if command_clap_flatten && !clappen_command {
return Err(syn::Error::new(
field.span(),
format!(
"'{FIELD_ATTR_CLAPPEN_COMMAND_APPLY}' must be specified when #[command(flatten)] is provided for clap",
),
));
}
if clappen_command && clappen_command_attributes.is_none() {
return Err(syn::Error::new(
field.span(),
format!(
"'{FIELD_ATTR_CLAPPEN_COMMAND_APPLY}' must be specified when #[{FIELD_ATTR_CLAPPEN_COMMAND}] is provided",
),
));
}
let Some(ident) = &field.ident else {
return Err(syn::Error::new(
field.span(),
"Ident field could not be parsed",
));
};
let prefixed = helper::prefixed_field(&prefix, &ident.to_string());
field.ident = Some(Ident::new(&prefixed, Span::call_site()));
if let (Some(command_attrs), Some(field_ident)) =
(clappen_command_attributes, &field.ident)
{
let (new_macro_call, new_type_full) = command_attrs.nested_macro_call(
default_prefix.as_str(),
struct_prefix.as_str(),
field_ident,
&field.ty,
);
nested_macro_calls.push(new_macro_call);
nested_macro_uses.push(command_attrs);
let ty: Result<Type, syn::Error> = syn::parse2(new_type_full);
match ty {
Ok(ty) => {
field.ty = ty;
}
Err(e) => {
return Err(syn::Error::new(
field.ty.span(),
format!(
"{}: unable to rewrite field '{}' type '{}' to new type",
e,
field.ident.to_token_stream(),
field.ty.to_token_stream(),
),
));
}
}
}
}
self.fields.iter_mut().for_each(|field| {
field
.attrs
.retain(|attr| !attr.path().is_ident(FIELD_ATTR_CLAPPEN_COMMAND));
});
let debug_nested_macro_uses: Vec<_> = nested_macro_uses
.iter()
.map(|e: &clappen_command::attrs::Attributes| e.apply.to_string())
.collect();
let debug_nested_macro_uses = debug_nested_macro_uses.join(",");
let expanded = self;
Ok(quote! {
#(#nested_macro_calls)*
#[doc=concat!(concat!(" Macros used for nested struct definition : [", #debug_nested_macro_uses, "]"))]
#expanded
})
}
}