glossa_codegen/
generator.rs

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