]> git.r.bdr.sh - rbdr/page/blob - src/file_handler/file_strategies/gemini.rs
Add first tests
[rbdr/page] / src / file_handler / file_strategies / gemini.rs
1 pub struct Strategy {}
2
3 use std::path::PathBuf;
4 use std::io::Write;
5 use std::fs::{create_dir_all, read_to_string, File as IOFile};
6
7 use crate::file_handler::{File, FileType, FileHandlerStrategy};
8 use crate::gemini_parser::parse;
9 use crate::html_renderer::render_html;
10
11 impl 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 }
28
29 impl FileHandlerStrategy for Strategy {
30 fn is(&self, path: &PathBuf) -> 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 match file_type {
43 FileType::Gemini => true,
44 _ => false,
45 }
46 }
47
48 fn handle_html(&self, source: &PathBuf, destination: &PathBuf, file: &File, layout: &str) {
49 let gemini_contents = read_to_string(&file.path).unwrap();
50
51 // Front matter extraction
52 let lines: Vec<&str> = gemini_contents.split("\n").collect();
53 let mut lines_found = 0;
54 let mut title = "";
55 let mut description = "";
56 if let Some(slice) = lines.get(..2) {
57 for line in slice.iter() {
58 if self.is_title(&line) {
59 title = self.get_title(&line).trim();
60 lines_found = lines_found + 1;
61 continue;
62 }
63 if self.is_description(&line) {
64 description = self.get_description(&line).trim();
65 lines_found = lines_found + 1;
66 continue;
67 }
68 }
69 }
70
71 let gemini_source = lines[lines_found..].join("\n");
72 let content_html = render_html(parse(&gemini_source[..]));
73
74 let generated_html = layout
75 .replace("{{ title }}", title)
76 .replace("{{ description }}", description)
77 .replace("{{ content }}", &content_html[..]);
78
79
80 let relative_path = file.path.strip_prefix(&source).unwrap();
81 let mut complete_destination = destination.join(relative_path);
82 complete_destination.set_extension("html");
83 let destination_parent = complete_destination.parent().unwrap();
84 create_dir_all(destination_parent).unwrap();
85
86 let mut destination_file = IOFile::create(&complete_destination).unwrap();
87 destination_file.write_all(generated_html.as_bytes()).unwrap();
88 }
89
90 fn handle_gemini(&self, source: &PathBuf, destination: &PathBuf, file: &File) {
91 let gemini_contents = read_to_string(&file.path).unwrap();
92
93 // Front matter extraction
94 let lines: Vec<&str> = gemini_contents.split("\n").collect();
95 let mut lines_found = 0;
96 if let Some(slice) = lines.get(..2) {
97 for line in slice.iter() {
98 if self.is_title(&line) {
99 lines_found = lines_found + 1;
100 continue;
101 }
102 if self.is_description(&line) {
103 lines_found = lines_found + 1;
104 continue;
105 }
106 }
107 }
108
109 let gemini_source = lines[lines_found..].join("\n");
110
111 let relative_path = file.path.strip_prefix(&source).unwrap();
112 let complete_destination = destination.join(relative_path);
113 let destination_parent = complete_destination.parent().unwrap();
114 create_dir_all(destination_parent).unwrap();
115
116 let mut destination_file = IOFile::create(&complete_destination).unwrap();
117 destination_file.write_all(gemini_source.as_bytes()).unwrap();
118 }
119 }
120
121 #[cfg(test)]
122 mod tests {
123 use super::*;
124 use std::fs;
125
126 fn setup() -> Strategy {
127 Strategy {}
128 }
129
130 fn fixtures_dir() -> PathBuf {
131 PathBuf::from("tests/fixtures")
132 }
133
134 fn fixture_path(filename: &str) -> PathBuf {
135 fixtures_dir().join(filename)
136 }
137
138 fn read_fixture(filename: &str) -> String {
139 fs::read_to_string(fixture_path(filename))
140 .unwrap_or_else(|_| panic!("Failed to read fixture file: {}", filename))
141 }
142
143 mod front_matter_tests {
144 use super::*;
145
146 #[test]
147 fn detects_title() {
148 let strategy = setup();
149 let content = read_fixture("test1.gmi");
150 let first_line = content.lines().next().unwrap();
151 assert!(strategy.is_title(first_line));
152 }
153
154 #[test]
155 fn detects_description() {
156 let strategy = setup();
157 let content = read_fixture("test1.gmi");
158 let second_line = content.lines().nth(1).unwrap();
159 assert!(strategy.is_description(second_line));
160 }
161
162 #[test]
163 fn extracts_title() {
164 let strategy = setup();
165 let content = read_fixture("test1.gmi");
166 let first_line = content.lines().next().unwrap();
167 assert_eq!(strategy.get_title(first_line).trim(), "Test Title");
168 }
169
170 #[test]
171 fn extracts_description() {
172 let strategy = setup();
173 let content = read_fixture("test1.gmi");
174 let second_line = content.lines().nth(1).unwrap();
175 assert_eq!(strategy.get_description(second_line).trim(), "Test Description");
176 }
177 }
178
179 mod file_type_tests {
180 use super::*;
181
182 #[test]
183 fn identifies_gemini_files() {
184 let strategy = setup();
185 assert!(strategy.is(&fixture_path("test1.gmi")));
186 assert!(!strategy.is(&fixture_path("_layout.html")));
187 assert!(!strategy.is(&fixtures_dir()));
188 }
189
190 #[test]
191 fn identifies_file_type() {
192 let strategy = setup();
193 assert!(matches!(strategy.identify(), FileType::Gemini));
194 }
195
196 #[test]
197 fn handles_correct_file_type() {
198 let strategy = setup();
199 assert!(strategy.can_handle(&FileType::Gemini));
200 assert!(!strategy.can_handle(&FileType::Layout));
201 assert!(!strategy.can_handle(&FileType::File));
202 assert!(!strategy.can_handle(&FileType::Unknown));
203 }
204 }
205
206 mod file_handling_tests {
207 use super::*;
208
209 #[test]
210 fn handles_html_generation() {
211 let strategy = setup();
212 let source = fixtures_dir();
213 let output = fixture_path("output");
214 let layout = read_fixture("_layout.html");
215
216 let file = File {
217 path: fixture_path("test1.gmi"),
218 file_type: FileType::Gemini,
219 };
220
221 strategy.handle_html(
222 &source,
223 &output,
224 &file,
225 &layout,
226 );
227
228 let generated_path = output.join("test1.html");
229 assert!(generated_path.exists());
230
231 let content = fs::read_to_string(generated_path.clone()).unwrap();
232 assert!(content.contains("Test Title"));
233 assert!(content.contains("<h1>"));
234
235 // Cleanup
236 let _ = fs::remove_file(generated_path);
237 }
238
239 #[test]
240 fn handles_gemini_generation() {
241 let strategy = setup();
242 let source = fixtures_dir();
243 let output = fixture_path("output");
244
245 let file = File {
246 path: fixture_path("test1.gmi"),
247 file_type: FileType::Gemini,
248 };
249
250 strategy.handle_gemini(
251 &source,
252 &output,
253 &file,
254 );
255
256 let generated_path = output.join("test1.gmi");
257 assert!(generated_path.exists());
258
259 let content = fs::read_to_string(&generated_path).unwrap();
260 assert!(content.contains("# Heading"));
261 assert!(!content.contains("Test Title")); // Front matter should be removed
262
263 // Cleanup
264 let _ = fs::remove_file(generated_path);
265 }
266
267 #[test]
268 fn handles_nested_structure() {
269 let strategy = setup();
270 let source = fixtures_dir();
271 let output = fixture_path("output");
272 let layout = read_fixture("_layout.html");
273
274 let file = File {
275 path: fixture_path("nested/nested.gmi"),
276 file_type: FileType::Gemini,
277 };
278
279 strategy.handle_html(
280 &source,
281 &output,
282 &file,
283 &layout,
284 );
285
286 let generated_path = output.join("nested").join("nested.html");
287 assert!(generated_path.exists());
288
289 // Cleanup
290 let _ = fs::remove_file(generated_path);
291 let _ = fs::remove_dir(output.join("nested"));
292 }
293 }
294 }