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.

212 lines
5.9KB

  1. use front_matter::InsertAnchor;
  2. use tera::{Context as TeraContext, Tera};
  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!(
  60. "<h{lvl} id=\"{id}\">{t}</h{lvl}>\n",
  61. lvl = self.level,
  62. t = self.html,
  63. id = self.id
  64. ),
  65. InsertAnchor::Left => format!(
  66. "<h{lvl} id=\"{id}\">{a}{t}</h{lvl}>\n",
  67. lvl = self.level,
  68. a = anchor_link,
  69. t = self.html,
  70. id = self.id
  71. ),
  72. InsertAnchor::Right => format!(
  73. "<h{lvl} id=\"{id}\">{t}{a}</h{lvl}>\n",
  74. lvl = self.level,
  75. a = anchor_link,
  76. t = self.html,
  77. id = self.id
  78. ),
  79. }
  80. }
  81. }
  82. impl Default for TempHeader {
  83. fn default() -> Self {
  84. TempHeader::new(0)
  85. }
  86. }
  87. /// Recursively finds children of a header
  88. fn find_children(
  89. parent_level: i32,
  90. start_at: usize,
  91. temp_headers: &[TempHeader],
  92. ) -> (usize, Vec<Header>) {
  93. let mut headers = vec![];
  94. let mut start_at = start_at;
  95. // If we have children, we will need to skip some headers since they are already inserted
  96. let mut to_skip = 0;
  97. for h in &temp_headers[start_at..] {
  98. // stop when we encounter a title at the same level or higher
  99. // than the parent one. Here a lower integer is considered higher as we are talking about
  100. // HTML headers: h1, h2, h3, h4, h5 and h6
  101. if h.level <= parent_level {
  102. return (start_at, headers);
  103. }
  104. // Do we need to skip some headers?
  105. if to_skip > 0 {
  106. to_skip -= 1;
  107. continue;
  108. }
  109. let (end, children) = find_children(h.level, start_at + 1, temp_headers);
  110. headers.push(Header::from_temp_header(h, children));
  111. // we didn't find any children
  112. if end == start_at {
  113. start_at += 1;
  114. to_skip = 0;
  115. } else {
  116. // calculates how many we need to skip. Since the find_children start_at starts at 1,
  117. // we need to remove 1 to ensure correctness
  118. to_skip = end - start_at - 1;
  119. start_at = end;
  120. }
  121. // we don't want to index out of bounds
  122. if start_at + 1 > temp_headers.len() {
  123. return (start_at, headers);
  124. }
  125. }
  126. (start_at, headers)
  127. }
  128. /// Converts the flat temp headers into a nested set of headers
  129. /// representing the hierarchy
  130. pub fn make_table_of_contents(temp_headers: &[TempHeader]) -> Vec<Header> {
  131. let mut toc = vec![];
  132. let mut start_idx = 0;
  133. for (i, h) in temp_headers.iter().enumerate() {
  134. if i < start_idx {
  135. continue;
  136. }
  137. let (end_idx, children) = find_children(h.level, start_idx + 1, temp_headers);
  138. start_idx = end_idx;
  139. toc.push(Header::from_temp_header(h, children));
  140. }
  141. toc
  142. }
  143. #[cfg(test)]
  144. mod tests {
  145. use super::*;
  146. #[test]
  147. fn can_make_basic_toc() {
  148. let input = vec![TempHeader::new(1), TempHeader::new(1), TempHeader::new(1)];
  149. let toc = make_table_of_contents(&input);
  150. assert_eq!(toc.len(), 3);
  151. }
  152. #[test]
  153. fn can_make_more_complex_toc() {
  154. let input = vec![
  155. TempHeader::new(1),
  156. TempHeader::new(2),
  157. TempHeader::new(2),
  158. TempHeader::new(3),
  159. TempHeader::new(2),
  160. TempHeader::new(1),
  161. TempHeader::new(2),
  162. TempHeader::new(3),
  163. TempHeader::new(3),
  164. ];
  165. let toc = make_table_of_contents(&input);
  166. assert_eq!(toc.len(), 2);
  167. assert_eq!(toc[0].children.len(), 3);
  168. assert_eq!(toc[1].children.len(), 1);
  169. assert_eq!(toc[0].children[1].children.len(), 1);
  170. assert_eq!(toc[1].children[0].children.len(), 2);
  171. }
  172. #[test]
  173. fn can_make_messy_toc() {
  174. let input = vec![
  175. TempHeader::new(3),
  176. TempHeader::new(2),
  177. TempHeader::new(2),
  178. TempHeader::new(3),
  179. TempHeader::new(2),
  180. TempHeader::new(1),
  181. TempHeader::new(4),
  182. ];
  183. let toc = make_table_of_contents(&input);
  184. assert_eq!(toc.len(), 5);
  185. assert_eq!(toc[2].children.len(), 1);
  186. assert_eq!(toc[4].children.len(), 1);
  187. }
  188. }