appcui_proc_macro 0.2.5

Procedural macros for the AppCUI TUI framework.
Documentation
use super::enumeration::Enum;
use super::enumeration::EnumVariant;
use core::panic;
use proc_macro::*;
use std::str::FromStr;

static TEMPLATE: &str = r#"
impl EnumSelector for $(ENUM_NAME) {
    const COUNT: u32 = $(COUNT);

    fn from_index(index: u32) -> Option<Self> {
        match index {
            $(FROM_INDEX_MATCH)
            _ => None,
        }
    }

    fn name(&self) -> &'static str {
        match self {
            $(NAME_MATCH)
            _ => "$(ENUM_NAME)",
        }
    }

    fn description(&self) -> &'static str {
        match self {
            $(DESCRIPTION_MATCH)
            _ => "",
        }
    }
}
"#;

fn generate_variant_match_arms(variant: &EnumVariant, index: usize) -> (String, String, String) {
    let mut name: Option<&String> = None;
    let mut description: Option<&String> = None;
    let mut init_code: Option<&String> = None;
    
    // modify to use VariantInfo
    for (attr_name, value) in variant.attributes.iter() {
        if let Some(next_str) = attr_name.strip_prefix("VariantInfo.")
        {
            match next_str.trim() {
                "name" | "Name" | "N" | "n" => {
                    if name.is_some() {
                        panic!("Duplicate 'name' attributes found for variant '{}'. Use only one!", variant.name);
                    }
                    name = Some(value);
                }
                "description" | "Description" | "details" | "Details" | "D" | "d" => {
                    if description.is_some() {
                        panic!("Duplicate 'description' attributes found for variant '{}'. Use only one!", variant.name);
                    }
                    description = Some(value);
                }
                "init" => {
                    if init_code.is_some() {
                        panic!("Duplicate 'initialization' attributes found for variant '{}'. Use only one!", variant.name);
                    }
                    init_code = Some(value)
                }
                _ => {
                    panic!(
                        "Unknown attribute: '{}' for field '{}'. Available attributes are: 'name' and 'description' and 'init'.",
                        &attr_name,
                        variant.name
                    );
                }
            }
        }
    }

    let name_value = name.unwrap_or(&variant.name);
    let empty = String::new();
    let description_value = description.unwrap_or(&empty);
    let fields_placeholder = if variant.fields.is_none() { "".to_string() } else { "{ .. }".to_string() };
    let init_value = init_code.unwrap_or(&empty);

    let from_index_arm = format!("{} => Some(Self::{} {}),", index, variant.name, init_value);
    let name_arm = format!("Self::{} {} => \"{}\",", variant.name, fields_placeholder, name_value);
    let description_arm = format!("Self::{} {} => \"{}\",", variant.name, fields_placeholder, description_value);

    (from_index_arm, name_arm, description_arm)
}



fn build_enumselector_code(en: &Enum) -> TokenStream {
    let count = en.variants.len();
    let mut from_index_match = String::new();
    let mut name_match = String::new();
    let mut description_match = String::new();

    for (i, variant) in en.variants.iter().enumerate() {
        let (fi, nm, dm) = generate_variant_match_arms(variant, i);
        from_index_match.push_str(&fi);
        from_index_match.push('\n');
        name_match.push_str(&nm);
        name_match.push('\n');
        description_match.push_str(&dm);
        description_match.push('\n');
    }
    
    let output = TEMPLATE
        .replace("$(ENUM_NAME)", &en.name)
        .replace("$(COUNT)", &count.to_string())
        .replace("$(FROM_INDEX_MATCH)", &from_index_match)
        .replace("$(NAME_MATCH)", &name_match)
        .replace("$(DESCRIPTION_MATCH)", &description_match);

    TokenStream::from_str(&output)
        .expect("Failed to convert enumselector derived definition to token stream")
}

pub fn derive(input: TokenStream) -> TokenStream {
    match Enum::from(input) {
        Ok(en) => build_enumselector_code(&en),
        Err(err) => panic!("Fail to derive EnumSelector for enum: {}", err),
    }
}