]> git.r.bdr.sh - rbdr/page/blob - src/file_handler/mod.rs
Remove no longer necessary code
[rbdr/page] / src / file_handler / mod.rs
1 mod file_strategies;
2
3 use file_strategies::file::Strategy as FileStrategy;
4 use file_strategies::gemini::Strategy as GeminiStrategy;
5 use file_strategies::layout::Strategy as LayoutStrategy;
6
7 use std::fs::read_to_string;
8 use std::path::{Path, PathBuf};
9
10 pub struct FileHandler {
11 pub strategies: Vec<Box<dyn Strategy>>,
12 pub layout: Option<String>,
13 }
14
15 impl Default for FileHandler {
16 fn default() -> FileHandler {
17 FileHandler {
18 strategies: vec![
19 Box::new(GeminiStrategy {}),
20 Box::new(LayoutStrategy {}),
21 Box::new(FileStrategy {}),
22 ],
23 layout: None,
24 }
25 }
26 }
27
28 impl FileHandler {
29 pub fn identify(&self, path: &Path) -> FileType {
30 for strategy in &self.strategies {
31 if strategy.is(path) {
32 return strategy.identify();
33 }
34 }
35 FileType::Unknown
36 }
37
38 pub fn get_layout_or_panic(&mut self, files: &[File]) -> Result<(), &str> {
39 for file in files {
40 if file.file_type == FileType::Layout {
41 let layout_text = read_to_string(&file.path).unwrap();
42 self.layout = Some(layout_text);
43 return Ok(());
44 }
45 }
46 Err("No layout found. Please ensure there's a _layout.html file at the root")
47 }
48
49 pub fn handle_all(
50 &self,
51 source: &Path,
52 html_destination: &Path,
53 gemini_destination: &Path,
54 files: &[File],
55 ) {
56 for file in files {
57 self.handle(source, html_destination, gemini_destination, file);
58 }
59 }
60
61 pub fn handle(
62 &self,
63 source: &Path,
64 html_destination: &Path,
65 gemini_destination: &Path,
66 file: &File,
67 ) {
68 if let Some(strategy) = self
69 .strategies
70 .iter()
71 .find(|s| s.can_handle(&file.file_type))
72 {
73 let layout = self
74 .layout
75 .as_ref()
76 .expect("Layout should be initialized before handling files");
77 strategy.handle_html(source, html_destination, file, layout);
78 strategy.handle_gemini(source, gemini_destination, file);
79 }
80 }
81 }
82
83 pub trait Strategy {
84 fn is(&self, path: &Path) -> bool;
85 fn identify(&self) -> FileType;
86 fn can_handle(&self, file_type: &FileType) -> bool;
87 fn handle_html(&self, source: &Path, destination: &Path, file: &File, layout: &str);
88 fn handle_gemini(&self, source: &Path, destination: &Path, file: &File);
89 }
90
91 #[derive(Debug, Clone, PartialEq)]
92 pub enum FileType {
93 Gemini,
94 File,
95 Layout,
96 Unknown,
97 }
98
99 #[derive(PartialEq, Debug)]
100 pub struct File {
101 pub path: PathBuf,
102 pub file_type: FileType,
103 }
104
105 #[cfg(test)]
106 mod tests {
107 use std::fs::create_dir_all;
108 use std::path::PathBuf;
109
110 use super::*;
111
112 use test_utilities::*;
113
114 fn create_test_internal_file(path: &str, file_type: FileType) -> File {
115 File {
116 path: PathBuf::from(path),
117 file_type,
118 }
119 }
120
121 #[test]
122 fn test_identify_gemini_file() {
123 let handler = FileHandler::default();
124 let path = PathBuf::from("test.gmi");
125 assert!(matches!(handler.identify(&path), FileType::Gemini));
126 }
127
128 #[test]
129 fn test_identify_layout_file() {
130 let handler = FileHandler::default();
131 let path = PathBuf::from("_layout.html");
132 assert!(matches!(handler.identify(&path), FileType::Layout));
133 }
134
135 #[test]
136 fn test_identify_regular_file() {
137 let handler = FileHandler::default();
138 let path = PathBuf::from("regular.html");
139 assert!(matches!(handler.identify(&path), FileType::File));
140 }
141
142 #[test]
143 fn test_identify_unknown_file() {
144 let handler = FileHandler::default();
145 let path = PathBuf::from("tests");
146 assert!(matches!(handler.identify(&path), FileType::Unknown));
147 }
148
149 #[test]
150 fn test_get_layout_success() {
151 let test_dir = setup_test_dir();
152 let layout_path = test_dir.join("_layout.html");
153 create_test_file(&layout_path, "");
154
155 let mut handler = FileHandler::default();
156 let files = vec![
157 create_test_internal_file("test.gmi", FileType::Gemini),
158 create_test_internal_file(
159 layout_path.to_str().expect("Could not encode layout"),
160 FileType::Layout,
161 ),
162 create_test_internal_file("regular.html", FileType::File),
163 ];
164
165 assert!(handler.get_layout_or_panic(&files).is_ok());
166 }
167
168 #[test]
169 fn test_get_layout_failure() {
170 let mut handler = FileHandler::default();
171 let files = vec![
172 create_test_internal_file("test.gmi", FileType::Gemini),
173 create_test_internal_file("regular.html", FileType::File),
174 ];
175
176 assert!(handler.get_layout_or_panic(&files).is_err());
177 }
178
179 // Mock strategy for testing
180 struct MockStrategy {
181 is_match: bool,
182 file_type: FileType,
183 }
184
185 impl Strategy for MockStrategy {
186 fn is(&self, _path: &Path) -> bool {
187 self.is_match
188 }
189
190 fn identify(&self) -> FileType {
191 self.file_type.clone()
192 }
193
194 fn can_handle(&self, file_type: &FileType) -> bool {
195 &self.file_type == file_type
196 }
197
198 fn handle_html(&self, _source: &Path, _destination: &Path, _file: &File, _layout: &str) {}
199 fn handle_gemini(&self, _source: &Path, _destination: &Path, _file: &File) {}
200 }
201
202 #[test]
203 fn test_custom_strategy() {
204 let mock_strategy = MockStrategy {
205 is_match: true,
206 file_type: FileType::Gemini,
207 };
208
209 let handler = FileHandler {
210 strategies: vec![Box::new(mock_strategy)],
211 layout: Some("None".to_string()),
212 };
213
214 let path = PathBuf::from("test.whatever");
215 let file = File{
216 path: path.clone(),
217 file_type: FileType::Gemini
218 };
219 assert!(matches!(handler.identify(&path), FileType::Gemini));
220 handler.handle(&path, &path, &path, &file);
221 }
222
223 #[test]
224 fn test_handle_all_empty_files() {
225 let handler = FileHandler::default();
226 let files: Vec<File> = vec![];
227
228 // Should not panic with empty vector
229 handler.handle_all(
230 &PathBuf::from("source"),
231 &PathBuf::from("output_html"),
232 &PathBuf::from("output_gemini"),
233 &files,
234 );
235 }
236
237 #[test]
238 fn test_handle_with_layout() {
239 let handler = FileHandler {
240 layout: Some("test layout".to_string()),
241 ..Default::default()
242 };
243
244 let test_dir = setup_test_dir();
245 create_dir_all(test_dir.join("output_html"))
246 .expect("Could not create output html test directory");
247 create_dir_all(test_dir.join("output_gemini"))
248 .expect("Could not create output gemini test directory");
249 let test_path = test_dir.join("test.gmi");
250 create_test_file(&test_path, "");
251 let file = create_test_internal_file(
252 test_path
253 .to_str()
254 .expect("Could not encode gemini test file"),
255 FileType::Gemini,
256 );
257
258 // Should not panic with valid layout
259 handler.handle(
260 &test_dir,
261 &test_dir.join("output_html"),
262 &test_dir.join("output_gemini"),
263 &file,
264 );
265 }
266
267 #[test]
268 #[should_panic(expected = "Layout should be initialized before handling files")]
269 fn test_handle_without_layout() {
270 let handler = FileHandler::default();
271 let file = create_test_internal_file("test.gmi", FileType::Gemini);
272
273 handler.handle(
274 &PathBuf::from("source"),
275 &PathBuf::from("output_html"),
276 &PathBuf::from("output_gemini"),
277 &file,
278 );
279 }
280
281 #[test]
282 fn test_slice_handling() {
283 let test_dir = setup_test_dir();
284 let layout_path = test_dir.join("_layout.html");
285 create_test_file(&layout_path, "");
286 create_test_file(&test_dir.join("test1.gmi"), "");
287 create_test_file(&test_dir.join("test2.gmi"), "");
288 create_test_file(&test_dir.join("test3.gmi"), "");
289 create_dir_all(test_dir.join("output_html"))
290 .expect("Could not create output html test directory");
291 create_dir_all(test_dir.join("output_gemini"))
292 .expect("Could not create output gemini test directory");
293
294 let mut handler = FileHandler::default();
295 let files = [
296 create_test_internal_file(
297 test_dir
298 .join("test1.gmi")
299 .to_str()
300 .expect("Could not encode test1"),
301 FileType::Gemini,
302 ),
303 create_test_internal_file(
304 layout_path.to_str().expect("Could not encode layout"),
305 FileType::Layout,
306 ),
307 create_test_internal_file(
308 test_dir
309 .join("test2.gmi")
310 .to_str()
311 .expect("Could not encode test2"),
312 FileType::Gemini,
313 ),
314 create_test_internal_file(
315 test_dir
316 .join("test3.gmi")
317 .to_str()
318 .expect("Could not encode test3"),
319 FileType::Gemini,
320 ),
321 ];
322
323 let _ = handler.get_layout_or_panic(&files[1..]);
324
325 // Test with slice
326 handler.handle_all(
327 &test_dir,
328 &test_dir.join("output_html"),
329 &test_dir.join("output_gemini"),
330 &files[1..], // Test with slice of last three elements
331 );
332 }
333 }