use dashmap::DashMap;
use pulldown_cmark::{Event, Parser, Tag, TagEnd, TextMergeStream};
use std::{
collections::{HashMap, HashSet},
fs, io,
path::PathBuf,
};
use crate::utils::create_options;
pub type SectionLinkMap = DashMap<PathBuf, HashSet<String>>;
pub fn parse_file_headings(path: &PathBuf) -> io::Result<HashSet<String>> {
fs::read_to_string(path)
.map(|content| crate::parser::collect_heading_links(&content))
}
#[must_use]
pub fn collect_heading_links(content: &str) -> HashSet<String> {
let mut headings = HashSet::new();
let mut heading_counter = HashMap::new();
let parser = TextMergeStream::new(Parser::new_ext(content, create_options()));
let mut current_heading = String::new();
let mut in_heading = false;
for event in parser {
match event {
Event::Start(Tag::Heading { .. }) => {
in_heading = true;
current_heading.clear();
}
Event::Text(text) | Event::Code(text) if in_heading => {
current_heading.push_str(&text);
}
Event::End(TagEnd::Heading { .. }) => {
let base_link = heading2link(¤t_heading);
let link = if let Some(counter) = heading_counter.get_mut(&base_link) {
let numbered_link = format!("{base_link}-{counter}");
*counter += 1;
numbered_link
} else {
heading_counter.insert(base_link.clone(), 1);
base_link
};
headings.insert(link);
in_heading = false;
}
_ => {}
}
}
headings
}
#[must_use]
pub fn heading2link(text: &str) -> String {
text.to_lowercase()
.chars()
.filter_map(|c| {
if c.is_alphanumeric() || c == '-' || c == '_' {
Some(c)
} else if c.is_whitespace() {
Some('-')
} else {
None
}
})
.collect()
}