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