}
fn string_to_line(preformat: bool, string: TextLine<'_>) -> RawLine {
let line = string.0;
RawLine::Line({
match line {
// ignore anything after the lead chars on preformat lines
"```" => {
return RawLine::Toggle {
alt: TextLine(&line[3..]),
}
}
// ignore any other formatting between preformat toggle lines
_ if preformat => Line::Preformatted(string),
"=> " => {
let line = &line[3..];
match line.split_once(' ') {
Some((url, desc)) => {
let url = url.trim_start_matches(' ');
Line::Link {
url: url.parse().expect("invalid link url"),
description: Some(TextLine(desc)),
}
}
None => Line::Link {
url: line.parse().expect("invalid link url"),
description: None,
},
}
}
"* " => Line::ListItem(TextLine(&line[2..])),
"# " => Line::Heading {
level: 1,
title: TextLine(&line[2..]),
},
"## " => Line::Heading {
level: 2,
title: TextLine(&line[3..]),
},
"### " => Line::Heading {
level: 3,
title: TextLine(&line[4..]),
},
"> " => Line::Quote(TextLine(&line[2..])),
_ => Line::Text(string),
}
})
}
fn string_to_lines(value: &str) -> Vec<Line> {
value
.lines()
.map(TextLine)
// start with preformatting set to off
.scan(false, |preformat, line| {
match string_to_line(*preformat, line) {
// if we hit a toggle line, switch preformatting mode
RawLine::Toggle { alt } => {
*preformat = !*preformat;
None
}
// otherwise, yield the line
RawLine::Line(l) => Some(l),
}
})
.collect()
}
// guaranteed to be only a single line
#[derive(Copy, Clone, Debug)]
pub struct TextLine<'a>(&'a str);
impl<'a> From<&'a str> for Gemini<'a> {
fn from(value: &'a str) -> Self {
Gemini {
lines: string_to_lines(value),
}
}