use std::collections::HashMap;
use procmeta::prelude::*;
use quote::{format_ident, ToTokens};
use syn::DeriveInput;
use crate::structure::get_fn_name_suffix;
#[derive(MetaParser)]
pub enum ParsedTypeAttr {
#[name("pull")]
Pull { source: Type },
#[name("push")]
Push { target: Type },
}
#[derive(MetaParser)]
pub enum ParsedFieldAttr {
#[name("pull")]
Pull {
source: Option<Type>,
map: Option<Ident>,
assign: Option<Expr>,
},
#[name("push")]
Push {
target: Option<Type>,
map: Option<Ident>,
assign: Option<Expr>,
},
}
pub struct PullItemContent {
pub source: Type,
pub fields: TokenStream,
}
impl PullItemContent {
pub fn new(source: Type) -> Self {
Self {
source,
fields: quote!(),
}
}
pub fn add_field(&mut self, ident: &Option<Ident>, map: Option<Ident>, assign: Option<Expr>) {
let mut value = quote!(source.#ident);
if let Some(map) = map {
value = quote!(source.#map);
}
if let Some(assign) = assign {
value = quote!(#assign);
}
let last_fields_token = &self.fields;
self.fields = quote! {
#last_fields_token
self.#ident = #value;
}
}
}
impl From<PullItemContent> for TokenStream {
fn from(value: PullItemContent) -> Self {
let source = value.source;
let fn_name = format_ident!(
"pull{}",
get_fn_name_suffix(source.to_token_stream().to_string())
);
let fields = value.fields;
quote! {
pub fn #fn_name (&mut self, source: #source) {
#fields
}
}
}
}
pub struct PushItemContent {
pub target: Type,
pub fields: TokenStream,
}
impl PushItemContent {
pub fn new(target: Type) -> Self {
Self {
target,
fields: quote!(),
}
}
pub fn add_field(&mut self, ident: &Option<Ident>, map: Option<Ident>, assign: Option<Expr>) {
let mut ident_token = quote!(#ident);
if let Some(map) = map {
ident_token = quote!(#map);
}
let mut value = quote!(self.#ident);
if let Some(assign) = assign {
value = quote!(#assign);
}
let last_fields_token = &self.fields;
self.fields = quote! {
#last_fields_token
target.#ident_token = #value;
}
}
}
impl From<PushItemContent> for TokenStream {
fn from(value: PushItemContent) -> Self {
let target = value.target;
let fn_name = format_ident!(
"push{}",
get_fn_name_suffix(target.to_token_stream().to_string())
);
let fields = value.fields;
quote! {
pub fn #fn_name (self, target: &mut #target) {
#fields
}
}
}
}
pub fn expand(input: DeriveInput) -> Result<TokenStream> {
let ty = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
match input.data {
syn::Data::Struct(data) => {
let mut first_source_name = String::default();
let mut pull_items: HashMap<String, PullItemContent> = HashMap::new();
let mut first_target_name = String::default();
let mut push_items: HashMap<String, PushItemContent> = HashMap::new();
let mut result = quote!();
for attr in input.attrs {
let parsed_attr = <ParsedTypeAttr as MetaParser>::parse(&attr.meta)?;
match parsed_attr {
ParsedTypeAttr::Pull { source } => {
let key = source.to_token_stream().to_string();
if first_source_name.is_empty() {
first_source_name = key.clone();
}
let content = PullItemContent::new(source);
pull_items.insert(key, content);
}
ParsedTypeAttr::Push { target } => {
let key = target.to_token_stream().to_string();
if first_target_name.is_empty() {
first_target_name = key.clone();
}
let content = PushItemContent::new(target);
push_items.insert(key, content);
}
}
}
for field in data.fields {
for attr in &field.attrs {
let parsed_attr = <ParsedFieldAttr as MetaParser>::parse(&attr.meta)?;
match parsed_attr {
ParsedFieldAttr::Pull {
source,
map,
assign,
} => {
let key = source
.map(|s| s.to_token_stream().to_string())
.unwrap_or(first_source_name.clone());
let pull_item_content = pull_items.get_mut(&key);
let pull_item_content = pull_item_content.ok_or(Error::new(
field.span(),
"expected already define source type",
))?;
pull_item_content.add_field(&field.ident, map, assign);
}
ParsedFieldAttr::Push {
target,
map,
assign,
} => {
let key = target
.map(|s| s.to_token_stream().to_string())
.unwrap_or(first_target_name.clone());
let push_item_content = push_items.get_mut(&key);
let push_item_content = push_item_content.ok_or(Error::new(
field.span(),
"expected already define target type",
))?;
push_item_content.add_field(&field.ident, map, assign);
}
}
}
}
for v in pull_items.into_values() {
let item_token: TokenStream = TokenStream::from(v);
result = quote! {
#result
#item_token
}
}
for v in push_items.into_values() {
let item_token: TokenStream = TokenStream::from(v);
result = quote! {
#result
#item_token
}
}
result = quote! {
impl #impl_generics #ty #ty_generics #where_clause {
#result
}
};
Ok(result)
}
_ => unimplemented!(),
}
}