use darling::{FromDeriveInput, FromField};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{
Data, DataStruct, DeriveInput, Expr, Field, Fields, FieldsNamed, GenericArgument, Path, Type,
TypePath,
};
#[derive(Debug, Default, FromDeriveInput)]
#[darling(default, attributes(convert))]
struct MetaOpts {
into: String,
from: String,
}
#[derive(Debug, Default, FromField)]
#[darling(default, attributes(convert_field))]
struct FiledOpts {
rename: String,
ignore: bool,
wrap: bool,
unwrap: bool,
option: bool,
to_string: bool,
}
struct Fd {
name: Ident,
opts: FiledOpts,
ty: Type,
optional: bool,
}
impl From<Field> for Fd {
fn from(f: Field) -> Self {
let (optional, ty) = get_option_inner(&f.ty);
let opts = FiledOpts::from_field(&f).unwrap_or_default();
Self {
name: f.ident.unwrap(),
optional,
opts,
ty: ty.to_owned(),
}
}
}
pub struct DeriveIntoContext {
name: Ident,
attrs: MetaOpts,
fields: Vec<Fd>,
}
impl DeriveIntoContext {
pub fn render(&self) -> TokenStream {
let name = &self.name;
let is_from = !self.attrs.from.is_empty();
if is_from {
let struct_name = Ident::new(&format!("{}", name), name.span());
let source_name = Ident::new(&format!("{}", self.attrs.from), name.span());
let assigns = self.gen_from_assigns();
quote! {
impl std::convert::From<#source_name> for #struct_name {
fn from(s: #source_name) -> Self {
#struct_name {
#(#assigns)*
..#struct_name::default()
}
}
}
}
}else {
let struct_name = Ident::new(&format!("{}", name), name.span());
let target_name = Ident::new(&format!("{}", self.attrs.into), name.span());
let assigns = self.gen_into_assigns();
quote! {
impl std::convert::From<#struct_name> for #target_name {
fn from(s: #struct_name) -> Self {
#target_name {
#(#assigns)*
..#target_name::default()
}
}
}
}
}
}
fn gen_from_assigns(&self) -> Vec<TokenStream> {
self.fields
.iter()
.map(
|Fd {
name,
optional,
opts,
..
}| {
let source_name: Ident = if opts.rename.is_empty() {
name.clone()
} else {
Ident::new(opts.rename.as_str(), name.span())
};
if opts.unwrap {
return quote! {
#name: s.#source_name.unwrap_or_default(),
};
}
if *optional && opts.wrap {
return quote! {
#name: Some(s.#source_name),
};
}
if opts.to_string {
return quote! {
#name: s.#source_name.to_string(),
};
}
quote! {
#name: s.#source_name.into(),
}
},
)
.collect()
}
fn gen_into_assigns(&self) -> Vec<TokenStream> {
self.fields
.iter()
.map(
|Fd {
name,
optional,
opts,
..
}| {
let target_name: Ident = if opts.rename.is_empty() {
name.clone()
} else {
Ident::new(opts.rename.as_str(), name.span())
};
if opts.ignore {
return quote!();
}
if *optional && opts.unwrap {
return quote! {
#target_name: s.#name.unwrap_or_default(),
};
}
if opts.option {
if *optional {
return quote! {
#target_name: s.#name,
};
} else {
return quote! {
#target_name: Some(s.#name),
};
}
}
if opts.to_string {
return quote! {
#target_name: s.#name.to_string(),
};
}
quote! {
#target_name: s.#name.into(),
}
},
)
.collect()
}
}
impl From<DeriveInput> for DeriveIntoContext {
fn from(input: DeriveInput) -> Self {
let attrs = match MetaOpts::from_derive_input(&input) {
Ok(v) => v,
Err(_e) => {
panic!("not args");
}
};
let name = input.ident;
let fields = if let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { named, .. }),
..
}) = input.data
{
named
} else {
panic!("Unsupported data type");
};
let fds = fields.into_iter().map(Fd::from).collect();
Self {
name,
fields: fds,
attrs,
}
}
}
fn get_option_inner(ty: &Type) -> (bool, &Type) {
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = ty
{
if let Some(v) = segments.iter().next() {
if v.ident == "Option" {
let t = match &v.arguments {
syn::PathArguments::AngleBracketed(a) => match a.args.iter().next() {
Some(GenericArgument::Type(t)) => t,
_ => panic!("Not sure what to do with other GenericArgument"),
},
_ => panic!("Not sure what to do with other PathArguments"),
};
return (true, t);
}
}
}
(false, ty)
}