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.

231 lines
8.5KB

  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].relative_path.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].relative_path.clone();
  83. site.permalinks.remove(&relative_path);
  84. match site.pages.remove(path) {
  85. Some(p) => {
  86. if p.meta.has_tags() || p.meta.category.is_some() {
  87. site.populate_tags_and_categories();
  88. }
  89. if site.find_parent_section(&p).is_some() {
  90. site.populate_sections();
  91. }
  92. },
  93. None => ()
  94. };
  95. }
  96. // Deletion is something that doesn't happen all the time so we
  97. // don't need to optimise it too much
  98. return site.build();
  99. }
  100. // A section was edited
  101. if is_section {
  102. match site.add_section(path, true)? {
  103. Some(prev) => {
  104. // Updating a section
  105. let current_meta = site.sections[path].meta.clone();
  106. // Front matter didn't change, only content did
  107. // so we render only the section page, not its pages
  108. if current_meta == prev.meta {
  109. return site.render_section(&site.sections[path], false);
  110. }
  111. // Front matter changed
  112. for changes in find_section_front_matter_changes(&current_meta, &prev.meta) {
  113. // Sort always comes first if present so the rendering will be fine
  114. match changes {
  115. SectionChangesNeeded::Sort => site.sort_sections_pages(Some(path)),
  116. SectionChangesNeeded::Render => site.render_section(&site.sections[path], false)?,
  117. SectionChangesNeeded::RenderWithPages => site.render_section(&site.sections[path], true)?,
  118. // can't be arsed to make the Delete efficient, it's not a common enough operation
  119. SectionChangesNeeded::Delete => {
  120. site.populate_sections();
  121. site.build()?;
  122. },
  123. };
  124. }
  125. return Ok(());
  126. },
  127. None => {
  128. // New section, only render that one
  129. site.populate_sections();
  130. return site.render_section(&site.sections[path], true);
  131. }
  132. };
  133. }
  134. // A page was edited
  135. match site.add_page(path, true)? {
  136. Some(prev) => {
  137. // Updating a page
  138. let current = site.pages[path].clone();
  139. // Front matter didn't change, only content did
  140. // so we render only the section page, not its pages
  141. if current.meta == prev.meta {
  142. return site.render_page(&site.pages[path]);
  143. }
  144. // Front matter changed
  145. for changes in find_page_front_matter_changes(&current.meta, &prev.meta) {
  146. // Sort always comes first if present so the rendering will be fine
  147. match changes {
  148. PageChangesNeeded::Tags => {
  149. site.populate_tags_and_categories();
  150. site.render_tags()?;
  151. },
  152. PageChangesNeeded::Categories => {
  153. site.populate_tags_and_categories();
  154. site.render_categories()?;
  155. },
  156. PageChangesNeeded::Sort => {
  157. let section_path = match site.find_parent_section(&site.pages[path]) {
  158. Some(s) => s.file_path.clone(),
  159. None => continue // Do nothing if it's an orphan page
  160. };
  161. site.populate_sections();
  162. site.sort_sections_pages(Some(&section_path));
  163. site.render_index()?;
  164. },
  165. PageChangesNeeded::Render => {
  166. site.render_page(&site.pages[path])?;
  167. },
  168. };
  169. }
  170. return Ok(());
  171. },
  172. None => {
  173. // It's a new page!
  174. site.populate_sections();
  175. site.populate_tags_and_categories();
  176. // No need to optimise that yet, we can revisit if it becomes an issue
  177. site.build()?;
  178. }
  179. }
  180. Ok(())
  181. }
  182. /// What happens when a template is changed
  183. pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> {
  184. site.tera.full_reload()?;
  185. match path.file_name().unwrap().to_str().unwrap() {
  186. "sitemap.xml" => site.render_sitemap(),
  187. "rss.xml" => site.render_rss_feed(),
  188. "robots.txt" => site.render_robots(),
  189. "categories.html" | "category.html" => site.render_categories(),
  190. "tags.html" | "tag.html" => site.render_tags(),
  191. "page.html" => {
  192. site.render_sections()?;
  193. site.render_orphan_pages()
  194. },
  195. "section.html" => site.render_sections(),
  196. // Either the index or some unknown template changed
  197. // We can't really know what this change affects so rebuild all
  198. // the things
  199. _ => {
  200. site.render_sections()?;
  201. site.render_orphan_pages()?;
  202. site.render_categories()?;
  203. site.render_tags()
  204. },
  205. }
  206. }