es_fluent_cli_helpers/
lib.rs1#![doc = include_str!("../README.md")]
2
3mod cli;
4mod generate;
5
6use es_fluent_derive_core::{EsFluentError, write_metadata_result};
7use es_fluent_toml::I18nConfig;
8use std::path::Path;
9
10#[cfg(test)]
11pub(crate) static TEST_CWD_LOCK: std::sync::LazyLock<std::sync::Mutex<()>> =
12 std::sync::LazyLock::new(|| std::sync::Mutex::new(()));
13
14pub use cli::{ExpectedKey, InventoryData, write_inventory_for_crate};
15pub use generate::{EsFluentGenerator, FluentParseMode, GeneratorArgs};
16
17pub type GeneratorError = EsFluentError;
19
20pub fn run_generate(i18n_toml_path: &str, crate_name: &str) -> bool {
30 let i18n_toml_path = Path::new(i18n_toml_path);
32 let i18n_dir = i18n_toml_path
33 .parent()
34 .expect("Failed to get i18n directory");
35 let config =
36 es_fluent_toml::I18nConfig::from_manifest_dir(i18n_dir).expect("Failed to read i18n.toml");
37 let output_path = I18nConfig::output_dir_from_manifest_dir(i18n_dir)
38 .expect("Failed to resolve output directory");
39 let assets_dir = config
40 .assets_dir_from_base(Some(i18n_dir))
41 .expect("Failed to resolve assets directory");
42
43 let changed = EsFluentGenerator::builder()
44 .output_path(output_path)
45 .assets_dir(assets_dir)
46 .manifest_dir(i18n_dir)
47 .crate_name(crate_name)
48 .build()
49 .run_cli()
50 .expect("Failed to run generator");
51
52 let result = serde_json::json!({ "changed": changed });
54 write_metadata_result(crate_name, &result).expect("Failed to write metadata result");
55 changed
56}
57
58pub fn run_generate_with_options(
62 i18n_toml_path: &str,
63 crate_name: &str,
64 mode: FluentParseMode,
65 dry_run: bool,
66) -> bool {
67 let i18n_toml_path = Path::new(i18n_toml_path);
68 let i18n_dir = i18n_toml_path
69 .parent()
70 .expect("Failed to get i18n directory");
71 let _config =
72 es_fluent_toml::I18nConfig::from_manifest_dir(i18n_dir).expect("Failed to read i18n.toml");
73 let output_path = I18nConfig::output_dir_from_manifest_dir(i18n_dir)
74 .expect("Failed to resolve output directory");
75
76 let changed = EsFluentGenerator::builder()
77 .output_path(output_path)
78 .assets_dir(
79 I18nConfig::assets_dir_from_manifest_dir(i18n_dir)
80 .expect("Failed to resolve assets directory"),
81 )
82 .manifest_dir(i18n_dir)
83 .crate_name(crate_name)
84 .mode(mode)
85 .dry_run(dry_run)
86 .build()
87 .generate()
88 .expect("Failed to run generator");
89
90 let result = serde_json::json!({ "changed": changed });
91 write_metadata_result(crate_name, &result).expect("Failed to write metadata result");
92 changed
93}
94
95pub fn run_check(crate_name: &str) {
99 write_inventory_for_crate(crate_name);
100}
101
102pub fn run_clean_with_options(
106 i18n_toml_path: &str,
107 crate_name: &str,
108 all_locales: bool,
109 dry_run: bool,
110) -> bool {
111 let i18n_toml_path = Path::new(i18n_toml_path);
112 let i18n_dir = i18n_toml_path
113 .parent()
114 .expect("Failed to get i18n directory");
115 let config =
116 es_fluent_toml::I18nConfig::from_manifest_dir(i18n_dir).expect("Failed to read i18n.toml");
117 let output_path = I18nConfig::output_dir_from_manifest_dir(i18n_dir)
118 .expect("Failed to resolve output directory");
119 let assets_dir = config
120 .assets_dir_from_base(Some(i18n_dir))
121 .expect("Failed to resolve assets directory");
122
123 let changed = EsFluentGenerator::builder()
124 .output_path(output_path)
125 .assets_dir(assets_dir)
126 .manifest_dir(i18n_dir)
127 .crate_name(crate_name)
128 .dry_run(dry_run)
129 .build()
130 .clean(all_locales, dry_run)
131 .expect("Failed to run clean");
132
133 let result = serde_json::json!({ "changed": changed });
134 write_metadata_result(crate_name, &result).expect("Failed to write metadata result");
135 changed
136}
137
138pub fn run() {
143 let args: Vec<String> = std::env::args().collect();
144
145 let command = args.get(1).map(|s| s.as_str()).unwrap_or("check");
146 let i18n_path = args.get(2).map(|s| s.as_str());
147
148 let target_crate = args
149 .iter()
150 .position(|s| s == "--crate")
151 .and_then(|i| args.get(i + 1))
152 .map(|s| s.as_str());
153
154 let mode_str = args
155 .iter()
156 .position(|s| s == "--mode")
157 .and_then(|i| args.get(i + 1))
158 .map(|s| s.as_str())
159 .unwrap_or("conservative");
160
161 let dry_run = args.iter().any(|s| s == "--dry-run");
162 let all_locales = args.iter().any(|s| s == "--all");
163
164 match command {
165 "generate" => {
166 let path = i18n_path.expect("Missing i18n.toml path");
167 let name = target_crate.expect("Missing --crate argument");
168 let mode = match mode_str {
169 "aggressive" => FluentParseMode::Aggressive,
170 _ => FluentParseMode::Conservative,
171 };
172 run_generate_with_options(path, name, mode, dry_run);
173 },
174 "clean" => {
175 let path = i18n_path.expect("Missing i18n.toml path");
176 let name = target_crate.expect("Missing --crate argument");
177 run_clean_with_options(path, name, all_locales, dry_run);
178 },
179 "check" => {
180 let name = target_crate.expect("Missing --crate argument");
181 run_check(name);
182 },
183 _ => {
184 eprintln!("Unknown command: {}", command);
185 std::process::exit(1);
186 },
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use tempfile::tempdir;
194
195 fn with_temp_cwd<T>(f: impl FnOnce(&Path) -> T) -> T {
196 let _guard = crate::TEST_CWD_LOCK.lock().expect("lock poisoned");
197 let original = std::env::current_dir().expect("cwd");
198 let temp = tempdir().expect("tempdir");
199 std::env::set_current_dir(temp.path()).expect("set cwd");
200 let result = f(temp.path());
201 std::env::set_current_dir(original).expect("restore cwd");
202 result
203 }
204
205 fn write_basic_manifest(manifest_dir: &Path) {
206 std::fs::create_dir_all(manifest_dir.join("i18n/en-US")).expect("mkdir en-US");
207 std::fs::create_dir_all(manifest_dir.join("i18n/fr")).expect("mkdir fr");
208 std::fs::write(
209 manifest_dir.join("i18n.toml"),
210 "fallback_language = \"en-US\"\nassets_dir = \"i18n\"\n",
211 )
212 .expect("write i18n.toml");
213 }
214
215 fn read_changed_result(base: &Path, crate_name: &str) -> bool {
216 let result_path = base.join("metadata").join(crate_name).join("result.json");
217 let content = std::fs::read_to_string(result_path).expect("read result json");
218 let value: serde_json::Value = serde_json::from_str(&content).expect("parse result json");
219 value["changed"].as_bool().expect("changed bool")
220 }
221
222 #[test]
223 fn run_generate_and_clean_with_options_write_metadata_result() {
224 with_temp_cwd(|cwd| {
225 write_basic_manifest(cwd);
226 let i18n_path = cwd.join("i18n.toml");
227
228 let changed = run_generate_with_options(
229 i18n_path.to_str().expect("path"),
230 "missing-crate",
231 FluentParseMode::Conservative,
232 false,
233 );
234 assert_eq!(changed, read_changed_result(cwd, "missing-crate"));
235
236 let clean_changed = run_clean_with_options(
237 i18n_path.to_str().expect("path"),
238 "missing-crate",
239 true,
240 true,
241 );
242 assert_eq!(clean_changed, read_changed_result(cwd, "missing-crate"));
243 });
244 }
245
246 #[test]
247 fn run_check_writes_inventory_json_for_requested_crate() {
248 with_temp_cwd(|cwd| {
249 run_check("unknown-crate");
250
251 let inventory_path = cwd.join("metadata/unknown-crate/inventory.json");
252 let content = std::fs::read_to_string(inventory_path).expect("read inventory");
253 let value: serde_json::Value = serde_json::from_str(&content).expect("parse json");
254 assert_eq!(
255 value["expected_keys"]
256 .as_array()
257 .expect("expected_keys")
258 .len(),
259 0
260 );
261 });
262 }
263}