zola/components/rendering/src/table_of_contents.rs

167 lines
4.9 KiB
Rust
Raw Normal View History

2019-12-21 21:52:39 +00:00
use serde_derive::Serialize;
2019-01-27 23:34:18 +00:00
/// Populated while receiving events from the markdown parser
2017-06-16 04:00:48 +00:00
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Heading {
2017-06-16 04:00:48 +00:00
#[serde(skip_serializing)]
pub level: u32,
2017-06-16 04:00:48 +00:00
pub id: String,
pub permalink: String,
2019-01-27 23:34:18 +00:00
pub title: String,
pub children: Vec<Heading>,
2017-06-16 04:00:48 +00:00
}
impl Heading {
pub fn new(level: u32) -> Heading {
Heading {
2017-06-16 04:00:48 +00:00
level,
id: String::new(),
permalink: String::new(),
title: String::new(),
2019-01-27 23:34:18 +00:00
children: Vec::new(),
2017-09-27 14:09:13 +00:00
}
}
2017-06-16 04:00:48 +00:00
}
impl Default for Heading {
2017-06-16 04:00:48 +00:00
fn default() -> Self {
Heading::new(0)
2017-06-16 04:00:48 +00:00
}
}
// Takes a potential (mutable) parent and a heading to try and insert into
// Returns true when it performed the insertion, false otherwise
fn insert_into_parent(potential_parent: Option<&mut Heading>, heading: &Heading) -> bool {
match potential_parent {
None => {
// No potential parent to insert into so it needs to be insert higher
2019-08-24 20:23:08 +00:00
false
}
Some(parent) => {
if heading.level <= parent.level {
// Heading is same level or higher so we don't insert here
return false;
2019-08-24 20:23:08 +00:00
}
if heading.level + 1 == parent.level {
// We have a direct child of the parent
parent.children.push(heading.clone());
return true;
}
// We need to go deeper
if !insert_into_parent(parent.children.iter_mut().last(), heading) {
// No, we need to insert it here
parent.children.push(heading.clone());
}
2019-08-24 20:23:08 +00:00
true
}
}
}
/// Converts the flat temp headings into a nested set of headings
2017-06-16 04:00:48 +00:00
/// representing the hierarchy
pub fn make_table_of_contents(headings: Vec<Heading>) -> Vec<Heading> {
2017-06-16 04:00:48 +00:00
let mut toc = vec![];
for heading in headings {
// First heading or we try to insert the current heading in a previous one
if toc.is_empty() || !insert_into_parent(toc.iter_mut().last(), &heading) {
toc.push(heading);
2019-01-27 23:34:18 +00:00
}
2017-06-16 04:00:48 +00:00
}
toc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_make_basic_toc() {
let input = vec![Heading::new(1), Heading::new(1), Heading::new(1)];
2019-01-27 23:34:18 +00:00
let toc = make_table_of_contents(input);
2017-06-16 04:00:48 +00:00
assert_eq!(toc.len(), 3);
}
#[test]
fn can_make_more_complex_toc() {
let input = vec![
Heading::new(1),
Heading::new(2),
Heading::new(2),
Heading::new(3),
Heading::new(2),
Heading::new(1),
Heading::new(2),
Heading::new(3),
Heading::new(3),
2017-06-16 04:00:48 +00:00
];
2019-01-27 23:34:18 +00:00
let toc = make_table_of_contents(input);
2017-06-16 04:00:48 +00:00
assert_eq!(toc.len(), 2);
assert_eq!(toc[0].children.len(), 3);
assert_eq!(toc[1].children.len(), 1);
assert_eq!(toc[0].children[1].children.len(), 1);
assert_eq!(toc[1].children[0].children.len(), 2);
}
#[test]
fn can_make_deep_toc() {
let input = vec![
Heading::new(1),
Heading::new(2),
Heading::new(3),
Heading::new(4),
Heading::new(5),
Heading::new(4),
];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 1);
assert_eq!(toc[0].children.len(), 1);
assert_eq!(toc[0].children[0].children.len(), 1);
assert_eq!(toc[0].children[0].children[0].children.len(), 2);
assert_eq!(toc[0].children[0].children[0].children[0].children.len(), 1);
}
#[test]
fn can_make_deep_messy_toc() {
let input = vec![
Heading::new(2), // toc[0]
Heading::new(3),
Heading::new(4),
Heading::new(5),
Heading::new(4),
Heading::new(2), // toc[1]
Heading::new(1), // toc[2]
Heading::new(2),
Heading::new(3),
Heading::new(4),
];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 3);
assert_eq!(toc[0].children.len(), 1);
assert_eq!(toc[0].children[0].children.len(), 2);
assert_eq!(toc[0].children[0].children[0].children.len(), 1);
assert_eq!(toc[1].children.len(), 0);
assert_eq!(toc[2].children.len(), 1);
assert_eq!(toc[2].children[0].children.len(), 1);
assert_eq!(toc[2].children[0].children[0].children.len(), 1);
}
2017-06-16 04:00:48 +00:00
#[test]
fn can_make_messy_toc() {
let input = vec![
Heading::new(3),
Heading::new(2),
Heading::new(2),
Heading::new(3),
Heading::new(2),
Heading::new(1),
Heading::new(4),
2017-06-16 04:00:48 +00:00
];
2019-01-27 23:34:18 +00:00
let toc = make_table_of_contents(input);
println!("{:#?}", toc);
2017-06-16 04:00:48 +00:00
assert_eq!(toc.len(), 5);
assert_eq!(toc[2].children.len(), 1);
assert_eq!(toc[4].children.len(), 1);
}
}