struct Marker<'a> {
character: char,
length: usize,
rest: &'a str,
}
impl Marker<'_> {
fn opens(&self) -> bool {
self.character == '~' || !self.rest.contains('`')
}
fn closes(&self, character: char, length: usize) -> bool {
self.character == character && self.length >= length && self.rest.trim().is_empty()
}
}
fn marker(line: &str) -> Option<Marker<'_>> {
let line = line.trim_start();
let character = line.chars().next().filter(|c| matches!(c, '`' | '~'))?;
let length = line.chars().take_while(|c| *c == character).count();
(length >= 3).then(|| Marker {
character,
length,
rest: &line[length..],
})
}
struct Open {
character: char,
length: usize,
line: u32,
}
pub struct Fences {
inside: Vec<bool>,
unclosed: Option<u32>,
}
impl Fences {
pub fn scan<S: AsRef<str>>(lines: &[S]) -> Self {
let mut inside = Vec::with_capacity(lines.len());
let mut open: Option<Open> = None;
for (index, line) in lines.iter().enumerate() {
let found = marker(line.as_ref());
match (&open, found) {
(None, Some(marker)) if marker.opens() => {
open = Some(Open {
character: marker.character,
length: marker.length,
line: index as u32 + 1,
});
inside.push(true);
}
(Some(block), Some(marker)) if marker.closes(block.character, block.length) => {
open = None;
inside.push(true);
}
_ => inside.push(open.is_some()),
}
}
Self {
inside,
unclosed: open.map(|block| block.line),
}
}
pub fn mask(&self) -> &[bool] {
&self.inside
}
pub fn unclosed(&self) -> Option<u32> {
self.unclosed
}
}