pub struct Strategy {} use std::fs::{File as IOFile, create_dir_all, read_to_string}; use std::io::Write; use std::path::Path; use crate::file_handler::{File, FileType, Strategy as FileHandlerStrategy}; use gema_texto::{gemini_parser::parse, html_renderer::render_html}; impl Strategy { fn is_title(line: &str) -> bool { line.starts_with("--- title:") } fn is_description(line: &str) -> bool { line.starts_with("--- description:") } fn get_title(line: &str) -> &str { line.split_once("--- title:").unwrap().1 } fn get_description(line: &str) -> &str { line.split_once("--- description:").unwrap().1 } } impl FileHandlerStrategy for Strategy { fn is(&self, path: &Path) -> bool { if let Some(extension) = path.extension() { return !path.is_dir() && extension == "gmi"; } false } fn identify(&self) -> FileType { FileType::Gemini } fn can_handle(&self, file_type: &FileType) -> bool { matches!(file_type, FileType::Gemini) } fn handle_html(&self, source: &Path, destination: &Path, file: &File, layout: &str) { let gemini_contents = read_to_string(&file.path).unwrap(); // Front matter extraction let lines: Vec<&str> = gemini_contents.split('\n').collect(); let mut lines_found = 0; let mut title = ""; let mut description = ""; if let Some(slice) = lines.get(..2) { for line in slice { if Strategy::is_title(line) { title = Strategy::get_title(line).trim(); lines_found += 1; continue; } if Strategy::is_description(line) { description = Strategy::get_description(line).trim(); lines_found += 1; continue; } } } let gemini_source = lines[lines_found..].join("\n"); let content_html = render_html(&parse(&gemini_source[..])); let generated_html = layout .replace("{{ title }}", title) .replace("{{ description }}", description) .replace("{{ content }}", &content_html[..]); let relative_path = file.path.strip_prefix(source).unwrap(); let mut complete_destination = destination.join(relative_path); complete_destination.set_extension("html"); let destination_parent = complete_destination.parent().unwrap(); create_dir_all(destination_parent).unwrap(); let mut destination_file = IOFile::create(&complete_destination).unwrap(); destination_file .write_all(generated_html.as_bytes()) .unwrap(); } fn handle_gemini(&self, source: &Path, destination: &Path, file: &File) { let gemini_contents = read_to_string(&file.path).unwrap(); // Front matter extraction let lines: Vec<&str> = gemini_contents.split('\n').collect(); let mut lines_found = 0; if let Some(slice) = lines.get(..2) { for line in slice { if Strategy::is_title(line) { lines_found += 1; continue; } if Strategy::is_description(line) { lines_found += 1; continue; } } } let gemini_source = lines[lines_found..].join("\n"); let relative_path = file.path.strip_prefix(source).unwrap(); let complete_destination = destination.join(relative_path); let destination_parent = complete_destination.parent().unwrap(); create_dir_all(destination_parent).unwrap(); let mut destination_file = IOFile::create(&complete_destination).unwrap(); destination_file .write_all(gemini_source.as_bytes()) .unwrap(); } } #[cfg(test)] mod tests { use std::fs::create_dir_all; use super::*; use test_utilities::*; #[test] fn detects_title() { assert!(Strategy::is_title("--- title: Hello!")); } #[test] fn does_not_detect_other_keys_as_title() { assert!(!Strategy::is_title("--- description: Hello!")); } #[test] fn detects_description() { assert!(Strategy::is_description("--- description: What is this?")); } #[test] fn does_not_detect_other_keys_as_description() { assert!(!Strategy::is_description("--- title: What is this?")); } #[test] fn extracts_title() { assert_eq!(Strategy::get_title("--- title: Hello!").trim(), "Hello!"); } #[test] fn extracts_description() { assert_eq!( Strategy::get_description("--- description: What is this?").trim(), "What is this?" ); } #[test] fn identifies_gemini_file() { let test_dir = setup_test_dir(); create_test_file(&test_dir.join("test.gmi"), ""); let strategy = Strategy {}; assert!(strategy.is(&test_dir.join("test.gmi"))); } #[test] fn rejects_non_gemini_file() { let test_dir = setup_test_dir(); create_test_file(&test_dir.join("_layout.html"), ""); create_test_file(&test_dir.join("image.png"), ""); let strategy = Strategy {}; assert!(!strategy.is(&test_dir.join("_layout.html"))); assert!(!strategy.is(&test_dir.join("image.png"))); assert!(!strategy.is(&test_dir)); } #[test] fn identifies_gemini_type() { let strategy = Strategy {}; assert!(matches!(strategy.identify(), FileType::Gemini)); } #[test] fn handles_gemini_type() { let strategy = Strategy {}; assert!(strategy.can_handle(&FileType::Gemini)); } #[test] fn rejects_non_gemini_types() { let strategy = Strategy {}; assert!(!strategy.can_handle(&FileType::Layout)); assert!(!strategy.can_handle(&FileType::File)); assert!(!strategy.can_handle(&FileType::Unknown)); } #[test] fn handles_html_generation() { let test_dir = setup_test_dir(); let source_dir = test_dir.join("source"); let output_dir = test_dir.join("output"); create_dir_all(&source_dir).expect("Could not create source test directory"); create_dir_all(&output_dir).expect("Could not create output test directory"); let layout = "\ {{ title }} {{ content }} "; create_test_file( &source_dir.join("test.gmi"), "\ --- title: Page Is Cool! --- description: My Description # Test Hello world ", ); let strategy = Strategy {}; let file = File { path: source_dir.join("test.gmi"), file_type: FileType::Gemini, }; strategy.handle_html(&source_dir, &output_dir, &file, layout); let html_output = output_dir.join("test.html"); assert!(html_output.exists()); assert_file_contents( &html_output, "\ Page Is Cool!

Test

Hello world

", ); } #[test] fn handles_gemini_generation() { let test_dir = setup_test_dir(); let source_dir = test_dir.join("source"); let output_dir = test_dir.join("output"); create_dir_all(&source_dir).expect("Could not create source test directory"); create_dir_all(&output_dir).expect("Could not create output test directory"); create_test_file( &source_dir.join("test.gmi"), "\ --- title: Page Is Cool! --- description: My Description # Test Hello world ", ); let strategy = Strategy {}; let file = File { path: source_dir.join("test.gmi"), file_type: FileType::Gemini, }; strategy.handle_gemini(&source_dir, &output_dir, &file); let gemini_output = output_dir.join("test.gmi"); assert!(gemini_output.exists()); assert_file_contents( &gemini_output, "\ # Test Hello world ", ); } #[test] fn handles_nested_structure() { let test_dir = setup_test_dir(); let source_dir = test_dir.join("source"); let output_dir = test_dir.join("output"); create_dir_all(source_dir.join("nested")).expect("Could not create source test directory"); create_dir_all(&output_dir).expect("Could not create output test directory"); let layout = "\ {{ title }} {{ content }} "; create_test_file( &source_dir.join("nested/test.gmi"), "\ --- title: Page Is Cool! --- description: My Description # Test Hello world ", ); let strategy = Strategy {}; let file = File { path: source_dir.join("nested/test.gmi"), file_type: FileType::Gemini, }; strategy.handle_html(&source_dir, &output_dir, &file, layout); let html_output = output_dir.join("nested/test.html"); assert!(html_output.exists()); assert_file_contents( &html_output, "\ Page Is Cool!

Test

Hello world

", ); } }