blob: 721988e29a67867bc750b59bb3ffa9846bb634bb [file] [log] [blame]
//! Table-of-contents creation.
/// A (recursive) table of contents
#[derive(Debug, PartialEq)]
pub 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<TocEntry>,
}
impl Toc {
fn count_entries_with_level(&self, level: u32) -> usize {
self.entries.iter().filter(|e| e.level == level).count()
}
}
#[derive(Debug, PartialEq)]
pub struct TocEntry {
level: u32,
sec_number: String,
name: String,
id: String,
children: Toc,
}
/// Progressive construction of a table of contents.
#[derive(PartialEq)]
pub 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<TocEntry>,
}
impl TocBuilder {
pub fn new() -> TocBuilder {
TocBuilder { top_level: Toc { entries: Vec::new() }, chain: Vec::new() }
}
/// Converts into a true `Toc` struct.
pub 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
/// `<num>.<num>.<num>` format.
pub 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_str(".");
(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) {
v.push_str("<ul>");
for entry in &self.entries {
// recursively format this table of contents
v.push_str(&format!(
"\n<li><a href=\"#{id}\">{num} {name}</a>",
id = entry.id,
num = entry.sec_number,
name = entry.name
));
entry.children.print_inner(&mut *v);
v.push_str("</li>");
}
v.push_str("</ul>");
}
crate fn print(&self) -> String {
let mut v = String::new();
self.print_inner(&mut v);
v
}
}
#[cfg(test)]
mod tests;