//! Table-of-contents creation. /// A (recursive) table of contents #[derive(Debug, PartialEq)] pub(crate) struct Toc { /// The levels are strictly decreasing, i.e. /// /// `entries[0].level >= entries[1].level >= ...` /// /// Normally they are equal, but can differ in cases like A and B, /// both of which end up in the same `Toc` as they have the same /// parent (Main). /// /// ```text /// # Main /// ### A /// ## B /// ``` entries: Vec, } impl Toc { fn count_entries_with_level(&self, level: u32) -> usize { self.entries.iter().filter(|e| e.level == level).count() } } #[derive(Debug, PartialEq)] pub(crate) struct TocEntry { level: u32, sec_number: String, name: String, id: String, children: Toc, } /// Progressive construction of a table of contents. #[derive(PartialEq)] pub(crate) struct TocBuilder { top_level: Toc, /// The current hierarchy of parent headings, the levels are /// strictly increasing (i.e., `chain[0].level < chain[1].level < /// ...`) with each entry being the most recent occurrence of a /// heading with that level (it doesn't include the most recent /// occurrences of every level, just, if it *is* in `chain` then /// it is the most recent one). /// /// We also have `chain[0].level <= top_level.entries[last]`. chain: Vec, } impl TocBuilder { pub(crate) fn new() -> TocBuilder { TocBuilder { top_level: Toc { entries: Vec::new() }, chain: Vec::new() } } /// Converts into a true `Toc` struct. pub(crate) fn into_toc(mut self) -> Toc { // we know all levels are >= 1. self.fold_until(0); self.top_level } /// Collapse the chain until the first heading more important than /// `level` (i.e., lower level) /// /// Example: /// /// ```text /// ## A /// # B /// # C /// ## D /// ## E /// ### F /// #### G /// ### H /// ``` /// /// If we are considering H (i.e., level 3), then A and B are in /// self.top_level, D is in C.children, and C, E, F, G are in /// self.chain. /// /// When we attempt to push H, we realize that first G is not the /// parent (level is too high) so it is popped from chain and put /// into F.children, then F isn't the parent (level is equal, aka /// sibling), so it's also popped and put into E.children. /// /// This leaves us looking at E, which does have a smaller level, /// and, by construction, it's the most recent thing with smaller /// level, i.e., it's the immediate parent of H. fn fold_until(&mut self, level: u32) { let mut this = None; loop { match self.chain.pop() { Some(mut next) => { next.children.entries.extend(this); if next.level < level { // this is the parent we want, so return it to // its rightful place. self.chain.push(next); return; } else { this = Some(next); } } None => { self.top_level.entries.extend(this); return; } } } } /// Push a level `level` heading into the appropriate place in the /// hierarchy, returning a string containing the section number in /// `..` format. pub(crate) fn push(&mut self, level: u32, name: String, id: String) -> &str { assert!(level >= 1); // collapse all previous sections into their parents until we // get to relevant heading (i.e., the first one with a smaller // level than us) self.fold_until(level); let mut sec_number; { let (toc_level, toc) = match self.chain.last() { None => { sec_number = String::new(); (0, &self.top_level) } Some(entry) => { sec_number = entry.sec_number.clone(); sec_number.push('.'); (entry.level, &entry.children) } }; // fill in any missing zeros, e.g., for // # Foo (1) // ### Bar (1.0.1) for _ in toc_level..level - 1 { sec_number.push_str("0."); } let number = toc.count_entries_with_level(level); sec_number.push_str(&(number + 1).to_string()) } self.chain.push(TocEntry { level, name, sec_number, id, children: Toc { entries: Vec::new() }, }); // get the thing we just pushed, so we can borrow the string // out of it with the right lifetime let just_inserted = self.chain.last_mut().unwrap(); &just_inserted.sec_number } } impl Toc { fn print_inner(&self, v: &mut String) { use std::fmt::Write as _; v.push_str("
    "); for entry in &self.entries { // recursively format this table of contents let _ = write!( v, "\n
  • {num} {name}", id = entry.id, num = entry.sec_number, name = entry.name ); entry.children.print_inner(&mut *v); v.push_str("
  • "); } v.push_str("
"); } pub(crate) fn print(&self) -> String { let mut v = String::new(); self.print_inner(&mut v); v } } #[cfg(test)] mod tests;