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  pub 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  #[cfg(feature = "ron")]
196  pub fn output_ron<'a>(
197    &self,
198    generator: &'a Generator<'a>,
199  ) -> crate::AnyResult<String> {
200    use ron::ser::PrettyConfig;
201
202    let cfg = PrettyConfig::new()
203      .depth_limit(5)
204      .separate_tuple_members(false);
205
206    match self.is_dsl() {
207      true => ron::ser::to_string_pretty(generator.get_or_init_dsl_maps(), cfg)?,
208      _ => ron::ser::to_string_pretty(self.get_non_dsl_maps(generator)?, cfg)?,
209    }
210    .pipe(Ok)
211  }
212
213  /// Returns `true` if the map type is [`DSL`].
214  ///
215  /// [`DSL`]: MapType::DSL
216  #[must_use]
217  pub fn is_dsl(&self) -> bool {
218    matches!(self, Self::DSL)
219  }
220}
221
222impl Default for MapType {
223  fn default() -> Self {
224    Self::DSL
225  }
226}
227
228#[cfg(test)]
229pub(crate) mod dbg_generator {
230  use super::*;
231  use crate::resources::dbg_shared;
232
233  pub(crate) fn new_generator<'h>() -> Generator<'h> {
234    let data = dbg_shared::new_resources();
235    Generator::default()
236      .with_resources(data)
237      .with_outdir("tmp")
238  }
239
240  #[cfg(feature = "highlight")]
241  pub(crate) fn highlight_generator<'h>() -> Generator<'h> {
242    let hmap = crate::highlight::dbg_shared::new_highlight_map();
243    new_generator().with_highlight(hmap)
244  }
245
246  pub(crate) fn en_generator<'h>() -> Generator<'h> {
247    let data = dbg_shared::new_resources().with_include_languages(["en"]);
248    new_generator().with_resources(data)
249  }
250
251  pub(crate) fn en_gb_generator<'h>() -> Generator<'h> {
252    let data = dbg_shared::new_resources().with_include_languages(["en-GB"]);
253    new_generator().with_resources(data)
254  }
255
256  pub(crate) fn es_generator<'h>() -> Generator<'h> {
257    // let es = ["es"].into_iter().collect();
258    let data = dbg_shared::new_resources().with_include_languages(["es"]);
259    new_generator().with_resources(data)
260  }
261
262  pub(crate) fn de_en_fr_pt_zh_generator<'h>() -> Generator<'h> {
263    let data = dbg_shared::new_resources()
264      .with_include_languages([
265        "de", // "zh-pinyin",
266        "zh", "pt", "fr", "en", "en-GB",
267      ])
268      .with_include_map_names(["yes-no"]);
269    new_generator().with_resources(data)
270  }
271}