palette_derive 0.4.1

Automatically implement traits from the palette crate.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::punctuated::Punctuated;
use syn::synom::Synom;
use syn::token::{Comma, Eq};
use syn::{Attribute, Data, Field, Fields, Ident, Index, LitStr, Type};

pub fn parse_attributes<T: MetaParser>(attributes: Vec<Attribute>) -> T {
    let mut result = T::default();

    for attribute in attributes {
        let attribute_name = attribute
            .path
            .segments
            .first()
            .unwrap()
            .into_value()
            .ident
            .clone();
        let is_palette_attribute = attribute_name.to_string().starts_with("palette_");

        if attribute.path.segments.len() > 1 {
            if is_palette_attribute {
                panic!(
                    "expected `{}`, but found `{}`",
                    attribute_name,
                    attribute.path.into_token_stream()
                );
            } else {
                continue;
            }
        }

        if attribute_name == "palette_internal" {
            assert_empty_attribute(&attribute_name, attribute.tts);
            result.internal();
        } else {
            result.parse_attribute(attribute_name, attribute.tts);
        }
    }

    result
}

pub fn parse_data_attributes<T: DataMetaParser>(data: Data) -> T {
    let mut result = T::default();

    match data {
        Data::Struct(struct_item) => {
            let fields = match struct_item.fields {
                Fields::Named(fields) => fields.named,
                Fields::Unnamed(fields) => fields.unnamed,
                Fields::Unit => Default::default(),
            };

            parse_struct_field_attributes(&mut result, fields)
        }
        Data::Enum(_) => {}
        Data::Union(_) => {}
    }

    result
}

pub fn parse_struct_field_attributes<T: DataMetaParser>(
    parser: &mut T,
    fields: Punctuated<Field, Comma>,
) {
    for (index, field) in fields.into_iter().enumerate() {
        let identifier = field
            .ident
            .map(IdentOrIndex::Ident)
            .unwrap_or_else(|| IdentOrIndex::Index(index.into()));

        for attribute in field.attrs {
            let attribute_name = attribute
                .path
                .segments
                .first()
                .unwrap()
                .into_value()
                .ident
                .clone();
            if !attribute_name.to_string().starts_with("palette_") {
                continue;
            }

            if attribute.path.segments.len() > 1 {
                panic!(
                    "expected `{}`, but found `{}`",
                    attribute_name,
                    attribute.path.into_token_stream()
                );
            }

            parser.parse_struct_field_attribute(
                identifier.clone(),
                field.ty.clone(),
                attribute_name,
                attribute.tts,
            );
        }
    }
}

pub fn assert_empty_attribute(attribute_name: &Ident, tts: TokenStream) {
    if !tts.is_empty() {
        panic!(
            "expected the attribute to be on the form `#[{name}]`, but found `#[{name}{tts}]`",
            name = attribute_name,
            tts = tts
        );
    }
}

pub fn parse_tuple_attribute<T: Synom>(
    attribute_name: &Ident,
    tts: TokenStream,
) -> Punctuated<T, Comma> {
    struct GenericTuple<T>(Punctuated<T, Comma>);

    impl<T: Synom> Synom for GenericTuple<T> {
        named!(parse -> Self, do_parse!(
            tuple: parens!(call!(Punctuated::parse_separated_nonempty)) >>
            (GenericTuple(tuple.1))
        ));
    }

    match ::syn::parse2::<GenericTuple<T>>(tts.clone()) {
        Ok(elements) => elements.0,
        Err(_) => panic!(
            "expected the attribute to be on the form `#[{name}(A, B, ...)]`, but found #[{name}{tts}]",
            name = attribute_name,
            tts = tts
        ),
    }
}

pub fn parse_equal_attribute<T: Synom>(attribute_name: &Ident, tts: TokenStream) -> T {
    struct Paren<T>(T);

    impl<T: Synom> Synom for Paren<T> {
        named!(parse -> Self, do_parse!(
            _eq: syn!(Eq) >>
            content: syn!(StringOrValue<T>) >>
            result: switch!(value!(content),
                StringOrValue::Value(value) => value!(value) |
                StringOrValue::String(string) => call!(parse_string, string)
            ) >>
            (Paren(result))
        ));
    }

    enum StringOrValue<T> {
        String(String),
        Value(T),
    }

    impl<T: Synom> Synom for StringOrValue<T> {
        named!(parse -> Self, alt!(
            syn!(T) => {StringOrValue::Value} |
            syn!(LitStr) => {|lit| StringOrValue::String(lit.value())}
        ));
    }

    fn parse_string<T: Synom>(
        cursor: ::syn::buffer::Cursor,
        string: String,
    ) -> ::syn::synom::PResult<T> {
        ::syn::parse2(string.parse().unwrap()).map(|value| (value, cursor))
    }

    match ::syn::parse2::<Paren<T>>(tts.clone()) {
        Ok(assign) => assign.0,
        Err(_) => panic!(
            "expected the attribute to be on the form `#[{name} = A]` or `#[{name} = \"A\"]`, but found #[{name}{tts}]",
            name = attribute_name,
            tts = tts
        ),
    }
}

#[derive(PartialEq)]
pub struct KeyValuePair {
    pub key: Ident,
    pub value: Option<Ident>,
}

impl ::syn::synom::Synom for KeyValuePair {
    named!(parse -> Self, do_parse!(
        key: syn!(Ident) >>
        value: option!(do_parse!(
            _eq: syn!(Eq) >>
            value: syn!(LitStr) >>
            (Ident::new(&value.value(), Span::call_site()))
        )) >>
        (KeyValuePair {
            key,
            value
        })
    ));
}

impl PartialEq<str> for KeyValuePair {
    fn eq(&self, other: &str) -> bool {
        self.key == other
    }
}

#[derive(Clone)]
pub enum IdentOrIndex {
    Index(Index),
    Ident(Ident),
}

impl PartialEq for IdentOrIndex {
    fn eq(&self, other: &IdentOrIndex) -> bool {
        match (self, other) {
            (&IdentOrIndex::Index(ref this), &IdentOrIndex::Index(ref other)) => {
                this.index == other.index
            }
            (&IdentOrIndex::Ident(ref this), &IdentOrIndex::Ident(ref other)) => this == other,
            _ => false,
        }
    }
}

impl ::std::cmp::Eq for IdentOrIndex {}

impl ::std::hash::Hash for IdentOrIndex {
    fn hash<H: ::std::hash::Hasher>(&self, hasher: &mut H) {
        ::std::mem::discriminant(self).hash(hasher);

        match *self {
            IdentOrIndex::Index(ref index) => index.index.hash(hasher),
            IdentOrIndex::Ident(ref ident) => ident.hash(hasher),
        }
    }
}

impl ::quote::ToTokens for IdentOrIndex {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match *self {
            IdentOrIndex::Index(ref index) => index.to_tokens(tokens),
            IdentOrIndex::Ident(ref ident) => ident.to_tokens(tokens),
        }
    }
}

pub trait MetaParser: Default {
    fn internal(&mut self);
    fn parse_attribute(&mut self, attribute_name: Ident, attribute_tts: TokenStream);
}

pub trait DataMetaParser: Default {
    fn parse_struct_field_attribute(
        &mut self,
        field_name: IdentOrIndex,
        ty: Type,
        attribute_name: Ident,
        attribute_tts: TokenStream,
    );
}