use crate::{stream::EventKind, NodeId};
use alloc::{collections::BTreeMap, string::String};
pub(crate) fn append_text<const HEADING: bool>(
kind: &EventKind<'_>,
source: &str,
text: &mut String,
) {
let value = match kind {
EventKind::Text(value)
| EventKind::SoftBreak(value)
| EventKind::Math(value)
| EventKind::Code { value, .. }
| EventKind::Html { value, .. } => value,
EventKind::CodeBlock { value, .. } if !HEADING => value,
EventKind::HardBreak => {
text.push('\n');
return;
}
_ => return,
};
text.push_str(
value
.resolve(source)
.expect("semantic text values are validated"),
);
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HeadingOptions {
pub min_level: u8,
pub max_level: u8,
}
impl Default for HeadingOptions {
fn default() -> Self {
Self {
min_level: 1,
max_level: 6,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HeadingEntry {
pub node: NodeId,
pub level: u8,
pub text: String,
pub anchor: String,
}
#[derive(Default)]
pub(crate) struct AnchorGenerator {
next_suffix: BTreeMap<String, usize>,
}
impl AnchorGenerator {
pub(crate) fn anchor_for(&mut self, text: &str) -> String {
let base = slug(text);
let Some(mut suffix) = self.next_suffix.get(&base).copied() else {
self.next_suffix.insert(base.clone(), 2);
return base;
};
let anchor = loop {
let candidate = alloc::format!("{base}-{suffix}");
suffix += 1;
if !self.next_suffix.contains_key(&candidate) {
break candidate;
}
};
self.next_suffix.insert(base, suffix);
self.next_suffix.insert(anchor.clone(), 2);
anchor
}
}
fn slug(text: &str) -> String {
let mut result = String::new();
let mut previous_was_whitespace = false;
let lowercase = text.to_lowercase();
for character in lowercase.chars() {
if character.is_whitespace() {
if !previous_was_whitespace {
result.push('-');
}
previous_was_whitespace = true;
} else {
result.push(character);
previous_was_whitespace = false;
}
}
result
}