use std::sync::LazyLock;
use regex::Regex;
static FENCE_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(\s*)(`{3,}|~{3,})([A-Za-z0-9_+.,-]*)\s*$").unwrap());
static ORPHAN_LANG_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[A-Za-z0-9_+.-]*[A-Za-z0-9_+\-](?:,[A-Za-z0-9_+.-]*[A-Za-z0-9_+\-])*$").unwrap()
});
#[inline]
fn is_null_lang(s: &str) -> bool {
let trimmed = s.trim();
trimmed.is_empty() || trimmed.eq_ignore_ascii_case("null")
}
fn normalize_specifier(line: &str) -> (String, String) {
let indent: String = line.chars().take_while(|c| c.is_whitespace()).collect();
let cleaned = line
.trim()
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(",")
.to_lowercase();
(cleaned, indent)
}
#[must_use]
pub fn compress_fences(lines: &[String]) -> Vec<String> {
lines
.iter()
.map(|line| {
if let Some(cap) = FENCE_RE.captures(line) {
let indent = cap.get(1).map_or("", |m| m.as_str());
let lang = cap.get(3).map_or("", |m| m.as_str());
if is_null_lang(lang) {
format!("{indent}```")
} else {
format!("{indent}```{lang}")
}
} else {
line.clone()
}
})
.collect()
}
fn attach_specifier_to_fence(fence_line: &str, specifier: &str, spec_indent: &str) -> String {
let fence_indent = FENCE_RE
.captures(fence_line)
.and_then(|cap| cap.get(1))
.map_or("", |m| m.as_str());
let final_indent = if fence_indent.is_empty() || spec_indent.starts_with(fence_indent) {
spec_indent
} else {
fence_indent
};
format!("{final_indent}```{specifier}")
}
#[must_use]
pub fn attach_orphan_specifiers(lines: &[String]) -> Vec<String> {
let mut out = Vec::with_capacity(lines.len());
let mut i = 0;
while i < lines.len() {
let line = &lines[i];
let (spec, indent) = normalize_specifier(line);
if ORPHAN_LANG_RE.is_match(&spec) && out.last().is_none_or(|l: &String| l.trim().is_empty())
{
let mut j = i + 1;
while j < lines.len() && lines[j].trim().is_empty() {
j += 1;
}
if j < lines.len()
&& let Some(cap) = FENCE_RE.captures(&lines[j])
{
let lang = cap.get(3).map_or("", |m| m.as_str());
if is_null_lang(lang) {
out.push(attach_specifier_to_fence(&lines[j], &spec, &indent));
i = j + 1;
continue;
}
}
out.push(line.clone());
i += 1;
continue;
}
out.push(line.clone());
i += 1;
}
out
}