pub(crate) mod block;
mod constructs;
pub(crate) mod derive;
pub(crate) mod profile;
mod tables;
use block::{BlockKind, Heading, List};
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::ops::Range;
pub(crate) use block::{parse_blocks, BlockTree};
pub(crate) use derive::{derive, Derived, DerivedNode};
pub(crate) use profile::StructureProfile;
pub(crate) fn heading_at(tree: &BlockTree, offset: usize) -> Option<&Heading> {
let after = tree.headings.partition_point(|h| h.range.start <= offset);
(after > 0).then(|| &tree.headings[after - 1])
}
const SPELLING: &[u8] = b"[]()!#";
pub(crate) fn mask_code_spans<'a>(body: &'a str, tree: &BlockTree) -> Cow<'a, str> {
if tree.code_spans.is_empty() {
return Cow::Borrowed(body);
}
let mut bytes = body.as_bytes().to_vec();
for span in &tree.code_spans {
for byte in &mut bytes[span.clone()] {
if SPELLING.contains(byte) {
*byte = 0;
}
}
}
Cow::Owned(
String::from_utf8(bytes).expect("only ASCII bytes were replaced, so UTF-8 still holds"),
)
}
pub(crate) fn scan_regions(body: &str, tree: &BlockTree) -> Vec<Range<usize>> {
let mut cuts: BTreeSet<usize> = BTreeSet::from([0, body.len()]);
let mut skipped: Vec<Range<usize>> = Vec::new();
for heading in &tree.headings {
cuts.insert(heading.range.start);
cuts.insert(heading.range.end);
}
for block in &tree.blocks {
cuts.insert(block.range.start);
cuts.insert(block.range.end);
match &block.kind {
BlockKind::Fence(_) => skipped.push(block.range.clone()),
BlockKind::List(list) => list_cuts(list, &mut cuts),
BlockKind::Table(table) => {
for cell in table.header.iter().chain(table.rows.iter().flatten()) {
cuts.insert(cell.range.start);
cuts.insert(cell.range.end);
}
}
_ => {}
}
}
for comment in &tree.comments {
cuts.insert(comment.start);
cuts.insert(comment.end);
skipped.push(comment.clone());
}
let cuts: Vec<usize> = cuts.into_iter().collect();
cuts.windows(2)
.map(|w| w[0]..w[1])
.filter(|r| {
r.start < r.end && !skipped.iter().any(|s| s.start <= r.start && r.end <= s.end)
})
.collect()
}
fn list_cuts(list: &List, cuts: &mut BTreeSet<usize>) {
for item in &list.items {
cuts.insert(item.range.start);
cuts.insert(item.range.end);
for child in &item.children {
list_cuts(child, cuts);
}
}
}