lang-generator 1.0.6

A procedural macro crate for generating language-related code in poly-translator
Documentation
mod iso639;

fn to_camel_case(name: &str) -> String {
    use heck::ToUpperCamelCase;

    let cleaned = name
        .split("(")
        .next()
        .unwrap_or(name)
        .replace(',', "")
        .replace('-', " ")
        .replace('_', " ");

    cleaned.trim().to_upper_camel_case()
}

use std::collections::HashMap;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;

use crate::iso639::read_csv;

#[proc_macro]
pub fn generate_language(_: TokenStream) -> TokenStream {
    let mut tokens = proc_macro2::TokenStream::new();

    let csv = read_csv().unwrap();
    let mut from_tos: HashMap<String, Vec<(Ident, Option<String>)>> = HashMap::new();
    let variant_idents = csv
        .into_iter()
        .map(|map| {
            let name = map.get("name").as_ref().unwrap().as_ref().unwrap();
            let enum_name = format_ident!("{}", to_camel_case(name));
            for (key, item) in map {
                from_tos
                    .entry(key.to_owned())
                    .or_default()
                    .push((enum_name.clone(), item));
            }
            enum_name
        })
        .collect::<Vec<_>>();
    tokens.extend(quote! {
        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
        pub enum Language {
            #(#variant_idents),*
        }

        impl Language {
            pub fn all() -> Vec<Self> {
                vec![#(Self::#variant_idents),*]
            }
        }
    });

    for (key, variants) in from_tos {
        let fn_from = format_ident!("from_{}", key.replace("/", "").replace("-", "_"));
        let fn_to = format_ident!("to_{}", key.replace("/", "").replace("-", "_"));

        let from_arms = variants
            .iter()
            .filter_map(|v| v.1.clone().map(|vv| (v.0.clone(), vv)))
            .map(|(variant, val)| {
                quote! { #val => Some(Language::#variant), }
            });

        let to_arms = variants
            .iter()
            .filter_map(|v| v.1.clone().map(|vv| (v.0.clone(), vv)))
            .map(|(variant, val)| {
                quote! { Language::#variant => Some(#val), }
            });

        let impl_block = quote! {
            impl Language {
                pub fn #fn_from(s: &str) -> Option<Self> {
                    match s {
                        #(#from_arms)*
                        _ => None,
                    }
                }

                pub fn #fn_to(&self) -> Option<&'static str> {
                    match self {
                        #(#to_arms)*
                        _ => None,
                    }
                }
            }
        };
        tokens.extend(impl_block);
    }

    tokens.into()
}