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.

167 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. /// Used in
  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(parent_level: i32, start_at: usize, temp_headers: &[TempHeader]) -> (usize, Vec<Header>) {
  46. let mut headers = vec![];
  47. let mut start_at = start_at;
  48. // If we have children, we will need to skip some headers since they are already inserted
  49. let mut to_skip = 0;
  50. for h in &temp_headers[start_at..] {
  51. // stop when we encounter a title at the same level or higher
  52. // than the parent one. Here a lower integer is considered higher as we are talking about
  53. // HTML headers: h1, h2, h3, h4, h5 and h6
  54. if h.level <= parent_level {
  55. return (start_at, headers);
  56. }
  57. // Do we need to skip some headers?
  58. if to_skip > 0 {
  59. to_skip -= 1;
  60. continue;
  61. }
  62. let (end, children) = find_children(h.level, start_at + 1, &temp_headers);
  63. headers.push(Header::from_temp_header(h, children));
  64. // we didn't find any children
  65. if end == start_at {
  66. start_at += 1;
  67. to_skip = 0;
  68. } else {
  69. // calculates how many we need to skip. Since the find_children start_at starts at 1,
  70. // we need to remove 1 to ensure correctness
  71. to_skip = end - start_at - 1;
  72. start_at = end;
  73. }
  74. // we don't want to index out of bounds
  75. if start_at + 1 > temp_headers.len() {
  76. return (start_at, headers);
  77. }
  78. }
  79. (start_at, headers)
  80. }
  81. /// Converts the flat temp headers into a nested set of headers
  82. /// representing the hierarchy
  83. pub fn make_table_of_contents(temp_headers: Vec<TempHeader>) -> Vec<Header> {
  84. let mut toc = vec![];
  85. let mut start_idx = 0;
  86. for (i, h) in temp_headers.iter().enumerate() {
  87. if i < start_idx {
  88. continue;
  89. }
  90. let (end_idx, children) = find_children(h.level, start_idx + 1, &temp_headers);
  91. start_idx = end_idx;
  92. toc.push(Header::from_temp_header(h, children));
  93. }
  94. toc
  95. }
  96. #[cfg(test)]
  97. mod tests {
  98. use super::*;
  99. #[test]
  100. fn can_make_basic_toc() {
  101. let input = vec![
  102. TempHeader::new(1),
  103. TempHeader::new(1),
  104. TempHeader::new(1),
  105. ];
  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. }