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.

164 lines
4.4KB

  1. #[derive(Debug, PartialEq, Clone, Serialize)]
  2. pub struct Header {
  3. #[serde(skip_serializing)]
  4. pub level: i32,
  5. pub id: String,
  6. pub title: String,
  7. pub permalink: String,
  8. pub children: Vec<Header>,
  9. }
  10. impl Header {
  11. pub fn from_temp_header(tmp: &TempHeader, children: Vec<Header>) -> Header {
  12. Header {
  13. level: tmp.level,
  14. id: tmp.id.clone(),
  15. title: tmp.title.clone(),
  16. permalink: tmp.permalink.clone(),
  17. children,
  18. }
  19. }
  20. }
  21. /// Populated while receiving events from the markdown parser
  22. #[derive(Debug, PartialEq, Clone)]
  23. pub struct TempHeader {
  24. pub level: i32,
  25. pub id: String,
  26. pub permalink: String,
  27. pub title: String,
  28. }
  29. impl TempHeader {
  30. pub fn new(level: i32) -> TempHeader {
  31. TempHeader {
  32. level,
  33. id: String::new(),
  34. permalink: String::new(),
  35. title: String::new(),
  36. }
  37. }
  38. }
  39. impl Default for TempHeader {
  40. fn default() -> Self {
  41. TempHeader::new(0)
  42. }
  43. }
  44. /// Recursively finds children of a header
  45. fn find_children(
  46. parent_level: i32,
  47. start_at: usize,
  48. temp_headers: &[TempHeader],
  49. ) -> (usize, Vec<Header>) {
  50. let mut headers = vec![];
  51. let mut start_at = start_at;
  52. // If we have children, we will need to skip some headers since they are already inserted
  53. let mut to_skip = 0;
  54. for h in &temp_headers[start_at..] {
  55. // stop when we encounter a title at the same level or higher
  56. // than the parent one. Here a lower integer is considered higher as we are talking about
  57. // HTML headers: h1, h2, h3, h4, h5 and h6
  58. if h.level <= parent_level {
  59. return (start_at, headers);
  60. }
  61. // Do we need to skip some headers?
  62. if to_skip > 0 {
  63. to_skip -= 1;
  64. continue;
  65. }
  66. let (end, children) = find_children(h.level, start_at + 1, temp_headers);
  67. headers.push(Header::from_temp_header(h, children));
  68. // we didn't find any children
  69. if end == start_at {
  70. start_at += 1;
  71. to_skip = 0;
  72. } else {
  73. // calculates how many we need to skip. Since the find_children start_at starts at 1,
  74. // we need to remove 1 to ensure correctness
  75. to_skip = end - start_at - 1;
  76. start_at = end;
  77. }
  78. // we don't want to index out of bounds
  79. if start_at + 1 > temp_headers.len() {
  80. return (start_at, headers);
  81. }
  82. }
  83. (start_at, headers)
  84. }
  85. /// Converts the flat temp headers into a nested set of headers
  86. /// representing the hierarchy
  87. pub fn make_table_of_contents(temp_headers: &[TempHeader]) -> Vec<Header> {
  88. let mut toc = vec![];
  89. let mut start_idx = 0;
  90. for (i, h) in temp_headers.iter().enumerate() {
  91. if i < start_idx {
  92. continue;
  93. }
  94. let (end_idx, children) = find_children(h.level, start_idx + 1, temp_headers);
  95. start_idx = end_idx;
  96. toc.push(Header::from_temp_header(h, children));
  97. }
  98. toc
  99. }
  100. #[cfg(test)]
  101. mod tests {
  102. use super::*;
  103. #[test]
  104. fn can_make_basic_toc() {
  105. let input = vec![TempHeader::new(1), TempHeader::new(1), TempHeader::new(1)];
  106. let toc = make_table_of_contents(&input);
  107. assert_eq!(toc.len(), 3);
  108. }
  109. #[test]
  110. fn can_make_more_complex_toc() {
  111. let input = vec![
  112. TempHeader::new(1),
  113. TempHeader::new(2),
  114. TempHeader::new(2),
  115. TempHeader::new(3),
  116. TempHeader::new(2),
  117. TempHeader::new(1),
  118. TempHeader::new(2),
  119. TempHeader::new(3),
  120. TempHeader::new(3),
  121. ];
  122. let toc = make_table_of_contents(&input);
  123. assert_eq!(toc.len(), 2);
  124. assert_eq!(toc[0].children.len(), 3);
  125. assert_eq!(toc[1].children.len(), 1);
  126. assert_eq!(toc[0].children[1].children.len(), 1);
  127. assert_eq!(toc[1].children[0].children.len(), 2);
  128. }
  129. #[test]
  130. fn can_make_messy_toc() {
  131. let input = vec![
  132. TempHeader::new(3),
  133. TempHeader::new(2),
  134. TempHeader::new(2),
  135. TempHeader::new(3),
  136. TempHeader::new(2),
  137. TempHeader::new(1),
  138. TempHeader::new(4),
  139. ];
  140. let toc = make_table_of_contents(&input);
  141. assert_eq!(toc.len(), 5);
  142. assert_eq!(toc[2].children.len(), 1);
  143. assert_eq!(toc[4].children.len(), 1);
  144. }
  145. }