3 use file_strategies::file::Strategy as FileStrategy;
4 use file_strategies::gemini::Strategy as GeminiStrategy;
5 use file_strategies::layout::Strategy as LayoutStrategy;
7 use std::fs::read_to_string;
8 use std::path::{Path, PathBuf};
10 pub struct FileHandler {
11 pub strategies: Vec<Box<dyn FileHandlerStrategy>>,
12 pub layout: Option<String>,
15 impl Default for FileHandler {
16 fn default() -> FileHandler {
19 Box::new(GeminiStrategy {}),
20 Box::new(LayoutStrategy {}),
21 Box::new(FileStrategy {}),
29 pub fn identify(&self, path: &Path) -> FileType {
30 for strategy in self.strategies.iter() {
31 if strategy.is(path) {
32 return strategy.identify();
38 pub fn get_layout_or_panic(&mut self, files: &[File]) -> Result<(), &str> {
40 if file.file_type == FileType::Layout {
41 let layout_text = read_to_string(&file.path).unwrap();
42 self.layout = Some(layout_text);
46 Err("No layout found. Please ensure there's a _layout.html file at the root")
52 html_destination: &Path,
53 gemini_destination: &Path,
56 files.iter().for_each(|file| {
57 self.handle(source, html_destination, gemini_destination, file);
64 html_destination: &Path,
65 gemini_destination: &Path,
68 if let Some(strategy) = self
71 .find(|s| s.can_handle(&file.file_type))
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);
83 pub trait FileHandlerStrategy {
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);
91 #[derive(Debug, Clone, PartialEq)]
99 #[derive(PartialEq, Debug)]
102 pub file_type: FileType,
107 use std::fs::create_dir_all;
108 use std::path::PathBuf;
112 use test_utilities::*;
114 fn create_test_internal_file(path: &str, file_type: FileType) -> File {
116 path: PathBuf::from(path),
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));
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));
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));
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));
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, "");
155 let mut handler = FileHandler::default();
157 create_test_internal_file("test.gmi", FileType::Gemini),
158 create_test_internal_file(
159 &layout_path.to_str().expect("Could not encode layout"),
162 create_test_internal_file("regular.html", FileType::File),
165 assert!(handler.get_layout_or_panic(&files).is_ok());
169 fn test_get_layout_failure() {
170 let mut handler = FileHandler::default();
172 create_test_internal_file("test.gmi", FileType::Gemini),
173 create_test_internal_file("regular.html", FileType::File),
176 assert!(handler.get_layout_or_panic(&files).is_err());
179 // Mock strategy for testing
180 struct MockStrategy {
185 impl FileHandlerStrategy for MockStrategy {
186 fn is(&self, _path: &Path) -> bool {
190 fn identify(&self) -> FileType {
191 self.file_type.clone()
194 fn can_handle(&self, file_type: &FileType) -> bool {
195 &self.file_type == file_type
198 fn handle_html(&self, _source: &Path, _destination: &Path, _file: &File, _layout: &str) {}
199 fn handle_gemini(&self, _source: &Path, _destination: &Path, _file: &File) {}
203 fn test_custom_strategy() {
204 let mock_strategy = MockStrategy {
206 file_type: FileType::Gemini,
209 let handler = FileHandler {
210 strategies: vec![Box::new(mock_strategy)],
214 let path = PathBuf::from("test.whatever");
215 assert!(matches!(handler.identify(&path), FileType::Gemini));
219 fn test_handle_all_empty_files() {
220 let handler = FileHandler::default();
221 let files: Vec<File> = vec![];
223 // Should not panic with empty vector
225 &PathBuf::from("source"),
226 &PathBuf::from("output_html"),
227 &PathBuf::from("output_gemini"),
233 fn test_handle_with_layout() {
234 let mut handler = FileHandler::default();
235 handler.layout = Some("test layout".to_string());
237 let test_dir = setup_test_dir();
238 create_dir_all(&test_dir.join("output_html"))
239 .expect("Could not create output html test directory");
240 create_dir_all(&test_dir.join("output_gemini"))
241 .expect("Could not create output gemini test directory");
242 let test_path = test_dir.join("test.gmi");
243 create_test_file(&test_path, "");
244 let file = create_test_internal_file(
247 .expect("Could not encode gemini test file"),
251 // Should not panic with valid layout
254 &test_dir.join("output_html"),
255 &test_dir.join("output_gemini"),
261 #[should_panic(expected = "Layout should be initialized before handling files")]
262 fn test_handle_without_layout() {
263 let handler = FileHandler::default();
264 let file = create_test_internal_file("test.gmi", FileType::Gemini);
267 &PathBuf::from("source"),
268 &PathBuf::from("output_html"),
269 &PathBuf::from("output_gemini"),
275 fn test_slice_handling() {
276 let test_dir = setup_test_dir();
277 let layout_path = test_dir.join("_layout.html");
278 create_test_file(&layout_path, "");
279 create_test_file(&test_dir.join("test1.gmi"), "");
280 create_test_file(&test_dir.join("test2.gmi"), "");
281 create_test_file(&test_dir.join("test3.gmi"), "");
282 create_dir_all(&test_dir.join("output_html"))
283 .expect("Could not create output html test directory");
284 create_dir_all(&test_dir.join("output_gemini"))
285 .expect("Could not create output gemini test directory");
287 let mut handler = FileHandler::default();
289 create_test_internal_file(
293 .expect("Could not encode test1"),
296 create_test_internal_file(
297 &layout_path.to_str().expect("Could not encode layout"),
300 create_test_internal_file(
304 .expect("Could not encode test2"),
307 create_test_internal_file(
311 .expect("Could not encode test3"),
316 let _ = handler.get_layout_or_panic(&files[1..]);
321 &test_dir.join("output_html"),
322 &test_dir.join("output_gemini"),
323 &files[1..], // Test with slice of last three elements