use crate::parser::utils::helpers::strip_leading_spaces;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DivFenceInfo {
pub attributes: String,
pub fence_count: usize,
}
pub(crate) fn try_parse_div_fence_open(content: &str) -> Option<DivFenceInfo> {
let trimmed = strip_leading_spaces(content);
if !trimmed.starts_with(':') {
return None;
}
let colon_count = trimmed.chars().take_while(|&c| c == ':').count();
if colon_count < 3 {
return None;
}
let after_colons = trimmed[colon_count..].trim_start();
let attributes = if after_colons.starts_with('{') {
if let Some(close_idx) = after_colons.find('}') {
after_colons[..=close_idx].to_string()
} else {
return None;
}
} else if after_colons.is_empty() {
return None;
} else {
let word_end = after_colons
.find(|c: char| c.is_whitespace() || c == ':')
.unwrap_or(after_colons.len());
let (first, rest) = after_colons.split_at(word_end);
if first.is_empty() {
return None;
}
let trailing = rest.trim();
if !trailing.is_empty() {
if trailing.chars().any(|c| c != ':') {
return None;
}
if trailing.len() < 3 {
return None;
}
} else {
let trailing_colons = after_colons[first.len()..].trim();
if !trailing_colons.is_empty() {
if trailing_colons.chars().any(|c| c != ':') {
return None;
}
if trailing_colons.len() < 3 {
return None;
}
}
}
first.to_string()
};
Some(DivFenceInfo {
attributes,
fence_count: colon_count,
})
}
pub(crate) fn is_div_closing_fence(content: &str) -> bool {
let trimmed = strip_leading_spaces(content);
if !trimmed.starts_with(':') {
return false;
}
let colon_count = trimmed.chars().take_while(|&c| c == ':').count();
if colon_count < 3 {
return false;
}
trimmed[colon_count..].trim().is_empty()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_div_fence_open_with_curly_braces() {
let line = "::: {.callout-note}";
let fence = try_parse_div_fence_open(line).unwrap();
assert_eq!(fence.attributes, "{.callout-note}");
}
#[test]
fn test_parse_div_fence_open_with_class_name() {
let line = "::: Warning";
let fence = try_parse_div_fence_open(line).unwrap();
assert_eq!(fence.attributes, "Warning");
}
#[test]
fn test_parse_div_fence_open_with_trailing_colons() {
let line = "::::: {#special .sidebar} :::::";
let fence = try_parse_div_fence_open(line).unwrap();
assert_eq!(fence.attributes, "{#special .sidebar}");
}
#[test]
fn test_parse_div_fence_open_with_class_name_and_trailing_colons() {
let line = "::: Warning :::";
let fence = try_parse_div_fence_open(line).unwrap();
assert_eq!(fence.attributes, "Warning");
}
#[test]
fn test_opening_fence_empty_attributes() {
let line = ":::";
assert!(try_parse_div_fence_open(line).is_none());
assert!(is_div_closing_fence(line));
}
#[test]
fn test_opening_fence_many_colons_empty_attributes() {
let line = "::::::::::::::";
assert!(try_parse_div_fence_open(line).is_none());
assert!(is_div_closing_fence(line));
}
#[test]
fn test_not_a_fence_too_few_colons() {
let line = ":: something";
assert!(try_parse_div_fence_open(line).is_none());
assert!(!is_div_closing_fence(line));
}
}