ricecoder_refactoring/config/
storage_loader.rs1use crate::error::Result;
4use crate::types::RefactoringConfig;
5use ricecoder_storage::manager::StorageManager;
6use ricecoder_storage::types::ResourceType;
7use std::sync::Arc;
8
9pub struct StorageConfigLoader {
11 storage: Arc<dyn StorageManager>,
12}
13
14impl StorageConfigLoader {
15 pub fn new(storage: Arc<dyn StorageManager>) -> Self {
17 Self { storage }
18 }
19
20 pub fn load_language_config(&self, language: &str) -> Result<RefactoringConfig> {
27 if let Some(project_path) = self
29 .storage
30 .project_resource_path(ResourceType::RefactoringLanguageConfig)
31 {
32 let config_path = project_path.join(format!("{}.yaml", language));
33 if config_path.exists() {
34 tracing::debug!(
35 "Loading refactoring config from project: {}",
36 config_path.display()
37 );
38 let config = self.load_from_file(&config_path)?;
39 config.validate()?;
40 return Ok(config);
41 }
42 }
43
44 let global_path = self
46 .storage
47 .global_resource_path(ResourceType::RefactoringLanguageConfig);
48 let config_path = global_path.join(format!("{}.yaml", language));
49 if config_path.exists() {
50 tracing::debug!(
51 "Loading refactoring config from user: {}",
52 config_path.display()
53 );
54 let config = self.load_from_file(&config_path)?;
55 config.validate()?;
56 return Ok(config);
57 }
58
59 tracing::debug!(
61 "No configuration found for language '{}', using generic refactoring",
62 language
63 );
64 Ok(RefactoringConfig::generic_fallback(language))
65 }
66
67 fn load_from_file(&self, path: &std::path::Path) -> Result<RefactoringConfig> {
69 use crate::config::loader::ConfigLoader;
70 ConfigLoader::load(path)
71 }
72
73 pub fn list_available_languages(&self) -> Result<Vec<String>> {
75 let mut languages = Vec::new();
76
77 if let Some(project_path) = self
79 .storage
80 .project_resource_path(ResourceType::RefactoringLanguageConfig)
81 {
82 if project_path.exists() {
83 if let Ok(entries) = std::fs::read_dir(&project_path) {
84 for entry in entries.flatten() {
85 if let Some(name) = entry.file_name().to_str() {
86 if name.ends_with(".yaml") || name.ends_with(".yml") {
87 let lang = name.trim_end_matches(".yaml").trim_end_matches(".yml");
88 if !languages.contains(&lang.to_string()) {
89 languages.push(lang.to_string());
90 }
91 }
92 }
93 }
94 }
95 }
96 }
97
98 let global_path = self
100 .storage
101 .global_resource_path(ResourceType::RefactoringLanguageConfig);
102 if global_path.exists() {
103 if let Ok(entries) = std::fs::read_dir(&global_path) {
104 for entry in entries.flatten() {
105 if let Some(name) = entry.file_name().to_str() {
106 if name.ends_with(".yaml") || name.ends_with(".yml") {
107 let lang = name.trim_end_matches(".yaml").trim_end_matches(".yml");
108 if !languages.contains(&lang.to_string()) {
109 languages.push(lang.to_string());
110 }
111 }
112 }
113 }
114 }
115 }
116
117 Ok(languages)
118 }
119
120 pub fn has_language_config(&self, language: &str) -> Result<bool> {
122 if let Some(project_path) = self
124 .storage
125 .project_resource_path(ResourceType::RefactoringLanguageConfig)
126 {
127 let config_path = project_path.join(format!("{}.yaml", language));
128 if config_path.exists() {
129 return Ok(true);
130 }
131 }
132
133 let global_path = self
135 .storage
136 .global_resource_path(ResourceType::RefactoringLanguageConfig);
137 let config_path = global_path.join(format!("{}.yaml", language));
138 Ok(config_path.exists())
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use ricecoder_storage::types::StorageMode;
146 use std::path::PathBuf;
147
148 struct MockStorageManager {
150 global_path: PathBuf,
151 project_path: Option<PathBuf>,
152 }
153
154 impl StorageManager for MockStorageManager {
155 fn global_path(&self) -> &PathBuf {
156 &self.global_path
157 }
158
159 fn project_path(&self) -> Option<&PathBuf> {
160 self.project_path.as_ref()
161 }
162
163 fn mode(&self) -> StorageMode {
164 StorageMode::Merged
165 }
166
167 fn global_resource_path(&self, resource_type: ResourceType) -> PathBuf {
168 self.global_path.join(resource_type.dir_name())
169 }
170
171 fn project_resource_path(&self, resource_type: ResourceType) -> Option<PathBuf> {
172 self.project_path
173 .as_ref()
174 .map(|p| p.join(resource_type.dir_name()))
175 }
176
177 fn is_first_run(&self) -> bool {
178 false
179 }
180 }
181
182 #[test]
183 fn test_load_language_config_fallback() -> Result<()> {
184 let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
185 let storage = Arc::new(MockStorageManager {
186 global_path: temp_dir.path().to_path_buf(),
187 project_path: None,
188 });
189
190 let loader = StorageConfigLoader::new(storage);
191 let config = loader.load_language_config("unknown_language")?;
192
193 assert_eq!(config.language, "unknown_language");
195 assert!(config.rules.is_empty());
196
197 Ok(())
198 }
199
200 #[test]
201 fn test_list_available_languages() -> Result<()> {
202 let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
203 let refactoring_dir = temp_dir.path().join("refactoring/languages");
204 std::fs::create_dir_all(&refactoring_dir).expect("Failed to create dir");
205
206 std::fs::write(refactoring_dir.join("rust.yaml"), "language: rust").ok();
208 std::fs::write(refactoring_dir.join("python.yaml"), "language: python").ok();
209
210 let storage = Arc::new(MockStorageManager {
211 global_path: temp_dir.path().to_path_buf(),
212 project_path: None,
213 });
214
215 let loader = StorageConfigLoader::new(storage);
216 let languages = loader.list_available_languages()?;
217
218 assert!(languages.contains(&"rust".to_string()));
219 assert!(languages.contains(&"python".to_string()));
220
221 Ok(())
222 }
223
224 #[test]
225 fn test_has_language_config() -> Result<()> {
226 let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
227 let refactoring_dir = temp_dir.path().join("refactoring/languages");
228 std::fs::create_dir_all(&refactoring_dir).expect("Failed to create dir");
229
230 std::fs::write(refactoring_dir.join("rust.yaml"), "language: rust").ok();
231
232 let storage = Arc::new(MockStorageManager {
233 global_path: temp_dir.path().to_path_buf(),
234 project_path: None,
235 });
236
237 let loader = StorageConfigLoader::new(storage);
238
239 assert!(loader.has_language_config("rust")?);
240 assert!(!loader.has_language_config("unknown")?);
241
242 Ok(())
243 }
244}