Skip to main content

lingora_core/config/
toml.rs

1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    config::{args::CoreArgs, config_inclusion_style::ConfigInclusionStyle},
7    domain::Locale,
8    error::LingoraError,
9};
10
11#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(default, deny_unknown_fields)]
13pub(crate) struct EngineSettings {
14    pub(crate) fluent_sources: Vec<PathBuf>,
15    pub(crate) canonical: Locale,
16    pub(crate) primaries: Vec<Locale>,
17}
18
19#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(default, deny_unknown_fields)]
21pub(crate) struct DioxusI18nSettings {
22    pub(crate) rust_sources: Vec<PathBuf>,
23    pub(crate) config_inclusion: ConfigInclusionStyle,
24}
25
26/// Top-level deserialized structure of a `Lingora.toml` configuration file.
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(default, deny_unknown_fields)]
29pub struct LingoraToml {
30    pub(crate) lingora: EngineSettings,
31    pub(crate) dioxus_i18n: DioxusI18nSettings,
32}
33
34impl std::str::FromStr for LingoraToml {
35    type Err = LingoraError;
36
37    fn from_str(s: &str) -> Result<Self, Self::Err> {
38        let toml = toml::from_str(s)?;
39        Ok(toml)
40    }
41}
42
43impl TryFrom<&Path> for LingoraToml {
44    type Error = LingoraError;
45
46    fn try_from(path: &Path) -> Result<Self, Self::Error> {
47        use std::str::FromStr;
48        let content = std::fs::read_to_string(path)?;
49        Self::from_str(&content)
50    }
51}
52
53impl TryFrom<&CoreArgs> for LingoraToml {
54    type Error = LingoraError;
55
56    fn try_from(args: &CoreArgs) -> Result<Self, Self::Error> {
57        let default_toml_path = Path::new("Lingora.toml");
58
59        let mut toml = if let Some(requested_toml_path) = &args.config {
60            Self::try_from(requested_toml_path.as_path())
61        } else if std::fs::exists(default_toml_path).unwrap_or(false) {
62            Self::try_from(default_toml_path)
63        } else {
64            Ok(Self::default())
65        }?;
66
67        toml.lingora
68            .fluent_sources
69            .append(&mut args.fluent_sources.clone());
70
71        if let Some(locale) = &args.canonical {
72            toml.lingora.canonical = locale.clone();
73        }
74
75        toml.lingora.primaries.append(&mut args.primaries.clone());
76
77        toml.dioxus_i18n
78            .rust_sources
79            .append(&mut args.rust_sources.clone());
80
81        if let Some(style) = &args.config_inclusion {
82            toml.dioxus_i18n.config_inclusion = *style;
83        }
84
85        Ok(toml)
86    }
87}
88
89impl Default for LingoraToml {
90    fn default() -> Self {
91        let fluent_sources = vec![Path::new("./i18n").to_path_buf()];
92        let canonical = Locale::default();
93        let primaries = Vec::default();
94        let rust_sources = vec![Path::new("./src").to_path_buf()];
95        let config_inclusion = ConfigInclusionStyle::Auto;
96
97        Self {
98            lingora: EngineSettings {
99                fluent_sources,
100                canonical,
101                primaries,
102            },
103            dioxus_i18n: DioxusI18nSettings {
104                rust_sources,
105                config_inclusion,
106            },
107        }
108    }
109}
110
111impl std::fmt::Display for LingoraToml {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        let toml =
114            toml::to_string_pretty(self).unwrap_or("failed to re-create lingora toml text".into());
115        toml.fmt(f)
116    }
117}
118
119#[cfg(test)]
120mod test {
121    use std::{path::Path, str::FromStr};
122
123    use crate::{
124        config::toml::{ConfigInclusionStyle, LingoraToml},
125        domain::Locale,
126        error::LingoraError,
127    };
128
129    #[test]
130    fn will_load_from_toml_file() {
131        let path = Path::new("./tests/data/toml/Lingora.toml");
132        let toml = LingoraToml::try_from(path).expect("failed to load from toml");
133        assert_eq!(toml.lingora.fluent_sources, [Path::new("./i18n")]);
134        assert_eq!(toml.lingora.canonical, Locale::from_str("en-GB").unwrap());
135        assert!(toml.lingora.primaries.is_empty());
136        assert_eq!(toml.dioxus_i18n.rust_sources, [Path::new("./src")]);
137        assert_eq!(
138            toml.dioxus_i18n.config_inclusion,
139            ConfigInclusionStyle::Auto
140        );
141    }
142
143    #[test]
144    fn will_not_load_from_non_existing_toml_file() {
145        let path = Path::new("./non_existing_lingora.toml");
146        let error = LingoraToml::try_from(path).expect_err("failed to detect error");
147        assert!(matches!(error, LingoraError::Io(_)));
148    }
149
150    #[test]
151    fn will_not_load_from_invalid_toml_file() {
152        let path = Path::new("./tests/data/toml/Lingora_error.toml");
153        let error = LingoraToml::try_from(path).expect_err("failed to detect error");
154        assert!(matches!(error, LingoraError::TomlParse(_)));
155    }
156
157    #[test]
158    fn will_default_when_no_file_provided() {
159        let toml = LingoraToml::default();
160        assert_eq!(toml.lingora.fluent_sources, [Path::new("./i18n")]);
161        assert_eq!(toml.lingora.canonical, Locale::default());
162        assert!(toml.lingora.primaries.is_empty());
163        assert_eq!(toml.dioxus_i18n.rust_sources, [Path::new("./src")]);
164        assert_eq!(
165            toml.dioxus_i18n.config_inclusion,
166            ConfigInclusionStyle::Auto
167        );
168    }
169
170    #[test]
171    fn will_load_from_str() {
172        let content = std::fs::read_to_string(Path::new("./tests/data/toml/Lingora.toml"))
173            .expect("read test file");
174        let toml = LingoraToml::from_str(&content).expect("failed to parse toml");
175        assert_eq!(toml.lingora.fluent_sources, [Path::new("./i18n")]);
176        assert_eq!(toml.lingora.canonical, Locale::from_str("en-GB").unwrap());
177        assert!(toml.lingora.primaries.is_empty());
178        assert_eq!(toml.dioxus_i18n.rust_sources, [Path::new("./src")]);
179        assert_eq!(
180            toml.dioxus_i18n.config_inclusion,
181            ConfigInclusionStyle::Auto
182        );
183    }
184
185    #[test]
186    fn will_load_from_args_config() {
187        use crate::config::args::CoreArgs;
188        let args =
189            CoreArgs::from_str("app_name --config=./tests/data/toml/Lingora_args.toml").unwrap();
190        let toml = LingoraToml::try_from(&args).unwrap();
191        assert_eq!(toml.lingora.fluent_sources, [Path::new("./args/i18n")]);
192        assert_eq!(toml.lingora.canonical, Locale::from_str("de-DE").unwrap());
193        assert_eq!(toml.lingora.primaries, [Locale::from_str("en-AU").unwrap()]);
194        assert_eq!(toml.dioxus_i18n.rust_sources, [Path::new("./args/src")]);
195        assert_eq!(
196            toml.dioxus_i18n.config_inclusion,
197            ConfigInclusionStyle::IncludeStr
198        );
199    }
200
201    #[test]
202    fn will_load_from_args_overridden() {
203        use crate::config::args::CoreArgs;
204        let args =
205            CoreArgs::from_str("app_name --config=./tests/data/toml/Lingora_args.toml --fluent-sources=./also/i18n,./also/branding --canonical=ja-JP --primaries=sk-SK,sr-Cryl-RS,bn-IN --rust-sources=./also/src --config-inclusion=pathbuf").unwrap();
206        let toml = LingoraToml::try_from(&args).unwrap();
207        assert_eq!(
208            toml.lingora.fluent_sources,
209            [
210                Path::new("./args/i18n"),
211                Path::new("./also/i18n"),
212                Path::new("./also/branding")
213            ]
214        );
215        assert_eq!(toml.lingora.canonical, Locale::from_str("ja-JP").unwrap());
216        assert_eq!(
217            toml.lingora.primaries,
218            [
219                Locale::from_str("en-AU").unwrap(),
220                Locale::from_str("sk-SK").unwrap(),
221                Locale::from_str("sr-Cryl-RS").unwrap(),
222                Locale::from_str("bn-IN").unwrap()
223            ]
224        );
225        assert_eq!(
226            toml.dioxus_i18n.rust_sources,
227            [Path::new("./args/src"), Path::new("./also/src")]
228        );
229        assert_eq!(
230            toml.dioxus_i18n.config_inclusion,
231            ConfigInclusionStyle::PathBuf
232        );
233    }
234}