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