You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
5.3KB

  1. /// Populated while receiving events from the markdown parser
  2. #[derive(Debug, PartialEq, Clone, Serialize)]
  3. pub struct Header {
  4. #[serde(skip_serializing)]
  5. pub level: i32,
  6. pub id: String,
  7. pub permalink: String,
  8. pub title: String,
  9. pub children: Vec<Header>,
  10. }
  11. impl Header {
  12. pub fn new(level: i32) -> Header {
  13. Header {
  14. level,
  15. id: String::new(),
  16. permalink: String::new(),
  17. title: String::new(),
  18. children: Vec::new(),
  19. }
  20. }
  21. }
  22. impl Default for Header {
  23. fn default() -> Self {
  24. Header::new(0)
  25. }
  26. }
  27. // Takes a potential (mutable) parent and a header to try and insert into
  28. // Returns true when it performed the insertion, false otherwise
  29. fn insert_into_parent(potential_parent: Option<&mut Header>, header: &Header) -> bool {
  30. match potential_parent {
  31. None => {
  32. // No potential parent to insert into so it needs to be insert higher
  33. return false;
  34. },
  35. Some(parent) => {
  36. let diff = header.level - parent.level;
  37. if diff <= 0 {
  38. // Heading is same level or higher so we don't insert here
  39. return false;
  40. }
  41. if diff == 1 {
  42. // We have a direct child of the parent
  43. parent.children.push(header.clone());
  44. return true;
  45. }
  46. // We need to go deeper
  47. if !insert_into_parent(parent.children.iter_mut().last(), header) {
  48. // No, we need to insert it here
  49. parent.children.push(header.clone());
  50. }
  51. return true;
  52. }
  53. }
  54. }
  55. /// Converts the flat temp headers into a nested set of headers
  56. /// representing the hierarchy
  57. pub fn make_table_of_contents(headers: Vec<Header>) -> Vec<Header> {
  58. let mut toc = vec![];
  59. for header in headers {
  60. if toc.is_empty() {
  61. // First header, nothing to compare it with
  62. toc.push(header);
  63. continue;
  64. }
  65. // We try to insert the current header in a previous one
  66. match insert_into_parent(toc.iter_mut().last(), &header) {
  67. true => {
  68. // Header was successfully inserted as a child of a previous element
  69. continue;
  70. },
  71. false => {
  72. // Couldn't insert in a previous header, so it's a top-level header
  73. toc.push(header);
  74. continue;
  75. }
  76. }
  77. }
  78. toc
  79. }
  80. #[cfg(test)]
  81. mod tests {
  82. use super::*;
  83. #[test]
  84. fn can_make_basic_toc() {
  85. let input = vec![Header::new(1), Header::new(1), Header::new(1)];
  86. let toc = make_table_of_contents(input);
  87. assert_eq!(toc.len(), 3);
  88. }
  89. #[test]
  90. fn can_make_more_complex_toc() {
  91. let input = vec![
  92. Header::new(1),
  93. Header::new(2),
  94. Header::new(2),
  95. Header::new(3),
  96. Header::new(2),
  97. Header::new(1),
  98. Header::new(2),
  99. Header::new(3),
  100. Header::new(3),
  101. ];
  102. let toc = make_table_of_contents(input);
  103. assert_eq!(toc.len(), 2);
  104. assert_eq!(toc[0].children.len(), 3);
  105. assert_eq!(toc[1].children.len(), 1);
  106. assert_eq!(toc[0].children[1].children.len(), 1);
  107. assert_eq!(toc[1].children[0].children.len(), 2);
  108. }
  109. #[test]
  110. fn can_make_deep_toc() {
  111. let input = vec![
  112. Header::new(1),
  113. Header::new(2),
  114. Header::new(3),
  115. Header::new(4),
  116. Header::new(5),
  117. Header::new(4),
  118. ];
  119. let toc = make_table_of_contents(input);
  120. assert_eq!(toc.len(), 1);
  121. assert_eq!(toc[0].children.len(), 1);
  122. assert_eq!(toc[0].children[0].children.len(), 1);
  123. assert_eq!(toc[0].children[0].children[0].children.len(), 2);
  124. assert_eq!(toc[0].children[0].children[0].children[0].children.len(), 1);
  125. }
  126. #[test]
  127. fn can_make_deep_messy_toc() {
  128. let input = vec![
  129. Header::new(2), // toc[0]
  130. Header::new(3),
  131. Header::new(4),
  132. Header::new(5),
  133. Header::new(4),
  134. Header::new(2), // toc[1]
  135. Header::new(1), // toc[2]
  136. Header::new(2),
  137. Header::new(3),
  138. Header::new(4),
  139. ];
  140. let toc = make_table_of_contents(input);
  141. assert_eq!(toc.len(), 3);
  142. assert_eq!(toc[0].children.len(), 1);
  143. assert_eq!(toc[0].children[0].children.len(), 2);
  144. assert_eq!(toc[0].children[0].children[0].children.len(), 1);
  145. assert_eq!(toc[1].children.len(), 0);
  146. assert_eq!(toc[2].children.len(), 1);
  147. assert_eq!(toc[2].children[0].children.len(), 1);
  148. assert_eq!(toc[2].children[0].children[0].children.len(), 1);
  149. }
  150. #[test]
  151. fn can_make_messy_toc() {
  152. let input = vec![
  153. Header::new(3),
  154. Header::new(2),
  155. Header::new(2),
  156. Header::new(3),
  157. Header::new(2),
  158. Header::new(1),
  159. Header::new(4),
  160. ];
  161. let toc = make_table_of_contents(input);
  162. println!("{:#?}", toc);
  163. assert_eq!(toc.len(), 5);
  164. assert_eq!(toc[2].children.len(), 1);
  165. assert_eq!(toc[4].children.len(), 1);
  166. }
  167. }