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 paths = 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 Ok(paths)
145 }
146
147 pub fn generate(&self) -> Result<bool, GeneratorError> {
149 let crate_name = self.resolve_crate_name()?;
150 let output_path = self.resolve_output_path()?;
151 let type_infos = collect_type_infos(&crate_name);
152
153 tracing::info!(
154 "Generating FTL files for {} types in crate '{}'",
155 type_infos.len(),
156 crate_name
157 );
158
159 let changed = es_fluent_generate::generate(
160 &crate_name,
161 output_path,
162 type_infos,
163 self.mode.clone(),
164 self.dry_run,
165 )?;
166
167 Ok(changed)
168 }
169
170 pub fn clean(&self, all_locales: bool, dry_run: bool) -> Result<bool, GeneratorError> {
172 let crate_name = self.resolve_crate_name()?;
173 let paths = self.resolve_clean_paths(all_locales)?;
174 let type_infos = collect_type_infos(&crate_name);
175
176 let mut any_changed = false;
177 for output_path in paths {
178 if !dry_run {
179 tracing::info!(
180 "Cleaning FTL files for {} types in crate '{}' at {}",
181 type_infos.len(),
182 crate_name,
183 output_path.display()
184 );
185 }
186
187 if es_fluent_generate::clean::clean(
188 &crate_name,
189 output_path,
190 type_infos.clone(),
191 dry_run,
192 )? {
193 any_changed = true;
194 }
195 }
196
197 Ok(any_changed)
198 }
199
200 fn detect_crate_name() -> Result<String, GeneratorError> {
202 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
203 .map_err(|_| GeneratorError::CrateName("CARGO_MANIFEST_DIR not set".to_string()))?;
204 let manifest_path = PathBuf::from(&manifest_dir).join("Cargo.toml");
205
206 cargo_metadata::MetadataCommand::new()
207 .exec()
208 .ok()
209 .and_then(|metadata| {
210 metadata
211 .packages
212 .iter()
213 .find(|pkg| pkg.manifest_path == manifest_path)
214 .map(|pkg| pkg.name.to_string())
215 })
216 .or_else(|| std::env::var("CARGO_PKG_NAME").ok())
217 .ok_or_else(|| GeneratorError::CrateName("Could not determine crate name".to_string()))
218 }
219}
220
221fn collect_type_infos(crate_name: &str) -> Vec<FtlTypeInfo> {
223 let crate_ident = crate_name.replace('-', "_");
224 es_fluent_core::registry::get_all_ftl_type_infos()
225 .into_iter()
226 .filter(|info| {
227 info.module_path == crate_ident
228 || info.module_path.starts_with(&format!("{}::", crate_ident))
229 })
230 .collect()
231}