#[derive(Debug, Default)]
pub struct FenceTracker {
fence: Option<(char, usize)>,
}
impl FenceTracker {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn in_fence(&self) -> bool {
self.fence.is_some()
}
pub fn process_line(&mut self, line: &str) -> bool {
if let Some((fence_char, fence_count)) = self.fence {
if is_closing_fence(line, fence_char, fence_count) {
self.fence = None;
}
return true;
}
if let Some(f) = detect_opening_fence(line) {
self.fence = Some(f);
return true;
}
false
}
}
pub(crate) fn detect_opening_fence(line: &str) -> Option<(char, usize)> {
let trimmed = line.trim_start();
let fence_char = trimmed.as_bytes().first().copied()?;
if fence_char != b'`' && fence_char != b'~' {
return None;
}
let fence_char = fence_char as char;
let count = trimmed.chars().take_while(|&c| c == fence_char).count();
if count >= 3 {
Some((fence_char, count))
} else {
None
}
}
pub(crate) fn is_closing_fence(line: &str, fence_char: char, min_count: usize) -> bool {
let trimmed = line.trim_start();
let count = trimmed.chars().take_while(|&c| c == fence_char).count();
if count < min_count {
return false;
}
trimmed[count * fence_char.len_utf8()..].trim().is_empty()
}
pub fn extract_fence_language(line: &str, fence_char: char, fence_count: usize) -> String {
let trimmed = line.trim_start();
let after_fence = &trimmed[fence_count * fence_char.len_utf8()..];
after_fence.trim().to_owned()
}