]> git.r.bdr.sh - rbdr/page/blame - src/file_handler/file_strategies/gemini.rs
Address pedantic issues
[rbdr/page] / src / file_handler / file_strategies / gemini.rs
CommitLineData
102a4884
RBR
1pub struct Strategy {}
2
68fa37d6 3use std::fs::{create_dir_all, read_to_string, File as IOFile};
5732d284
RBR
4use std::io::Write;
5use std::path::Path;
102a4884 6
8766e441 7use crate::file_handler::{File, FileType, Strategy as FileHandlerStrategy};
68fa37d6 8use crate::gemini_parser::parse;
260e8ec6 9use crate::html_renderer::render_html;
68fa37d6
RBR
10
11impl Strategy {
8766e441 12 fn is_title(line: &str) -> bool {
68fa37d6
RBR
13 line.starts_with("--- title:")
14 }
15
8766e441 16 fn is_description(line: &str) -> bool {
68fa37d6
RBR
17 line.starts_with("--- description:")
18 }
19
8766e441 20 fn get_title(line: &str) -> &str {
68fa37d6
RBR
21 line.split_once("--- title:").unwrap().1
22 }
23
8766e441 24 fn get_description(line: &str) -> &str {
68fa37d6
RBR
25 line.split_once("--- description:").unwrap().1
26 }
27}
102a4884
RBR
28
29impl FileHandlerStrategy for Strategy {
5732d284 30 fn is(&self, path: &Path) -> bool {
102a4884 31 if let Some(extension) = path.extension() {
5732d284 32 return !path.is_dir() && extension == "gmi";
102a4884
RBR
33 }
34 false
35 }
36
37 fn identify(&self) -> FileType {
38 FileType::Gemini
39 }
40
4fd89b80 41 fn can_handle(&self, file_type: &FileType) -> bool {
5732d284 42 matches!(file_type, FileType::Gemini)
102a4884
RBR
43 }
44
5732d284 45 fn handle_html(&self, source: &Path, destination: &Path, file: &File, layout: &str) {
68fa37d6
RBR
46 let gemini_contents = read_to_string(&file.path).unwrap();
47
48 // Front matter extraction
8766e441 49 let lines: Vec<&str> = gemini_contents.split('\n').collect();
68fa37d6
RBR
50 let mut lines_found = 0;
51 let mut title = "";
52 let mut description = "";
260e8ec6 53 if let Some(slice) = lines.get(..2) {
8766e441
RBR
54 for line in slice {
55 if Strategy::is_title(line) {
56 title = Strategy::get_title(line).trim();
5732d284 57 lines_found += 1;
260e8ec6
RBR
58 continue;
59 }
8766e441
RBR
60 if Strategy::is_description(line) {
61 description = Strategy::get_description(line).trim();
5732d284 62 lines_found += 1;
260e8ec6
RBR
63 continue;
64 }
68fa37d6
RBR
65 }
66 }
67
68 let gemini_source = lines[lines_found..].join("\n");
8766e441 69 let content_html = render_html(&parse(&gemini_source[..]));
68fa37d6
RBR
70
71 let generated_html = layout
72 .replace("{{ title }}", title)
73 .replace("{{ description }}", description)
74 .replace("{{ content }}", &content_html[..]);
75
5732d284 76 let relative_path = file.path.strip_prefix(source).unwrap();
0e3bcda2
RBR
77 let mut complete_destination = destination.join(relative_path);
78 complete_destination.set_extension("html");
68fa37d6
RBR
79 let destination_parent = complete_destination.parent().unwrap();
80 create_dir_all(destination_parent).unwrap();
81
82 let mut destination_file = IOFile::create(&complete_destination).unwrap();
5732d284
RBR
83 destination_file
84 .write_all(generated_html.as_bytes())
85 .unwrap();
102a4884 86 }
dd0a540c 87
5732d284 88 fn handle_gemini(&self, source: &Path, destination: &Path, file: &File) {
dd0a540c
RBR
89 let gemini_contents = read_to_string(&file.path).unwrap();
90
91 // Front matter extraction
8766e441 92 let lines: Vec<&str> = gemini_contents.split('\n').collect();
dd0a540c 93 let mut lines_found = 0;
260e8ec6 94 if let Some(slice) = lines.get(..2) {
8766e441
RBR
95 for line in slice {
96 if Strategy::is_title(line) {
5732d284 97 lines_found += 1;
260e8ec6
RBR
98 continue;
99 }
8766e441 100 if Strategy::is_description(line) {
5732d284 101 lines_found += 1;
260e8ec6
RBR
102 continue;
103 }
dd0a540c
RBR
104 }
105 }
106
107 let gemini_source = lines[lines_found..].join("\n");
108
5732d284 109 let relative_path = file.path.strip_prefix(source).unwrap();
dd0a540c
RBR
110 let complete_destination = destination.join(relative_path);
111 let destination_parent = complete_destination.parent().unwrap();
112 create_dir_all(destination_parent).unwrap();
113
114 let mut destination_file = IOFile::create(&complete_destination).unwrap();
5732d284
RBR
115 destination_file
116 .write_all(gemini_source.as_bytes())
117 .unwrap();
dd0a540c 118 }
102a4884 119}
260e8ec6
RBR
120
121#[cfg(test)]
122mod tests {
5732d284
RBR
123 use std::fs::create_dir_all;
124
260e8ec6 125 use super::*;
260e8ec6 126
5732d284 127 use test_utilities::*;
260e8ec6 128
5732d284
RBR
129 #[test]
130 fn detects_title() {
8766e441 131 assert!(Strategy::is_title("--- title: Hello!"));
260e8ec6
RBR
132 }
133
5732d284
RBR
134 #[test]
135 fn does_not_detect_other_keys_as_title() {
8766e441 136 assert!(!Strategy::is_title("--- description: Hello!"));
260e8ec6
RBR
137 }
138
5732d284
RBR
139 #[test]
140 fn detects_description() {
8766e441 141 assert!(Strategy::is_description("--- description: What is this?"));
260e8ec6
RBR
142 }
143
5732d284
RBR
144 #[test]
145 fn does_not_detect_other_keys_as_description() {
8766e441 146 assert!(!Strategy::is_description("--- title: What is this?"));
260e8ec6
RBR
147 }
148
5732d284
RBR
149 #[test]
150 fn extracts_title() {
8766e441 151 assert_eq!(Strategy::get_title("--- title: Hello!").trim(), "Hello!");
5732d284 152 }
260e8ec6 153
5732d284
RBR
154 #[test]
155 fn extracts_description() {
5732d284 156 assert_eq!(
8766e441
RBR
157 Strategy
158 ::get_description("--- description: What is this?")
5732d284
RBR
159 .trim(),
160 "What is this?"
161 );
260e8ec6
RBR
162 }
163
5732d284
RBR
164 #[test]
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")));
170 }
260e8ec6 171
5732d284
RBR
172 #[test]
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));
181 }
260e8ec6 182
5732d284
RBR
183 #[test]
184 fn identifies_gemini_type() {
185 let strategy = Strategy {};
186 assert!(matches!(strategy.identify(), FileType::Gemini));
187 }
260e8ec6 188
5732d284
RBR
189 #[test]
190 fn handles_gemini_type() {
191 let strategy = Strategy {};
192 assert!(strategy.can_handle(&FileType::Gemini));
193 }
260e8ec6 194
5732d284
RBR
195 #[test]
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));
201 }
260e8ec6 202
5732d284
RBR
203 #[test]
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");
210 let layout = "\
211<html>
212<head>
213<title>{{ title }}</title>
214<meta name=\"description\" content=\"{{ description }}\">
215</head>
216<body>{{ content }}</body>
217</html>
218";
219 create_test_file(
220 &source_dir.join("test.gmi"),
221 "\
222--- title: Page Is Cool!
223--- description: My Description
224# Test
225Hello world
226",
227 );
228
229 let strategy = Strategy {};
230 let file = File {
231 path: source_dir.join("test.gmi"),
232 file_type: FileType::Gemini,
233 };
234
b0328413 235 strategy.handle_html(&source_dir, &output_dir, &file, layout);
5732d284
RBR
236
237 let html_output = output_dir.join("test.html");
238 assert!(html_output.exists());
239 assert_file_contents(
240 &html_output,
241 "\
242<html>
243<head>
244<title>Page Is Cool!</title>
245<meta name=\"description\" content=\"My Description\">
246</head>
247<body><section class=\"h1\">
248<h1> Test</h1>
249<p>Hello world</p>
250</section>
251</body>
252</html>
253",
254 );
255 }
260e8ec6 256
5732d284
RBR
257 #[test]
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");
264 create_test_file(
265 &source_dir.join("test.gmi"),
266 "\
267--- title: Page Is Cool!
268--- description: My Description
269# Test
270Hello world
271",
272 );
273
274 let strategy = Strategy {};
275 let file = File {
276 path: source_dir.join("test.gmi"),
277 file_type: FileType::Gemini,
278 };
279
280 strategy.handle_gemini(&source_dir, &output_dir, &file);
281
282 let gemini_output = output_dir.join("test.gmi");
283 assert!(gemini_output.exists());
284 assert_file_contents(
285 &gemini_output,
286 "\
287# Test
288Hello world
289",
290 );
291 }
260e8ec6 292
5732d284
RBR
293 #[test]
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");
b0328413 298 create_dir_all(source_dir.join("nested")).expect("Could not create source test directory");
5732d284
RBR
299 create_dir_all(&output_dir).expect("Could not create output test directory");
300 let layout = "\
301<html>
302<head>
303<title>{{ title }}</title>
304<meta name=\"description\" content=\"{{ description }}\">
305</head>
306<body>{{ content }}</body>
307</html>
308";
309 create_test_file(
310 &source_dir.join("nested/test.gmi"),
311 "\
312--- title: Page Is Cool!
313--- description: My Description
314# Test
315Hello world
316",
317 );
318
319 let strategy = Strategy {};
320 let file = File {
321 path: source_dir.join("nested/test.gmi"),
322 file_type: FileType::Gemini,
323 };
324
b0328413 325 strategy.handle_html(&source_dir, &output_dir, &file, layout);
5732d284
RBR
326
327 let html_output = output_dir.join("nested/test.html");
328 assert!(html_output.exists());
329 assert_file_contents(
330 &html_output,
331 "\
332<html>
333<head>
334<title>Page Is Cool!</title>
335<meta name=\"description\" content=\"My Description\">
336</head>
337<body><section class=\"h1\">
338<h1> Test</h1>
339<p>Hello world</p>
340</section>
341</body>
342</html>
343",
344 );
260e8ec6
RBR
345 }
346}