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