use crate::config::HeadingConfig;
pub fn format_headings(content: &str, config: &HeadingConfig) -> String {
let lines: Vec<&str> = content.lines().collect();
let mut result: Vec<String> = Vec::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim();
if is_heading(trimmed) {
if i > 0 && !result.is_empty() {
let mut blank_count = 0;
for j in (0..result.len()).rev() {
if result[j].trim().is_empty() {
blank_count += 1;
} else {
break;
}
}
while blank_count < config.blank_lines_before {
result.push(String::new());
blank_count += 1;
}
while blank_count > config.blank_lines_before {
result.pop();
blank_count -= 1;
}
}
let normalized = if config.space_after_hash {
normalize_heading(trimmed)
} else {
trimmed.to_string()
};
result.push(normalized);
for _ in 0..config.blank_lines_after {
result.push(String::new());
}
i += 1;
while i < lines.len() && lines[i].trim().is_empty() {
i += 1;
}
continue;
}
result.push(line.to_string());
i += 1;
}
result.join("\n")
}
fn is_heading(line: &str) -> bool {
let trimmed = line.trim();
trimmed.starts_with('#') && !trimmed.starts_with("```")
}
fn normalize_heading(line: &str) -> String {
let trimmed = line.trim();
let mut chars = trimmed.chars();
let mut hashes = String::new();
for ch in chars.by_ref() {
if ch == '#' {
hashes.push(ch);
} else {
if ch == ' ' {
return format!("{hashes} {}", chars.as_str());
}
return format!("{hashes} {ch}{}", chars.as_str());
}
}
hashes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_heading() {
assert_eq!(normalize_heading("#Heading"), "# Heading");
assert_eq!(normalize_heading("# Heading"), "# Heading");
assert_eq!(normalize_heading("##No Space"), "## No Space");
}
#[test]
fn test_is_heading() {
assert!(is_heading("# Heading"));
assert!(is_heading("## Another"));
assert!(!is_heading("Not a heading"));
assert!(!is_heading("```code"));
}
#[test]
fn test_format_headings() {
let input = "# Heading\nText\n## Another";
let config = HeadingConfig {
blank_lines_before: 1,
blank_lines_after: 1,
space_after_hash: true,
};
let result = format_headings(input, &config);
assert!(result.contains("# Heading"));
}
}