3 use std::fs::{create_dir_all, read_to_string, File as IOFile};
7 use crate::file_handler::{File, FileType, Strategy as FileHandlerStrategy};
8 use crate::gemini_parser::parse;
9 use crate::html_renderer::render_html;
12 fn is_title(line: &str) -> bool {
13 line.starts_with("--- title:")
16 fn is_description(line: &str) -> bool {
17 line.starts_with("--- description:")
20 fn get_title(line: &str) -> &str {
21 line.split_once("--- title:").unwrap().1
24 fn get_description(line: &str) -> &str {
25 line.split_once("--- description:").unwrap().1
29 impl FileHandlerStrategy for Strategy {
30 fn is(&self, path: &Path) -> 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 {
42 matches!(file_type, FileType::Gemini)
45 fn handle_html(&self, source: &Path, destination: &Path, file: &File, layout: &str) {
46 let gemini_contents = read_to_string(&file.path).unwrap();
48 // Front matter extraction
49 let lines: Vec<&str> = gemini_contents.split('\n').collect();
50 let mut lines_found = 0;
52 let mut description = "";
53 if let Some(slice) = lines.get(..2) {
55 if Strategy::is_title(line) {
56 title = Strategy::get_title(line).trim();
60 if Strategy::is_description(line) {
61 description = Strategy::get_description(line).trim();
68 let gemini_source = lines[lines_found..].join("\n");
69 let content_html = render_html(&parse(&gemini_source[..]));
71 let generated_html = layout
72 .replace("{{ title }}", title)
73 .replace("{{ description }}", description)
74 .replace("{{ content }}", &content_html[..]);
76 let relative_path = file.path.strip_prefix(source).unwrap();
77 let mut complete_destination = destination.join(relative_path);
78 complete_destination.set_extension("html");
79 let destination_parent = complete_destination.parent().unwrap();
80 create_dir_all(destination_parent).unwrap();
82 let mut destination_file = IOFile::create(&complete_destination).unwrap();
84 .write_all(generated_html.as_bytes())
88 fn handle_gemini(&self, source: &Path, destination: &Path, file: &File) {
89 let gemini_contents = read_to_string(&file.path).unwrap();
91 // Front matter extraction
92 let lines: Vec<&str> = gemini_contents.split('\n').collect();
93 let mut lines_found = 0;
94 if let Some(slice) = lines.get(..2) {
96 if Strategy::is_title(line) {
100 if Strategy::is_description(line) {
107 let gemini_source = lines[lines_found..].join("\n");
109 let relative_path = file.path.strip_prefix(source).unwrap();
110 let complete_destination = destination.join(relative_path);
111 let destination_parent = complete_destination.parent().unwrap();
112 create_dir_all(destination_parent).unwrap();
114 let mut destination_file = IOFile::create(&complete_destination).unwrap();
116 .write_all(gemini_source.as_bytes())
123 use std::fs::create_dir_all;
127 use test_utilities::*;
131 assert!(Strategy::is_title("--- title: Hello!"));
135 fn does_not_detect_other_keys_as_title() {
136 assert!(!Strategy::is_title("--- description: Hello!"));
140 fn detects_description() {
141 assert!(Strategy::is_description("--- description: What is this?"));
145 fn does_not_detect_other_keys_as_description() {
146 assert!(!Strategy::is_description("--- title: What is this?"));
150 fn extracts_title() {
151 assert_eq!(Strategy::get_title("--- title: Hello!").trim(), "Hello!");
155 fn extracts_description() {
158 ::get_description("--- description: What is this?")
165 fn identifies_gemini_file() {
166 let test_dir = setup_test_dir();
167 create_test_file(&test_dir.join("test.gmi"), "");
168 let strategy = Strategy {};
169 assert!(strategy.is(&test_dir.join("test.gmi")));
173 fn rejects_non_gemini_file() {
174 let test_dir = setup_test_dir();
175 create_test_file(&test_dir.join("_layout.html"), "");
176 create_test_file(&test_dir.join("image.png"), "");
177 let strategy = Strategy {};
178 assert!(!strategy.is(&test_dir.join("_layout.html")));
179 assert!(!strategy.is(&test_dir.join("image.png")));
180 assert!(!strategy.is(&test_dir));
184 fn identifies_gemini_type() {
185 let strategy = Strategy {};
186 assert!(matches!(strategy.identify(), FileType::Gemini));
190 fn handles_gemini_type() {
191 let strategy = Strategy {};
192 assert!(strategy.can_handle(&FileType::Gemini));
196 fn rejects_non_gemini_types() {
197 let strategy = Strategy {};
198 assert!(!strategy.can_handle(&FileType::Layout));
199 assert!(!strategy.can_handle(&FileType::File));
200 assert!(!strategy.can_handle(&FileType::Unknown));
204 fn handles_html_generation() {
205 let test_dir = setup_test_dir();
206 let source_dir = test_dir.join("source");
207 let output_dir = test_dir.join("output");
208 create_dir_all(&source_dir).expect("Could not create source test directory");
209 create_dir_all(&output_dir).expect("Could not create output test directory");
213 <title>{{ title }}</title>
214 <meta name=\"description\" content=\"{{ description }}\">
216 <body>{{ content }}</body>
220 &source_dir.join("test.gmi"),
222 --- title: Page Is Cool!
223 --- description: My Description
229 let strategy = Strategy {};
231 path: source_dir.join("test.gmi"),
232 file_type: FileType::Gemini,
235 strategy.handle_html(&source_dir, &output_dir, &file, layout);
237 let html_output = output_dir.join("test.html");
238 assert!(html_output.exists());
239 assert_file_contents(
244 <title>Page Is Cool!</title>
245 <meta name=\"description\" content=\"My Description\">
247 <body><section class=\"h1\">
258 fn handles_gemini_generation() {
259 let test_dir = setup_test_dir();
260 let source_dir = test_dir.join("source");
261 let output_dir = test_dir.join("output");
262 create_dir_all(&source_dir).expect("Could not create source test directory");
263 create_dir_all(&output_dir).expect("Could not create output test directory");
265 &source_dir.join("test.gmi"),
267 --- title: Page Is Cool!
268 --- description: My Description
274 let strategy = Strategy {};
276 path: source_dir.join("test.gmi"),
277 file_type: FileType::Gemini,
280 strategy.handle_gemini(&source_dir, &output_dir, &file);
282 let gemini_output = output_dir.join("test.gmi");
283 assert!(gemini_output.exists());
284 assert_file_contents(
294 fn handles_nested_structure() {
295 let test_dir = setup_test_dir();
296 let source_dir = test_dir.join("source");
297 let output_dir = test_dir.join("output");
298 create_dir_all(source_dir.join("nested")).expect("Could not create source test directory");
299 create_dir_all(&output_dir).expect("Could not create output test directory");
303 <title>{{ title }}</title>
304 <meta name=\"description\" content=\"{{ description }}\">
306 <body>{{ content }}</body>
310 &source_dir.join("nested/test.gmi"),
312 --- title: Page Is Cool!
313 --- description: My Description
319 let strategy = Strategy {};
321 path: source_dir.join("nested/test.gmi"),
322 file_type: FileType::Gemini,
325 strategy.handle_html(&source_dir, &output_dir, &file, layout);
327 let html_output = output_dir.join("nested/test.html");
328 assert!(html_output.exists());
329 assert_file_contents(
334 <title>Page Is Cool!</title>
335 <meta name=\"description\" content=\"My Description\">
337 <body><section class=\"h1\">