glossa_codegen/generator/
locales.rs1use std::io;
2
3use anyhow::bail;
4use glossa_shared::{
5 ToCompactString, fmt_compact,
6 tap::{Pipe, Tap},
7};
8use itertools::Itertools;
9use lang_id::{LangID, RawID};
10
11use crate::{
12 AnyResult, Generator, MiniStr,
13 generator::{MapType, to_lower_snake_case},
14};
15
16impl Generator<'_> {
17 pub fn output_locales_fn(
58 &self,
59 map_type: MapType,
60 const_lang_id: bool,
61 ) -> AnyResult<String> {
62 let raw_locales = self.collect_raw_locales(map_type)?;
63 let locales_len = raw_locales.len();
64 let new_header = || self.new_locales_fn_header(&locales_len, &const_lang_id);
65
66 if !const_lang_id {
67 return new_header()
68 .tap_mut(|buf| {
69 let push_str = |s| buf.push_str(s);
70 [&format!("{raw_locales:#?}"), "}\n"].map(push_str);
71 })
72 .pipe(Ok);
73 }
74
75 raw_locales
77 .iter()
78 .map(try_conv_const_id)
79 .try_fold(
80 new_header(), |mut acc, fn_name| {
82 let push_str = |s| acc.push_str(s);
83 ["\n ", &fn_name?, ","].map(push_str);
84 Ok::<_, anyhow::Error>(acc)
85 },
86 )?
87 .tap_mut(|buf| buf.push_str(" ]\n}"))
88 .pipe(Ok)
89 }
90
91 pub fn collect_raw_locales(&self, map_type: MapType) -> io::Result<Vec<MiniStr>> {
93 match map_type.is_dsl() {
94 true => match self.get_or_init_dsl_maps() {
95 x if x.is_empty() => "// Error: Empty DSL Map"
96 .pipe(io::Error::other)
97 .pipe(Err),
98 data => data
99 .iter()
100 .map(|(id, _)| id.to_compact_string())
101 .collect_vec()
102 .pipe(Ok),
103 },
104 _ => map_type
105 .get_non_dsl_maps(self)?
106 .iter()
107 .map(|(id, _)| id.to_compact_string())
108 .collect_vec()
109 .pipe(Ok),
110 }
111 }
112
113 pub(crate) fn new_locales_fn_header(
114 &self,
115 locales_len: &usize,
116 const_lang_id: &bool,
117 ) -> String {
119 let ret_type = {
121 match *const_lang_id {
122 true => fmt_compact!(
123 r#"[super::lang_id::LangID; {locales_len}] {{
124 #[allow(unused_imports)]
125 use super::lang_id::RawID;
126 use super::lang_id::consts::*;
127 ["#
128 ),
129 _ => fmt_compact!("[&'static str; {locales_len}] {{\n "),
130 }
131 };
132
133 let s_header = format!("const fn all_locales() -> {ret_type}",);
134 self.new_fn_header(&s_header)
135 }
136
137 pub fn output_mod_rs(&self, map_type: MapType) -> io::Result<String> {
157 let mod_vis = self.get_mod_visibility();
158
159 let feature_names = self.collect_cargo_feature_names(map_type)?;
160 let module_names = self.collect_rs_mod_names(map_type)?;
161 ensure_length_equal(feature_names.len(), module_names.len())?;
162
163 feature_names
164 .iter()
165 .zip(module_names)
166 .map(|(feat_name, mod_name)| {
167 fmt_compact!(
168 r###"
169#[cfg(feature = "{feat_name}")]
170{mod_vis}mod {mod_name};
171 "###
172 )
173 })
174 .collect::<String>()
175 .pipe(Ok)
176 }
177
178 pub fn collect_cargo_feature_names(
180 &self,
181 map_type: MapType,
182 ) -> io::Result<Vec<MiniStr>> {
183 let feat_prefix = self.get_feature_prefix();
184
185 self
186 .collect_raw_locales(map_type)?
187 .into_iter()
188 .map(|x| fmt_compact!("{feat_prefix}{x}"))
189 .collect_vec()
190 .pipe(Ok)
191 }
192
193 pub fn collect_rs_mod_names(&self, map_type: MapType) -> io::Result<Vec<MiniStr>> {
195 let mod_prefix = self.get_mod_prefix();
196
197 self
198 .collect_raw_locales(map_type)?
199 .into_iter()
200 .map(|x| fmt_compact!("{mod_prefix}{id}", id = to_lower_snake_case(x)))
201 .collect_vec()
202 .pipe(Ok)
203 }
204
205 #[cfg(feature = "json")]
215 pub fn output_cargo_features(&self, map_type: MapType) -> io::Result<String> {
216 let feat_prefix = self.get_feature_prefix();
217 let all_feat_locales = self.collect_cargo_feature_names(map_type)?;
218
219 let all_arr_json = serde_json::to_string(&all_feat_locales)?;
222
223 format!(
224 "{} = []\n{feat_prefix}all = {all_arr_json}",
225 all_feat_locales.join(" = []\n")
226 )
227 .pipe(Ok)
228 }
229}
230
231fn try_conv_const_id(id: &MiniStr) -> AnyResult<MiniStr> {
239 use lang_id::matches::{get_fn_name, match_id};
240
241 match match_id(id.as_bytes())
242 .to_compact_string()
243 .as_str()
244 {
245 x if x == id => id
248 .as_bytes()
249 .pipe(get_fn_name)
250 .to_compact_string()
251 .pipe(Ok),
252 _ => {
253 let id = id.parse::<LangID>()?;
254 if id.variants().count() >= 1 {
255 bail!("This ID ({id}) contains variants and cannot be converted to const.")
256 }
257 RawID::try_from_str(
258 id.language.as_str(),
259 id.script
260 .map(|x| x.to_compact_string())
261 .unwrap_or_default()
262 .as_str(),
263 id.region
264 .map(|x| x.to_compact_string())
265 .unwrap_or_default()
266 .as_str(),
267 )?
268 .to_compact_string()
269 .pipe(Ok)
270 }
271 }
272}
273pub(crate) fn ensure_length_equal(
274 feat_names_len: usize,
275 mod_names_len: usize,
276) -> io::Result<()> {
277 (mod_names_len == feat_names_len)
278 .then_some(())
279 .ok_or_else(|| {
280 io::Error::other("feature_names and module_names are not equal in length")
281 })
282}
283
284#[cfg(test)]
285mod tests {
286 use glossa_shared::display::puts;
287
288 use super::*;
289 use crate::generator::dbg_generator::new_generator;
290
291 #[ignore]
292 #[test]
293 fn test_list_all_locales() -> AnyResult<()> {
294 new_generator()
295 .with_visibility(crate::Visibility::Pub)
296 .output_locales_fn(
297 MapType::Regular,
298 true,
300 )?
301 .pipe_ref(puts)
302 .pipe(Ok)
303 }
304
305 #[ignore]
306 #[test]
307 fn test_gen_mod_rs_str() -> AnyResult<()> {
308 let generator = new_generator().with_mod_visibility(crate::Visibility::Pub);
309
310 let raw_locales = generator.collect_raw_locales(MapType::Regular)?;
311 let mod_prefix = generator.get_mod_prefix();
312 let feature_prefix = generator.get_feature_prefix();
313 let mod_vis = generator.get_mod_visibility();
314
315 let fn_content = raw_locales
316 .iter()
317 .map(|id| {
318 let mod_name = to_lower_snake_case(id);
319 fmt_compact!(
320 r###"
321#[cfg(feature = "{feature_prefix}{id}")]
322{mod_vis}mod {mod_prefix}{mod_name};
323 "###
324 )
325 })
326 .collect::<String>();
327
328 println!("{fn_content}");
329 Ok(())
330 }
331
332 #[ignore]
333 #[test]
334 fn test_output_mod_rs() -> AnyResult<()> {
335 new_generator()
336 .with_mod_visibility(crate::Visibility::PubCrate)
337 .output_mod_rs(MapType::Regular)?
338 .pipe_ref(puts)
339 .pipe(Ok)
340 }
341
342 #[test]
343 #[ignore]
344 #[cfg(feature = "json")]
345 fn test_gen_cargo_features() -> AnyResult<()> {
346 use glossa_shared::display::puts;
347
348 let generator = new_generator();
349 let feat_prefix = generator.get_feature_prefix();
350
351 let all_locales = generator
352 .collect_raw_locales(MapType::Regular)?
353 .into_iter()
354 .map(|x| fmt_compact!("{feat_prefix}{x}"))
355 .collect_vec();
356
357 let all = serde_json::to_string(&all_locales)?;
358
359 format!(
360 "{} = []\n{feat_prefix}all = {all}",
361 all_locales.join(" = []\n")
362 )
363 .pipe_ref(puts)
364 .pipe(Ok)
365 }
366
367 #[ignore]
368 #[test]
369 fn test_output_cargo_features() -> AnyResult<()> {
370 new_generator()
371 .output_cargo_features(MapType::DSL)?
372 .pipe_ref(puts)
373 .pipe(Ok)
374 }
375}