use std::{
collections::{HashMap, HashSet},
iter::FromIterator,
};
use convert_case::{Case, Casing};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use crate::{
builder::{LanguageId, RosettaConfig},
parser::{FormattedKey, SimpleKey, TranslationData, TranslationKey},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CodeGenerator<'a> {
keys: &'a HashMap<String, TranslationKey>,
languages: Vec<&'a LanguageId>,
fallback: &'a LanguageId,
name: Ident,
}
impl<'a> CodeGenerator<'a> {
pub(crate) fn new(data: &'a TranslationData, config: &'a RosettaConfig) -> Self {
let name = Ident::new(&config.name, Span::call_site());
CodeGenerator {
keys: &data.keys,
languages: config.languages(),
fallback: &config.fallback.0,
name,
}
}
pub(crate) fn generate(&self) -> TokenStream {
let languages: Vec<_> = self
.languages
.iter()
.map(|lang| lang.value().to_case(Case::Pascal))
.collect();
let name = &self.name;
let fields = languages
.iter()
.map(|lang| Ident::new(lang, Span::call_site()));
let language_impl = self.impl_language();
let methods = self.keys.iter().map(|(key, value)| match value {
TranslationKey::Simple(inner) => self.method_simple(key, inner),
TranslationKey::Formatted(inner) => self.method_formatted(key, inner),
});
quote! {
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum #name {
#(#fields),*
}
impl #name {
#(#methods)*
}
#language_impl
}
}
fn method_simple(&self, key: &str, data: &SimpleKey) -> TokenStream {
let name = Ident::new(&key.to_case(Case::Snake), Span::call_site());
let fallback = &data.fallback;
let arms = data
.others
.iter()
.map(|(language, value)| self.match_arm_simple(language, value));
quote! {
#[allow(clippy::all)]
pub fn #name(&self) -> &'static str {
match self {
#(#arms,)*
_ => #fallback
}
}
}
}
fn match_arm_simple(&self, language: &LanguageId, value: &str) -> TokenStream {
let name = &self.name;
let lang = Ident::new(&language.value().to_case(Case::Pascal), Span::call_site());
quote! { #name::#lang => #value }
}
fn method_formatted(&self, key: &str, data: &FormattedKey) -> TokenStream {
let name = Ident::new(&key.to_case(Case::Snake), Span::call_site());
let mut sorted = Vec::from_iter(&data.parameters);
sorted.sort_by_key(|s| s.to_lowercase());
let params = sorted
.iter()
.map(|param| Ident::new(param, Span::call_site()))
.map(|param| quote!(#param: impl ::std::fmt::Display));
let arms = data
.others
.iter()
.map(|(language, value)| self.match_arm_formatted(language, value, &data.parameters));
let fallback = self.format_formatted(&data.fallback, &data.parameters);
quote! {
#[allow(clippy::all)]
pub fn #name(&self, #(#params),*) -> ::std::string::String {
match self {
#(#arms,)*
_ => #fallback
}
}
}
}
fn match_arm_formatted(
&self,
language: &LanguageId,
value: &str,
parameters: &HashSet<String>,
) -> TokenStream {
let name = &self.name;
let format_value = self.format_formatted(value, parameters);
let lang = Ident::new(&language.value().to_case(Case::Pascal), Span::call_site());
quote! { #name::#lang => #format_value }
}
fn format_formatted(&self, value: &str, parameters: &HashSet<String>) -> TokenStream {
let params = parameters
.iter()
.map(|param| Ident::new(param, Span::call_site()))
.map(|param| quote!(#param = #param));
quote!(format!(#value, #(#params),*))
}
fn impl_language(&self) -> TokenStream {
let name = &self.name;
let fallback = Ident::new(
&self.fallback.value().to_case(Case::Pascal),
Span::call_site(),
);
let language_id_idents = self.languages.iter().map(|lang| lang.value()).map(|lang| {
(
lang,
Ident::new(&lang.to_case(Case::Pascal), Span::call_site()),
)
});
let from_language_id_arms = language_id_idents
.clone()
.map(|(lang, ident)| quote!(#lang => ::core::option::Option::Some(Self::#ident)));
let to_language_id_arms = language_id_idents
.map(|(lang, ident)| quote!(Self::#ident => ::rosetta_i18n::LanguageId::new(#lang)));
quote! {
impl ::rosetta_i18n::Language for #name {
fn from_language_id(language_id: &::rosetta_i18n::LanguageId) -> ::core::option::Option<Self> {
match language_id.value() {
#(#from_language_id_arms,)*
_ => ::core::option::Option::None
}
}
fn language_id(&self) -> ::rosetta_i18n::LanguageId {
match self {
#(#to_language_id_arms,)*
}
}
fn fallback() -> Self {
Self::#fallback
}
}
}
}
}