es_fluent_cli/utils/
discovery.rs1use crate::core::{CrateInfo, WorkspaceInfo};
2use anyhow::{Context as _, Result};
3use cargo_metadata::MetadataCommand;
4use std::path::{Path, PathBuf};
5
6pub fn discover_workspace(root_dir: &Path) -> Result<WorkspaceInfo> {
9 let root_dir = root_dir
10 .canonicalize()
11 .context("Failed to canonicalize root directory")?;
12
13 let metadata = MetadataCommand::new()
14 .current_dir(&root_dir)
15 .no_deps()
16 .exec()
17 .context("Failed to get cargo metadata")?;
18
19 let workspace_root: PathBuf = metadata.workspace_root.clone().into();
20 let target_dir: PathBuf = metadata.target_directory.clone().into();
21
22 let mut crates = Vec::new();
23
24 for package in metadata.workspace_packages() {
25 let manifest_dir: PathBuf = package.manifest_path.parent().unwrap().into();
26
27 let i18n_config_path = manifest_dir.join("i18n.toml");
28 if !i18n_config_path.exists() {
29 continue;
30 }
31
32 let i18n_config = es_fluent_toml::I18nConfig::read_from_path(&i18n_config_path)
33 .with_context(|| format!("Failed to read {}", i18n_config_path.display()))?;
34
35 let ftl_output_dir = manifest_dir
36 .join(&i18n_config.assets_dir)
37 .join(&i18n_config.fallback_language);
38
39 let src_dir = manifest_dir.join("src");
40 let has_lib_rs = src_dir.join("lib.rs").exists();
41
42 crates.push(CrateInfo {
43 name: package.name.to_string(),
44 manifest_dir,
45 src_dir,
46 i18n_config_path,
47 ftl_output_dir,
48 has_lib_rs,
49 fluent_features: i18n_config
50 .fluent_feature
51 .as_ref()
52 .map(|f| f.as_vec())
53 .unwrap_or_default(),
54 });
55 }
56
57 crates.sort_by(|a, b| a.name.cmp(&b.name));
59
60 Ok(WorkspaceInfo {
61 root_dir: workspace_root,
62 target_dir,
63 crates,
64 })
65}
66
67pub fn discover_crates(root_dir: &Path) -> Result<Vec<CrateInfo>> {
70 discover_workspace(root_dir).map(|ws| ws.crates)
71}
72
73pub fn count_ftl_resources(ftl_output_dir: &Path, crate_name: &str) -> usize {
75 let ftl_file = ftl_output_dir.join(format!("{}.ftl", crate_name));
76
77 if !ftl_file.exists() {
78 return 0;
79 }
80
81 let Ok(content) = std::fs::read_to_string(&ftl_file) else {
82 return 0;
83 };
84
85 content
88 .lines()
89 .filter(|line| {
90 let trimmed = line.trim();
91 !trimmed.is_empty()
92 && !trimmed.starts_with('#')
93 && !line.starts_with(' ')
94 && !line.starts_with('\t')
95 && trimmed.contains('=')
96 })
97 .count()
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_count_ftl_resources_empty() {
106 let temp = tempfile::tempdir().unwrap();
107 assert_eq!(count_ftl_resources(temp.path(), "test-crate"), 0);
108 }
109
110 #[test]
111 fn test_count_ftl_resources_nonexistent() {
112 assert_eq!(
113 count_ftl_resources(Path::new("/nonexistent/path"), "test-crate"),
114 0
115 );
116 }
117}