3 use std::path::PathBuf;
5 use std::fs::{create_dir_all, read_to_string, File as IOFile};
7 use crate::file_handler::{File, FileType, FileHandlerStrategy};
8 use crate::gemini_parser::parse;
9 use crate::html_renderer::render_html;
12 fn is_title(&self, line: &str) -> bool {
13 line.starts_with("--- title:")
16 fn is_description(&self, line: &str) -> bool {
17 line.starts_with("--- description:")
20 fn get_title<'a>(&self, line: &'a str) -> &'a str {
21 line.split_once("--- title:").unwrap().1
24 fn get_description<'a>(&self, line: &'a str) -> &'a str {
25 line.split_once("--- description:").unwrap().1
29 impl FileHandlerStrategy for Strategy {
30 fn is(&self, path: &PathBuf) -> bool {
31 if let Some(extension) = path.extension() {
32 return !path.is_dir() && extension == "gmi"
37 fn identify(&self) -> FileType {
41 fn can_handle(&self, file_type: &FileType) -> bool {
43 FileType::Gemini => true,
48 fn handle_html(&self, source: &PathBuf, destination: &PathBuf, file: &File, layout: &str) {
49 let gemini_contents = read_to_string(&file.path).unwrap();
51 // Front matter extraction
52 let lines: Vec<&str> = gemini_contents.split("\n").collect();
53 let mut lines_found = 0;
55 let mut description = "";
56 if let Some(slice) = lines.get(..2) {
57 for line in slice.iter() {
58 if self.is_title(&line) {
59 title = self.get_title(&line).trim();
60 lines_found = lines_found + 1;
63 if self.is_description(&line) {
64 description = self.get_description(&line).trim();
65 lines_found = lines_found + 1;
71 let gemini_source = lines[lines_found..].join("\n");
72 let content_html = render_html(parse(&gemini_source[..]));
74 let generated_html = layout
75 .replace("{{ title }}", title)
76 .replace("{{ description }}", description)
77 .replace("{{ content }}", &content_html[..]);
80 let relative_path = file.path.strip_prefix(&source).unwrap();
81 let mut complete_destination = destination.join(relative_path);
82 complete_destination.set_extension("html");
83 let destination_parent = complete_destination.parent().unwrap();
84 create_dir_all(destination_parent).unwrap();
86 let mut destination_file = IOFile::create(&complete_destination).unwrap();
87 destination_file.write_all(generated_html.as_bytes()).unwrap();
90 fn handle_gemini(&self, source: &PathBuf, destination: &PathBuf, file: &File) {
91 let gemini_contents = read_to_string(&file.path).unwrap();
93 // Front matter extraction
94 let lines: Vec<&str> = gemini_contents.split("\n").collect();
95 let mut lines_found = 0;
96 if let Some(slice) = lines.get(..2) {
97 for line in slice.iter() {
98 if self.is_title(&line) {
99 lines_found = lines_found + 1;
102 if self.is_description(&line) {
103 lines_found = lines_found + 1;
109 let gemini_source = lines[lines_found..].join("\n");
111 let relative_path = file.path.strip_prefix(&source).unwrap();
112 let complete_destination = destination.join(relative_path);
113 let destination_parent = complete_destination.parent().unwrap();
114 create_dir_all(destination_parent).unwrap();
116 let mut destination_file = IOFile::create(&complete_destination).unwrap();
117 destination_file.write_all(gemini_source.as_bytes()).unwrap();
126 fn setup() -> Strategy {
130 fn fixtures_dir() -> PathBuf {
131 PathBuf::from("tests/fixtures")
134 fn fixture_path(filename: &str) -> PathBuf {
135 fixtures_dir().join(filename)
138 fn read_fixture(filename: &str) -> String {
139 fs::read_to_string(fixture_path(filename))
140 .unwrap_or_else(|_| panic!("Failed to read fixture file: {}", filename))
143 mod front_matter_tests {
148 let strategy = setup();
149 let content = read_fixture("test1.gmi");
150 let first_line = content.lines().next().unwrap();
151 assert!(strategy.is_title(first_line));
155 fn detects_description() {
156 let strategy = setup();
157 let content = read_fixture("test1.gmi");
158 let second_line = content.lines().nth(1).unwrap();
159 assert!(strategy.is_description(second_line));
163 fn extracts_title() {
164 let strategy = setup();
165 let content = read_fixture("test1.gmi");
166 let first_line = content.lines().next().unwrap();
167 assert_eq!(strategy.get_title(first_line).trim(), "Test Title");
171 fn extracts_description() {
172 let strategy = setup();
173 let content = read_fixture("test1.gmi");
174 let second_line = content.lines().nth(1).unwrap();
175 assert_eq!(strategy.get_description(second_line).trim(), "Test Description");
179 mod file_type_tests {
183 fn identifies_gemini_files() {
184 let strategy = setup();
185 assert!(strategy.is(&fixture_path("test1.gmi")));
186 assert!(!strategy.is(&fixture_path("_layout.html")));
187 assert!(!strategy.is(&fixtures_dir()));
191 fn identifies_file_type() {
192 let strategy = setup();
193 assert!(matches!(strategy.identify(), FileType::Gemini));
197 fn handles_correct_file_type() {
198 let strategy = setup();
199 assert!(strategy.can_handle(&FileType::Gemini));
200 assert!(!strategy.can_handle(&FileType::Layout));
201 assert!(!strategy.can_handle(&FileType::File));
202 assert!(!strategy.can_handle(&FileType::Unknown));
206 mod file_handling_tests {
210 fn handles_html_generation() {
211 let strategy = setup();
212 let source = fixtures_dir();
213 let output = fixture_path("output");
214 let layout = read_fixture("_layout.html");
217 path: fixture_path("test1.gmi"),
218 file_type: FileType::Gemini,
221 strategy.handle_html(
228 let generated_path = output.join("test1.html");
229 assert!(generated_path.exists());
231 let content = fs::read_to_string(generated_path.clone()).unwrap();
232 assert!(content.contains("Test Title"));
233 assert!(content.contains("<h1>"));
236 let _ = fs::remove_file(generated_path);
240 fn handles_gemini_generation() {
241 let strategy = setup();
242 let source = fixtures_dir();
243 let output = fixture_path("output");
246 path: fixture_path("test1.gmi"),
247 file_type: FileType::Gemini,
250 strategy.handle_gemini(
256 let generated_path = output.join("test1.gmi");
257 assert!(generated_path.exists());
259 let content = fs::read_to_string(&generated_path).unwrap();
260 assert!(content.contains("# Heading"));
261 assert!(!content.contains("Test Title")); // Front matter should be removed
264 let _ = fs::remove_file(generated_path);
268 fn handles_nested_structure() {
269 let strategy = setup();
270 let source = fixtures_dir();
271 let output = fixture_path("output");
272 let layout = read_fixture("_layout.html");
275 path: fixture_path("nested/nested.gmi"),
276 file_type: FileType::Gemini,
279 strategy.handle_html(
286 let generated_path = output.join("nested").join("nested.html");
287 assert!(generated_path.exists());
290 let _ = fs::remove_file(generated_path);
291 let _ = fs::remove_dir(output.join("nested"));