1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use std::path::PathBuf;
4
5use crate::assets::AssetManager;
6
7pub struct FileSystemAssetManager {
9 base_path: PathBuf,
10}
11
12impl FileSystemAssetManager {
13 pub fn new(base_path: PathBuf) -> Self {
15 Self { base_path }
16 }
17}
18
19#[async_trait]
20impl AssetManager for FileSystemAssetManager {
21 fn get_template(&self, template_type: &str) -> Result<String> {
22 let direct_path = self.base_path.join(format!("{template_type}.md"));
24 let templates_path = self
25 .base_path
26 .join("templates")
27 .join(format!("{template_type}.md"));
28
29 let template_path = if direct_path.exists() {
30 direct_path
31 } else if templates_path.exists() {
32 templates_path
33 } else {
34 return Err(anyhow::anyhow!("Template '{}' not found", template_type));
35 };
36
37 std::fs::read_to_string(&template_path)
38 .with_context(|| format!("Failed to read template file: {}", template_path.display()))
39 }
40
41 fn get_dockerfile(&self, dockerfile_name: &str) -> Result<Vec<u8>> {
42 let dockerfile_path = self
43 .base_path
44 .join("dockerfiles")
45 .join(dockerfile_name)
46 .join("Dockerfile");
47
48 if !dockerfile_path.exists() {
49 return Err(anyhow::anyhow!(
50 "Dockerfile '{}' not found",
51 dockerfile_name
52 ));
53 }
54
55 std::fs::read(&dockerfile_path)
56 .with_context(|| format!("Failed to read dockerfile: {}", dockerfile_path.display()))
57 }
58
59 fn get_dockerfile_file(&self, dockerfile_name: &str, file_path: &str) -> Result<Vec<u8>> {
60 let file_full_path = self
61 .base_path
62 .join("dockerfiles")
63 .join(dockerfile_name)
64 .join(file_path);
65
66 if !file_full_path.exists() {
67 return Err(anyhow::anyhow!(
68 "Dockerfile file '{}/{}' not found",
69 dockerfile_name,
70 file_path
71 ));
72 }
73
74 std::fs::read(&file_full_path).with_context(|| {
75 format!(
76 "Failed to read dockerfile file: {}",
77 file_full_path.display()
78 )
79 })
80 }
81
82 fn list_templates(&self) -> Vec<String> {
83 let mut templates = Vec::new();
84
85 if let Ok(entries) = std::fs::read_dir(&self.base_path) {
87 for entry in entries.flatten() {
88 if let Ok(file_type) = entry.file_type() {
89 if file_type.is_file() {
90 if let Some(file_name) = entry.file_name().to_str() {
91 if file_name.ends_with(".md") {
92 let template_name = file_name.trim_end_matches(".md");
93 templates.push(template_name.to_string());
94 }
95 }
96 }
97 }
98 }
99 }
100
101 let templates_dir = self.base_path.join("templates");
103 if let Ok(entries) = std::fs::read_dir(&templates_dir) {
104 for entry in entries.flatten() {
105 if let Ok(file_type) = entry.file_type() {
106 if file_type.is_file() {
107 if let Some(file_name) = entry.file_name().to_str() {
108 if file_name.ends_with(".md") {
109 let template_name = file_name.trim_end_matches(".md");
110 if !templates.contains(&template_name.to_string()) {
111 templates.push(template_name.to_string());
112 }
113 }
114 }
115 }
116 }
117 }
118 }
119
120 templates.sort();
121 templates
122 }
123
124 fn list_dockerfiles(&self) -> Vec<String> {
125 let mut dockerfiles = Vec::new();
126 let dockerfiles_dir = self.base_path.join("dockerfiles");
127
128 if let Ok(entries) = std::fs::read_dir(&dockerfiles_dir) {
129 for entry in entries.flatten() {
130 if let Ok(file_type) = entry.file_type() {
131 if file_type.is_dir() {
132 if let Some(dir_name) = entry.file_name().to_str() {
133 let layer_dir = dockerfiles_dir.join(dir_name);
135 if let Ok(layer_entries) = std::fs::read_dir(&layer_dir) {
136 for layer_entry in layer_entries.flatten() {
137 if let Ok(layer_file_type) = layer_entry.file_type() {
138 if layer_file_type.is_dir() {
139 if let Some(layer_name) =
140 layer_entry.file_name().to_str()
141 {
142 let dockerfile_path =
143 layer_dir.join(layer_name).join("Dockerfile");
144 if dockerfile_path.exists() {
145 dockerfiles
146 .push(format!("{dir_name}/{layer_name}"));
147 }
148 }
149 }
150 }
151 }
152 }
153 }
154 }
155 }
156 }
157 }
158
159 dockerfiles.sort();
160 dockerfiles
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use std::fs;
168 use tempfile::TempDir;
169
170 #[test]
171 fn test_get_template_success() {
172 let temp_dir = TempDir::new().unwrap();
173 let templates_dir = temp_dir.path().join("templates");
174 fs::create_dir(&templates_dir).unwrap();
175
176 let template_content = "# Feature Template\n\n{{DESCRIPTION}}";
177 fs::write(templates_dir.join("feat.md"), template_content).unwrap();
178
179 let manager = FileSystemAssetManager::new(templates_dir);
180 let result = manager.get_template("feat").unwrap();
181
182 assert_eq!(result, template_content);
183 }
184
185 #[test]
186 fn test_get_template_not_found() {
187 let temp_dir = TempDir::new().unwrap();
188 let templates_dir = temp_dir.path().join("templates");
189 fs::create_dir(&templates_dir).unwrap();
190
191 let manager = FileSystemAssetManager::new(templates_dir);
192 let result = manager.get_template("nonexistent");
193
194 assert!(result.is_err());
195 assert!(result.unwrap_err().to_string().contains("not found"));
196 }
197
198 #[test]
199 fn test_list_templates() {
200 let temp_dir = TempDir::new().unwrap();
201 let templates_dir = temp_dir.path().join("templates");
202 fs::create_dir(&templates_dir).unwrap();
203
204 fs::write(templates_dir.join("feat.md"), "content").unwrap();
205 fs::write(templates_dir.join("fix.md"), "content").unwrap();
206 fs::write(templates_dir.join("doc.md"), "content").unwrap();
207 fs::write(templates_dir.join("not-a-template.txt"), "content").unwrap();
208
209 let manager = FileSystemAssetManager::new(templates_dir);
210 let templates = manager.list_templates();
211
212 assert_eq!(templates, vec!["doc", "feat", "fix"]);
213 }
214
215 #[test]
216 fn test_list_templates_empty_directory() {
217 let temp_dir = TempDir::new().unwrap();
218 let templates_dir = temp_dir.path().join("templates");
219 fs::create_dir(&templates_dir).unwrap();
220
221 let manager = FileSystemAssetManager::new(templates_dir);
222 let templates = manager.list_templates();
223
224 assert!(templates.is_empty());
225 }
226
227 #[test]
228 fn test_list_templates_nonexistent_directory() {
229 let temp_dir = TempDir::new().unwrap();
230 let templates_dir = temp_dir.path().join("nonexistent");
231
232 let manager = FileSystemAssetManager::new(templates_dir);
233 let templates = manager.list_templates();
234
235 assert!(templates.is_empty());
236 }
237
238 #[test]
239 fn test_dockerfile_methods_return_error() {
240 let temp_dir = TempDir::new().unwrap();
241 let manager = FileSystemAssetManager::new(temp_dir.path().to_path_buf());
242
243 assert!(manager.get_dockerfile("test").is_err());
244 assert!(manager.get_dockerfile_file("test", "file").is_err());
245 assert!(manager.list_dockerfiles().is_empty());
246 }
247}