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.

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