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.

198 lines
5.6KB

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