use std::collections::HashMap;
use procmeta::prelude::*;
use quote::{format_ident, ToTokens};
use syn::{braced, parse::Parse, DeriveInput};
use crate::structure::get_fn_name_suffix;
pub struct TargetPlugFields {
pub items: Vec<PlugField>,
}
pub struct PlugField {
pub ident: Ident,
pub value: Expr,
}
impl Parse for TargetPlugFields {
fn parse(input: ParseStream) -> Result<Self> {
let content;
let _ = braced!(content in input);
let mut items = vec![];
while !content.is_empty() {
let ident: Ident = content.parse()?;
let _: Token![:] = content.parse()?;
let value: Expr = content.parse()?;
if !content.is_empty() {
let _: Token![,] = content.parse()?;
}
items.push(PlugField { ident, value })
}
Ok(TargetPlugFields { items })
}
}
impl Converter<Self> for TargetPlugFields {
fn into(self) -> Result<Self> {
Ok(self)
}
}
#[derive(MetaParser)]
pub enum ParsedTypeAttr {
#[name("from")]
From { source: Type, adaptor: Option<Type> },
#[name("into")]
Into {
target: Type,
adaptor: Option<Type>,
#[converter(TargetPlugFields)]
plug: Option<TargetPlugFields>,
},
}
#[derive(MetaParser)]
pub enum ParsedFieldAttr {
#[name("from")]
From {
source: Option<Type>,
map: Option<Ident>,
assign: Option<Expr>,
},
#[name("into")]
Into {
target: Option<Type>,
map: Option<Ident>,
assign: Option<Expr>,
skip: Option<bool>,
},
}
pub struct FromItemContent {
pub adaptor: TokenStream,
pub source: Type,
pub fields: TokenStream,
}
impl FromItemContent {
pub fn new(source: Type, adaptor: Option<Type>) -> Self {
Self {
adaptor: adaptor.map(|t| quote!(, adaptor: #t)).unwrap_or_default(),
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
#ident: #value,
}
}
}
impl From<FromItemContent> for TokenStream {
fn from(value: FromItemContent) -> Self {
let source = value.source;
let fn_name = format_ident!(
"from{}",
get_fn_name_suffix(source.to_token_stream().to_string())
);
let adaptor = value.adaptor;
let fields = value.fields;
quote! {
pub fn #fn_name (source: #source #adaptor) -> Self {
Self {
#fields
}
}
}
}
}
pub struct IntoItemContent {
pub adaptor: TokenStream,
pub target: Type,
pub fields: TokenStream,
}
impl IntoItemContent {
pub fn new(target: Type, adaptor: Option<Type>, plug: Option<TargetPlugFields>) -> Self {
Self {
adaptor: adaptor.map(|t| quote!(, adaptor: #t)).unwrap_or_default(),
target,
fields: plug
.map(|t| {
let mut fields = quote!();
for item in t.items {
let ident = item.ident;
let value = item.value;
fields = quote! {
#fields
#ident: #value,
};
}
fields
})
.unwrap_or_default(),
}
}
pub fn add_field(
&mut self,
ident: &Option<Ident>,
map: Option<Ident>,
assign: Option<Expr>,
skip: Option<bool>,
) {
let skip = skip.unwrap_or(false);
if skip {
return;
}
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
#ident_token: #value,
}
}
}
impl From<IntoItemContent> for TokenStream {
fn from(value: IntoItemContent) -> Self {
let target = value.target;
let fn_name = format_ident!(
"into{}",
get_fn_name_suffix(target.to_token_stream().to_string())
);
let adaptor = value.adaptor;
let fields = value.fields;
quote! {
pub fn #fn_name (self #adaptor) -> #target {
#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 from_items: HashMap<String, FromItemContent> = HashMap::new();
let mut first_target_name = String::default();
let mut into_items: HashMap<String, IntoItemContent> = HashMap::new();
let mut result = quote!();
for attr in input.attrs {
let parsed_attr = <ParsedTypeAttr as MetaParser>::parse(&attr.meta)?;
match parsed_attr {
ParsedTypeAttr::From { source, adaptor } => {
let key = source.to_token_stream().to_string();
if first_source_name.is_empty() {
first_source_name = key.clone();
}
let content = FromItemContent::new(source, adaptor);
from_items.insert(key, content);
}
ParsedTypeAttr::Into {
target,
adaptor,
plug,
} => {
let key = target.to_token_stream().to_string();
if first_target_name.is_empty() {
first_target_name = key.clone();
}
let content = IntoItemContent::new(target, adaptor, plug);
into_items.insert(key, content);
}
}
}
for field in data.fields {
if field.attrs.is_empty() {
for v in from_items.values_mut() {
v.add_field(&field.ident, None, None);
}
for v in into_items.values_mut() {
v.add_field(&field.ident, None, None, None);
}
continue;
}
for attr in &field.attrs {
let parsed_attr = <ParsedFieldAttr as MetaParser>::parse(&attr.meta)?;
match parsed_attr {
ParsedFieldAttr::From {
source,
map,
assign,
} => {
let key = source
.map(|s| s.to_token_stream().to_string())
.unwrap_or(first_source_name.clone());
let from_item_content = from_items.get_mut(&key);
let from_item_content = from_item_content.ok_or(Error::new(
field.span(),
"expected already define source type",
))?;
from_item_content.add_field(&field.ident, map, assign);
}
ParsedFieldAttr::Into {
target,
map,
assign,
skip,
} => {
let key = target
.map(|s| s.to_token_stream().to_string())
.unwrap_or(first_target_name.clone());
let into_item_content = into_items.get_mut(&key);
let into_item_content = into_item_content.ok_or(Error::new(
field.span(),
"expected already define target type",
))?;
into_item_content.add_field(&field.ident, map, assign, skip);
}
}
}
}
for v in from_items.into_values() {
let item_token: TokenStream = TokenStream::from(v);
result = quote! {
#result
#item_token
}
}
for v in into_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!(),
}
}