embedded-menu-macros 0.5.0

Derive macro implementations for embedded-menu
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DataEnum, DeriveInput, LitStr};

pub fn expand_next_fn(input: &DataEnum) -> syn::Result<TokenStream> {
    let variants = input.variants.iter().map(|variant| {
        if let syn::Fields::Unit = variant.fields {
            &variant.ident
        } else {
            unimplemented!("SelectValue can only be derived on enums with unit variants")
        }
    });

    let branches = variants
        .clone()
        .zip(variants.cycle().skip(1))
        .map(|(current, next)| {
            quote! { Self::#current => Self::#next }
        });

    Ok(quote! {
        *self = match self {
            #(#branches),*
        };
    })
}

pub fn expand_name_fn(
    input: &DataEnum,
    top_level_name: &Option<String>,
) -> syn::Result<TokenStream> {
    let variants = input
        .variants
        .iter()
        .map(|v| {
            let mut manual_name = None;
            for attr in &v.attrs {
                if attr.path().is_ident("display_as") {
                    if manual_name.is_some() {
                        return Err(syn::Error::new_spanned(
                            attr,
                            "Only one display_as can be placed on each variant",
                        ));
                    }

                    manual_name = Some(attr.parse_args::<LitStr>()?.value());
                }
            }

            let path = &v.ident;
            let name_str = manual_name
                .or(top_level_name.clone())
                .unwrap_or_else(|| v.ident.to_string());

            Ok(quote! {
                Self::#path => #name_str
            })
        })
        .collect::<Result<Vec<_>, syn::Error>>()?;

    Ok(quote! {
        match self {
            #(#variants),*
        }
    })
}

pub fn expand_select_value(input: DeriveInput) -> syn::Result<TokenStream> {
    let Data::Enum(data) = &input.data else {
        unimplemented!("SelectValue can only be derived on enums");
    };
    let top_level_manual_name = match input
        .attrs
        .iter()
        .find(|attr| attr.meta.path().is_ident("display_as"))
        .map(syn::Attribute::parse_args::<LitStr>)
    {
        None => None,
        Some(Ok(top_level_name)) => Some(top_level_name.value()),
        Some(Err(err)) => return Err(err),
    };

    let enum_name = input.ident;

    let next_body = expand_next_fn(data)?;
    let name_body = expand_name_fn(data, &top_level_manual_name)?;

    Ok(quote! {
        impl embedded_menu::items::menu_item::SelectValue for #enum_name {
            fn next(&mut self) {
                #next_body
            }

            fn marker(&self) -> &str {
                #name_body
            }
        }
    })
}