es_fluent_cli/commands/
common.rs1use crate::core::{CliError, CrateInfo, GenerateResult, GenerationAction, WorkspaceInfo};
2use crate::utils::{count_ftl_resources, filter_crates_by_package, partition_by_lib_rs, ui};
3use clap::Args;
4use std::path::PathBuf;
5use std::time::Instant;
6
7#[derive(Args, Clone, Debug)]
8pub struct WorkspaceArgs {
9 #[arg(short, long)]
11 pub path: Option<PathBuf>,
12 #[arg(short = 'P', long)]
14 pub package: Option<String>,
15}
16
17#[derive(Args, Clone, Debug)]
21pub struct LocaleProcessingArgs {
22 #[arg(long)]
24 pub all: bool,
25
26 #[arg(long)]
28 pub dry_run: bool,
29}
30
31#[derive(Clone, Debug)]
33pub struct WorkspaceCrates {
34 pub path: PathBuf,
36 pub workspace_info: WorkspaceInfo,
38 pub crates: Vec<CrateInfo>,
40 pub valid: Vec<CrateInfo>,
42 pub skipped: Vec<CrateInfo>,
44}
45
46impl WorkspaceCrates {
47 pub fn discover(args: WorkspaceArgs) -> Result<Self, CliError> {
49 use crate::utils::discover_workspace;
50
51 let path = args.path.unwrap_or_else(|| PathBuf::from("."));
52 let workspace_info = discover_workspace(&path)?;
53 let crates = filter_crates_by_package(workspace_info.crates.clone(), args.package.as_ref());
54 let (valid_refs, skipped_refs) = partition_by_lib_rs(&crates);
55 let valid = valid_refs.into_iter().cloned().collect();
56 let skipped = skipped_refs.into_iter().cloned().collect();
57
58 Ok(Self {
59 path,
60 workspace_info,
61 crates,
62 valid,
63 skipped,
64 })
65 }
66
67 pub fn print_discovery(&self, header: impl Fn()) -> bool {
71 header();
72
73 if self.crates.is_empty() {
74 ui::print_discovered(&[]);
75 return false;
76 }
77
78 ui::print_discovered(&self.crates);
79
80 for krate in &self.skipped {
81 ui::print_missing_lib_rs(&krate.name);
82 }
83
84 true
85 }
86}
87
88fn read_changed_status(temp_dir: &std::path::Path, crate_name: &str) -> bool {
95 let result_json_path = temp_dir
96 .join("metadata")
97 .join(crate_name)
98 .join("result.json");
99
100 if !result_json_path.exists() {
101 return false;
102 }
103
104 match std::fs::read_to_string(&result_json_path) {
105 Ok(json_str) => match serde_json::from_str::<serde_json::Value>(&json_str) {
106 Ok(json) => json["changed"].as_bool().unwrap_or(false),
107 Err(_) => false,
108 },
109 Err(_) => false,
110 }
111}
112
113pub fn parallel_generate(
118 workspace: &WorkspaceInfo,
119 crates: &[CrateInfo],
120 action: &GenerationAction,
121) -> Vec<GenerateResult> {
122 use crate::generation::{generate_for_crate_monolithic, prepare_monolithic_runner_crate};
123
124 if let Err(e) = prepare_monolithic_runner_crate(workspace) {
126 return crates
128 .iter()
129 .map(|k| {
130 GenerateResult::failure(k.name.clone(), std::time::Duration::ZERO, e.to_string())
131 })
132 .collect();
133 }
134
135 let pb = ui::create_progress_bar(crates.len() as u64, "Processing crates...");
136
137 crates
140 .iter()
141 .map(|krate| {
142 let start = Instant::now();
143 let result = generate_for_crate_monolithic(krate, workspace, action);
144 let duration = start.elapsed();
145
146 pb.inc(1);
147
148 let resource_count = result
149 .as_ref()
150 .ok()
151 .map(|_| count_ftl_resources(&krate.ftl_output_dir, &krate.name))
152 .unwrap_or(0);
153
154 match result {
155 Ok(output) => {
156 let temp_dir = workspace.root_dir.join(".es-fluent");
158 let changed = read_changed_status(&temp_dir, &krate.name);
159
160 let output_opt = if output.is_empty() {
161 None
162 } else {
163 Some(output.to_string())
164 };
165
166 GenerateResult::success(
167 krate.name.clone(),
168 duration,
169 resource_count,
170 output_opt,
171 changed,
172 )
173 },
174 Err(e) => GenerateResult::failure(krate.name.clone(), duration, e.to_string()),
175 }
176 })
177 .collect()
178}
179
180pub fn render_generation_results(
184 results: &[GenerateResult],
185 on_success: impl Fn(&GenerateResult),
186 on_error: impl Fn(&GenerateResult),
187) -> bool {
188 let mut has_errors = false;
189
190 for result in results {
191 if result.error.is_some() {
192 has_errors = true;
193 on_error(result);
194 } else {
195 on_success(result);
196 }
197 }
198
199 has_errors
200}