2014-03-08 01:13:17 +11:00
|
|
|
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
//! Table-of-contents creation.
|
|
|
|
|
|
|
|
use std::fmt;
|
2014-05-22 16:57:53 -07:00
|
|
|
use std::string::String;
|
2014-03-08 01:13:17 +11:00
|
|
|
|
|
|
|
/// A (recursive) table of contents
|
2014-05-29 17:45:07 -07:00
|
|
|
#[deriving(PartialEq)]
|
2014-03-08 01:13:17 +11:00
|
|
|
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).
|
|
|
|
///
|
2014-12-10 13:54:56 -05:00
|
|
|
/// ```text
|
2014-03-08 01:13:17 +11:00
|
|
|
/// # Main
|
|
|
|
/// ### A
|
|
|
|
/// ## B
|
2014-12-10 13:54:56 -05:00
|
|
|
/// ```
|
2014-03-28 10:27:24 -07:00
|
|
|
entries: Vec<TocEntry>
|
2014-03-08 01:13:17 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Toc {
|
|
|
|
fn count_entries_with_level(&self, level: u32) -> uint {
|
2014-06-05 23:18:51 -07:00
|
|
|
self.entries.iter().filter(|e| e.level == level).count()
|
2014-03-08 01:13:17 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-29 17:45:07 -07:00
|
|
|
#[deriving(PartialEq)]
|
2014-03-08 01:13:17 +11:00
|
|
|
pub struct TocEntry {
|
2014-03-28 10:27:24 -07:00
|
|
|
level: u32,
|
2014-05-22 16:57:53 -07:00
|
|
|
sec_number: String,
|
|
|
|
name: String,
|
|
|
|
id: String,
|
2014-03-28 10:27:24 -07:00
|
|
|
children: Toc,
|
2014-03-08 01:13:17 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Progressive construction of a table of contents.
|
2014-05-29 17:45:07 -07:00
|
|
|
#[deriving(PartialEq)]
|
2014-03-08 01:13:17 +11:00
|
|
|
pub struct TocBuilder {
|
2014-03-28 10:27:24 -07:00
|
|
|
top_level: Toc,
|
2014-06-09 00:00:52 -04:00
|
|
|
/// The current hierarchy of parent headings, the levels are
|
2014-03-08 01:13:17 +11:00
|
|
|
/// strictly increasing (i.e. chain[0].level < chain[1].level <
|
2014-06-09 00:00:52 -04:00
|
|
|
/// ...) with each entry being the most recent occurrence of a
|
2014-03-08 01:13:17 +11:00
|
|
|
/// heading with that level (it doesn't include the most recent
|
2014-06-09 00:00:52 -04:00
|
|
|
/// occurrences of every level, just, if *is* in `chain` then is is
|
2014-03-08 01:13:17 +11:00
|
|
|
/// the most recent one).
|
|
|
|
///
|
|
|
|
/// We also have `chain[0].level <= top_level.entries[last]`.
|
2014-03-28 10:27:24 -07:00
|
|
|
chain: Vec<TocEntry>
|
2014-03-08 01:13:17 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TocBuilder {
|
|
|
|
pub fn new() -> TocBuilder {
|
|
|
|
TocBuilder { top_level: Toc { entries: Vec::new() }, chain: Vec::new() }
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Convert 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:
|
|
|
|
///
|
2014-12-10 13:54:56 -05:00
|
|
|
/// ```text
|
2014-03-08 01:13:17 +11:00
|
|
|
/// ## A
|
|
|
|
/// # B
|
|
|
|
/// # C
|
|
|
|
/// ## D
|
|
|
|
/// ## E
|
|
|
|
/// ### F
|
|
|
|
/// #### G
|
|
|
|
/// ### H
|
2014-12-10 13:54:56 -05:00
|
|
|
/// ```
|
2014-03-08 01:13:17 +11:00
|
|
|
///
|
|
|
|
/// 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 realise 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) => {
|
|
|
|
this.map(|e| next.children.entries.push(e));
|
|
|
|
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 => {
|
|
|
|
this.map(|e| self.top_level.entries.push(e));
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Push a level `level` heading into the appropriate place in the
|
2014-06-09 00:00:52 -04:00
|
|
|
/// hierarchy, returning a string containing the section number in
|
2014-03-08 01:13:17 +11:00
|
|
|
/// `<num>.<num>.<num>` format.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub fn push<'a>(&'a mut self, level: u32, name: String, id: String) -> &'a str {
|
2014-03-08 01:13:17 +11:00
|
|
|
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 => {
|
2014-05-22 16:57:53 -07:00
|
|
|
sec_number = String::new();
|
2014-03-08 01:13:17 +11:00
|
|
|
(0, &self.top_level)
|
|
|
|
}
|
|
|
|
Some(entry) => {
|
2014-05-22 16:57:53 -07:00
|
|
|
sec_number = String::from_str(entry.sec_number
|
2014-05-12 13:44:59 -07:00
|
|
|
.as_slice());
|
2014-03-08 01:13:17 +11:00
|
|
|
sec_number.push_str(".");
|
|
|
|
(entry.level, &entry.children)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// fill in any missing zeros, e.g. for
|
|
|
|
// # Foo (1)
|
|
|
|
// ### Bar (1.0.1)
|
|
|
|
for _ in range(toc_level, level - 1) {
|
|
|
|
sec_number.push_str("0.");
|
|
|
|
}
|
|
|
|
let number = toc.count_entries_with_level(level);
|
2014-05-27 20:44:58 -07:00
|
|
|
sec_number.push_str(format!("{}", number + 1).as_slice())
|
2014-03-08 01:13:17 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
self.chain.push(TocEntry {
|
2014-04-02 16:54:22 -07:00
|
|
|
level: level,
|
|
|
|
name: name,
|
2014-05-12 13:44:59 -07:00
|
|
|
sec_number: sec_number,
|
2014-04-02 16:54:22 -07:00
|
|
|
id: id,
|
|
|
|
children: Toc { entries: Vec::new() }
|
|
|
|
});
|
2014-03-08 01:13:17 +11:00
|
|
|
|
|
|
|
// get the thing we just pushed, so we can borrow the string
|
|
|
|
// out of it with the right lifetime
|
2014-09-14 20:27:36 -07:00
|
|
|
let just_inserted = self.chain.last_mut().unwrap();
|
2014-03-08 01:13:17 +11:00
|
|
|
just_inserted.sec_number.as_slice()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Show for Toc {
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
2014-05-10 14:05:06 -07:00
|
|
|
try!(write!(fmt, "<ul>"));
|
2014-03-08 01:13:17 +11:00
|
|
|
for entry in self.entries.iter() {
|
|
|
|
// recursively format this table of contents (the
|
|
|
|
// `{children}` is the key).
|
2014-05-10 14:05:06 -07:00
|
|
|
try!(write!(fmt,
|
2014-06-14 11:03:34 -07:00
|
|
|
"\n<li><a href=\"#{id}\">{num} {name}</a>{children}</li>",
|
2014-03-08 01:13:17 +11:00
|
|
|
id = entry.id,
|
|
|
|
num = entry.sec_number, name = entry.name,
|
|
|
|
children = entry.children))
|
|
|
|
}
|
2014-05-10 14:05:06 -07:00
|
|
|
write!(fmt, "</ul>")
|
2014-03-08 01:13:17 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::{TocBuilder, Toc, TocEntry};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn builder_smoke() {
|
|
|
|
let mut builder = TocBuilder::new();
|
|
|
|
|
|
|
|
// this is purposely not using a fancy macro like below so
|
|
|
|
// that we're sure that this is doing the correct thing, and
|
|
|
|
// there's been no macro mistake.
|
|
|
|
macro_rules! push {
|
|
|
|
($level: expr, $name: expr) => {
|
2014-05-12 13:44:59 -07:00
|
|
|
assert_eq!(builder.push($level,
|
2014-05-25 03:17:19 -07:00
|
|
|
$name.to_string(),
|
|
|
|
"".to_string()),
|
2014-05-12 13:44:59 -07:00
|
|
|
$name);
|
2014-03-08 01:13:17 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
push!(2, "0.1");
|
|
|
|
push!(1, "1");
|
|
|
|
{
|
|
|
|
push!(2, "1.1");
|
|
|
|
{
|
|
|
|
push!(3, "1.1.1");
|
|
|
|
push!(3, "1.1.2");
|
|
|
|
}
|
|
|
|
push!(2, "1.2");
|
|
|
|
{
|
|
|
|
push!(3, "1.2.1");
|
|
|
|
push!(3, "1.2.2");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
push!(1, "2");
|
|
|
|
push!(1, "3");
|
|
|
|
{
|
|
|
|
push!(4, "3.0.0.1");
|
|
|
|
{
|
|
|
|
push!(6, "3.0.0.1.0.1");
|
|
|
|
}
|
|
|
|
push!(4, "3.0.0.2");
|
|
|
|
push!(2, "3.1");
|
|
|
|
{
|
|
|
|
push!(4, "3.1.0.1");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! toc {
|
|
|
|
($(($level: expr, $name: expr, $(($sub: tt))* )),*) => {
|
|
|
|
Toc {
|
|
|
|
entries: vec!(
|
|
|
|
$(
|
|
|
|
TocEntry {
|
|
|
|
level: $level,
|
2014-05-25 03:17:19 -07:00
|
|
|
name: $name.to_string(),
|
|
|
|
sec_number: $name.to_string(),
|
|
|
|
id: "".to_string(),
|
2014-03-08 01:13:17 +11:00
|
|
|
children: toc!($($sub),*)
|
|
|
|
}
|
|
|
|
),*
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let expected = toc!(
|
|
|
|
(2, "0.1", ),
|
|
|
|
|
|
|
|
(1, "1",
|
|
|
|
((2, "1.1", ((3, "1.1.1", )) ((3, "1.1.2", ))))
|
|
|
|
((2, "1.2", ((3, "1.2.1", )) ((3, "1.2.2", ))))
|
|
|
|
),
|
|
|
|
|
|
|
|
(1, "2", ),
|
|
|
|
|
|
|
|
(1, "3",
|
|
|
|
((4, "3.0.0.1", ((6, "3.0.0.1.0.1", ))))
|
|
|
|
((4, "3.0.0.2", ))
|
|
|
|
((2, "3.1", ((4, "3.1.0.1", ))))
|
|
|
|
)
|
|
|
|
);
|
|
|
|
assert_eq!(expected, builder.into_toc());
|
|
|
|
}
|
|
|
|
}
|