]> git.r.bdr.sh - rbdr/page/blob - src/file_handler/mod.rs
Address pedantic issues
[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: None,
212 };
213
214 let path = PathBuf::from("test.whatever");
215 assert!(matches!(handler.identify(&path), FileType::Gemini));
216 }
217
218 #[test]
219 fn test_handle_all_empty_files() {
220 let handler = FileHandler::default();
221 let files: Vec<File> = vec![];
222
223 // Should not panic with empty vector
224 handler.handle_all(
225 &PathBuf::from("source"),
226 &PathBuf::from("output_html"),
227 &PathBuf::from("output_gemini"),
228 &files,
229 );
230 }
231
232 #[test]
233 fn test_handle_with_layout() {
234 let handler = FileHandler {
235 layout: Some("test layout".to_string()),
236 ..Default::default()
237 };
238
239 let test_dir = setup_test_dir();
240 create_dir_all(test_dir.join("output_html"))
241 .expect("Could not create output html test directory");
242 create_dir_all(test_dir.join("output_gemini"))
243 .expect("Could not create output gemini test directory");
244 let test_path = test_dir.join("test.gmi");
245 create_test_file(&test_path, "");
246 let file = create_test_internal_file(
247 test_path
248 .to_str()
249 .expect("Could not encode gemini test file"),
250 FileType::Gemini,
251 );
252
253 // Should not panic with valid layout
254 handler.handle(
255 &test_dir,
256 &test_dir.join("output_html"),
257 &test_dir.join("output_gemini"),
258 &file,
259 );
260 }
261
262 #[test]
263 #[should_panic(expected = "Layout should be initialized before handling files")]
264 fn test_handle_without_layout() {
265 let handler = FileHandler::default();
266 let file = create_test_internal_file("test.gmi", FileType::Gemini);
267
268 handler.handle(
269 &PathBuf::from("source"),
270 &PathBuf::from("output_html"),
271 &PathBuf::from("output_gemini"),
272 &file,
273 );
274 }
275
276 #[test]
277 fn test_slice_handling() {
278 let test_dir = setup_test_dir();
279 let layout_path = test_dir.join("_layout.html");
280 create_test_file(&layout_path, "");
281 create_test_file(&test_dir.join("test1.gmi"), "");
282 create_test_file(&test_dir.join("test2.gmi"), "");
283 create_test_file(&test_dir.join("test3.gmi"), "");
284 create_dir_all(test_dir.join("output_html"))
285 .expect("Could not create output html test directory");
286 create_dir_all(test_dir.join("output_gemini"))
287 .expect("Could not create output gemini test directory");
288
289 let mut handler = FileHandler::default();
290 let files = [
291 create_test_internal_file(
292 test_dir
293 .join("test1.gmi")
294 .to_str()
295 .expect("Could not encode test1"),
296 FileType::Gemini,
297 ),
298 create_test_internal_file(
299 layout_path.to_str().expect("Could not encode layout"),
300 FileType::Layout,
301 ),
302 create_test_internal_file(
303 test_dir
304 .join("test2.gmi")
305 .to_str()
306 .expect("Could not encode test2"),
307 FileType::Gemini,
308 ),
309 create_test_internal_file(
310 test_dir
311 .join("test3.gmi")
312 .to_str()
313 .expect("Could not encode test3"),
314 FileType::Gemini,
315 ),
316 ];
317
318 let _ = handler.get_layout_or_panic(&files[1..]);
319
320 // Test with slice
321 handler.handle_all(
322 &test_dir,
323 &test_dir.join("output_html"),
324 &test_dir.join("output_gemini"),
325 &files[1..], // Test with slice of last three elements
326 );
327 }
328 }