]>
Commit | Line | Data |
---|---|---|
1e2d00b6 RBR |
1 | mod file_strategies; |
2 | ||
3 | use file_strategies::file::Strategy as FileStrategy; | |
102a4884 RBR |
4 | use file_strategies::gemini::Strategy as GeminiStrategy; |
5 | use file_strategies::layout::Strategy as LayoutStrategy; | |
6 | ||
1e2d00b6 | 7 | use std::path::PathBuf; |
4fd89b80 | 8 | use std::fs::read_to_string; |
1e2d00b6 RBR |
9 | |
10 | pub struct FileHandler { | |
4fd89b80 RBR |
11 | pub strategies: Vec<Box<dyn FileHandlerStrategy>>, |
12 | pub layout: Option<String> | |
1e2d00b6 RBR |
13 | } |
14 | ||
15 | impl Default for FileHandler { | |
16 | fn default() -> FileHandler { | |
17 | FileHandler { | |
102a4884 RBR |
18 | strategies: vec![ |
19 | Box::new(GeminiStrategy{}), | |
20 | Box::new(LayoutStrategy{}), | |
21 | Box::new(FileStrategy{}), | |
4fd89b80 RBR |
22 | ], |
23 | layout: None | |
1e2d00b6 RBR |
24 | } |
25 | } | |
26 | } | |
27 | ||
28 | impl FileHandler { | |
29 | pub fn identify(&self, path: &PathBuf) -> FileType { | |
30 | for strategy in self.strategies.iter() { | |
31 | if strategy.is(&path) { | |
32 | return strategy.identify(); | |
33 | } | |
34 | } | |
35 | FileType::Unknown | |
36 | } | |
37 | ||
260e8ec6 | 38 | pub fn get_layout_or_panic(&mut self, files: &[File]) -> Result<(), &str> { |
4fd89b80 RBR |
39 | for file in files { |
40 | match file.file_type { | |
41 | FileType::Layout => { | |
42 | let layout_text = read_to_string(&file.path).unwrap(); | |
43 | self.layout = Some(layout_text); | |
48ea9080 | 44 | return Ok(()); |
4fd89b80 RBR |
45 | }, |
46 | _ => {} | |
47 | } | |
48 | } | |
48ea9080 | 49 | Err("No layout found. Please ensure there's a _layout.html file at the root") |
4fd89b80 RBR |
50 | } |
51 | ||
260e8ec6 RBR |
52 | pub fn handle_all(&self, source: &PathBuf, html_destination: &PathBuf, gemini_destination: &PathBuf, files: &[File]) { |
53 | files.iter().for_each(|file| { | |
dd0a540c | 54 | self.handle(source, html_destination, gemini_destination, file); |
260e8ec6 | 55 | }); |
4fd89b80 RBR |
56 | } |
57 | ||
dd0a540c | 58 | pub fn handle(&self, source: &PathBuf, html_destination: &PathBuf, gemini_destination: &PathBuf, file: &File) { |
260e8ec6 RBR |
59 | if let Some(strategy) = self.strategies |
60 | .iter() | |
61 | .find(|s| s.can_handle(&file.file_type)) | |
62 | { | |
63 | let layout = self.layout.as_ref() | |
64 | .expect("Layout should be initialized before handling files"); | |
dd0a540c RBR |
65 | strategy.handle_html(source, html_destination, file, layout); |
66 | strategy.handle_gemini(source, gemini_destination, file); | |
67 | return; | |
1e2d00b6 RBR |
68 | } |
69 | } | |
70 | } | |
71 | ||
72 | pub trait FileHandlerStrategy { | |
73 | fn is(&self, path: &PathBuf) -> bool; | |
74 | fn identify(&self) -> FileType; | |
4fd89b80 | 75 | fn can_handle(&self, file_type: &FileType) -> bool; |
260e8ec6 | 76 | fn handle_html(&self, source: &PathBuf, destination: &PathBuf, file: &File, layout: &str); |
dd0a540c | 77 | fn handle_gemini(&self, source: &PathBuf, destination: &PathBuf, file: &File); |
1e2d00b6 RBR |
78 | } |
79 | ||
260e8ec6 | 80 | #[derive(Debug, Clone, PartialEq)] |
1e2d00b6 RBR |
81 | pub enum FileType { |
82 | Gemini, | |
83 | File, | |
84 | Layout, | |
85 | Unknown, | |
86 | } | |
87 | ||
260e8ec6 | 88 | #[derive(PartialEq, Debug)] |
1e2d00b6 RBR |
89 | pub struct File { |
90 | pub path: PathBuf, | |
91 | pub file_type: FileType, | |
92 | } | |
260e8ec6 RBR |
93 | |
94 | ||
95 | #[cfg(test)] | |
96 | mod tests { | |
97 | use super::*; | |
98 | use std::path::PathBuf; | |
99 | ||
100 | fn create_test_file(path: &str, file_type: FileType) -> File { | |
101 | File { | |
102 | path: PathBuf::from(path), | |
103 | file_type, | |
104 | } | |
105 | } | |
106 | ||
107 | #[test] | |
108 | fn test_identify_gemini_file() { | |
109 | let handler = FileHandler::default(); | |
110 | let path = PathBuf::from("test.gmi"); | |
111 | assert!(matches!(handler.identify(&path), FileType::Gemini)); | |
112 | } | |
113 | ||
114 | #[test] | |
115 | fn test_identify_layout_file() { | |
116 | let handler = FileHandler::default(); | |
117 | let path = PathBuf::from("_layout.html"); | |
118 | assert!(matches!(handler.identify(&path), FileType::Layout)); | |
119 | } | |
120 | ||
121 | #[test] | |
122 | fn test_identify_regular_file() { | |
123 | let handler = FileHandler::default(); | |
124 | let path = PathBuf::from("regular.html"); | |
125 | assert!(matches!(handler.identify(&path), FileType::File)); | |
126 | } | |
127 | ||
128 | #[test] | |
129 | fn test_identify_unknown_file() { | |
130 | let handler = FileHandler::default(); | |
131 | let path = PathBuf::from("tests/fixtures"); | |
132 | assert!(matches!(handler.identify(&path), FileType::Unknown)); | |
133 | } | |
134 | ||
135 | #[test] | |
136 | fn test_get_layout_success() { | |
137 | let mut handler = FileHandler::default(); | |
138 | let files = vec![ | |
139 | create_test_file("test.gmi", FileType::Gemini), | |
140 | create_test_file("tests/fixtures/_layout.html", FileType::Layout), | |
141 | create_test_file("regular.html", FileType::File), | |
142 | ]; | |
143 | ||
144 | assert!(handler.get_layout_or_panic(&files).is_ok()); | |
145 | } | |
146 | ||
147 | #[test] | |
148 | fn test_get_layout_failure() { | |
149 | let mut handler = FileHandler::default(); | |
150 | let files = vec![ | |
151 | create_test_file("test.gmi", FileType::Gemini), | |
152 | create_test_file("regular.html", FileType::File), | |
153 | ]; | |
154 | ||
155 | assert!(handler.get_layout_or_panic(&files).is_err()); | |
156 | } | |
157 | ||
158 | // Mock strategy for testing | |
159 | struct MockStrategy { | |
160 | is_match: bool, | |
161 | file_type: FileType, | |
162 | } | |
163 | ||
164 | impl FileHandlerStrategy for MockStrategy { | |
165 | fn is(&self, _path: &PathBuf) -> bool { | |
166 | self.is_match | |
167 | } | |
168 | ||
169 | fn identify(&self) -> FileType { | |
170 | self.file_type.clone() | |
171 | } | |
172 | ||
173 | fn can_handle(&self, file_type: &FileType) -> bool { | |
174 | &self.file_type == file_type | |
175 | } | |
176 | ||
177 | fn handle_html(&self, _source: &PathBuf, _destination: &PathBuf, _file: &File, _layout: &str) {} | |
178 | fn handle_gemini(&self, _source: &PathBuf, _destination: &PathBuf, _file: &File) {} | |
179 | } | |
180 | ||
181 | #[test] | |
182 | fn test_custom_strategy() { | |
183 | let mock_strategy = MockStrategy { | |
184 | is_match: true, | |
185 | file_type: FileType::Gemini, | |
186 | }; | |
187 | ||
188 | let handler = FileHandler { | |
189 | strategies: vec![Box::new(mock_strategy)], | |
190 | layout: None, | |
191 | }; | |
192 | ||
193 | let path = PathBuf::from("test.whatever"); | |
194 | assert!(matches!(handler.identify(&path), FileType::Gemini)); | |
195 | } | |
196 | ||
197 | #[test] | |
198 | fn test_handle_all_empty_files() { | |
199 | let handler = FileHandler::default(); | |
200 | let files: Vec<File> = vec![]; | |
201 | ||
202 | // Should not panic with empty vector | |
203 | handler.handle_all( | |
204 | &PathBuf::from("source"), | |
205 | &PathBuf::from("tests/fixtures/output"), | |
206 | &PathBuf::from("tests/fixtures/output"), | |
207 | &files | |
208 | ); | |
209 | } | |
210 | ||
211 | #[test] | |
212 | fn test_handle_with_layout() { | |
213 | let mut handler = FileHandler::default(); | |
214 | handler.layout = Some("test layout".to_string()); | |
215 | ||
216 | let file = create_test_file("tests/fixtures/test1.gmi", FileType::Gemini); | |
217 | ||
218 | // Should not panic with valid layout | |
219 | handler.handle( | |
220 | &PathBuf::from(""), | |
221 | &PathBuf::from("tests/fixtures/output"), | |
222 | &PathBuf::from("tests/fixtures/output"), | |
223 | &file | |
224 | ); | |
225 | } | |
226 | ||
227 | #[test] | |
228 | #[should_panic(expected = "Layout should be initialized before handling files")] | |
229 | fn test_handle_without_layout() { | |
230 | let handler = FileHandler::default(); | |
231 | let file = create_test_file("test.gmi", FileType::Gemini); | |
232 | ||
233 | handler.handle( | |
234 | &PathBuf::from("source"), | |
235 | &PathBuf::from("tests/fixtures/output"), | |
236 | &PathBuf::from("tests/fixtures/output"), | |
237 | &file | |
238 | ); | |
239 | } | |
240 | ||
241 | #[test] | |
242 | fn test_slice_handling() { | |
243 | let mut handler = FileHandler::default(); | |
244 | let files = vec![ | |
245 | create_test_file("tests/fixtures/test1.gmi", FileType::Gemini), | |
246 | create_test_file("tests/fixtures/_layout.html", FileType::Layout), | |
247 | create_test_file("tests/fixtures/test2.gmi", FileType::Gemini), | |
248 | create_test_file("tests/fixtures/test3.gmi", FileType::Gemini), | |
249 | ]; | |
250 | ||
251 | let _ = handler.get_layout_or_panic(&files[1..]); | |
252 | ||
253 | // Test with slice | |
254 | handler.handle_all( | |
255 | &PathBuf::from(""), | |
256 | &PathBuf::from("tests/fixtures/output"), | |
257 | &PathBuf::from("tests/fixtures/output"), | |
258 | &files[1..] // Test with slice of last three elements | |
259 | ); | |
260 | } | |
261 | } |