1use std::io::{self, Write};
2
3use glossa_shared::{
4 ToCompactString, fmt_compact,
5 tap::{Pipe, Tap},
6};
7
8use crate::{
9 MiniStr,
10 generator::{Generator, MapType},
11};
12
13impl<'h> Generator<'h> {
14 pub fn output_match_fn(&self, non_dsl: MapType) -> io::Result<()> {
17 const HEADER: &str = r##"const fn map(map_name: &[u8], key: &[u8]) -> &'static str {
18 match (map_name, key) {
19 "##;
20 let new_header = || self.new_fn_header(HEADER);
21
22 non_dsl
24 .get_non_dsl_maps(self)?
25 .iter()
26 .map(|(lang, map_entry)| {
28 let match_fn_string = map_entry
29 .iter()
30 .fold(
31 new_header(), |mut acc, ((map_name, data_k), data_v)| {
33 [
35 "(",
36 key_as_bytes(map_name).as_str(),
37 ", ",
38 key_as_bytes(data_k).as_str(),
39 r#") => r#####"#,
40 "\"", data_v.as_str(),
42 "\"", r###########"#####,"###########, "\n",
45 ]
46 .iter()
47 .for_each(|s| acc.push_str(s));
48 acc
49 },
50 )
51 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"));
52 (lang, match_fn_string)
53 })
54 .try_for_each(|(lang, s)| {
55 self
57 .create_rs_mod_file(lang)?
58 .write_all(s.as_bytes())
59 })
60 }
61
62 pub fn output_match_fn_all_in_one(
114 &'h self,
115 non_dsl: MapType,
116 ) -> io::Result<String> {
117 const S_HEADER: &str = r##"const fn map(lang: &[u8], map_name: &[u8], key: &[u8])
118 -> &'static str {
119 match (lang, map_name, key) {
120 "##;
121
122 let new_header = || self.new_fn_header(S_HEADER);
123
124 non_dsl
125 .get_non_dsl_maps(self)?
126 .iter()
127 .flat_map(|(lang, map_entry)| {
129 map_entry
130 .iter()
131 .map(move |e| (lang, e))
132 })
133 .fold(
134 new_header(), |mut acc, (lang, ((name, key), value))| {
136 [
138 "(b\"",
139 lang
140 .to_compact_string()
141 .as_str(),
142 "\", ",
143 key_as_bytes(name).as_str(),
144 ", ",
145 key_as_bytes(key).as_str(),
146 ") => r#####",
147 "\"",
148 value.as_str(),
149 "\"",
150 "#####,",
151 "\n",
152 ]
153 .iter()
154 .for_each(|s| acc.push_str(s));
155 acc
156 },
157 )
158 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"))
159 .pipe(Ok)
160 }
161
162 pub fn output_match_fn_all_in_one_by_language(
170 &'h self,
171 non_dsl: MapType,
172 ) -> io::Result<String> {
173 const S_HEADER: &str = r##"const fn map(language: &[u8]) -> &'static str {
174 match language {
175 "##;
176
177 let new_header = || self.new_fn_header(S_HEADER);
178
179 non_dsl
180 .get_non_dsl_maps(self)?
181 .iter()
182 .flat_map(|(lang, map_entry)| {
183 map_entry
184 .values()
185 .map(move |v| (lang, v))
186 })
187 .fold(
188 new_header(), |mut acc, (lang, value)| {
190 [
191 "b\"",
192 lang
193 .to_compact_string()
194 .as_str(),
195 "\" => r#####",
196 "\"",
197 value.as_str(),
198 "\"",
199 "#####,",
200 "\n",
201 ]
202 .iter()
203 .for_each(|s| acc.push_str(s));
204 acc
205 },
206 )
207 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"))
208 .pipe(Ok)
209 }
210
211 pub fn output_match_fn_all_in_one_without_map_name(
242 &'h self,
243 non_dsl: MapType,
244 ) -> io::Result<String> {
245 const S_HEADER: &str = r##"const fn map(language: &[u8], key: &[u8])
246 -> &'static str {
247 match (language, key) {
248 "##;
249
250 let new_header = || self.new_fn_header(S_HEADER);
251
252 non_dsl
253 .get_non_dsl_maps(self)?
254 .iter()
255 .flat_map(|(lang, map_entry)| {
256 map_entry
257 .iter()
258 .map(move |((_name, key), value)| (lang, key, value))
259 })
260 .fold(
261 new_header(), |mut acc, (lang, key, value)| {
263 [
264 "(b\"",
265 lang
266 .to_compact_string()
267 .as_str(),
268 "\", ",
269 key_as_bytes(key).as_str(),
270 ") => r#####",
271 "\"",
272 value.as_str(),
273 "\"",
274 "#####,",
275 "\n",
276 ]
277 .iter()
278 .for_each(|s| acc.push_str(s));
279 acc
280 },
281 )
282 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"))
283 .pipe(Ok)
284 }
285
286 pub fn new_fn_header(&self, header: &str) -> String {
288 let vis_fn = self.get_visibility().as_str();
289 String::with_capacity(8192).tap_mut(|buf| {
290 [vis_fn, " ", header]
291 .iter()
292 .for_each(|s| buf.push_str(s));
293 })
294 }
295
296 pub fn output_match_fn_without_map_name(
302 &'h self,
303 non_dsl: MapType,
304 ) -> io::Result<()> {
305 const HEADER: &str = r##"const fn map(key: &[u8]) -> &'static str {
306 match key {
307 "##;
308 let new_header = || self.new_fn_header(HEADER);
309
310 non_dsl
312 .get_non_dsl_maps(self)?
313 .iter()
314 .map(|(lang, map_entry)| {
316 let match_fn_string = map_entry
317 .iter()
318 .fold(
319 new_header(), |mut acc, ((_, data_k), data_v)| {
321 [
323 key_as_bytes(data_k).as_str(),
324 r#" => r#####"#,
325 "\"", data_v.as_str(),
327 "\"", r###########"#####,"###########, "\n",
330 ]
331 .iter()
332 .for_each(|s| acc.push_str(s));
333 acc
334 },
335 )
336 .tap_mut(|buf| buf.push_str(" _ => \"\",\n}}"));
337 (lang, match_fn_string)
338 })
339 .try_for_each(|(lang, s)| {
340 self
342 .create_rs_mod_file(lang)?
343 .write_all(s.as_bytes())
344 })
345 }
346}
347
348pub(crate) fn key_as_bytes(key: &str) -> MiniStr {
350 use fmt_compact as fmt;
351
352 match key.is_ascii() {
353 true => fmt!("b{key:?}"),
354 _ => fmt!("{:?}", key.as_bytes()),
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use glossa_shared::display::puts;
361
362 use super::*;
363 use crate::{
364 AnyResult,
365 generator::dbg_generator::{en_generator, new_generator},
366 };
367
368 #[ignore]
369 #[test]
370 fn test_output_match_fn() -> AnyResult<()> {
371 new_generator().output_match_fn(MapType::Regular)?;
372 Ok(())
373 }
374
375 #[ignore]
376 #[test]
377 fn test_output_aio_match_fn() -> AnyResult<()> {
378 en_generator()
379 .output_match_fn_all_in_one(MapType::Regular)?
380 .pipe_ref(puts)
381 .pipe(Ok)
382 }
383
384 const fn map(language: &[u8], map_name: &[u8], key: &[u8]) -> &'static str {
385 match (language, map_name, key) {
386 (b"de", b"error", b"text-not-found") => {
387 r###"Kein lokalisierter Text gefunden"###
388 }
389 (b"el", b"error", b"text-not-found") => {
390 r###"Δεν βρέθηκε κανένα τοπικό κείμενο"###
391 }
392 (b"en", b"error", b"text-not-found") => r###"No localized text found"###,
393 (b"en", b"test", [240, 159, 145, 139, 240, 159, 140, 144]) => {
394 r###"hello world"###
395 }
396 (b"en", b"test", b"hello") => r###"world"###,
397 (b"en-GB", b"error", b"text-not-found") => r###"No localised text found"###,
398 (b"zh", b"error", b"text-not-found") => r###"未找到本地化文本"###,
399 (b"zh", b"test", b"quote") => r###"""no"''""###,
400 (b"zh-Hant", b"error", b"text-not-found") => r###"沒有找到本地化文本"###,
401 _ => "",
402 }
403 }
404
405 #[ignore]
406 #[test]
407 fn test_get_match_map() {
408 const S: &str = map(b"de", b"error", "text-not-found".as_bytes());
409 assert!(!S.is_empty());
410 println!("{S}");
411 }
412
413 #[ignore]
414 #[test]
415 fn doc_test_all_in_one_match_fn() -> io::Result<()> {
416 use crate::L10nResources;
417 const L10N_DIR: &str = "../../locales/";
418
419 let data = L10nResources::new(L10N_DIR).with_include_languages([
420 "en-GB",
421 "de",
422 "es",
423 "pt",
424 "zh-pinyin",
425 ]);
426
427 let function_data = Generator::default()
428 .with_resources(data)
429 .output_match_fn_all_in_one(MapType::Regular)?;
430
431 assert_eq!(function_data, r#######"
432 pub(crate) const fn map(lang: &[u8], map_name: &[u8], key: &[u8]) ->
433 &'static str {
434 match (lang, map_name, key) {
435 (b"de", b"error", b"text-not-found") => r#####"Kein lokalisierter Text gefunden"#####,
436(b"en-GB", b"error", b"text-not-found") => r#####"No localised text found"#####,
437(b"es", b"error", b"text-not-found") => r#####"No se encontró texto localizado"#####,
438(b"pt", b"error", b"text-not-found") => r#####"Nenhum texto localizado encontrado"#####,
439(b"zh-Latn-CN", b"error", b"text-not-found") => r#####"MeiYou ZhaoDao BenDiHua WenBen"#####,
440 _ => "",
441}}
442 "#######.trim());
443
444 println!("{function_data}");
445 Ok(())
446 }
447}