nestruct 0.1.0

A Rust library that provides macros to easily flatten or nest structs and enums in your code.
Documentation
use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{
    braced, bracketed, parenthesized, parse_str, token, Attribute, Ident, Token, Type, TypePath,
};

struct Nestruct {
    attrs: Vec<Attribute>,
    ident: Ident,
    fields: Punctuated<NestableField, Token![,]>,
}

struct NestableField {
    field_attrs: Vec<Attribute>,
    name: Ident,
    fvtype: FVType,
}

enum NestableType {
    Nestruct(Nestruct),
    Type(Type),
}

enum FVType {
    Field { meta_types: Vec<TypePath>, ty: NestableType },
    UnitVariant,
    TupleVariant { types: Punctuated<(Vec<TypePath>, Type), Token![,]> },
    StructVariant { nestruct: Nestruct },
}

impl Parse for Nestruct {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let attrs = input.call(Attribute::parse_outer)?;
        let ident = input.parse()?;
        let content;
        braced!(content in input);
        let fields = content.parse_terminated(NestableField::parse)?;
        Ok(Nestruct { attrs, ident, fields })
    }
}

fn parse_nest_types(
    input: ParseStream,
    ident: Ident,
) -> syn::Result<(Vec<TypePath>, NestableType)> {
    let parse_nestable_type = |input: ParseStream| {
        let attrs = input.call(Attribute::parse_outer)?;
        NestableType::parse_with_context(input, attrs, ident.clone())
    };
    parse_shorthand_types(input, parse_nestable_type)
}

fn parse_simple_types(input: ParseStream) -> syn::Result<(Vec<TypePath>, Type)> {
    let parse_simple_type = |input: ParseStream| input.parse::<Type>();
    parse_shorthand_types(input, parse_simple_type)
}

fn parse_shorthand_types<T>(
    input: ParseStream,
    parser: impl Fn(ParseStream) -> syn::Result<T>,
) -> syn::Result<(Vec<TypePath>, T)> {
    let mut outer_types = Vec::new();
    let buffer;
    let (mut inner_types, ty) = if input.peek(token::Bracket) {
        bracketed!(buffer in input);
        outer_types.push(parse_str::<TypePath>("Vec")?);
        parse_shorthand_types(&buffer, parser)?
    } else {
        (Vec::new(), parser(input)?)
    };
    if input.parse::<Option<Token![?]>>()?.is_some() {
        outer_types.push(parse_str::<TypePath>("Option")?);
    }
    inner_types.extend(outer_types);
    Ok((inner_types, ty))
}

impl Parse for NestableField {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let field_attrs = input.call(Attribute::parse_outer)?;
        let name: Ident = input.parse()?;
        let ident = format_ident!("{}", name.to_string().to_case(Case::Pascal));
        if input.peek(token::Colon) {
            input.parse::<token::Colon>()?;
            let (meta_types, ty) = parse_nest_types(input, ident)?;
            Ok(NestableField { field_attrs, name, fvtype: FVType::Field { meta_types, ty } })
        } else if input.peek(token::Brace) {
            let content;
            braced!(content in input);
            let fields = content.parse_terminated(NestableField::parse)?;
            let nestruct = Nestruct { attrs: vec![], ident, fields };
            Ok(NestableField { field_attrs, name, fvtype: FVType::StructVariant { nestruct } })
        } else if input.peek(token::Paren) {
            let content;
            parenthesized!(content in input);
            let types = content.parse_terminated(parse_simple_types)?;
            let fvtype = FVType::TupleVariant { types };
            Ok(NestableField { field_attrs, name, fvtype })
        } else {
            Ok(NestableField { field_attrs, name, fvtype: FVType::UnitVariant })
        }
    }
}

impl NestableType {
    fn parse_with_context(
        input: ParseStream,
        attrs: Vec<Attribute>,
        ident: Ident,
    ) -> syn::Result<Self> {
        if input.peek(token::Brace) {
            let content;
            braced!(content in input);
            let fields = content.parse_terminated(NestableField::parse)?;
            Ok(Self::Nestruct(Nestruct { attrs, ident, fields }))
        } else {
            Ok(Self::Type(input.parse()?))
        }
    }
}

fn generate_field_type<'a>(
    ty: &'a NestableType,
    meta_types: &[TypePath],
    nest: bool,
) -> (TokenStream2, Option<&'a Nestruct>) {
    let (mut ty_token, nestruct) = match ty {
        NestableType::Nestruct(nestruct) => {
            let ident = nestruct.ident.clone();
            if nest {
                let ns = format_ident!("{}", ident.to_string().to_case(Case::Snake));
                (quote! { #ns::#ident }, Some(nestruct))
            } else {
                (quote! { #ident }, Some(nestruct))
            }
        }
        NestableType::Type(ty) => (quote! { #ty }, None),
    };
    for meta_type in meta_types {
        ty_token = quote! { #meta_type<#ty_token> };
    }
    (ty_token, nestruct)
}

fn generate_fields<'a>(
    nestablefields: &[&'a NestableField],
    nest: bool,
    pubtoken: TokenStream2,
) -> (Vec<&'a Nestruct>, bool, Vec<TokenStream2>) {
    let mut fields = Vec::new();
    let mut variants = Vec::new();
    let mut children = Vec::new();
    for field in nestablefields {
        let field_attrs = &field.field_attrs;
        let name = &field.name;
        let vname = format_ident!("{}", name.to_string().to_case(Case::Pascal));
        match &field.fvtype {
            FVType::Field { meta_types, ty } => {
                let (ty_token, nestruct) = generate_field_type(ty, meta_types, nest);
                fields.push(quote! { #(#field_attrs)* #pubtoken #name : #ty_token });
                if let Some(nestruct) = nestruct {
                    children.push(nestruct);
                }
            }
            FVType::UnitVariant => {
                variants.push(quote! { #(#field_attrs)* #vname });
            }
            FVType::TupleVariant { types } => {
                let mut ty_tokens = Vec::new();
                for (meta_types, ty) in types {
                    let (ty_token, _) =
                        generate_field_type(&NestableType::Type(ty.clone()), meta_types, nest);
                    ty_tokens.push(ty_token);
                }
                variants.push(quote! { #(#field_attrs)* #vname(#(#ty_tokens),*) });
            }
            FVType::StructVariant { nestruct } => {
                let fields: Vec<&NestableField> = nestruct.fields.iter().collect();
                let (grandchildren, is_variant, fields) = generate_fields(&fields, nest, quote! {});
                if is_variant {
                    panic!("Children of struct variants shoukd not be variants");
                } else {
                    variants.push(quote! { #(#field_attrs)* #vname{#(#fields),*} });
                }
                children.extend(grandchildren);
            }
        }
    }
    if !variants.is_empty() {
        if !fields.is_empty() {
            panic!("Cannot have both variants and fields in a brace");
        } else {
            (children, true, variants)
        }
    } else {
        (children, false, fields)
    }
}

fn is_reset_attr(attr: &Attribute) -> bool {
    if let Some(ident) = attr.path.get_ident() {
        if ident == "nestruct" {
            if let Ok(arg) = attr.parse_args_with(Ident::parse_any) {
                return arg == "reset";
            }
        }
    }
    false
}

fn generate_structs(nest: bool, nestruct: &Nestruct, parent_attrs: &[Attribute]) -> TokenStream2 {
    let mut tokens = Vec::new();
    let fields: Vec<&NestableField> = nestruct.fields.iter().collect();
    let mut attrs = Vec::from(parent_attrs);
    for attr in nestruct.attrs.clone() {
        if is_reset_attr(&attr) {
            attrs.clear()
        } else {
            attrs.push(attr)
        }
    }
    let (children, is_variant, fvs) = generate_fields(&fields, nest, quote! { pub });
    for child in children {
        tokens.push(generate_structs(nest, child, &attrs))
    }
    let ident = nestruct.ident.clone();
    tokens.push(if is_variant {
        quote! { #(#attrs)* pub enum #ident { #(#fvs),* } }
    } else {
        quote! { #(#attrs)* pub struct #ident { #(#fvs),* } }
    });
    let token = tokens.into_iter().collect::<TokenStream2>();
    if nest {
        let ns = format_ident!("{}", ident.to_string().to_case(Case::Snake));
        quote! { pub mod #ns { #token } }
    } else {
        token
    }
}

#[proc_macro]
pub fn nest(input: TokenStream) -> TokenStream {
    let nestruct = syn::parse_macro_input!(input as Nestruct);
    let attrs = vec![];
    generate_structs(true, &nestruct, &attrs).into()
}

#[proc_macro]
pub fn flatten(input: TokenStream) -> TokenStream {
    let nestruct = syn::parse_macro_input!(input as Nestruct);
    let attrs = vec![];
    generate_structs(false, &nestruct, &attrs).into()
}