X-Git-Url: https://git.r.bdr.sh/rbdr/page/blobdiff_plain/260e8ec69b8e08b9fd105bf688e7a3a9fafecd61..4d946aebe3f70ad18e235d68474b6d489757c927:/src/gemini_parser.rs diff --git a/src/gemini_parser.rs b/src/gemini_parser.rs index 418b758..f7aa0cc 100644 --- a/src/gemini_parser.rs +++ b/src/gemini_parser.rs @@ -5,36 +5,34 @@ pub enum GeminiLine { Heading(u8, String), Link(String, String), Quote(String), - ListItem(String) + ListItem(String), } -/// Parses gemtext source code into a vector of GeminiLine elements. -/// +/// Parses gemtext source code into a vector of `GeminiLine` elements. +/// /// # Arguments /// * `source` - A string slice that contains the gemtext -/// +/// /// # Returns /// A `Vec` containing the rendered HTML. pub fn parse(source: &str) -> Vec { - source.lines() - .fold( - (Vec::new(), false), - |(mut lines, is_preformatted), line| { - let parsed = if is_preformatted { - parse_preformatted_line(line) - } else { - parse_line(line) - }; - - let new_is_preformatted = match parsed { - GeminiLine::PreformattedToggle(x, _) => x, - _ => is_preformatted - }; - - lines.push(parsed); - (lines, new_is_preformatted) - } - ) + source + .lines() + .fold((Vec::new(), false), |(mut lines, is_preformatted), line| { + let parsed = if is_preformatted { + parse_preformatted_line(line) + } else { + parse_line(line) + }; + + let new_is_preformatted = match parsed { + GeminiLine::PreformattedToggle(x, _) => x, + _ => is_preformatted, + }; + + lines.push(parsed); + (lines, new_is_preformatted) + }) .0 } @@ -49,16 +47,18 @@ fn parse_line(line: &str) -> GeminiLine { match line { s if s.starts_with("###") => GeminiLine::Heading(3, s[3..].to_string()), s if s.starts_with("##") => GeminiLine::Heading(2, s[2..].to_string()), - s if s.starts_with("#") => GeminiLine::Heading(1, s[1..].to_string()), + s if s.starts_with('#') => GeminiLine::Heading(1, s[1..].to_string()), s if s.starts_with("=>") => { let content = s[2..].trim(); match content.split_once(char::is_whitespace) { - Some((url, text)) => GeminiLine::Link(url.trim().to_string(), text.trim().to_string()), + Some((url, text)) => { + GeminiLine::Link(url.trim().to_string(), text.trim().to_string()) + } None => GeminiLine::Link(content.trim().to_string(), String::new()), } - }, + } s if s.starts_with("* ") => GeminiLine::ListItem(s[2..].to_string()), - s if s.starts_with(">") => GeminiLine::Quote(s[1..].to_string()), + s if s.starts_with('>') => GeminiLine::Quote(s[1..].to_string()), s if s.starts_with("```") => GeminiLine::PreformattedToggle(true, s[3..].to_string()), _ => GeminiLine::Text(line.to_string(), false), } @@ -70,16 +70,37 @@ mod tests { #[test] fn test_headings() { - assert_eq!(parse_line("### Heading"), GeminiLine::Heading(3, " Heading".to_string())); - assert_eq!(parse_line("## Heading"), GeminiLine::Heading(2, " Heading".to_string())); - assert_eq!(parse_line("# Heading"), GeminiLine::Heading(1, " Heading".to_string())); - assert_eq!(parse_line("###"), GeminiLine::Heading(3, "".to_string())); - assert_eq!(parse_line("#####"), GeminiLine::Heading(3, "##".to_string())); + assert_eq!( + parse_line("### Heading"), + GeminiLine::Heading(3, " Heading".to_string()) + ); + assert_eq!( + parse_line("## Heading"), + GeminiLine::Heading(2, " Heading".to_string()) + ); + assert_eq!( + parse_line("# Heading"), + GeminiLine::Heading(1, " Heading".to_string()) + ); + assert_eq!(parse_line("###"), GeminiLine::Heading(3, String::new())); + assert_eq!( + parse_line("#####"), + GeminiLine::Heading(3, "##".to_string()) + ); assert_eq!(parse_line("# "), GeminiLine::Heading(1, " ".to_string())); - assert_eq!(parse_preformatted_line("### Heading"), GeminiLine::Text("### Heading".to_string(), true)); - assert_eq!(parse_preformatted_line("## Heading"), GeminiLine::Text("## Heading".to_string(), true)); - assert_eq!(parse_preformatted_line("# Heading"), GeminiLine::Text("# Heading".to_string(), true)); + assert_eq!( + parse_preformatted_line("### Heading"), + GeminiLine::Text("### Heading".to_string(), true) + ); + assert_eq!( + parse_preformatted_line("## Heading"), + GeminiLine::Text("## Heading".to_string(), true) + ); + assert_eq!( + parse_preformatted_line("# Heading"), + GeminiLine::Text("# Heading".to_string(), true) + ); } #[test] @@ -90,16 +111,16 @@ mod tests { ); assert_eq!( parse_line("=> /local/path"), - GeminiLine::Link("/local/path".to_string(), "".to_string()) + GeminiLine::Link("/local/path".to_string(), String::new()) ); assert_eq!( parse_line("=>"), - GeminiLine::Link("".to_string(), "".to_string()) + GeminiLine::Link(String::new(), String::new()) ); assert_eq!( parse_line("=> "), - GeminiLine::Link("".to_string(), "".to_string()) + GeminiLine::Link(String::new(), String::new()) ); assert_eq!( parse_line("=> multiple spaces in text"), @@ -119,10 +140,16 @@ mod tests { GeminiLine::ListItem("List item".to_string()) ); - assert_eq!(parse_line("* "), GeminiLine::ListItem("".to_string())); + assert_eq!(parse_line("* "), GeminiLine::ListItem(String::new())); assert_eq!(parse_line("*"), GeminiLine::Text("*".to_string(), false)); - assert_eq!(parse_line("*WithText"), GeminiLine::Text("*WithText".to_string(), false)); - assert_eq!(parse_line("* Multiple spaces"), GeminiLine::ListItem(" Multiple spaces".to_string())); + assert_eq!( + parse_line("*WithText"), + GeminiLine::Text("*WithText".to_string(), false) + ); + assert_eq!( + parse_line("* Multiple spaces"), + GeminiLine::ListItem(" Multiple spaces".to_string()) + ); } #[test] @@ -132,9 +159,12 @@ mod tests { GeminiLine::Quote("Quote text".to_string()) ); - assert_eq!(parse_line(">"), GeminiLine::Quote("".to_string())); + assert_eq!(parse_line(">"), GeminiLine::Quote(String::new())); assert_eq!(parse_line("> "), GeminiLine::Quote(" ".to_string())); - assert_eq!(parse_line(">>Nested"), GeminiLine::Quote(">Nested".to_string())); + assert_eq!( + parse_line(">>Nested"), + GeminiLine::Quote(">Nested".to_string()) + ); } #[test] @@ -144,19 +174,27 @@ mod tests { GeminiLine::PreformattedToggle(true, "alt-text".to_string()) ); - assert_eq!(parse_line("```"), GeminiLine::PreformattedToggle(true, "".to_string())); - assert_eq!(parse_line("``` "), GeminiLine::PreformattedToggle(true, " ".to_string())); - assert_eq!(parse_line("````"), GeminiLine::PreformattedToggle(true, "`".to_string())); + assert_eq!( + parse_line("```"), + GeminiLine::PreformattedToggle(true, String::new()) + ); + assert_eq!( + parse_line("``` "), + GeminiLine::PreformattedToggle(true, " ".to_string()) + ); + assert_eq!( + parse_line("````"), + GeminiLine::PreformattedToggle(true, "`".to_string()) + ); assert_eq!( parse_preformatted_line("```alt-text"), - GeminiLine::PreformattedToggle(false, "".to_string()) + GeminiLine::PreformattedToggle(false, String::new()) ); assert_eq!( parse_preformatted_line("```"), - GeminiLine::PreformattedToggle(false, "".to_string()) + GeminiLine::PreformattedToggle(false, String::new()) ); - } #[test] @@ -168,16 +206,25 @@ mod tests { ); // Edge cases - assert_eq!(parse_line(""), GeminiLine::Text("".to_string(), false)); + assert_eq!(parse_line(""), GeminiLine::Text(String::new(), false)); assert_eq!(parse_line(" "), GeminiLine::Text(" ".to_string(), false)); assert_eq!(parse_line(" "), GeminiLine::Text(" ".to_string(), false)); } #[test] fn test_malformed_input() { - assert_eq!(parse_line("= >Not a link"), GeminiLine::Text("= >Not a link".to_string(), false)); - assert_eq!(parse_line("``Not preformatted"), GeminiLine::Text("``Not preformatted".to_string(), false)); - assert_eq!(parse_line("** Not a list"), GeminiLine::Text("** Not a list".to_string(), false)); + assert_eq!( + parse_line("= >Not a link"), + GeminiLine::Text("= >Not a link".to_string(), false) + ); + assert_eq!( + parse_line("``Not preformatted"), + GeminiLine::Text("``Not preformatted".to_string(), false) + ); + assert_eq!( + parse_line("** Not a list"), + GeminiLine::Text("** Not a list".to_string(), false) + ); } #[test] @@ -200,23 +247,26 @@ code >Quote ```trailing alt"; let result = parse(input); - assert_eq!(result, vec![ - GeminiLine::Heading(1, " Heading 1".to_string()), - GeminiLine::Heading(2, " Heading 2".to_string()), - GeminiLine::Heading(3, " Heading 3".to_string()), - GeminiLine::Text("Regular text".to_string(), false), - GeminiLine::Link("https://example.com".to_string(), "Link text".to_string()), - GeminiLine::ListItem("List item".to_string()), - GeminiLine::Quote("Quote".to_string()), - GeminiLine::PreformattedToggle(true, "alt".to_string()), - GeminiLine::Text("code".to_string(), true), - GeminiLine::Text("# Heading 1".to_string(), true), - GeminiLine::Text("## Heading 2".to_string(), true), - GeminiLine::Text("### Heading 3".to_string(), true), - GeminiLine::Text("=> https://example.com Link text".to_string(), true), - GeminiLine::Text("* List item".to_string(), true), - GeminiLine::Text(">Quote".to_string(), true), - GeminiLine::PreformattedToggle(false, "".to_string()), - ]); + assert_eq!( + result, + vec![ + GeminiLine::Heading(1, " Heading 1".to_string()), + GeminiLine::Heading(2, " Heading 2".to_string()), + GeminiLine::Heading(3, " Heading 3".to_string()), + GeminiLine::Text("Regular text".to_string(), false), + GeminiLine::Link("https://example.com".to_string(), "Link text".to_string()), + GeminiLine::ListItem("List item".to_string()), + GeminiLine::Quote("Quote".to_string()), + GeminiLine::PreformattedToggle(true, "alt".to_string()), + GeminiLine::Text("code".to_string(), true), + GeminiLine::Text("# Heading 1".to_string(), true), + GeminiLine::Text("## Heading 2".to_string(), true), + GeminiLine::Text("### Heading 3".to_string(), true), + GeminiLine::Text("=> https://example.com Link text".to_string(), true), + GeminiLine::Text("* List item".to_string(), true), + GeminiLine::Text(">Quote".to_string(), true), + GeminiLine::PreformattedToggle(false, String::new()), + ] + ); } }