wrapped-vec 0.3.0

Macro for generating wrapped Vec types and associated boilerplate
Documentation
extern crate proc_macro;

use quote::quote;

use proc_macro2::{Span, TokenStream};
use syn::{parse_macro_input, DeriveInput, Ident};

struct Idents {
    item: Ident,
    collection: Ident,
    derives: Option<Vec<Ident>>,
}

impl Idents {
    fn new(input: &DeriveInput) -> Result<Idents, String> {
        let collection_name =
            attr_string_val(input, "CollectionName").expect("Need [CollectionName=\"...\"]");
        let derives = Idents::parse_derives(input);

        Ok(Idents {
            item: input.ident.clone(),
            collection: Ident::new(&collection_name, Span::call_site()),
            derives: derives,
        })
    }

    fn parse_derives(input: &DeriveInput) -> Option<Vec<Ident>> {
        match attr_string_val(input, "CollectionDerives") {
            Some(derives_str) => {
                if derives_str.is_empty() {
                    return None;
                }

                Some(
                    derives_str
                        .split(",")
                        .map(|s| Ident::new(s.trim(), Span::call_site()))
                        .collect(),
                )
            }
            None => None,
        }
    }

    fn as_parts(&self) -> (&Ident, &Ident, &Option<Vec<Ident>>) {
        (&self.item, &self.collection, &self.derives)
    }
}

struct Docs {
    wrapper: String,
    new: String,
    is_empty: String,
    len: String,
    iter: String,
}

macro_rules! doc_attr {
    ($input:ident, $attr:expr, $default:expr) => {
        attr_string_val($input, $attr).unwrap_or($default);
    };
}

impl Docs {
    fn new(input: &DeriveInput, idents: &Idents) -> Docs {
        let wrapper = doc_attr!(
            input,
            "CollectionDoc",
            format!("A collection of {}s", idents.item)
        );
        let new = doc_attr!(
            input,
            "CollectionNewDoc",
            format!("Creates a new, empty {}", idents.collection)
        );
        let is_empty = doc_attr!(
            input,
            "CollectionIsEmptyDoc",
            format!(
                "Returns true if the {} contains no {}s",
                idents.collection, idents.item
            )
        );
        let len = doc_attr!(
            input,
            "CollectionLenDoc",
            format!(
                "Returns the number of {}s in the {}",
                idents.item, idents.collection
            )
        );
        let iter = doc_attr!(
            input,
            "CollectionIterDoc",
            format!("Returns an iterator over the {}", idents.collection)
        );

        Docs {
            wrapper: wrapper,
            new: new,
            is_empty: is_empty,
            len: len,
            iter: iter,
        }
    }

    fn as_parts(&self) -> (&String, &String, &String, &String, &String) {
        (
            &self.wrapper,
            &self.new,
            &self.is_empty,
            &self.len,
            &self.iter,
        )
    }
}

#[proc_macro_derive(
    WrappedVec,
    attributes(
        CollectionName,
        CollectionDerives,
        CollectionDoc,
        CollectionNewDoc,
        CollectionIsEmptyDoc,
        CollectionLenDoc,
        CollectionIterDoc
    )
)]
pub fn wrapped_vec(token_stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(token_stream as DeriveInput);
    impl_wrapped_vec(&input).unwrap().into()
}

fn impl_wrapped_vec(input: &DeriveInput) -> Result<TokenStream, String> {
    let idents = Idents::new(input)?;
    let docs = Docs::new(input, &idents);
    Ok(generate_wrapped_vec(&idents, &docs))
}

fn generate_wrapped_vec(idents: &Idents, docs: &Docs) -> TokenStream {
    let (item_ident, collection_ident, collection_derive) = idents.as_parts();
    let (collection_doc, new_doc, is_empty_doc, len_doc, iter_doc) = docs.as_parts();

    let derives_toks = match collection_derive.clone() {
        Some(derives) => {
            quote! { #[derive(#(#derives),*)] }
        }
        None => {
            quote! {}
        }
    };

    quote! {
        #[doc=#collection_doc]
        #derives_toks
        pub struct #collection_ident(Vec<#item_ident>);

        impl ::std::iter::FromIterator<#item_ident> for #collection_ident {
            fn from_iter<I: IntoIterator<Item=#item_ident>>(iter: I) -> Self {
                let mut inner = vec![];
                inner.extend(iter);
                #collection_ident(inner)
            }
        }

        impl From<Vec<#item_ident>> for #collection_ident {
            fn from(ids: Vec<#item_ident>) -> #collection_ident {
                let mut new = #collection_ident::new();
                new.extend(ids);
                new
            }
        }

        impl IntoIterator for #collection_ident {
            type Item = #item_ident;
            type IntoIter = ::std::vec::IntoIter<#item_ident>;

            fn into_iter(self) -> Self::IntoIter {
                self.0.into_iter()
            }
        }

        impl<'a> IntoIterator for &'a #collection_ident {
            type Item = &'a #item_ident;
            type IntoIter = ::std::slice::Iter<'a, #item_ident>;

            fn into_iter(self) -> Self::IntoIter {
                self.0.iter()
            }
        }

        impl Extend<#item_ident> for #collection_ident {
            fn extend<T: IntoIterator<Item=#item_ident>>(&mut self, iter: T) {
                self.0.extend(iter);
            }
        }

        impl #collection_ident {

            #[doc=#new_doc]
            pub fn new() -> #collection_ident {
                #collection_ident(vec![])
            }

            #[doc=#is_empty_doc]
            pub fn is_empty(&self) -> bool {
                self.0.is_empty()
            }

            #[doc=#len_doc]
            pub fn len(&self) -> usize {
                self.0.len()
            }

            #[doc=#iter_doc]
            pub fn iter<'a>(&'a self) -> ::std::slice::Iter<'a, #item_ident> {
                self.into_iter()
            }

        }
    }
}

fn attr_string_val(input: &DeriveInput, attr_name: &'static str) -> Option<String> {
    input.attrs.iter().find_map(|input| {
        if let Ok(attribute) = input.parse_meta() {
            if let syn::Meta::NameValue(name_value) = attribute {
                if name_value.path.is_ident(attr_name) {
                    if let syn::Lit::Str(s) = &name_value.lit {
                        return Some(s.value());
                    }
                }
            }
        }
        None
    })
}