Fix ToC generation for heading levels > 3 (bugfix) (#774)

* Fix ToC generation for heading levels > 3

* typo

* Add tests for deep ToCs

* Code style change
This commit is contained in:
southerntofu 2019-08-15 08:19:32 +00:00 committed by Vincent Prouillet
parent 36f4ad9a4b
commit 4695b029a0

View file

@ -27,33 +27,58 @@ impl Default for Header {
} }
} }
// Takes a potential (mutable) parent and a header to try and insert into
// Returns true when it performed the insertion, false otherwise
fn insert_into_parent(potential_parent: Option<&mut Header>, header: &Header) -> bool {
match potential_parent {
None => {
// No potential parent to insert into so it needs to be insert higher
return false;
},
Some(parent) => {
let diff = header.level - parent.level;
if diff <= 0 {
// Heading is same level or higher so we don't insert here
return false;
}
if diff == 1 {
// We have a direct child of the parent
parent.children.push(header.clone());
return true;
}
// We need to go deeper
if !insert_into_parent(parent.children.iter_mut().last(), header) {
// No, we need to insert it here
parent.children.push(header.clone());
}
return true;
}
}
}
/// Converts the flat temp headers into a nested set of headers /// Converts the flat temp headers into a nested set of headers
/// representing the hierarchy /// representing the hierarchy
pub fn make_table_of_contents(headers: Vec<Header>) -> Vec<Header> { pub fn make_table_of_contents(headers: Vec<Header>) -> Vec<Header> {
let mut toc = vec![]; let mut toc = vec![];
'parent: for header in headers { for header in headers {
if toc.is_empty() { if toc.is_empty() {
// First header, nothing to compare it with
toc.push(header); toc.push(header);
continue; continue;
} }
// See if we have to insert as a child of a previous header // We try to insert the current header in a previous one
for h in toc.iter_mut().rev() { match insert_into_parent(toc.iter_mut().last(), &header) {
// Look in its children first true => {
for child in h.children.iter_mut().rev() { // Header was successfully inserted as a child of a previous element
if header.level > child.level { continue;
child.children.push(header); },
continue 'parent; false => {
// Couldn't insert in a previous header, so it's a top-level header
toc.push(header);
continue;
} }
} }
if header.level > h.level {
h.children.push(header);
continue 'parent;
}
}
// Nop, just insert it
toc.push(header)
} }
toc toc
@ -91,6 +116,49 @@ mod tests {
assert_eq!(toc[1].children[0].children.len(), 2); assert_eq!(toc[1].children[0].children.len(), 2);
} }
#[test]
fn can_make_deep_toc() {
let input = vec![
Header::new(1),
Header::new(2),
Header::new(3),
Header::new(4),
Header::new(5),
Header::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![
Header::new(2), // toc[0]
Header::new(3),
Header::new(4),
Header::new(5),
Header::new(4),
Header::new(2), // toc[1]
Header::new(1), // toc[2]
Header::new(2),
Header::new(3),
Header::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);
}
#[test] #[test]
fn can_make_messy_toc() { fn can_make_messy_toc() {
let input = vec![ let input = vec![