1 //! Table-of-contents creation.
4 use std::string::String;
6 /// A (recursive) table of contents
9 /// The levels are strictly decreasing, i.e.
11 /// entries[0].level >= entries[1].level >= ...
13 /// Normally they are equal, but can differ in cases like A and B,
14 /// both of which end up in the same `Toc` as they have the same
22 entries: Vec<TocEntry>
26 fn count_entries_with_level(&self, level: u32) -> usize {
27 self.entries.iter().filter(|e| e.level == level).count()
40 /// Progressive construction of a table of contents.
42 pub struct TocBuilder {
44 /// The current hierarchy of parent headings, the levels are
45 /// strictly increasing (i.e., chain[0].level < chain[1].level <
46 /// ...) with each entry being the most recent occurrence of a
47 /// heading with that level (it doesn't include the most recent
48 /// occurrences of every level, just, if it *is* in `chain` then
49 /// it is the most recent one).
51 /// We also have `chain[0].level <= top_level.entries[last]`.
56 pub fn new() -> TocBuilder {
57 TocBuilder { top_level: Toc { entries: Vec::new() }, chain: Vec::new() }
61 /// Converts into a true `Toc` struct.
62 pub fn into_toc(mut self) -> Toc {
63 // we know all levels are >= 1.
68 /// Collapse the chain until the first heading more important than
69 /// `level` (i.e., lower level)
84 /// If we are considering H (i.e., level 3), then A and B are in
85 /// self.top_level, D is in C.children, and C, E, F, G are in
88 /// When we attempt to push H, we realize that first G is not the
89 /// parent (level is too high) so it is popped from chain and put
90 /// into F.children, then F isn't the parent (level is equal, aka
91 /// sibling), so it's also popped and put into E.children.
93 /// This leaves us looking at E, which does have a smaller level,
94 /// and, by construction, it's the most recent thing with smaller
95 /// level, i.e., it's the immediate parent of H.
96 fn fold_until(&mut self, level: u32) {
99 match self.chain.pop() {
101 this.map(|e| next.children.entries.push(e));
102 if next.level < level {
103 // this is the parent we want, so return it to
104 // its rightful place.
105 self.chain.push(next);
112 this.map(|e| self.top_level.entries.push(e));
119 /// Push a level `level` heading into the appropriate place in the
120 /// hierarchy, returning a string containing the section number in
121 /// `<num>.<num>.<num>` format.
122 pub fn push<'a>(&'a mut self, level: u32, name: String, id: String) -> &'a str {
125 // collapse all previous sections into their parents until we
126 // get to relevant heading (i.e., the first one with a smaller
128 self.fold_until(level);
132 let (toc_level, toc) = match self.chain.last() {
134 sec_number = String::new();
138 sec_number = entry.sec_number.clone();
139 sec_number.push_str(".");
140 (entry.level, &entry.children)
143 // fill in any missing zeros, e.g., for
146 for _ in toc_level..level - 1 {
147 sec_number.push_str("0.");
149 let number = toc.count_entries_with_level(level);
150 sec_number.push_str(&(number + 1).to_string())
153 self.chain.push(TocEntry {
158 children: Toc { entries: Vec::new() }
161 // get the thing we just pushed, so we can borrow the string
162 // out of it with the right lifetime
163 let just_inserted = self.chain.last_mut().unwrap();
164 &just_inserted.sec_number
168 impl fmt::Debug for Toc {
169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170 fmt::Display::fmt(self, f)
174 impl fmt::Display for Toc {
175 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
176 write!(fmt, "<ul>")?;
177 for entry in &self.entries {
178 // recursively format this table of contents (the
179 // `{children}` is the key).
181 "\n<li><a href=\"#{id}\">{num} {name}</a>{children}</li>",
183 num = entry.sec_number, name = entry.name,
184 children = entry.children)?
192 use super::{TocBuilder, Toc, TocEntry};
196 let mut builder = TocBuilder::new();
198 // this is purposely not using a fancy macro like below so
199 // that we're sure that this is doing the correct thing, and
200 // there's been no macro mistake.
202 ($level: expr, $name: expr) => {
203 assert_eq!(builder.push($level,
228 push!(6, "3.0.0.1.0.1");
238 ($(($level: expr, $name: expr, $(($sub: tt))* )),*) => {
244 name: $name.to_string(),
245 sec_number: $name.to_string(),
247 children: toc!($($sub),*)
258 ((2, "1.1", ((3, "1.1.1", )) ((3, "1.1.2", ))))
259 ((2, "1.2", ((3, "1.2.1", )) ((3, "1.2.2", ))))
265 ((4, "3.0.0.1", ((6, "3.0.0.1.0.1", ))))
267 ((2, "3.1", ((4, "3.1.0.1", ))))
270 assert_eq!(expected, builder.into_toc());