extern crate proc_macro;
use std::iter::FromIterator;
use quote::quote;
use quote::ToTokens;
use syn::parse_macro_input;
use syn::Data;
use syn::DeriveInput;
use syn::Fields;
use proc_macro2::Ident;
use proc_macro2::Span;
use proc_macro2::TokenStream;
use proc_macro2::TokenTree;
fn get_impl_for_field(stream: TokenStream, field_name_ident: Ident) -> Option<TokenStream> {
let mut tokens: Vec<TokenTree> = stream.into_iter().collect();
let mut output_stream = TokenStream::new();
if tokens.len() <= 2 {
return None;
};
let first_token = tokens.remove(0);
if let TokenTree::Ident(first_ident) = first_token {
if first_ident == Ident::new("Option", Span::call_site()) {
tokens.remove(0);
tokens.pop();
let inner_type = TokenStream::from_iter(tokens.into_iter());
let with_func_name =
Ident::new(&format!("with_{}", field_name_ident), Span::call_site());
let without_func_name =
Ident::new(&format!("without_{}", field_name_ident), Span::call_site());
let new_func = quote! {
pub fn #with_func_name<IN: Into<#inner_type>>(mut self, inner: IN) -> Self {
self.#field_name_ident = Some(inner.into());
self
}
pub fn #without_func_name(mut self) -> Self {
self.#field_name_ident = None;
self
}
};
output_stream.extend(new_func);
}
}
if output_stream.is_empty() {
None
} else {
Some(output_stream)
}
}
fn is_optional_builder_skip(attribute: syn::Attribute) -> bool {
if let Some(segment) = attribute.path.segments.first() {
let ident = segment.ident.clone();
if ident == Ident::new("optbuilder", ident.span()) {
let tokens: Vec<TokenTree> = attribute.tokens.into_iter().collect();
if tokens.len() > 1 {
return false;
}
if let TokenTree::Group(ref group) = tokens[0] {
let group_tokens: Vec<TokenTree> = group.clone().stream().into_iter().collect();
if group_tokens.len() > 1 {
return false;
}
if let TokenTree::Ident(ref ident) = group_tokens[0] {
return ident == &Ident::new("skip", ident.span());
}
}
}
}
false
}
#[proc_macro_derive(OptionalBuilder, attributes(optbuilder))]
pub fn optional_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut derive_input = parse_macro_input!(input as DeriveInput);
let mut builder_impls: TokenStream = TokenStream::new();
if let Data::Struct(ref mut data_struct) = derive_input.data {
if let Fields::Named(ref mut named_fields) = data_struct.fields {
for field in named_fields.named.iter_mut() {
let mut to_remove: Vec<usize> = Vec::new();
for (i, attribute) in field.attrs.iter().enumerate() {
if is_optional_builder_skip(attribute.clone()) {
to_remove.push(i);
}
}
if !to_remove.is_empty() {
to_remove.sort();
for (idx_counter, idx) in to_remove.iter().enumerate() {
field.attrs.remove(idx - idx_counter);
}
} else {
let mut stream = TokenStream::new();
field.ty.to_tokens(&mut stream);
let field_name_ident = field
.ident
.clone()
.expect("optional_builder require named fields.");
let field_impl_option = get_impl_for_field(stream, field_name_ident);
if let Some(field_impl) = field_impl_option {
builder_impls.extend(field_impl);
}
}
}
}
}
if !builder_impls.is_empty() {
let mut input_stream = TokenStream::new();
derive_input.to_tokens(&mut input_stream);
let struct_name = derive_input.ident.clone();
let (impl_generics, ty_generics, where_clause) = derive_input.generics.split_for_impl();
let implementations = quote! {
impl #impl_generics #struct_name #ty_generics
#where_clause
{
#builder_impls
}
};
implementations.into()
} else {
proc_macro::TokenStream::new()
}
}