cs/parse/
key_extractor.rs1use crate::error::Result;
4use std::path::Path;
5use walkdir::WalkDir;
6
7use super::translation::TranslationEntry;
8use super::yaml_parser::YamlParser;
9use super::json_parser::JsonParser;
10
11pub struct KeyExtractor;
15
16impl KeyExtractor {
17 pub fn new() -> Self {
19 Self
20 }
21
22 pub fn extract(&self, base_dir: &Path, query: &str) -> Result<Vec<TranslationEntry>> {
27 let mut matches = Vec::new();
28 let lowered = query.to_lowercase();
29
30 for entry in WalkDir::new(base_dir)
31 .into_iter()
32 .filter_map(|e| e.ok())
33 .filter(|e| e.file_type().is_file())
34 {
35 let path = entry.path();
36 if let Some(ext) = path.extension() {
37 let ext_str = ext.to_string_lossy();
38 if ext_str == "yml" || ext_str == "yaml" {
39 let entries = YamlParser::parse_file(path)?;
40 for e in entries {
41 if e.value.to_lowercase().contains(&lowered) {
42 matches.push(e);
43 }
44 }
45 } else if ext_str == "json" {
46 let entries = JsonParser::parse_file(path)?;
47 for e in entries {
48 if e.value.to_lowercase().contains(&lowered) {
49 matches.push(e);
50 }
51 }
52 }
53 }
54 }
55 Ok(matches)
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use std::fs;
63
64 use tempfile::tempdir;
65
66 #[test]
67 fn test_key_extractor_simple() -> Result<()> {
68 let dir = tempdir()?;
69 let en_path = dir.path().join("en.yml");
70 let fr_path = dir.path().join("fr.yml");
71
72 fs::write(
74 &en_path,
75 "greeting:\n hello: \"Hello World\"\n goodbye: \"Goodbye\"",
76 )?;
77 fs::write(
78 &fr_path,
79 "greeting:\n hello: \"Bonjour World\"\n goodbye: \"Au revoir\"",
80 )?;
81
82 let extractor = KeyExtractor::new();
83 let results = extractor.extract(dir.path(), "world")?;
84
85 assert_eq!(results.len(), 2);
87 let keys: Vec<_> = results.iter().map(|e| e.key.clone()).collect();
88 assert!(keys.contains(&"greeting.hello".to_string()));
89 Ok(())
90 }
91
92 #[test]
93 fn test_key_extractor_case_insensitive() -> Result<()> {
94 let dir = tempdir()?;
95 let yaml_path = dir.path().join("test.yml");
96
97 fs::write(
98 &yaml_path,
99 "app:\n title: \"My Application\"\n description: \"A great APP for everyone\"",
100 )?;
101
102 let extractor = KeyExtractor::new();
103
104 let results = extractor.extract(dir.path(), "APP")?;
106 assert_eq!(results.len(), 2); let values: Vec<_> = results.iter().map(|e| e.value.clone()).collect();
109 assert!(values.contains(&"My Application".to_string()));
110 assert!(values.contains(&"A great APP for everyone".to_string()));
111
112 Ok(())
113 }
114
115 #[test]
116 fn test_key_extractor_multiple_files() -> Result<()> {
117 let dir = tempdir()?;
118
119 let en_path = dir.path().join("en.yml");
121 let fr_path = dir.path().join("fr.yml");
122 let de_path = dir.path().join("de.yml");
123
124 fs::write(&en_path, "common:\n action: \"Save Data\"")?;
125 fs::write(&fr_path, "common:\n action: \"Sauvegarder Data\"")?;
126 fs::write(&de_path, "common:\n action: \"Speichern Data\"")?;
127
128 let extractor = KeyExtractor::new();
129 let results = extractor.extract(dir.path(), "data")?;
130
131 assert_eq!(results.len(), 3);
133
134 let files: Vec<_> = results
135 .iter()
136 .map(|e| e.file.file_name().unwrap().to_string_lossy().to_string())
137 .collect();
138 assert!(files.contains(&"en.yml".to_string()));
139 assert!(files.contains(&"fr.yml".to_string()));
140 assert!(files.contains(&"de.yml".to_string()));
141
142 Ok(())
143 }
144
145 #[test]
146 fn test_key_extractor_deep_nested() -> Result<()> {
147 let dir = tempdir()?;
148 let yaml_path = dir.path().join("nested.yml");
149
150 fs::write(
151 &yaml_path,
152 "level1:\n level2:\n level3:\n deep_key: \"Deep nested value\"\n another: \"test value\"",
153 )?;
154
155 let extractor = KeyExtractor::new();
156 let results = extractor.extract(dir.path(), "deep")?;
157
158 assert_eq!(results.len(), 1);
159 assert_eq!(results[0].key, "level1.level2.level3.deep_key");
160 assert_eq!(results[0].value, "Deep nested value");
161
162 Ok(())
163 }
164
165 #[test]
166 fn test_key_extractor_no_matches() -> Result<()> {
167 let dir = tempdir()?;
168 let yaml_path = dir.path().join("test.yml");
169
170 fs::write(
171 &yaml_path,
172 "greeting:\n hello: \"Hello\"\n goodbye: \"Goodbye\"",
173 )?;
174
175 let extractor = KeyExtractor::new();
176 let results = extractor.extract(dir.path(), "nonexistent")?;
177
178 assert_eq!(results.len(), 0);
179
180 Ok(())
181 }
182
183 #[test]
184 fn test_key_extractor_supports_json_and_yaml() -> Result<()> {
185 let dir = tempdir()?;
186 let yaml_path = dir.path().join("test.yml");
187 let txt_path = dir.path().join("test.txt");
188 let json_path = dir.path().join("test.json");
189
190 fs::write(&yaml_path, "key: \"test value\"")?;
191 fs::write(&txt_path, "key: test value")?; fs::write(&json_path, "{\"key\": \"test value\"}")?; let extractor = KeyExtractor::new();
195 let results = extractor.extract(dir.path(), "test")?;
196
197 assert_eq!(results.len(), 2);
199 let extensions: Vec<_> = results.iter()
200 .map(|e| e.file.extension().unwrap().to_string_lossy().to_string())
201 .collect();
202 assert!(extensions.contains(&"yml".to_string()));
203 assert!(extensions.contains(&"json".to_string()));
204
205 Ok(())
206 }
207}