use serde_derive::Serialize; /// Populated while receiving events from the markdown parser #[derive(Debug, PartialEq, Clone, Serialize)] pub struct Heading { #[serde(skip_serializing)] pub level: u32, pub id: String, pub permalink: String, pub title: String, pub children: Vec, } impl Heading { pub fn new(level: u32) -> Heading { Heading { level, id: String::new(), permalink: String::new(), title: String::new(), children: Vec::new(), } } } impl Default for Heading { fn default() -> Self { Heading::new(0) } } // 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 false } Some(parent) => { if heading.level <= parent.level { // Heading is same level or higher so we don't insert here return false; } 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()); } true } } } /// Converts the flat temp headings into a nested set of headings /// representing the hierarchy pub fn make_table_of_contents(headings: Vec) -> Vec { 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); } } 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)]; let toc = make_table_of_contents(input); 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), ]; let toc = make_table_of_contents(input); 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); } #[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), ]; let toc = make_table_of_contents(input); println!("{:#?}", toc); assert_eq!(toc.len(), 5); assert_eq!(toc[2].children.len(), 1); assert_eq!(toc[4].children.len(), 1); } }