es_fluent_cli/commands/
generate.rs1use crate::commands::{
4 GenerationVerb, WorkspaceArgs, WorkspaceCrates, parallel_generate,
5 render_generation_results_with_dry_run,
6};
7use crate::core::{CliError, FluentParseMode, GenerationAction};
8use crate::utils::ui;
9use clap::Parser;
10
11#[derive(Parser)]
13pub struct GenerateArgs {
14 #[command(flatten)]
15 pub workspace: WorkspaceArgs,
16
17 #[arg(short, long, value_enum, default_value_t = FluentParseMode::default())]
19 pub mode: FluentParseMode,
20
21 #[arg(long)]
23 pub dry_run: bool,
24
25 #[arg(long)]
27 pub force_run: bool,
28}
29
30pub fn run_generate(args: GenerateArgs) -> Result<(), CliError> {
32 let workspace = WorkspaceCrates::discover(args.workspace)?;
33
34 if !workspace.print_discovery(ui::print_header) {
35 return Ok(());
36 }
37
38 let results = parallel_generate(
39 &workspace.workspace_info,
40 &workspace.valid,
41 &GenerationAction::Generate {
42 mode: args.mode,
43 dry_run: args.dry_run,
44 },
45 args.force_run,
46 );
47 let has_errors =
48 render_generation_results_with_dry_run(&results, args.dry_run, GenerationVerb::Generate);
49
50 if has_errors {
51 std::process::exit(1);
52 }
53
54 Ok(())
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use crate::generation::cache::{RunnerCache, compute_content_hash};
61 use std::fs;
62 use std::time::SystemTime;
63 use tempfile::tempdir;
64
65 fn create_test_crate_workspace() -> tempfile::TempDir {
66 let temp = tempdir().unwrap();
67
68 fs::create_dir_all(temp.path().join("src")).unwrap();
69 fs::create_dir_all(temp.path().join("i18n/en")).unwrap();
70 fs::write(
71 temp.path().join("Cargo.toml"),
72 r#"[package]
73name = "test-app"
74version = "0.1.0"
75edition = "2024"
76"#,
77 )
78 .unwrap();
79 fs::write(temp.path().join("src/lib.rs"), "pub struct Demo;\n").unwrap();
80 fs::write(
81 temp.path().join("i18n.toml"),
82 "fallback_language = \"en\"\nassets_dir = \"i18n\"\n",
83 )
84 .unwrap();
85 fs::write(temp.path().join("i18n/en/test-app.ftl"), "hello = Hello\n").unwrap();
86
87 temp
88 }
89
90 #[cfg(unix)]
91 fn set_executable(path: &std::path::Path) {
92 use std::os::unix::fs::PermissionsExt;
93 let mut perms = fs::metadata(path).expect("metadata").permissions();
94 perms.set_mode(0o755);
95 fs::set_permissions(path, perms).expect("set permissions");
96 }
97
98 #[cfg(not(unix))]
99 fn set_executable(_path: &std::path::Path) {}
100
101 fn setup_fake_runner_and_cache(temp: &tempfile::TempDir) {
102 let binary_path = temp.path().join("target/debug/es-fluent-runner");
103 fs::create_dir_all(binary_path.parent().unwrap()).expect("create target/debug");
104 fs::write(&binary_path, "#!/bin/sh\necho generated\n").expect("write runner");
105 set_executable(&binary_path);
106
107 let src_dir = temp.path().join("src");
108 let i18n_toml = temp.path().join("i18n.toml");
109 let hash = compute_content_hash(&src_dir, Some(&i18n_toml));
110 let mtime = fs::metadata(&binary_path)
111 .and_then(|m| m.modified())
112 .expect("runner mtime")
113 .duration_since(SystemTime::UNIX_EPOCH)
114 .expect("mtime duration")
115 .as_secs();
116
117 let temp_dir = es_fluent_derive_core::get_es_fluent_temp_dir(temp.path());
118 fs::create_dir_all(&temp_dir).expect("create temp dir");
119 let mut crate_hashes = indexmap::IndexMap::new();
120 crate_hashes.insert("test-app".to_string(), hash);
121 RunnerCache {
122 crate_hashes,
123 runner_mtime: mtime,
124 cli_version: env!("CARGO_PKG_VERSION").to_string(),
125 }
126 .save(&temp_dir)
127 .expect("save runner cache");
128 }
129
130 #[test]
131 fn run_generate_returns_ok_when_package_filter_matches_nothing() {
132 let temp = create_test_crate_workspace();
133 let result = run_generate(GenerateArgs {
134 workspace: WorkspaceArgs {
135 path: Some(temp.path().to_path_buf()),
136 package: Some("missing-crate".to_string()),
137 },
138 mode: FluentParseMode::default(),
139 dry_run: false,
140 force_run: false,
141 });
142
143 assert!(result.is_ok());
144 }
145
146 #[test]
147 fn run_generate_executes_with_fake_runner() {
148 let temp = create_test_crate_workspace();
149 setup_fake_runner_and_cache(&temp);
150
151 let result = run_generate(GenerateArgs {
152 workspace: WorkspaceArgs {
153 path: Some(temp.path().to_path_buf()),
154 package: None,
155 },
156 mode: FluentParseMode::default(),
157 dry_run: false,
158 force_run: false,
159 });
160
161 assert!(result.is_ok());
162 }
163}