pinwheel_macro 0.2.1

Build web applications with Rust.
Documentation
use quote::quote;
use syn::spanned::Spanned;

pub fn new(input: proc_macro2::TokenStream) -> syn::Result<proc_macro2::TokenStream> {
	let input: syn::DeriveInput = syn::parse2(input)?;
	let ident = &input.ident;
	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
	let data_struct = match &input.data {
		syn::Data::Struct(data) => data,
		_ => {
			return Err(syn::Error::new(
				input.span(),
				"this macro can only be used on a struct",
			))
		}
	};
	let struct_attrs = attrs(&input)?;
	if struct_attrs.default {
		return Ok(quote! {
			impl #ident {
				pub fn new() -> #ident {
					#ident::default()
				}
			}
		});
	}
	let mut args = Vec::new();
	let mut arg_fields = Vec::new();
	let mut default_fields = Vec::new();
	for field in data_struct.fields.iter() {
		let field_ident = &field.ident;
		let field_ty = &field.ty;
		let field_attrs = field_attrs(field)?;
		match field_attrs.default {
			None => {
				args.push(quote! {
					#field_ident: impl Into<#field_ty>
				});
				arg_fields.push(quote! {
					#field_ident: #field_ident.into()
				});
			}
			Some(FieldDefault::Default) => {
				default_fields.push(quote! {
					#field_ident: Default::default()
				});
			}
			Some(FieldDefault::DefaultLit(lit)) => {
				default_fields.push(quote! {
					#field_ident: #lit
				});
			}
		};
	}
	Ok(quote! {
		impl#impl_generics #ident#ty_generics #where_clause {
			pub fn new(#(#args),*) -> #ident#ty_generics {
				#ident {
					#(#arg_fields,)*
					#(#default_fields,)*
				}
			}
		}
	})
}

struct StructAttrs {
	default: bool,
}

fn attrs(input: &syn::DeriveInput) -> syn::Result<StructAttrs> {
	let attr = input.attrs.iter().find(|attr| attr.path.is_ident("new"));
	let attr = if let Some(attr) = attr {
		Some(attr.parse_meta()?)
	} else {
		None
	};
	let mut default = None;
	if let Some(attr) = attr {
		let list = match attr {
			syn::Meta::List(list) => list,
			_ => {
				return Err(syn::Error::new(
					input.span(),
					"expected attribute to be a list",
				))
			}
		};
		for item in list.nested.iter() {
			match item {
				syn::NestedMeta::Meta(syn::Meta::Path(path)) if path.is_ident("default") => {
					default = Some(true)
				}
				_ => {}
			};
		}
	}
	let default = default.unwrap_or(false);
	Ok(StructAttrs { default })
}

struct FieldAttrs {
	default: Option<FieldDefault>,
}

enum FieldDefault {
	Default,
	DefaultLit(syn::Lit),
}

fn field_attrs(input: &syn::Field) -> syn::Result<FieldAttrs> {
	let attr = input.attrs.iter().find(|attr| attr.path.is_ident("new"));
	let attr = if let Some(attr) = attr {
		Some(attr.parse_meta()?)
	} else {
		None
	};
	let mut default = None;
	if let Some(attr) = attr {
		let list = match attr {
			syn::Meta::List(list) => list,
			_ => {
				return Err(syn::Error::new(
					input.span(),
					"expected attribute to be a list",
				))
			}
		};
		for item in list.nested.iter() {
			match item {
				syn::NestedMeta::Meta(syn::Meta::Path(path)) if path.is_ident("default") => {
					default = Some(FieldDefault::Default);
				}
				syn::NestedMeta::Meta(syn::Meta::NameValue(name_value))
					if name_value.path.is_ident("default") =>
				{
					default = Some(FieldDefault::DefaultLit(name_value.lit.clone()));
				}
				_ => {}
			};
		}
	}
	Ok(FieldAttrs { default })
}