glossa_codegen/
generator.rs

1pub(crate) mod flattening;
2mod init_lazy_data;
3pub(crate) mod locales;
4mod router;
5
6pub(crate) mod output_bincode;
7pub(crate) mod output_match;
8pub(crate) mod output_phf;
9
10use std::{io, path::PathBuf, sync::OnceLock};
11
12use getset::{Getters, MutGetters, WithSetters};
13use glossa_shared::{fmt_compact, tap::Pipe};
14pub use output_phf::to_lower_snake_case;
15
16use crate::{
17  L10nResources, MiniStr, Visibility,
18  generator::flattening::{L10nDSLMaps, L10nMaps},
19  internal_aliases::HighlightCfgMap,
20};
21
22#[derive(Getters, WithSetters, MutGetters, Debug, Clone)]
23#[getset(get = "pub with_prefix", set_with = "pub", get_mut = "pub with_prefix")]
24pub struct Generator<'h> {
25  #[getset(skip)]
26  #[getset(get = "pub")]
27  resources: Box<L10nResources>,
28
29  #[getset(get_mut)]
30  /// function visibility
31  visibility: Visibility,
32
33  mod_visibility: Visibility,
34
35  #[getset(skip)]
36  #[getset(get = "pub", get_mut = "pub")]
37  outdir: Option<PathBuf>,
38
39  bincode_suffix: MiniStr,
40  mod_prefix: MiniStr,
41  feature_prefix: MiniStr,
42
43  #[getset(skip)]
44  #[getset(get = "pub")]
45  highlight: Option<Box<HighlightCfgMap<'h>>>,
46
47  #[getset(skip)]
48  /// get: `Self::get_or_init_*maps`
49  lazy_maps: Box<LazyMaps>,
50}
51
52#[cfg(feature = "highlight")]
53impl<'h> Generator<'h> {
54  pub fn with_highlight(mut self, highlight: HighlightCfgMap<'h>) -> Self {
55    self.highlight = Some(highlight.into());
56    self.clear_highlight_cache();
57    self
58  }
59
60  pub fn set_highlight(&mut self, highlight: Option<HighlightCfgMap<'h>>) {
61    self.highlight = highlight.map(|data| data.into());
62    self.clear_highlight_cache();
63  }
64  fn clear_highlight_cache(&mut self) {
65    self.lazy_maps.highlight.take();
66    self.lazy_maps.merged.take();
67  }
68}
69
70impl Generator<'_> {
71  pub fn with_outdir<P: Into<PathBuf>>(mut self, outdir: P) -> Self {
72    self.outdir = Some(outdir.into());
73    self
74  }
75}
76
77impl Generator<'_> {
78  /// Configures the generator with localization resources, resetting cached
79  /// mappings.
80  ///
81  /// This method performs two primary actions:
82  ///
83  /// 1. Replaces the current [`L10nResources`] with the provided instance
84  /// 2. Resets the internal `lazy_maps` to their default empty state
85  ///
86  /// # Side Effects
87  ///
88  /// The cache clearance ensures any existing lazy-loaded mappings won't
89  /// conflict with new resources. Subsequent operations will rebuild mappings
90  /// from the updated resources.
91  ///
92  /// # Example
93  ///
94  /// ```
95  /// use glossa_codegen::{Generator, L10nResources};
96  ///
97  /// // Initialize resources from a localization directory
98  /// let resources = L10nResources::new("../../locales/");
99  ///
100  /// // Create generator with configured resources
101  /// let _generator = Generator::default()
102  ///   .with_resources(resources);
103  /// ```
104  pub fn with_resources(mut self, resources: L10nResources) -> Self {
105    self.resources = resources.into();
106    self.lazy_maps = Default::default();
107    self
108  }
109}
110
111#[derive(Default, Debug, Clone)]
112struct LazyMaps {
113  /// get: [Generator::get_or_init_maps]
114  regular: OnceLock<L10nMaps>,
115
116  /// get: [Generator::get_or_init_highlight_maps]
117  highlight: OnceLock<Option<L10nMaps>>,
118
119  /// get: [Generator::get_or_init_dsl_maps]
120  dsl: OnceLock<L10nDSLMaps>,
121
122  /// get: [Generator::get_or_init_merged_maps]
123  merged: OnceLock<L10nMaps>,
124}
125
126impl Default for Generator<'_> {
127  /// Default:
128  ///
129  /// ```ignore
130  /// {
131  ///   bincode_suffix: ".bincode",
132  ///   mod_prefix: "l10n_",
133  ///   visibility: Visibility::PubCrate,
134  ///   mod_visibility: Visibility::Private,
135  ///   ..Default::default()
136  /// }
137  /// ```
138  fn default() -> Self {
139    Self {
140      bincode_suffix: MiniStr::const_new(".bincode"),
141      outdir: Default::default(),
142      resources: Default::default(),
143      // match_bound: 200,
144      // doc: true,
145      mod_prefix: MiniStr::const_new("l10n_"),
146      feature_prefix: MiniStr::const_new("l10n-"),
147      // cargo_feature_prefix: "l10n-".into(),
148      // overwrite: true,
149      visibility: Default::default(),
150      mod_visibility: Visibility::Private,
151      highlight: Default::default(),
152      lazy_maps: Default::default(),
153    }
154  }
155}
156
157#[derive(Debug, Clone, Copy)]
158pub enum MapType {
159  Regular,
160  Highlight,
161  RegularAndHighlight,
162  DSL,
163}
164
165impl core::str::FromStr for MapType {
166  type Err = io::Error;
167
168  fn from_str(s: &str) -> Result<Self, Self::Err> {
169    use MapType::*;
170    match s {
171      "regular" | "r" => Regular,
172      "highlight" | "h" => Highlight,
173      "dsl" | "tmpl" => DSL,
174      "regular-and-highlight" | "regularandhighlight" | "_" => RegularAndHighlight,
175      other => Err(io::Error::new(
176        io::ErrorKind::InvalidInput,
177        fmt_compact!(
178          r#"
179    Expected: "regular" | "highlight" | "dsl" |  "_"
180    Actual: "{other}""#
181        ),
182      ))?,
183    }
184    .pipe(Ok)
185  }
186}
187
188impl MapType {
189  pub fn get_non_dsl_maps<'a>(
190    &self,
191    generator: &'a Generator<'a>,
192  ) -> io::Result<&'a L10nMaps> {
193    use MapType::*;
194    match self {
195      Regular => generator.get_or_init_maps(),
196      Highlight => generator
197        .get_or_init_highlight_maps()
198        .ok_or_else(|| io::Error::other("Failed to get highlight maps"))?,
199      RegularAndHighlight => generator.get_or_init_merged_maps(),
200      _ => return io::Error::other("DSL Maps are not supported.").pipe(Err),
201    }
202    .pipe(Ok)
203  }
204
205  #[cfg(feature = "ron")]
206  pub fn output_ron<'a>(
207    &self,
208    generator: &'a Generator<'a>,
209  ) -> crate::AnyResult<String> {
210    use ron::ser::PrettyConfig;
211
212    let cfg = PrettyConfig::new()
213      .depth_limit(5)
214      .separate_tuple_members(false);
215
216    match self.is_dsl() {
217      true => ron::ser::to_string_pretty(generator.get_or_init_dsl_maps(), cfg)?,
218      _ => ron::ser::to_string_pretty(self.get_non_dsl_maps(generator)?, cfg)?,
219    }
220    .pipe(Ok)
221  }
222
223  /// Returns `true` if the map type is [`DSL`].
224  ///
225  /// [`DSL`]: MapType::DSL
226  #[must_use]
227  pub fn is_dsl(&self) -> bool {
228    matches!(self, Self::DSL)
229  }
230}
231
232impl Default for MapType {
233  fn default() -> Self {
234    Self::DSL
235  }
236}
237
238#[cfg(test)]
239pub(crate) mod dbg_generator {
240  use super::*;
241  use crate::resources::dbg_shared;
242
243  pub(crate) fn new_generator<'h>() -> Generator<'h> {
244    let data = dbg_shared::new_resources();
245    Generator::default()
246      .with_resources(data)
247      .with_outdir("tmp")
248  }
249
250  #[cfg(feature = "highlight")]
251  pub(crate) fn highlight_generator<'h>() -> Generator<'h> {
252    let hmap = crate::highlight::dbg_shared::new_highlight_map();
253    new_generator().with_highlight(hmap)
254  }
255
256  pub(crate) fn en_generator<'h>() -> Generator<'h> {
257    let data = dbg_shared::new_resources().with_include_languages(["en"]);
258    new_generator().with_resources(data)
259  }
260
261  pub(crate) fn en_gb_generator<'h>() -> Generator<'h> {
262    let data = dbg_shared::new_resources().with_include_languages(["en-GB"]);
263    new_generator().with_resources(data)
264  }
265
266  pub(crate) fn es_generator<'h>() -> Generator<'h> {
267    // let es = ["es"].into_iter().collect();
268    let data = dbg_shared::new_resources().with_include_languages(["es"]);
269    new_generator().with_resources(data)
270  }
271
272  pub(crate) fn de_en_fr_pt_zh_generator<'h>() -> Generator<'h> {
273    let data = dbg_shared::new_resources()
274      .with_include_languages([
275        "de", // "zh-pinyin",
276        "zh", "pt", "fr", "en", "en-GB",
277      ])
278      .with_include_map_names(["yes-no"]);
279    new_generator().with_resources(data)
280  }
281}