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.

228 lines
8.4KB

  1. use std::path::Path;
  2. use gutenberg::{Site, SectionFrontMatter, PageFrontMatter};
  3. use gutenberg::errors::Result;
  4. #[derive(Debug, Clone, Copy, PartialEq)]
  5. enum PageChangesNeeded {
  6. /// Editing `tags`
  7. Tags,
  8. /// Editing `categories`
  9. Categories,
  10. /// Editing `date` or `order`
  11. Sort,
  12. /// Editing anything else
  13. Render,
  14. }
  15. // TODO: seems like editing sort_by/render do weird stuff
  16. #[derive(Debug, Clone, Copy, PartialEq)]
  17. enum SectionChangesNeeded {
  18. /// Editing `sort_by`
  19. Sort,
  20. /// Editing `title`, `description`, `extra`, `template` or setting `render` to true
  21. Render,
  22. /// Editing `paginate_by` or `paginate_path`
  23. RenderWithPages,
  24. /// Setting `render` to false
  25. Delete,
  26. }
  27. /// Evaluates all the params in the front matter that changed so we can do the smallest
  28. /// delta in the serve command
  29. fn find_section_front_matter_changes(current: &SectionFrontMatter, other: &SectionFrontMatter) -> Vec<SectionChangesNeeded> {
  30. let mut changes_needed = vec![];
  31. if current.sort_by != other.sort_by {
  32. changes_needed.push(SectionChangesNeeded::Sort);
  33. }
  34. if !current.should_render() && other.should_render() {
  35. changes_needed.push(SectionChangesNeeded::Delete);
  36. // Nothing else we can do
  37. return changes_needed;
  38. }
  39. if current.paginate_by != other.paginate_by || current.paginate_path != other.paginate_path {
  40. changes_needed.push(SectionChangesNeeded::RenderWithPages);
  41. // Nothing else we can do
  42. return changes_needed;
  43. }
  44. // Any other change will trigger a re-rendering of the section page only
  45. changes_needed.push(SectionChangesNeeded::Render);
  46. changes_needed
  47. }
  48. /// Evaluates all the params in the front matter that changed so we can do the smallest
  49. /// delta in the serve command
  50. fn find_page_front_matter_changes(current: &PageFrontMatter, other: &PageFrontMatter) -> Vec<PageChangesNeeded> {
  51. let mut changes_needed = vec![];
  52. if current.tags != other.tags {
  53. changes_needed.push(PageChangesNeeded::Tags);
  54. }
  55. if current.category != other.category {
  56. changes_needed.push(PageChangesNeeded::Categories);
  57. }
  58. if current.date != other.date || current.order != other.order {
  59. changes_needed.push(PageChangesNeeded::Sort);
  60. }
  61. changes_needed.push(PageChangesNeeded::Render);
  62. changes_needed
  63. }
  64. // What happens when a section or a page is changed
  65. pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> {
  66. let is_section = path.file_name().unwrap() == "_index.md";
  67. // A page or section got deleted
  68. if !path.exists() {
  69. if is_section {
  70. // A section was deleted, many things can be impacted:
  71. // - the pages of the section are becoming orphans
  72. // - any page that was referencing the section (index, etc)
  73. let relative_path = site.sections[path].file.relative.clone();
  74. // Remove the link to it and the section itself from the Site
  75. site.permalinks.remove(&relative_path);
  76. site.sections.remove(path);
  77. site.populate_sections();
  78. } else {
  79. // A page was deleted, many things can be impacted:
  80. // - the section the page is in
  81. // - any page that was referencing the section (index, etc)
  82. let relative_path = site.pages[path].file.relative.clone();
  83. site.permalinks.remove(&relative_path);
  84. if let Some(p) = site.pages.remove(path) {
  85. if p.meta.has_tags() || p.meta.category.is_some() {
  86. site.populate_tags_and_categories();
  87. }
  88. if site.find_parent_section(&p).is_some() {
  89. site.populate_sections();
  90. }
  91. };
  92. }
  93. // Deletion is something that doesn't happen all the time so we
  94. // don't need to optimise it too much
  95. return site.build();
  96. }
  97. // A section was edited
  98. if is_section {
  99. match site.add_section(path, true)? {
  100. Some(prev) => {
  101. // Updating a section
  102. let current_meta = site.sections[path].meta.clone();
  103. // Front matter didn't change, only content did
  104. // so we render only the section page, not its pages
  105. if current_meta == prev.meta {
  106. return site.render_section(&site.sections[path], false);
  107. }
  108. // Front matter changed
  109. for changes in find_section_front_matter_changes(&current_meta, &prev.meta) {
  110. // Sort always comes first if present so the rendering will be fine
  111. match changes {
  112. SectionChangesNeeded::Sort => site.sort_sections_pages(Some(path)),
  113. SectionChangesNeeded::Render => site.render_section(&site.sections[path], false)?,
  114. SectionChangesNeeded::RenderWithPages => site.render_section(&site.sections[path], true)?,
  115. // can't be arsed to make the Delete efficient, it's not a common enough operation
  116. SectionChangesNeeded::Delete => {
  117. site.populate_sections();
  118. site.build()?;
  119. },
  120. };
  121. }
  122. return Ok(());
  123. },
  124. None => {
  125. // New section, only render that one
  126. site.populate_sections();
  127. return site.render_section(&site.sections[path], true);
  128. }
  129. };
  130. }
  131. // A page was edited
  132. match site.add_page(path, true)? {
  133. Some(prev) => {
  134. // Updating a page
  135. let current = site.pages[path].clone();
  136. // Front matter didn't change, only content did
  137. // so we render only the section page, not its pages
  138. if current.meta == prev.meta {
  139. return site.render_page(&site.pages[path]);
  140. }
  141. // Front matter changed
  142. for changes in find_page_front_matter_changes(&current.meta, &prev.meta) {
  143. // Sort always comes first if present so the rendering will be fine
  144. match changes {
  145. PageChangesNeeded::Tags => {
  146. site.populate_tags_and_categories();
  147. site.render_tags()?;
  148. },
  149. PageChangesNeeded::Categories => {
  150. site.populate_tags_and_categories();
  151. site.render_categories()?;
  152. },
  153. PageChangesNeeded::Sort => {
  154. let section_path = match site.find_parent_section(&site.pages[path]) {
  155. Some(s) => s.file.path.clone(),
  156. None => continue // Do nothing if it's an orphan page
  157. };
  158. site.populate_sections();
  159. site.sort_sections_pages(Some(&section_path));
  160. site.render_index()?;
  161. },
  162. PageChangesNeeded::Render => {
  163. site.render_page(&site.pages[path])?;
  164. },
  165. };
  166. }
  167. return Ok(());
  168. },
  169. None => {
  170. // It's a new page!
  171. site.populate_sections();
  172. site.populate_tags_and_categories();
  173. // No need to optimise that yet, we can revisit if it becomes an issue
  174. site.build()?;
  175. }
  176. }
  177. Ok(())
  178. }
  179. /// What happens when a template is changed
  180. pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
  181. site.tera.full_reload()?;
  182. match path.file_name().unwrap().to_str().unwrap() {
  183. "sitemap.xml" => site.render_sitemap(),
  184. "rss.xml" => site.render_rss_feed(),
  185. "robots.txt" => site.render_robots(),
  186. "categories.html" | "category.html" => site.render_categories(),
  187. "tags.html" | "tag.html" => site.render_tags(),
  188. "page.html" => {
  189. site.render_sections()?;
  190. site.render_orphan_pages()
  191. },
  192. "section.html" => site.render_sections(),
  193. // Either the index or some unknown template changed
  194. // We can't really know what this change affects so rebuild all
  195. // the things
  196. _ => {
  197. site.render_sections()?;
  198. site.render_orphan_pages()?;
  199. site.render_categories()?;
  200. site.render_tags()
  201. },
  202. }
  203. }