1use std::{
14 collections::{HashMap, HashSet},
15 iter::FromIterator,
16};
17
18use convert_case::{Case, Casing};
19use proc_macro2::{Ident, Span, TokenStream};
20use quote::quote;
21
22use crate::{
23 builder::{LanguageId, RosettaConfig},
24 parser::{FormattedKey, SimpleKey, TranslationData, TranslationKey},
25};
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub(crate) struct CodeGenerator<'a> {
30 keys: &'a HashMap<String, TranslationKey>,
31 languages: Vec<&'a LanguageId>,
32 fallback: &'a LanguageId,
33 name: Ident,
34}
35
36impl<'a> CodeGenerator<'a> {
37 pub(crate) fn new(data: &'a TranslationData, config: &'a RosettaConfig) -> Self {
39 let name = Ident::new(&config.name, Span::call_site());
40
41 CodeGenerator {
42 keys: &data.keys,
43 languages: config.languages(),
44 fallback: &config.fallback.0,
45 name,
46 }
47 }
48
49 pub(crate) fn generate(&self) -> TokenStream {
51 let languages: Vec<_> = self
53 .languages
54 .iter()
55 .map(|lang| lang.value().to_case(Case::Pascal))
56 .collect();
57
58 let name = &self.name;
59 let fields = languages
60 .iter()
61 .map(|lang| Ident::new(lang, Span::call_site()));
62
63 let language_impl = self.impl_language();
64 let methods = self.keys.iter().map(|(key, value)| match value {
65 TranslationKey::Simple(inner) => self.method_simple(key, inner),
66 TranslationKey::Formatted(inner) => self.method_formatted(key, inner),
67 });
68
69 quote! {
70 #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
72 pub enum #name {
73 #(#fields),*
74 }
75
76 impl #name {
77 #(#methods)*
78 }
79
80 #language_impl
81 }
82 }
83
84 fn method_simple(&self, key: &str, data: &SimpleKey) -> TokenStream {
86 let name = Ident::new(&key.to_case(Case::Snake), Span::call_site());
87 let fallback = &data.fallback;
88 let arms = data
89 .others
90 .iter()
91 .map(|(language, value)| self.match_arm_simple(language, value));
92
93 quote! {
94 #[allow(clippy::all)]
95 pub fn #name(&self) -> &'static str {
96 match self {
97 #(#arms,)*
98 _ => #fallback
99 }
100 }
101 }
102 }
103
104 fn match_arm_simple(&self, language: &LanguageId, value: &str) -> TokenStream {
106 let name = &self.name;
107 let lang = Ident::new(&language.value().to_case(Case::Pascal), Span::call_site());
108
109 quote! { #name::#lang => #value }
110 }
111
112 fn method_formatted(&self, key: &str, data: &FormattedKey) -> TokenStream {
114 let name = Ident::new(&key.to_case(Case::Snake), Span::call_site());
115
116 let mut sorted = Vec::from_iter(&data.parameters);
118 sorted.sort_by_key(|s| s.to_lowercase());
119 let params = sorted
120 .iter()
121 .map(|param| Ident::new(param, Span::call_site()))
122 .map(|param| quote!(#param: impl ::std::fmt::Display));
123
124 let arms = data
125 .others
126 .iter()
127 .map(|(language, value)| self.match_arm_formatted(language, value, &data.parameters));
128 let fallback = self.format_formatted(&data.fallback, &data.parameters);
129
130 quote! {
131 #[allow(clippy::all)]
132 pub fn #name(&self, #(#params),*) -> ::std::string::String {
133 match self {
134 #(#arms,)*
135 _ => #fallback
136 }
137 }
138 }
139 }
140
141 fn match_arm_formatted(
143 &self,
144 language: &LanguageId,
145 value: &str,
146 parameters: &HashSet<String>,
147 ) -> TokenStream {
148 let name = &self.name;
149 let format_value = self.format_formatted(value, parameters);
150 let lang = Ident::new(&language.value().to_case(Case::Pascal), Span::call_site());
151
152 quote! { #name::#lang => #format_value }
153 }
154
155 fn format_formatted(&self, value: &str, parameters: &HashSet<String>) -> TokenStream {
157 let params = parameters
158 .iter()
159 .map(|param| Ident::new(param, Span::call_site()))
160 .map(|param| quote!(#param = #param));
161
162 quote!(format!(#value, #(#params),*))
163 }
164
165 fn impl_language(&self) -> TokenStream {
167 let name = &self.name;
168 let fallback = Ident::new(
169 &self.fallback.value().to_case(Case::Pascal),
170 Span::call_site(),
171 );
172
173 let language_id_idents = self.languages.iter().map(|lang| lang.value()).map(|lang| {
174 (
175 lang,
176 Ident::new(&lang.to_case(Case::Pascal), Span::call_site()),
177 )
178 });
179
180 let from_language_id_arms = language_id_idents
181 .clone()
182 .map(|(lang, ident)| quote!(#lang => ::core::option::Option::Some(Self::#ident)));
183
184 let to_language_id_arms = language_id_idents
185 .map(|(lang, ident)| quote!(Self::#ident => ::rosetta_i18n::LanguageId::new(#lang)));
186
187 quote! {
188 impl ::rosetta_i18n::Language for #name {
189 fn from_language_id(language_id: &::rosetta_i18n::LanguageId) -> ::core::option::Option<Self> {
190 match language_id.value() {
191 #(#from_language_id_arms,)*
192 _ => ::core::option::Option::None
193 }
194 }
195
196 fn language_id(&self) -> ::rosetta_i18n::LanguageId {
197 match self {
198 #(#to_language_id_arms,)*
199 }
200 }
201
202 fn fallback() -> Self {
203 Self::#fallback
204 }
205 }
206 }
207 }
208}