]> git.r.bdr.sh - rbdr/page/blame - src/gemini_parser.rs
Add first tests
[rbdr/page] / src / gemini_parser.rs
CommitLineData
260e8ec6
RBR
1#[derive(PartialEq, Eq, Debug)]
2pub enum GeminiLine {
3 Text(String, bool),
4 PreformattedToggle(bool, String),
5 Heading(u8, String),
6 Link(String, String),
7 Quote(String),
8 ListItem(String)
8d4fac52
RBR
9}
10
260e8ec6
RBR
11/// Parses gemtext source code into a vector of GeminiLine elements.
12///
13/// # Arguments
14/// * `source` - A string slice that contains the gemtext
15///
16/// # Returns
17/// A `Vec<GeminiLine>` containing the rendered HTML.
18pub fn parse(source: &str) -> Vec<GeminiLine> {
19 source.lines()
20 .fold(
21 (Vec::new(), false),
22 |(mut lines, is_preformatted), line| {
23 let parsed = if is_preformatted {
24 parse_preformatted_line(line)
25 } else {
26 parse_line(line)
27 };
6a8515fe 28
260e8ec6
RBR
29 let new_is_preformatted = match parsed {
30 GeminiLine::PreformattedToggle(x, _) => x,
31 _ => is_preformatted
32 };
6a8515fe 33
260e8ec6
RBR
34 lines.push(parsed);
35 (lines, new_is_preformatted)
6a8515fe 36 }
260e8ec6
RBR
37 )
38 .0
6a8515fe
RBR
39}
40
260e8ec6
RBR
41fn parse_preformatted_line(line: &str) -> GeminiLine {
42 match line {
43 s if s.starts_with("```") => GeminiLine::PreformattedToggle(false, String::new()),
44 _ => GeminiLine::Text(line.to_string(), true),
6a8515fe 45 }
6a8515fe
RBR
46}
47
260e8ec6
RBR
48fn parse_line(line: &str) -> GeminiLine {
49 match line {
50 s if s.starts_with("###") => GeminiLine::Heading(3, s[3..].to_string()),
51 s if s.starts_with("##") => GeminiLine::Heading(2, s[2..].to_string()),
52 s if s.starts_with("#") => GeminiLine::Heading(1, s[1..].to_string()),
53 s if s.starts_with("=>") => {
54 let content = s[2..].trim();
55 match content.split_once(char::is_whitespace) {
56 Some((url, text)) => GeminiLine::Link(url.trim().to_string(), text.trim().to_string()),
57 None => GeminiLine::Link(content.trim().to_string(), String::new()),
f0739225
RBR
58 }
59 },
260e8ec6
RBR
60 s if s.starts_with("* ") => GeminiLine::ListItem(s[2..].to_string()),
61 s if s.starts_with(">") => GeminiLine::Quote(s[1..].to_string()),
62 s if s.starts_with("```") => GeminiLine::PreformattedToggle(true, s[3..].to_string()),
63 _ => GeminiLine::Text(line.to_string(), false),
8d4fac52
RBR
64 }
65}
66
260e8ec6
RBR
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_headings() {
73 assert_eq!(parse_line("### Heading"), GeminiLine::Heading(3, " Heading".to_string()));
74 assert_eq!(parse_line("## Heading"), GeminiLine::Heading(2, " Heading".to_string()));
75 assert_eq!(parse_line("# Heading"), GeminiLine::Heading(1, " Heading".to_string()));
76 assert_eq!(parse_line("###"), GeminiLine::Heading(3, "".to_string()));
77 assert_eq!(parse_line("#####"), GeminiLine::Heading(3, "##".to_string()));
78 assert_eq!(parse_line("# "), GeminiLine::Heading(1, " ".to_string()));
79
80 assert_eq!(parse_preformatted_line("### Heading"), GeminiLine::Text("### Heading".to_string(), true));
81 assert_eq!(parse_preformatted_line("## Heading"), GeminiLine::Text("## Heading".to_string(), true));
82 assert_eq!(parse_preformatted_line("# Heading"), GeminiLine::Text("# Heading".to_string(), true));
83 }
84
85 #[test]
86 fn test_links() {
87 assert_eq!(
88 parse_line("=> https://example.com Link text"),
89 GeminiLine::Link("https://example.com".to_string(), "Link text".to_string())
90 );
91 assert_eq!(
92 parse_line("=> /local/path"),
93 GeminiLine::Link("/local/path".to_string(), "".to_string())
94 );
95
96 assert_eq!(
97 parse_line("=>"),
98 GeminiLine::Link("".to_string(), "".to_string())
99 );
100 assert_eq!(
101 parse_line("=> "),
102 GeminiLine::Link("".to_string(), "".to_string())
103 );
104 assert_eq!(
105 parse_line("=> multiple spaces in text"),
106 GeminiLine::Link("multiple".to_string(), "spaces in text".to_string())
107 );
108
109 assert_eq!(
110 parse_preformatted_line("=> https://example.com Link text"),
111 GeminiLine::Text("=> https://example.com Link text".to_string(), true)
112 );
113 }
114
115 #[test]
116 fn test_list_items() {
117 assert_eq!(
118 parse_line("* List item"),
119 GeminiLine::ListItem("List item".to_string())
120 );
121
122 assert_eq!(parse_line("* "), GeminiLine::ListItem("".to_string()));
123 assert_eq!(parse_line("*"), GeminiLine::Text("*".to_string(), false));
124 assert_eq!(parse_line("*WithText"), GeminiLine::Text("*WithText".to_string(), false));
125 assert_eq!(parse_line("* Multiple spaces"), GeminiLine::ListItem(" Multiple spaces".to_string()));
126 }
127
128 #[test]
129 fn test_quotes() {
130 assert_eq!(
131 parse_line(">Quote text"),
132 GeminiLine::Quote("Quote text".to_string())
133 );
134
135 assert_eq!(parse_line(">"), GeminiLine::Quote("".to_string()));
136 assert_eq!(parse_line("> "), GeminiLine::Quote(" ".to_string()));
137 assert_eq!(parse_line(">>Nested"), GeminiLine::Quote(">Nested".to_string()));
138 }
139
140 #[test]
141 fn test_preformatted() {
142 assert_eq!(
143 parse_line("```alt-text"),
144 GeminiLine::PreformattedToggle(true, "alt-text".to_string())
145 );
146
147 assert_eq!(parse_line("```"), GeminiLine::PreformattedToggle(true, "".to_string()));
148 assert_eq!(parse_line("``` "), GeminiLine::PreformattedToggle(true, " ".to_string()));
149 assert_eq!(parse_line("````"), GeminiLine::PreformattedToggle(true, "`".to_string()));
150
151 assert_eq!(
152 parse_preformatted_line("```alt-text"),
153 GeminiLine::PreformattedToggle(false, "".to_string())
154 );
155 assert_eq!(
156 parse_preformatted_line("```"),
157 GeminiLine::PreformattedToggle(false, "".to_string())
158 );
159
160 }
161
162 #[test]
163 fn test_text() {
164 // Normal case
165 assert_eq!(
166 parse_line("Regular text"),
167 GeminiLine::Text("Regular text".to_string(), false)
168 );
169
170 // Edge cases
171 assert_eq!(parse_line(""), GeminiLine::Text("".to_string(), false));
172 assert_eq!(parse_line(" "), GeminiLine::Text(" ".to_string(), false));
173 assert_eq!(parse_line(" "), GeminiLine::Text(" ".to_string(), false));
174 }
175
176 #[test]
177 fn test_malformed_input() {
178 assert_eq!(parse_line("= >Not a link"), GeminiLine::Text("= >Not a link".to_string(), false));
179 assert_eq!(parse_line("``Not preformatted"), GeminiLine::Text("``Not preformatted".to_string(), false));
180 assert_eq!(parse_line("** Not a list"), GeminiLine::Text("** Not a list".to_string(), false));
181 }
182
183 #[test]
184 fn test_full_document() {
185 let input = "\
186# Heading 1
187## Heading 2
188### Heading 3
189Regular text
190=> https://example.com Link text
191* List item
192>Quote
193```alt
194code
195# Heading 1
196## Heading 2
197### Heading 3
198=> https://example.com Link text
199* List item
200>Quote
201```trailing alt";
202 let result = parse(input);
203 assert_eq!(result, vec![
204 GeminiLine::Heading(1, " Heading 1".to_string()),
205 GeminiLine::Heading(2, " Heading 2".to_string()),
206 GeminiLine::Heading(3, " Heading 3".to_string()),
207 GeminiLine::Text("Regular text".to_string(), false),
208 GeminiLine::Link("https://example.com".to_string(), "Link text".to_string()),
209 GeminiLine::ListItem("List item".to_string()),
210 GeminiLine::Quote("Quote".to_string()),
211 GeminiLine::PreformattedToggle(true, "alt".to_string()),
212 GeminiLine::Text("code".to_string(), true),
213 GeminiLine::Text("# Heading 1".to_string(), true),
214 GeminiLine::Text("## Heading 2".to_string(), true),
215 GeminiLine::Text("### Heading 3".to_string(), true),
216 GeminiLine::Text("=> https://example.com Link text".to_string(), true),
217 GeminiLine::Text("* List item".to_string(), true),
218 GeminiLine::Text(">Quote".to_string(), true),
219 GeminiLine::PreformattedToggle(false, "".to_string()),
220 ]);
8d4fac52
RBR
221 }
222}