1use std::io::{self, Write};
2
3use anyhow::bail;
4use glossa_shared::{
5 ToCompactString, fmt_compact,
6 tap::{Pipe, Tap},
7};
8use itertools::Itertools;
10use lang_id::{LangID, RawID};
11
12use crate::{
13 AnyResult, MiniStr,
14 generator::{Generator, MapType},
15};
16
17impl<'h> Generator<'h> {
18 pub fn output_match_fn_all_in_one(
70 &'h self,
71 non_dsl: MapType,
72 ) -> io::Result<String> {
73 const S_HEADER: &str = r##"const fn map(lang: &[u8], map_name: &[u8], key: &[u8])
74 -> &'static str {
75 match (lang, map_name, key) {
76 "##;
77
78 let new_header = || self.new_match_fn_header(S_HEADER);
79
80 non_dsl
81 .get_non_dsl_maps(self)?
82 .iter()
83 .flat_map(|(lang, map_entry)| {
85 map_entry
86 .iter()
87 .map(move |e| (lang, e))
88 })
89 .fold(
90 new_header(), |mut acc, (lang, ((name, key), value))| {
92 [
94 "(b\"",
95 lang
96 .to_compact_string()
97 .as_str(),
98 "\", ",
99 key_as_bytes(name).as_str(),
100 ", ",
101 key_as_bytes(key).as_str(),
102 ") => r#####",
103 "\"",
104 value.as_str(),
105 "\"",
106 "#####,",
107 "\n",
108 ]
109 .map(|s| acc.push_str(s));
110 acc
111 },
112 )
113 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"))
114 .pipe(Ok)
115 }
116
117 pub fn output_match_fn_all_in_one_by_language(
125 &'h self,
126 non_dsl: MapType,
127 ) -> io::Result<String> {
128 const S_HEADER: &str = r##"const fn map(language: &[u8]) -> &'static str {
129 match language {
130 "##;
131
132 let new_header = || self.new_match_fn_header(S_HEADER);
133
134 non_dsl
135 .get_non_dsl_maps(self)?
136 .iter()
137 .flat_map(|(lang, map_entry)| {
138 map_entry
139 .iter()
140 .map(move |(_ks, v)| (lang, v))
141 })
142 .fold(
143 new_header(), |mut acc, (lang, value)| {
145 [
146 "b\"",
147 lang
148 .to_compact_string()
149 .as_str(),
150 "\" => r#####",
151 "\"",
152 value.as_str(),
153 "\"",
154 "#####,",
155 "\n",
156 ]
157 .map(|s| acc.push_str(s));
158 acc
159 },
160 )
161 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"))
162 .pipe(Ok)
163 }
164
165 pub fn output_match_fn_all_in_one_by_language_and_key(
196 &'h self,
197 non_dsl: MapType,
198 ) -> io::Result<String> {
199 const S_HEADER: &str = r##"const fn map(language: &[u8], key: &[u8])
200 -> &'static str {
201 match (language, key) {
202 "##;
203
204 let new_header = || self.new_match_fn_header(S_HEADER);
205
206 non_dsl
207 .get_non_dsl_maps(self)?
208 .iter()
209 .flat_map(|(lang, map_entry)| {
210 map_entry
211 .iter()
212 .map(move |((_name, key), value)| (lang, key, value))
213 })
214 .fold(
215 new_header(), |mut acc, (lang, key, value)| {
217 [
218 "(b\"",
219 lang
220 .to_compact_string()
221 .as_str(),
222 "\", ",
223 key_as_bytes(key).as_str(),
224 ") => r#####",
225 "\"",
226 value.as_str(),
227 "\"",
228 "#####,",
229 "\n",
230 ]
231 .map(|s| acc.push_str(s));
232 acc
233 },
234 )
235 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"))
236 .pipe(Ok)
237 }
238
239 pub fn output_locales_fn(
280 &'h self,
281 map_type: MapType,
282 const_lang_id: bool,
283 ) -> AnyResult<String> {
284 let raw_locales = self.collect_raw_locales(map_type)?;
285 let locales_len = raw_locales.len();
286 let new_header = || self.new_locales_fn_header(&locales_len, &const_lang_id);
287
288 if !const_lang_id {
289 return new_header()
290 .tap_mut(|buf| {
291 let push_str = |s| buf.push_str(s);
292 [&format!("{raw_locales:#?}"), "}\n"].map(push_str);
293 })
294 .pipe(Ok);
295 }
296
297 raw_locales
299 .iter()
300 .map(try_conv_const_id)
301 .try_fold(
302 new_header(), |mut acc, fn_name| {
304 let push_str = |s| acc.push_str(s);
305 ["\n ", &fn_name?, ","].map(push_str);
306 Ok::<_, anyhow::Error>(acc)
307 },
308 )?
309 .tap_mut(|buf| buf.push_str(" ]\n}"))
310 .pipe(Ok)
311 }
312
313 fn collect_raw_locales(&'h self, map_type: MapType) -> io::Result<Vec<MiniStr>> {
314 match map_type.is_dsl() {
315 true => match self.get_or_init_dsl_maps() {
316 x if x.is_empty() => "// Error: Empty DSL Map"
317 .pipe(io::Error::other)
318 .pipe(Err),
319 data => data
320 .iter()
321 .map(|(id, _)| id.to_compact_string())
322 .collect_vec()
323 .pipe(Ok),
324 },
325 _ => map_type
326 .get_non_dsl_maps(self)?
327 .iter()
328 .map(|(id, _)| id.to_compact_string())
329 .collect_vec()
330 .pipe(Ok),
331 }
332 }
333 fn new_locales_fn_header(
334 &'h self,
335 locales_len: &usize,
336 const_lang_id: &bool,
337 ) -> String {
339 let ret_type = {
341 match *const_lang_id {
342 true => fmt_compact!(
343 r#"[super::lang_id::LangID; {locales_len}] {{
344 #[allow(unused_imports)]
345 use super::lang_id::RawID;
346 use super::lang_id::consts::*;
347 ["#
348 ),
349 _ => fmt_compact!("[&'static str; {locales_len}] {{\n "),
350 }
351 };
352
353 let s_header = format!("const fn all_locales() -> {ret_type}",);
354 self.new_match_fn_header(&s_header)
355 }
356
357 fn new_match_fn_header(&'h self, header: &str) -> String {
359 let vis_fn = self.get_visibility().as_str();
360 String::with_capacity(8192).tap_mut(|buf| {
361 [vis_fn, " ", header].map(|s| buf.push_str(s));
362 })
363 }
364
365 pub fn output_match_fn(&'h self, non_dsl: MapType) -> io::Result<()> {
368 const HEADER: &str = r##"const fn map(map_name: &[u8], key: &[u8]) -> &'static str {
369 match (map_name, key) {
370 "##;
371 let new_header = || self.new_match_fn_header(HEADER);
372
373 non_dsl
375 .get_non_dsl_maps(self)?
376 .iter()
377 .map(|(lang, map_entry)| {
379 let match_fn_string = map_entry
380 .iter()
381 .fold(
382 new_header(), |mut acc, ((map_name, data_k), data_v)| {
384 [
386 "(",
387 key_as_bytes(map_name).as_str(),
388 ", ",
389 key_as_bytes(data_k).as_str(),
390 r#") => r#####"#,
391 "\"", data_v.as_str(),
393 "\"", r###########"#####,"###########, "\n",
396 ]
397 .map(|s| acc.push_str(s));
398 acc
399 },
400 )
401 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"));
402 (lang, match_fn_string)
403 })
404 .try_for_each(|(lang, s)| {
405 self
407 .create_rs_mod_file(lang)?
408 .write_all(s.as_bytes())
409 })
410 }
411
412 pub fn output_match_fn_by_key(&'h self, non_dsl: MapType) -> io::Result<()> {
418 const HEADER: &str = r##"const fn map(key: &[u8]) -> &'static str {
419 match key {
420 "##;
421 let new_header = || self.new_match_fn_header(HEADER);
422
423 non_dsl
425 .get_non_dsl_maps(self)?
426 .iter()
427 .map(|(lang, map_entry)| {
429 let match_fn_string = map_entry
430 .iter()
431 .fold(
432 new_header(), |mut acc, ((_, data_k), data_v)| {
434 [
436 key_as_bytes(data_k).as_str(),
437 r#" => r#####"#,
438 "\"", data_v.as_str(),
440 "\"", r###########"#####,"###########, "\n",
443 ]
444 .map(|s| acc.push_str(s));
445 acc
446 },
447 )
448 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"));
449 (lang, match_fn_string)
450 })
451 .try_for_each(|(lang, s)| {
452 self
454 .create_rs_mod_file(lang)?
455 .write_all(s.as_bytes())
456 })
457 }
458}
459
460fn try_conv_const_id(id: &MiniStr) -> Result<MiniStr, anyhow::Error> {
461 use lang_id::matches::{get_fn_name, match_id};
462
463 match match_id(id.as_bytes())
464 .to_compact_string()
465 .as_str()
466 {
467 x if x == id => id
468 .as_bytes()
469 .pipe(get_fn_name)
470 .to_compact_string()
471 .pipe(Ok),
472 _ => {
473 let id = id.parse::<LangID>()?;
474 if id.variants().count() >= 1 {
475 bail!("This ID ({id}) contains variants and cannot be converted to const.")
476 }
477 RawID::try_from_str(
478 id.language.as_str(),
479 id.script
480 .map(|x| x.to_compact_string())
481 .unwrap_or_default()
482 .as_str(),
483 id.region
484 .map(|x| x.to_compact_string())
485 .unwrap_or_default()
486 .as_str(),
487 )?
488 .to_compact_string()
489 .pipe(Ok)
490 }
491 }
492}
493
494fn key_as_bytes(key: &str) -> MiniStr {
496 use fmt_compact as fmt;
497
498 match key.is_ascii() {
499 true => fmt!("b{key:?}"),
500 _ => fmt!("{:?}", key.as_bytes()),
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507 use crate::{
508 AnyResult,
509 generator::dbg_generator::{en_generator, new_generator},
510 };
511
512 #[ignore]
513 #[test]
514 fn test_output_match_fn() -> AnyResult<()> {
515 new_generator().output_match_fn(MapType::Regular)?;
516 Ok(())
517 }
518
519 #[ignore]
520 #[test]
521 fn test_output_aio_match_fn() -> AnyResult<()> {
522 en_generator()
523 .output_match_fn_all_in_one(MapType::Regular)?
524 .tap(|s| println!("{s}"));
525 Ok(())
526 }
527
528 const fn map(language: &[u8], map_name: &[u8], key: &[u8]) -> &'static str {
529 match (language, map_name, key) {
530 (b"de", b"error", b"text-not-found") => {
531 r###"Kein lokalisierter Text gefunden"###
532 }
533 (b"el", b"error", b"text-not-found") => {
534 r###"Δεν βρέθηκε κανένα τοπικό κείμενο"###
535 }
536 (b"en", b"error", b"text-not-found") => r###"No localized text found"###,
537 (b"en", b"test", [240, 159, 145, 139, 240, 159, 140, 144]) => {
538 r###"hello world"###
539 }
540 (b"en", b"test", b"hello") => r###"world"###,
541 (b"en-GB", b"error", b"text-not-found") => r###"No localised text found"###,
542 (b"zh", b"error", b"text-not-found") => r###"未找到本地化文本"###,
543 (b"zh", b"test", b"quote") => r###"""no"''""###,
544 (b"zh-Hant", b"error", b"text-not-found") => r###"沒有找到本地化文本"###,
545 _ => "",
546 }
547 }
548
549 #[ignore]
550 #[test]
551 fn test_get_match_map() {
552 const S: &str = map(b"de", b"error", "text-not-found".as_bytes());
553 assert!(!S.is_empty());
554 println!("{S}");
555 }
556
557 #[ignore]
558 #[test]
559 fn test_show_all_locales() -> AnyResult<()> {
560 let s = new_generator()
561 .with_visibility(crate::Visibility::Pub)
562 .output_locales_fn(
563 MapType::Regular, true,
566 )?;
567 println!("{s}");
568
569 Ok(())
570 }
571
572 #[ignore]
573 #[test]
574 fn doc_test_all_in_one_match_fn() -> io::Result<()> {
575 use crate::L10nResources;
576 const L10N_DIR: &str = "../../locales/";
577
578 let data = L10nResources::new(L10N_DIR).with_include_languages([
579 "en-GB",
580 "de",
581 "es",
582 "pt",
583 "zh-pinyin",
584 ]);
585
586 let function_data = Generator::default()
587 .with_resources(data)
588 .output_match_fn_all_in_one(MapType::Regular)?;
589
590 assert_eq!(function_data, r#######"
591 pub(crate) const fn map(lang: &[u8], map_name: &[u8], key: &[u8]) ->
592 &'static str {
593 match (lang, map_name, key) {
594 (b"de", b"error", b"text-not-found") => r#####"Kein lokalisierter Text gefunden"#####,
595(b"en-GB", b"error", b"text-not-found") => r#####"No localised text found"#####,
596(b"es", b"error", b"text-not-found") => r#####"No se encontró texto localizado"#####,
597(b"pt", b"error", b"text-not-found") => r#####"Nenhum texto localizado encontrado"#####,
598(b"zh-Latn-CN", b"error", b"text-not-found") => r#####"MeiYou ZhaoDao BenDiHua WenBen"#####,
599 _ => "",
600}}
601 "#######.trim());
602
603 println!("{function_data}");
604 Ok(())
605 }
606}