genesis-impl 0.2.2

proc macro crate for genesis
Documentation
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::Attribute;
use syn::Token;
use syn::{Data, DataStruct, DeriveInput, Field, Ident, Result, Visibility};

pub(crate) struct Input {
    pub world_name: Ident,
    pub component_enum_name: Ident,
    pub template_name: Ident,
    pub components: Vec<WorldComponent>,
    pub vis: Visibility,
    pub attributes: Vec<Attribute>,
}

pub struct InputArgs {
    pub component_name: Ident,
    pub template_name: Ident,
}

impl Parse for InputArgs {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let component_name = input.parse::<Ident>()?;
        let _separator = input.parse::<Token![,]>()?;
        let template_name = input.parse::<Ident>()?;
        Ok(Self {
            component_name,
            template_name,
        })
    }
}

pub(crate) struct WorldComponent {
    pub field: Field,
    pub template_name: Ident,
    pub storage_type: ComponentStorageType,
}

#[derive(Debug, Copy, Clone)]
pub(crate) enum ComponentStorageType {
    Vec,
    Map,
}
impl ComponentStorageType {
    pub(crate) fn name(self) -> &'static str {
        match self {
            ComponentStorageType::Vec => "VecStorage",
            ComponentStorageType::Map => "MapStorage",
        }
    }
}

impl Default for ComponentStorageType {
    fn default() -> Self {
        ComponentStorageType::Vec
    }
}

impl Parse for ComponentStorageType {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let inner;
        syn::parenthesized!(inner in input);

        let ident = inner.parse::<Ident>()?.to_string();

        if ident == "map" {
            Ok(Self::Map)
        } else {
            Ok(Self::Vec)
        }
    }
}

pub(crate) struct TemplateName {
    pub ident: Ident,
}

impl Parse for TemplateName {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let inner;
        syn::parenthesized!(inner in input);

        let ident = inner.parse::<Ident>()?;

        Ok(Self { ident })
    }
}

const EXPECTED_NAMED_STRUCT_FIELDS: &'static str = "Only structs with named fields are supported.";

impl Input {
    pub(crate) fn new(args: InputArgs, input: &DeriveInput) -> Result<Self> {
        match &input.data {
            Data::Struct(DataStruct {
                fields: syn::Fields::Named(fields_named),
                ..
            }) => {
                let fields = fields_named
                    .named
                    .iter()
                    .map(|f| {
                        let (ty, name) = get_field_storage_type_and_template_name(f);
                        WorldComponent {
                            field: f.clone(),
                            storage_type: ty,
                            template_name: name,
                        }
                    })
                    .collect();
                Ok(Self {
                    world_name: input.ident.clone(),
                    template_name: args.template_name,
                    component_enum_name: args.component_name,
                    components: fields,
                    vis: input.vis.clone(),
                    attributes: input.attrs.clone(),
                })
            }
            Data::Struct(data_struct) => {
                if let syn::Fields::Unnamed(fields_unnamed) = &data_struct.fields {
                    Err(syn::Error::new(
                        fields_unnamed.span(),
                        EXPECTED_NAMED_STRUCT_FIELDS,
                    ))
                } else {
                    Err(syn::Error::new(input.span(), EXPECTED_NAMED_STRUCT_FIELDS))
                }
            }
            _ => Err(syn::Error::new(input.span(), EXPECTED_NAMED_STRUCT_FIELDS)),
        }
    }
}

fn get_field_storage_type_and_template_name(f: &Field) -> (ComponentStorageType, Ident) {
    let mut component_type = ComponentStorageType::Vec;
    let mut template_name = f.ident.as_ref().unwrap().clone();
    for attr in f.attrs.iter() {
        let path_ident = attr.path.get_ident();
        if path_ident.is_some() && path_ident.unwrap() == "component" {
            let tokens = attr.tokens.clone();
            if let Ok(ty) = syn::parse2::<ComponentStorageType>(tokens) {
                component_type = ty;
            }
        } else if path_ident.is_some() && path_ident.unwrap() == "template_name" {
            let tokens = attr.tokens.clone();
            if let Ok(name) = syn::parse2::<TemplateName>(tokens) {
                template_name = name.ident;
            }
        }
    }
    (component_type, template_name)
}