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