es_fluent_cli_helpers/
generate.rs1use es_fluent_core::registry::FtlTypeInfo;
4use std::path::PathBuf;
5
6pub use es_fluent_generate::FluentParseMode;
7pub use es_fluent_generate::error::FluentGenerateError;
8
9#[derive(Debug, thiserror::Error)]
11pub enum GeneratorError {
12 #[error("Configuration error: {0}")]
14 Config(#[from] es_fluent_toml::I18nConfigError),
15
16 #[error("Failed to detect crate name: {0}")]
18 CrateName(String),
19
20 #[error("Generation error: {0}")]
22 Generate(#[from] FluentGenerateError),
23}
24
25#[derive(bon::Builder)]
30pub struct EsFluentGenerator {
31 #[builder(default)]
34 mode: FluentParseMode,
35
36 #[builder(into)]
38 crate_name: Option<String>,
39
40 #[builder(into)]
42 output_path: Option<PathBuf>,
43
44 #[builder(into)]
46 assets_dir: Option<PathBuf>,
47
48 #[builder(default)]
50 dry_run: bool,
51}
52
53#[derive(clap::Parser)]
55pub struct GeneratorArgs {
56 #[command(subcommand)]
57 action: Action,
58}
59
60#[derive(clap::Subcommand)]
61enum Action {
62 Generate {
64 #[arg(long, default_value_t = FluentParseMode::default())]
66 mode: FluentParseMode,
67 #[arg(long)]
69 dry_run: bool,
70 },
71 Clean {
73 #[arg(long)]
75 all: bool,
76 #[arg(long)]
78 dry_run: bool,
79 },
80}
81
82impl EsFluentGenerator {
83 pub fn run_cli(self) -> Result<bool, GeneratorError> {
85 use clap::Parser;
86 let args = GeneratorArgs::parse();
87
88 match args.action {
89 Action::Generate { mode, dry_run } => {
90 let mut generator = self;
91 generator.mode = mode;
92 generator.dry_run = dry_run;
93 generator.generate()
94 },
95 Action::Clean { all, dry_run } => self.clean(all, dry_run),
96 }
97 }
98
99 fn resolve_crate_name(&self) -> Result<String, GeneratorError> {
103 self.crate_name
104 .clone()
105 .map_or_else(Self::detect_crate_name, Ok)
106 }
107
108 fn resolve_output_path(&self) -> Result<PathBuf, GeneratorError> {
110 if let Some(path) = &self.output_path {
111 return Ok(path.clone());
112 }
113 let config = es_fluent_toml::I18nConfig::read_from_manifest_dir()?;
114 Ok(config.assets_dir.join(&config.fallback_language))
115 }
116
117 fn resolve_assets_dir(&self) -> Result<PathBuf, GeneratorError> {
119 if let Some(path) = &self.assets_dir {
120 return Ok(path.clone());
121 }
122 let config = es_fluent_toml::I18nConfig::read_from_manifest_dir()?;
123 Ok(config.assets_dir)
124 }
125
126 fn resolve_clean_paths(&self, all_locales: bool) -> Result<Vec<PathBuf>, GeneratorError> {
128 if !all_locales {
129 return Ok(vec![self.resolve_output_path()?]);
130 }
131
132 let assets_dir = self.resolve_assets_dir()?;
133 let mut paths: Vec<PathBuf> = std::fs::read_dir(&assets_dir)
134 .ok()
135 .map(|entries| {
136 entries
137 .filter_map(|e| e.ok())
138 .filter(|e| e.path().is_dir())
139 .map(|e| e.path())
140 .collect()
141 })
142 .unwrap_or_else(|| self.output_path.clone().into_iter().collect());
143
144 paths.sort();
146
147 Ok(paths)
148 }
149
150 pub fn generate(&self) -> Result<bool, GeneratorError> {
152 let crate_name = self.resolve_crate_name()?;
153 let output_path = self.resolve_output_path()?;
154 let type_infos = collect_type_infos(&crate_name);
155
156 tracing::info!(
157 "Generating FTL files for {} types in crate '{}'",
158 type_infos.len(),
159 crate_name
160 );
161
162 let changed = es_fluent_generate::generate(
163 &crate_name,
164 output_path,
165 type_infos,
166 self.mode.clone(),
167 self.dry_run,
168 )?;
169
170 Ok(changed)
171 }
172
173 pub fn clean(&self, all_locales: bool, dry_run: bool) -> Result<bool, GeneratorError> {
175 let crate_name = self.resolve_crate_name()?;
176 let paths = self.resolve_clean_paths(all_locales)?;
177 let type_infos = collect_type_infos(&crate_name);
178
179 let mut any_changed = false;
180 for output_path in paths {
181 if !dry_run {
182 tracing::info!(
183 "Cleaning FTL files for {} types in crate '{}' at {}",
184 type_infos.len(),
185 crate_name,
186 output_path.display()
187 );
188 }
189
190 if es_fluent_generate::clean::clean(
191 &crate_name,
192 output_path,
193 type_infos.clone(),
194 dry_run,
195 )? {
196 any_changed = true;
197 }
198 }
199
200 Ok(any_changed)
201 }
202
203 fn detect_crate_name() -> Result<String, GeneratorError> {
205 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
206 .map_err(|_| GeneratorError::CrateName("CARGO_MANIFEST_DIR not set".to_string()))?;
207 let manifest_path = PathBuf::from(&manifest_dir).join("Cargo.toml");
208
209 cargo_metadata::MetadataCommand::new()
210 .exec()
211 .ok()
212 .and_then(|metadata| {
213 metadata
214 .packages
215 .iter()
216 .find(|pkg| pkg.manifest_path == manifest_path)
217 .map(|pkg| pkg.name.to_string())
218 })
219 .or_else(|| std::env::var("CARGO_PKG_NAME").ok())
220 .ok_or_else(|| GeneratorError::CrateName("Could not determine crate name".to_string()))
221 }
222}
223
224fn collect_type_infos(crate_name: &str) -> Vec<FtlTypeInfo> {
226 let crate_ident = crate_name.replace('-', "_");
227 es_fluent_core::registry::get_all_ftl_type_infos()
228 .into_iter()
229 .filter(|info| {
230 info.module_path == crate_ident
231 || info.module_path.starts_with(&format!("{}::", crate_ident))
232 })
233 .collect()
234}