replman_derive_impl 0.1.0

An opinionated REPL framework
Documentation
use quote::quote;
use syn::{parse_quote, Arm, DataEnum, Fields};

use crate::common::effective_variant_name;
use crate::enum_attributes::EnumAttributes;
use crate::field_attributes::{self, FieldAttributes};
use crate::variant_attributes::VariantAttributes;

pub fn derive_parse_method(
    data_enum: &DataEnum,
    attrs: &EnumAttributes,
) -> syn::ImplItemMethod {
    let variant_matches = variant_matches(data_enum, attrs);

    let parse_impl: syn::ImplItemMethod = syn::parse_quote! {
        fn parse<'a, I>(mut parts: I) -> anyhow::Result<Self>
        where
            Self: Sized,
            I: Iterator<Item = anyhow::Result<&'a str>> + 'a
        {
            let cmd_word = parts.next().unwrap()?;

            match cmd_word {
                #(#variant_matches)*
                cmd => Err(anyhow::anyhow!("unrecognized command '{}'", cmd)),
            }
        }
    };

    parse_impl
}

fn variant_matches<'a>(
    data_enum: &'a syn::DataEnum,
    attrs: &'a EnumAttributes,
) -> impl Iterator<Item = Arm> + 'a {
    data_enum.variants.iter().map(|variant| {
        let variant_name = &variant.ident;
        let variant_attributes = VariantAttributes::extract(&variant.attrs);
        let effective_variant_name = effective_variant_name(variant, attrs, &variant_attributes);

        let main_name = &effective_variant_name.main_name;
        let variant_match = if effective_variant_name.aliases.is_empty() {
            quote!(#main_name)
        } else {
            let aliases = &effective_variant_name.aliases;
            quote!(#main_name | #(#aliases)|*)
        };

        match &variant.fields {
            Fields::Named(named) => {
                let field_parses = named.named.iter().map(|field| {
                    let ident = field.ident.as_ref().unwrap();

                    let field_attributes = FieldAttributes::extract(&field.attrs);

                    match field_attributes.default {
                        field_attributes::FieldDefault::None => quote! {
                            #ident: ::replman::ReplCmdParse::parse(parts.next().transpose()?)?,
                        },
                        field_attributes::FieldDefault::Some(default_value) => quote! {
                            #ident: ::replman::ReplCmdParse::parse_default(parts.next().transpose()?.unwrap_or(#default_value))?,
                        },
                        field_attributes::FieldDefault::Default => quote! {
                            #ident: match parts.next() {
                                Some(s) => ::replman::ReplCmdParse::parse_default(s?)?,
                                None => Default::default(),
                            },
                        },
                    }
                });

                parse_quote! {
                    #variant_match => {
                        Ok(Self::#variant_name {
                            #(#field_parses)*
                        })
                    }
                }
            }
            Fields::Unit => {
                parse_quote! {
                    #variant_match => { Ok(Self::#variant_name) }
                }
            }
            Fields::Unnamed(unnamed) => {
                let field_parses = unnamed.unnamed.iter().map(|_| {
                    quote! {
                        ::replman::ReplCmdParse::parse(parts.next().transpose()?)?,
                    }
                });

                parse_quote! {
                    #variant_match => {
                        Ok(Self::#variant_name(
                            #(#field_parses)*
                        ))
                    }
                }
            }
        }
    })
}