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 handler = FileHandler {
235 layout: Some("test layout".to_string()),
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(
249 .expect("Could not encode gemini test file"),
253 // Should not panic with valid layout
256 &test_dir.join("output_html"),
257 &test_dir.join("output_gemini"),
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);
269 &PathBuf::from("source"),
270 &PathBuf::from("output_html"),
271 &PathBuf::from("output_gemini"),
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");
289 let mut handler = FileHandler::default();
291 create_test_internal_file(
295 .expect("Could not encode test1"),
298 create_test_internal_file(
299 layout_path.to_str().expect("Could not encode layout"),
302 create_test_internal_file(
306 .expect("Could not encode test2"),
309 create_test_internal_file(
313 .expect("Could not encode test3"),
318 let _ = handler.get_layout_or_panic(&files[1..]);
323 &test_dir.join("output_html"),
324 &test_dir.join("output_gemini"),
325 &files[1..], // Test with slice of last three elements