]> git.r.bdr.sh - rbdr/page/blame - src/file_handler/file_strategies/gemini.rs
Format and lint the code
[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
5732d284 7use crate::file_handler::{File, FileHandlerStrategy, FileType};
68fa37d6 8use crate::gemini_parser::parse;
260e8ec6 9use crate::html_renderer::render_html;
68fa37d6
RBR
10
11impl Strategy {
12 fn is_title(&self, line: &str) -> bool {
13 line.starts_with("--- title:")
14 }
15
16 fn is_description(&self, line: &str) -> bool {
17 line.starts_with("--- description:")
18 }
19
20 fn get_title<'a>(&self, line: &'a str) -> &'a str {
21 line.split_once("--- title:").unwrap().1
22 }
23
24 fn get_description<'a>(&self, line: &'a str) -> &'a str {
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
49 let lines: Vec<&str> = gemini_contents.split("\n").collect();
50 let mut lines_found = 0;
51 let mut title = "";
52 let mut description = "";
260e8ec6
RBR
53 if let Some(slice) = lines.get(..2) {
54 for line in slice.iter() {
5732d284
RBR
55 if self.is_title(line) {
56 title = self.get_title(line).trim();
57 lines_found += 1;
260e8ec6
RBR
58 continue;
59 }
5732d284
RBR
60 if self.is_description(line) {
61 description = self.get_description(line).trim();
62 lines_found += 1;
260e8ec6
RBR
63 continue;
64 }
68fa37d6
RBR
65 }
66 }
67
68 let gemini_source = lines[lines_found..].join("\n");
260e8ec6 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
92 let lines: Vec<&str> = gemini_contents.split("\n").collect();
93 let mut lines_found = 0;
260e8ec6
RBR
94 if let Some(slice) = lines.get(..2) {
95 for line in slice.iter() {
5732d284
RBR
96 if self.is_title(line) {
97 lines_found += 1;
260e8ec6
RBR
98 continue;
99 }
5732d284
RBR
100 if self.is_description(line) {
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() {
131 let strategy = Strategy {};
132 assert!(strategy.is_title("--- title: Hello!"));
260e8ec6
RBR
133 }
134
5732d284
RBR
135 #[test]
136 fn does_not_detect_other_keys_as_title() {
137 let strategy = Strategy {};
138 assert!(!strategy.is_title("--- description: Hello!"));
260e8ec6
RBR
139 }
140
5732d284
RBR
141 #[test]
142 fn detects_description() {
143 let strategy = Strategy {};
144 assert!(strategy.is_description("--- description: What is this?"));
260e8ec6
RBR
145 }
146
5732d284
RBR
147 #[test]
148 fn does_not_detect_other_keys_as_description() {
149 let strategy = Strategy {};
150 assert!(!strategy.is_description("--- title: What is this?"));
260e8ec6
RBR
151 }
152
5732d284
RBR
153 #[test]
154 fn extracts_title() {
155 let strategy = Strategy {};
156 assert_eq!(strategy.get_title("--- title: Hello!").trim(), "Hello!");
157 }
260e8ec6 158
5732d284
RBR
159 #[test]
160 fn extracts_description() {
161 let strategy = Strategy {};
162 assert_eq!(
163 strategy
164 .get_description("--- description: What is this?")
165 .trim(),
166 "What is this?"
167 );
260e8ec6
RBR
168 }
169
5732d284
RBR
170 #[test]
171 fn identifies_gemini_file() {
172 let test_dir = setup_test_dir();
173 create_test_file(&test_dir.join("test.gmi"), "");
174 let strategy = Strategy {};
175 assert!(strategy.is(&test_dir.join("test.gmi")));
176 }
260e8ec6 177
5732d284
RBR
178 #[test]
179 fn rejects_non_gemini_file() {
180 let test_dir = setup_test_dir();
181 create_test_file(&test_dir.join("_layout.html"), "");
182 create_test_file(&test_dir.join("image.png"), "");
183 let strategy = Strategy {};
184 assert!(!strategy.is(&test_dir.join("_layout.html")));
185 assert!(!strategy.is(&test_dir.join("image.png")));
186 assert!(!strategy.is(&test_dir));
187 }
260e8ec6 188
5732d284
RBR
189 #[test]
190 fn identifies_gemini_type() {
191 let strategy = Strategy {};
192 assert!(matches!(strategy.identify(), FileType::Gemini));
193 }
260e8ec6 194
5732d284
RBR
195 #[test]
196 fn handles_gemini_type() {
197 let strategy = Strategy {};
198 assert!(strategy.can_handle(&FileType::Gemini));
199 }
260e8ec6 200
5732d284
RBR
201 #[test]
202 fn rejects_non_gemini_types() {
203 let strategy = Strategy {};
204 assert!(!strategy.can_handle(&FileType::Layout));
205 assert!(!strategy.can_handle(&FileType::File));
206 assert!(!strategy.can_handle(&FileType::Unknown));
207 }
260e8ec6 208
5732d284
RBR
209 #[test]
210 fn handles_html_generation() {
211 let test_dir = setup_test_dir();
212 let source_dir = test_dir.join("source");
213 let output_dir = test_dir.join("output");
214 create_dir_all(&source_dir).expect("Could not create source test directory");
215 create_dir_all(&output_dir).expect("Could not create output test directory");
216 let layout = "\
217<html>
218<head>
219<title>{{ title }}</title>
220<meta name=\"description\" content=\"{{ description }}\">
221</head>
222<body>{{ content }}</body>
223</html>
224";
225 create_test_file(
226 &source_dir.join("test.gmi"),
227 "\
228--- title: Page Is Cool!
229--- description: My Description
230# Test
231Hello world
232",
233 );
234
235 let strategy = Strategy {};
236 let file = File {
237 path: source_dir.join("test.gmi"),
238 file_type: FileType::Gemini,
239 };
240
241 strategy.handle_html(&source_dir, &output_dir, &file, &layout);
242
243 let html_output = output_dir.join("test.html");
244 assert!(html_output.exists());
245 assert_file_contents(
246 &html_output,
247 "\
248<html>
249<head>
250<title>Page Is Cool!</title>
251<meta name=\"description\" content=\"My Description\">
252</head>
253<body><section class=\"h1\">
254<h1> Test</h1>
255<p>Hello world</p>
256</section>
257</body>
258</html>
259",
260 );
261 }
260e8ec6 262
5732d284
RBR
263 #[test]
264 fn handles_gemini_generation() {
265 let test_dir = setup_test_dir();
266 let source_dir = test_dir.join("source");
267 let output_dir = test_dir.join("output");
268 create_dir_all(&source_dir).expect("Could not create source test directory");
269 create_dir_all(&output_dir).expect("Could not create output test directory");
270 create_test_file(
271 &source_dir.join("test.gmi"),
272 "\
273--- title: Page Is Cool!
274--- description: My Description
275# Test
276Hello world
277",
278 );
279
280 let strategy = Strategy {};
281 let file = File {
282 path: source_dir.join("test.gmi"),
283 file_type: FileType::Gemini,
284 };
285
286 strategy.handle_gemini(&source_dir, &output_dir, &file);
287
288 let gemini_output = output_dir.join("test.gmi");
289 assert!(gemini_output.exists());
290 assert_file_contents(
291 &gemini_output,
292 "\
293# Test
294Hello world
295",
296 );
297 }
260e8ec6 298
5732d284
RBR
299 #[test]
300 fn handles_nested_structure() {
301 let test_dir = setup_test_dir();
302 let source_dir = test_dir.join("source");
303 let output_dir = test_dir.join("output");
304 create_dir_all(&source_dir.join("nested")).expect("Could not create source test directory");
305 create_dir_all(&output_dir).expect("Could not create output test directory");
306 let layout = "\
307<html>
308<head>
309<title>{{ title }}</title>
310<meta name=\"description\" content=\"{{ description }}\">
311</head>
312<body>{{ content }}</body>
313</html>
314";
315 create_test_file(
316 &source_dir.join("nested/test.gmi"),
317 "\
318--- title: Page Is Cool!
319--- description: My Description
320# Test
321Hello world
322",
323 );
324
325 let strategy = Strategy {};
326 let file = File {
327 path: source_dir.join("nested/test.gmi"),
328 file_type: FileType::Gemini,
329 };
330
331 strategy.handle_html(&source_dir, &output_dir, &file, &layout);
332
333 let html_output = output_dir.join("nested/test.html");
334 assert!(html_output.exists());
335 assert_file_contents(
336 &html_output,
337 "\
338<html>
339<head>
340<title>Page Is Cool!</title>
341<meta name=\"description\" content=\"My Description\">
342</head>
343<body><section class=\"h1\">
344<h1> Test</h1>
345<p>Hello world</p>
346</section>
347</body>
348</html>
349",
350 );
260e8ec6
RBR
351 }
352}