Move test_site and turn rebuild.rs into a componentindex-subcmd
@@ -13,9 +13,6 @@ | |||||
[submodule "sublime_syntaxes/Julia-sublime"] | [submodule "sublime_syntaxes/Julia-sublime"] | ||||
path = sublime_syntaxes/Julia-sublime | path = sublime_syntaxes/Julia-sublime | ||||
url = https://github.com/JuliaEditorSupport/Julia-sublime.git | url = https://github.com/JuliaEditorSupport/Julia-sublime.git | ||||
[submodule "sublime_syntaxes/Elm.tmLanguage"] | |||||
path = sublime_syntaxes/Elm.tmLanguage | |||||
url = https://github.com/elm-community/Elm.tmLanguage.git | |||||
[submodule "sublime_syntaxes/sublime_toml_highlighting"] | [submodule "sublime_syntaxes/sublime_toml_highlighting"] | ||||
path = sublime_syntaxes/sublime_toml_highlighting | path = sublime_syntaxes/sublime_toml_highlighting | ||||
url = https://github.com/Jayflux/sublime_toml_highlighting.git | url = https://github.com/Jayflux/sublime_toml_highlighting.git | ||||
@@ -31,3 +28,6 @@ | |||||
[submodule "sublime_syntaxes/TypeScript-TmLanguage"] | [submodule "sublime_syntaxes/TypeScript-TmLanguage"] | ||||
path = sublime_syntaxes/TypeScript-TmLanguage | path = sublime_syntaxes/TypeScript-TmLanguage | ||||
url = https://github.com/Microsoft/TypeScript-TmLanguage | url = https://github.com/Microsoft/TypeScript-TmLanguage | ||||
[submodule "sublime_syntaxes/SublimeElmLanguageSupport"] | |||||
path = sublime_syntaxes/SublimeElmLanguageSupport | |||||
url = https://github.com/elm-community/SublimeElmLanguageSupport |
@@ -282,6 +282,11 @@ dependencies = [ | |||||
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
[[package]] | |||||
name = "fs_extra" | |||||
version = "1.1.0" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
[[package]] | [[package]] | ||||
name = "fsevent" | name = "fsevent" | ||||
version = "0.2.17" | version = "0.2.17" | ||||
@@ -342,6 +347,7 @@ dependencies = [ | |||||
"iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | "iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"mount 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | "mount 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", | "notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"rebuild 0.1.0", | |||||
"site 0.1.0", | "site 0.1.0", | ||||
"staticfile 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", | "staticfile 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"term-painter 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", | "term-painter 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -864,6 +870,19 @@ dependencies = [ | |||||
"rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", | "rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
] | ] | ||||
[[package]] | |||||
name = "rebuild" | |||||
version = "0.1.0" | |||||
dependencies = [ | |||||
"content 0.1.0", | |||||
"errors 0.1.0", | |||||
"front_matter 0.1.0", | |||||
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"highlighting 0.1.0", | |||||
"site 0.1.0", | |||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | |||||
[[package]] | [[package]] | ||||
name = "redox_syscall" | name = "redox_syscall" | ||||
version = "0.1.37" | version = "0.1.37" | ||||
@@ -1446,6 +1465,7 @@ dependencies = [ | |||||
"checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" | "checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" | ||||
"checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909" | "checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909" | ||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" | "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" | ||||
"checksum fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674" | |||||
"checksum fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c4bbbf71584aeed076100b5665ac14e3d85eeb31fdbb45fbd41ef9a682b5ec05" | "checksum fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c4bbbf71584aeed076100b5665ac14e3d85eeb31fdbb45fbd41ef9a682b5ec05" | ||||
"checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" | "checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874" | ||||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" | ||||
@@ -36,6 +36,7 @@ errors = { path = "components/errors" } | |||||
content = { path = "components/content" } | content = { path = "components/content" } | ||||
front_matter = { path = "components/front_matter" } | front_matter = { path = "components/front_matter" } | ||||
utils = { path = "components/utils" } | utils = { path = "components/utils" } | ||||
rebuild = { path = "components/rebuild" } | |||||
[workspace] | [workspace] | ||||
members = [ | members = [ | ||||
@@ -45,6 +46,7 @@ members = [ | |||||
"components/front_matter", | "components/front_matter", | ||||
"components/highlighting", | "components/highlighting", | ||||
"components/pagination", | "components/pagination", | ||||
"components/rebuild", | |||||
"components/rendering", | "components/rendering", | ||||
"components/site", | "components/site", | ||||
"components/taxonomies", | "components/taxonomies", | ||||
@@ -45,7 +45,7 @@ You can also add a submodule to the repository of the wanted syntax: | |||||
```bash | ```bash | ||||
$ cd sublime_syntaxes | $ cd sublime_syntaxes | ||||
$ git submodule add https://github.com/elm-community/Elm.tmLanguage.git | |||||
$ git submodule add https://github.com/elm-community/SublimeElmLanguageSupport | |||||
``` | ``` | ||||
Note that you can also only copy manually the updated syntax definition file but this means | Note that you can also only copy manually the updated syntax definition file but this means | ||||
@@ -59,7 +59,7 @@ impl Page { | |||||
Page { | Page { | ||||
file: FileInfo::new_page(file_path), | file: FileInfo::new_page(file_path), | ||||
meta: meta, | |||||
meta, | |||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
assets: vec![], | assets: vec![], | ||||
content: "".to_string(), | content: "".to_string(), | ||||
@@ -0,0 +1,15 @@ | |||||
[package] | |||||
name = "rebuild" | |||||
version = "0.1.0" | |||||
authors = ["Vincent Prouillet <vincent@wearewizards.io>"] | |||||
[dependencies] | |||||
errors = { path = "../errors" } | |||||
front_matter = { path = "../front_matter" } | |||||
highlighting = { path = "../highlighting" } | |||||
content = { path = "../content" } | |||||
site = { path = "../site" } | |||||
[dev-dependencies] | |||||
tempdir = "0.3" | |||||
fs_extra = "1.1.0" |
@@ -0,0 +1,371 @@ | |||||
extern crate site; | |||||
extern crate errors; | |||||
extern crate content; | |||||
extern crate front_matter; | |||||
use std::path::{Path, Component}; | |||||
use errors::Result; | |||||
use site::Site; | |||||
use content::{Page, Section}; | |||||
use front_matter::{PageFrontMatter, SectionFrontMatter}; | |||||
/// Finds the section that contains the page given if there is one | |||||
pub fn find_parent_section<'a>(site: &'a Site, page: &Page) -> Option<&'a Section> { | |||||
for section in site.sections.values() { | |||||
if section.is_child_page(&page.file.path) { | |||||
return Some(section) | |||||
} | |||||
} | |||||
None | |||||
} | |||||
#[derive(Debug, Clone, Copy, PartialEq)] | |||||
pub enum PageChangesNeeded { | |||||
/// Editing `tags` | |||||
Tags, | |||||
/// Editing `categories` | |||||
Categories, | |||||
/// Editing `date`, `order` or `weight` | |||||
Sort, | |||||
/// Editing anything causes a re-render of the page | |||||
Render, | |||||
} | |||||
#[derive(Debug, Clone, Copy, PartialEq)] | |||||
pub enum SectionChangesNeeded { | |||||
/// Editing `sort_by` | |||||
Sort, | |||||
/// Editing `title`, `description`, `extra`, `template` or setting `render` to true | |||||
Render, | |||||
/// Editing `paginate_by`, `paginate_path` or `insert_anchor_links` | |||||
RenderWithPages, | |||||
/// Setting `render` to false | |||||
Delete, | |||||
} | |||||
/// Evaluates all the params in the front matter that changed so we can do the smallest | |||||
/// delta in the serve command | |||||
/// Order matters as the actions will be done in insertion order | |||||
fn find_section_front_matter_changes(current: &SectionFrontMatter, new: &SectionFrontMatter) -> Vec<SectionChangesNeeded> { | |||||
let mut changes_needed = vec![]; | |||||
if current.sort_by != new.sort_by { | |||||
changes_needed.push(SectionChangesNeeded::Sort); | |||||
} | |||||
// We want to hide the section | |||||
// TODO: what to do on redirect_path change? | |||||
if current.should_render() && !new.should_render() { | |||||
changes_needed.push(SectionChangesNeeded::Delete); | |||||
// Nothing else we can do | |||||
return changes_needed; | |||||
} | |||||
if current.paginate_by != new.paginate_by | |||||
|| current.paginate_path != new.paginate_path | |||||
|| current.insert_anchor_links != new.insert_anchor_links { | |||||
changes_needed.push(SectionChangesNeeded::RenderWithPages); | |||||
// Nothing else we can do | |||||
return changes_needed; | |||||
} | |||||
// Any new change will trigger a re-rendering of the section page only | |||||
changes_needed.push(SectionChangesNeeded::Render); | |||||
changes_needed | |||||
} | |||||
/// Evaluates all the params in the front matter that changed so we can do the smallest | |||||
/// delta in the serve command | |||||
/// Order matters as the actions will be done in insertion order | |||||
fn find_page_front_matter_changes(current: &PageFrontMatter, other: &PageFrontMatter) -> Vec<PageChangesNeeded> { | |||||
let mut changes_needed = vec![]; | |||||
if current.tags != other.tags { | |||||
changes_needed.push(PageChangesNeeded::Tags); | |||||
} | |||||
if current.category != other.category { | |||||
changes_needed.push(PageChangesNeeded::Categories); | |||||
} | |||||
if current.date != other.date || current.order != other.order || current.weight != other.weight { | |||||
changes_needed.push(PageChangesNeeded::Sort); | |||||
} | |||||
changes_needed.push(PageChangesNeeded::Render); | |||||
changes_needed | |||||
} | |||||
/// Handles a path deletion: could be a page, a section, a folder | |||||
fn delete_element(site: &mut Site, path: &Path, is_section: bool) -> Result<()> { | |||||
// Ignore the event if this path was not known | |||||
if !site.sections.contains_key(path) && !site.pages.contains_key(path) { | |||||
return Ok(()); | |||||
} | |||||
if is_section { | |||||
if let Some(s) = site.pages.remove(path) { | |||||
site.permalinks.remove(&s.file.relative); | |||||
site.populate_sections(); | |||||
} | |||||
} else { | |||||
if let Some(p) = site.pages.remove(path) { | |||||
site.permalinks.remove(&p.file.relative); | |||||
if p.meta.has_tags() || p.meta.category.is_some() { | |||||
site.populate_tags_and_categories(); | |||||
} | |||||
// if there is a parent section, we will need to re-render it | |||||
// most likely | |||||
if find_parent_section(site, &p).is_some() { | |||||
site.populate_sections(); | |||||
} | |||||
}; | |||||
} | |||||
// Ensure we have our fn updated so it doesn't contain the permalink(s)/section/page deleted | |||||
site.register_tera_global_fns(); | |||||
// Deletion is something that doesn't happen all the time so we | |||||
// don't need to optimise it too much | |||||
return site.build(); | |||||
} | |||||
/// Handles a `_index.md` (a section) being edited in some ways | |||||
fn handle_section_editing(site: &mut Site, path: &Path) -> Result<()> { | |||||
let section = Section::from_file(path, &site.config)?; | |||||
match site.add_section(section, true)? { | |||||
// Updating a section | |||||
Some(prev) => { | |||||
if site.sections[path].meta == prev.meta { | |||||
// Front matter didn't change, only content did | |||||
// so we render only the section page, not its pages | |||||
return site.render_section(&site.sections[path], false); | |||||
} | |||||
// Front matter changed | |||||
for changes in find_section_front_matter_changes(&site.sections[path].meta, &prev.meta) { | |||||
// Sort always comes first if present so the rendering will be fine | |||||
match changes { | |||||
SectionChangesNeeded::Sort => { | |||||
site.sort_sections_pages(Some(path)); | |||||
site.register_tera_global_fns(); | |||||
}, | |||||
SectionChangesNeeded::Render => site.render_section(&site.sections[path], false)?, | |||||
SectionChangesNeeded::RenderWithPages => site.render_section(&site.sections[path], true)?, | |||||
// not a common enough operation to make it worth optimizing | |||||
SectionChangesNeeded::Delete => { | |||||
site.populate_sections(); | |||||
site.build()?; | |||||
}, | |||||
}; | |||||
} | |||||
return Ok(()); | |||||
}, | |||||
// New section, only render that one | |||||
None => { | |||||
site.populate_sections(); | |||||
site.register_tera_global_fns(); | |||||
return site.render_section(&site.sections[path], true); | |||||
} | |||||
}; | |||||
} | |||||
macro_rules! render_parent_section { | |||||
($site: expr, $path: expr) => { | |||||
match find_parent_section($site, &$site.pages[$path]) { | |||||
Some(s) => { | |||||
$site.render_section(s, false)?; | |||||
}, | |||||
None => (), | |||||
}; | |||||
} | |||||
} | |||||
/// Handles a page being edited in some ways | |||||
fn handle_page_editing(site: &mut Site, path: &Path) -> Result<()> { | |||||
let page = Page::from_file(path, &site.config)?; | |||||
match site.add_page(page, true)? { | |||||
// Updating a page | |||||
Some(prev) => { | |||||
// Front matter didn't change, only content did | |||||
if site.pages[path].meta == prev.meta { | |||||
// Other than the page itself, the summary might be seen | |||||
// on a paginated list for a blog for example | |||||
if site.pages[path].summary.is_some() { | |||||
render_parent_section!(site, path); | |||||
} | |||||
// TODO: register_tera_global_fns is expensive as it involves lots of cloning | |||||
// I can't think of a valid usecase where you would need the content | |||||
// of a page through a global fn so it's commented out for now | |||||
// site.register_tera_global_fns(); | |||||
return site.render_page(& site.pages[path]); | |||||
} | |||||
// Front matter changed | |||||
let mut taxonomies_populated = false; | |||||
let mut sections_populated = false; | |||||
for changes in find_page_front_matter_changes(&site.pages[path].meta, &prev.meta) { | |||||
// Sort always comes first if present so the rendering will be fine | |||||
match changes { | |||||
PageChangesNeeded::Tags => { | |||||
if !taxonomies_populated { | |||||
site.populate_tags_and_categories(); | |||||
taxonomies_populated = true; | |||||
} | |||||
site.register_tera_global_fns(); | |||||
site.render_tags()?; | |||||
}, | |||||
PageChangesNeeded::Categories => { | |||||
if !taxonomies_populated { | |||||
site.populate_tags_and_categories(); | |||||
taxonomies_populated = true; | |||||
} | |||||
site.register_tera_global_fns(); | |||||
site.render_categories()?; | |||||
}, | |||||
PageChangesNeeded::Sort => { | |||||
let section_path = match find_parent_section(site, &site.pages[path]) { | |||||
Some(s) => s.file.path.clone(), | |||||
None => continue // Do nothing if it's an orphan page | |||||
}; | |||||
if !sections_populated { | |||||
site.populate_sections(); | |||||
sections_populated = true; | |||||
} | |||||
site.sort_sections_pages(Some(§ion_path)); | |||||
site.register_tera_global_fns(); | |||||
site.render_index()?; | |||||
}, | |||||
PageChangesNeeded::Render => { | |||||
if !sections_populated { | |||||
site.populate_sections(); | |||||
sections_populated = true; | |||||
} | |||||
site.register_tera_global_fns(); | |||||
render_parent_section!(site, path); | |||||
site.render_page(&site.pages[path])?; | |||||
}, | |||||
}; | |||||
} | |||||
Ok(()) | |||||
}, | |||||
// It's a new page! | |||||
None => { | |||||
site.populate_sections(); | |||||
site.populate_tags_and_categories(); | |||||
site.register_tera_global_fns(); | |||||
// No need to optimise that yet, we can revisit if it becomes an issue | |||||
site.build() | |||||
} | |||||
} | |||||
} | |||||
// What happens when a section or a page is changed | |||||
pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
let is_section = path.file_name().unwrap() == "_index.md"; | |||||
// A page or section got deleted | |||||
if !path.exists() { | |||||
delete_element(site, path, is_section)?; | |||||
} | |||||
if is_section { | |||||
handle_section_editing(site, path) | |||||
} else { | |||||
handle_page_editing(site, path) | |||||
} | |||||
} | |||||
/// What happens when a template is changed | |||||
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
site.tera.full_reload()?; | |||||
let filename = path.file_name().unwrap().to_str().unwrap(); | |||||
match filename { | |||||
"sitemap.xml" => site.render_sitemap(), | |||||
"rss.xml" => site.render_rss_feed(), | |||||
"robots.txt" => site.render_robots(), | |||||
"categories.html" | "category.html" => site.render_categories(), | |||||
"tags.html" | "tag.html" => site.render_tags(), | |||||
"page.html" => { | |||||
site.render_sections()?; | |||||
site.render_orphan_pages() | |||||
}, | |||||
"section.html" => site.render_sections(), | |||||
// Either the index or some unknown template changed | |||||
// We can't really know what this change affects so rebuild all | |||||
// the things | |||||
_ => { | |||||
// If we are updating a shortcode, re-render the markdown of all pages/site | |||||
// because we have no clue which one needs rebuilding | |||||
// TODO: look if there the shortcode is used in the markdown instead of re-rendering | |||||
// everything | |||||
if path.components().collect::<Vec<_>>().contains(&Component::Normal("shortcodes".as_ref())) { | |||||
site.render_markdown()?; | |||||
} | |||||
site.populate_sections(); | |||||
site.render_sections()?; | |||||
site.render_orphan_pages()?; | |||||
site.render_categories()?; | |||||
site.render_tags() | |||||
}, | |||||
} | |||||
} | |||||
#[cfg(test)] | |||||
mod tests { | |||||
use front_matter::{PageFrontMatter, SectionFrontMatter, SortBy}; | |||||
use super::{ | |||||
find_page_front_matter_changes, find_section_front_matter_changes, | |||||
PageChangesNeeded, SectionChangesNeeded | |||||
}; | |||||
#[test] | |||||
fn can_find_tag_changes_in_page_frontmatter() { | |||||
let new = PageFrontMatter { tags: Some(vec!["a tag".to_string()]), ..PageFrontMatter::default() }; | |||||
let changes = find_page_front_matter_changes(&PageFrontMatter::default(), &new); | |||||
assert_eq!(changes, vec![PageChangesNeeded::Tags, PageChangesNeeded::Render]); | |||||
} | |||||
#[test] | |||||
fn can_find_category_changes_in_page_frontmatter() { | |||||
let current = PageFrontMatter { category: Some("a category".to_string()), ..PageFrontMatter::default() }; | |||||
let changes = find_page_front_matter_changes(¤t, &PageFrontMatter::default()); | |||||
assert_eq!(changes, vec![PageChangesNeeded::Categories, PageChangesNeeded::Render]); | |||||
} | |||||
#[test] | |||||
fn can_find_multiple_changes_in_page_frontmatter() { | |||||
let current = PageFrontMatter { category: Some("a category".to_string()), order: Some(1), ..PageFrontMatter::default() }; | |||||
let changes = find_page_front_matter_changes(¤t, &PageFrontMatter::default()); | |||||
assert_eq!(changes, vec![PageChangesNeeded::Categories, PageChangesNeeded::Sort, PageChangesNeeded::Render]); | |||||
} | |||||
#[test] | |||||
fn can_find_sort_changes_in_section_frontmatter() { | |||||
let new = SectionFrontMatter { sort_by: Some(SortBy::Date), ..SectionFrontMatter::default() }; | |||||
let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new); | |||||
assert_eq!(changes, vec![SectionChangesNeeded::Sort, SectionChangesNeeded::Render]); | |||||
} | |||||
#[test] | |||||
fn can_find_render_changes_in_section_frontmatter() { | |||||
let new = SectionFrontMatter { render: Some(false), ..SectionFrontMatter::default() }; | |||||
let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new); | |||||
assert_eq!(changes, vec![SectionChangesNeeded::Delete]); | |||||
} | |||||
#[test] | |||||
fn can_find_paginate_by_changes_in_section_frontmatter() { | |||||
let new = SectionFrontMatter { paginate_by: Some(10), ..SectionFrontMatter::default() }; | |||||
let changes = find_section_front_matter_changes(&SectionFrontMatter::default(), &new); | |||||
assert_eq!(changes, vec![SectionChangesNeeded::RenderWithPages]); | |||||
} | |||||
} |
@@ -0,0 +1,126 @@ | |||||
extern crate rebuild; | |||||
extern crate site; | |||||
extern crate tempdir; | |||||
extern crate fs_extra; | |||||
use std::env; | |||||
use std::fs::{remove_dir_all, File}; | |||||
use std::io::prelude::*; | |||||
use fs_extra::dir; | |||||
use tempdir::TempDir; | |||||
use site::Site; | |||||
use rebuild::after_content_change; | |||||
// Loads the test_site in a tempdir and build it there | |||||
// Returns (site_path_in_tempdir, site) | |||||
macro_rules! load_and_build_site { | |||||
($tmp_dir: expr) => { | |||||
{ | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | |||||
let mut options = dir::CopyOptions::new(); | |||||
options.copy_inside = true; | |||||
dir::copy(&path, &$tmp_dir, &options).unwrap(); | |||||
let site_path = $tmp_dir.path().join("test_site"); | |||||
// delete useless sections for those tests | |||||
remove_dir_all(site_path.join("content").join("paginated")).unwrap(); | |||||
remove_dir_all(site_path.join("content").join("posts")).unwrap(); | |||||
let mut site = Site::new(&site_path, "config.toml").unwrap(); | |||||
site.load().unwrap(); | |||||
let public = &site_path.join("public"); | |||||
site.set_output_path(&public); | |||||
site.build().unwrap(); | |||||
(site_path, site) | |||||
} | |||||
} | |||||
} | |||||
/// Replace the file at the path (starting from root) by the given content | |||||
/// and return the file path that was modified | |||||
macro_rules! edit_file { | |||||
($site_path: expr, $path: expr, $content: expr) => { | |||||
{ | |||||
let mut t = $site_path.clone(); | |||||
for c in $path.split('/') { | |||||
t.push(c); | |||||
} | |||||
let mut file = File::create(&t).expect("Could not open/create file"); | |||||
file.write_all($content).expect("Could not write to the file"); | |||||
t | |||||
} | |||||
} | |||||
} | |||||
macro_rules! file_contains { | |||||
($site_path: expr, $path: expr, $text: expr) => { | |||||
{ | |||||
let mut path = $site_path.clone(); | |||||
for component in $path.split("/") { | |||||
path.push(component); | |||||
} | |||||
let mut file = File::open(&path).unwrap(); | |||||
let mut s = String::new(); | |||||
file.read_to_string(&mut s).unwrap(); | |||||
println!("{:?} -> {}", path, s); | |||||
s.contains($text) | |||||
} | |||||
} | |||||
} | |||||
#[test] | |||||
fn can_rebuild_after_simple_change_to_page_content() { | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
let (site_path, mut site) = load_and_build_site!(tmp_dir); | |||||
let file_path = edit_file!(site_path, "content/rebuild/first.md", br#" | |||||
+++ | |||||
title = "first" | |||||
order = 1 | |||||
date = 2017-01-01 | |||||
+++ | |||||
Some content"#); | |||||
let res = after_content_change(&mut site, &file_path); | |||||
assert!(res.is_ok()); | |||||
assert!(file_contains!(site_path, "public/rebuild/first/index.html", "<p>Some content</p>")); | |||||
} | |||||
#[test] | |||||
fn can_rebuild_after_title_change_page_global_func_usage() { | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
let (site_path, mut site) = load_and_build_site!(tmp_dir); | |||||
let file_path = edit_file!(site_path, "content/rebuild/first.md", br#" | |||||
+++ | |||||
title = "Premier" | |||||
order = 10 | |||||
date = 2017-01-01 | |||||
+++ | |||||
# A title"#); | |||||
let res = after_content_change(&mut site, &file_path); | |||||
assert!(res.is_ok()); | |||||
assert!(file_contains!(site_path, "public/rebuild/index.html", "<h1>Premier</h1>")); | |||||
} | |||||
#[test] | |||||
fn can_rebuild_after_sort_change_in_section() { | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
let (site_path, mut site) = load_and_build_site!(tmp_dir); | |||||
let file_path = edit_file!(site_path, "content/rebuild/_index.md", br#" | |||||
+++ | |||||
paginate_by = 1 | |||||
sort_by = "order" | |||||
template = "rebuild.html" | |||||
+++ | |||||
"#); | |||||
let res = after_content_change(&mut site, &file_path); | |||||
assert!(res.is_ok()); | |||||
assert!(file_contains!(site_path, "public/rebuild/index.html", "<h1>second</h1><h1>first</h1>")); | |||||
} |
@@ -21,6 +21,5 @@ pagination = { path = "../pagination" } | |||||
taxonomies = { path = "../taxonomies" } | taxonomies = { path = "../taxonomies" } | ||||
content = { path = "../content" } | content = { path = "../content" } | ||||
[dev-dependencies] | [dev-dependencies] | ||||
tempdir = "0.3" | tempdir = "0.3" |
@@ -275,7 +275,7 @@ impl Site { | |||||
/// Add a page to the site | /// Add a page to the site | ||||
/// The `render` parameter is used in the serve command, when rebuilding a page. | /// The `render` parameter is used in the serve command, when rebuilding a page. | ||||
/// If `true`, it will also render the markdown for that page | /// If `true`, it will also render the markdown for that page | ||||
/// Returns the previous page struct if there was one | |||||
/// Returns the previous page struct if there was one at the same path | |||||
pub fn add_page(&mut self, page: Page, render: bool) -> Result<Option<Page>> { | pub fn add_page(&mut self, page: Page, render: bool) -> Result<Option<Page>> { | ||||
let path = page.file.path.clone(); | let path = page.file.path.clone(); | ||||
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone()); | self.permalinks.insert(page.file.relative.clone(), page.permalink.clone()); | ||||
@@ -293,7 +293,7 @@ impl Site { | |||||
/// Add a section to the site | /// Add a section to the site | ||||
/// The `render` parameter is used in the serve command, when rebuilding a page. | /// The `render` parameter is used in the serve command, when rebuilding a page. | ||||
/// If `true`, it will also render the markdown for that page | /// If `true`, it will also render the markdown for that page | ||||
/// Returns the previous section struct if there was one | |||||
/// Returns the previous section struct if there was one at the same path | |||||
pub fn add_section(&mut self, section: Section, render: bool) -> Result<Option<Section>> { | pub fn add_section(&mut self, section: Section, render: bool) -> Result<Option<Section>> { | ||||
let path = section.file.path.clone(); | let path = section.file.path.clone(); | ||||
self.permalinks.insert(section.file.relative.clone(), section.permalink.clone()); | self.permalinks.insert(section.file.relative.clone(), section.permalink.clone()); | ||||
@@ -333,11 +333,11 @@ impl Site { | |||||
section.ignored_pages = vec![]; | section.ignored_pages = vec![]; | ||||
} | } | ||||
// TODO: use references instead of cloning to avoid having to call populate_section on | |||||
// content change | |||||
for page in self.pages.values() { | for page in self.pages.values() { | ||||
let parent_section_path = page.file.parent.join("_index.md"); | let parent_section_path = page.file.parent.join("_index.md"); | ||||
if self.sections.contains_key(&parent_section_path) { | if self.sections.contains_key(&parent_section_path) { | ||||
// TODO: use references instead of cloning to avoid having to call populate_section on | |||||
// content change | |||||
self.sections.get_mut(&parent_section_path).unwrap().pages.push(page.clone()); | self.sections.get_mut(&parent_section_path).unwrap().pages.push(page.clone()); | ||||
} | } | ||||
} | } | ||||
@@ -12,13 +12,13 @@ use site::Site; | |||||
#[test] | #[test] | ||||
fn can_parse_site() { | fn can_parse_site() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
// Correct number of pages (sections are pages too) | // Correct number of pages (sections are pages too) | ||||
assert_eq!(site.pages.len(), 12); | |||||
assert_eq!(site.pages.len(), 14); | |||||
let posts_path = path.join("content").join("posts"); | let posts_path = path.join("content").join("posts"); | ||||
// Make sure we remove all the pwd + content from the sections | // Make sure we remove all the pwd + content from the sections | ||||
@@ -34,11 +34,11 @@ fn can_parse_site() { | |||||
assert_eq!(asset_folder_post.file.components, vec!["posts".to_string()]); | assert_eq!(asset_folder_post.file.components, vec!["posts".to_string()]); | ||||
// That we have the right number of sections | // That we have the right number of sections | ||||
assert_eq!(site.sections.len(), 6); | |||||
assert_eq!(site.sections.len(), 7); | |||||
// And that the sections are correct | // And that the sections are correct | ||||
let index_section = &site.sections[&path.join("content").join("_index.md")]; | let index_section = &site.sections[&path.join("content").join("_index.md")]; | ||||
assert_eq!(index_section.subsections.len(), 2); | |||||
assert_eq!(index_section.subsections.len(), 3); | |||||
assert_eq!(index_section.pages.len(), 1); | assert_eq!(index_section.pages.len(), 1); | ||||
let posts_section = &site.sections[&posts_path.join("_index.md")]; | let posts_section = &site.sections[&posts_path.join("_index.md")]; | ||||
@@ -91,7 +91,7 @@ macro_rules! file_contains { | |||||
#[test] | #[test] | ||||
fn can_build_site_without_live_reload() { | fn can_build_site_without_live_reload() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
@@ -152,7 +152,7 @@ fn can_build_site_without_live_reload() { | |||||
#[test] | #[test] | ||||
fn can_build_site_with_live_reload() { | fn can_build_site_with_live_reload() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
@@ -190,7 +190,7 @@ fn can_build_site_with_live_reload() { | |||||
#[test] | #[test] | ||||
fn can_build_site_with_categories() { | fn can_build_site_with_categories() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.config.generate_categories_pages = Some(true); | site.config.generate_categories_pages = Some(true); | ||||
@@ -244,7 +244,7 @@ fn can_build_site_with_categories() { | |||||
#[test] | #[test] | ||||
fn can_build_site_with_tags() { | fn can_build_site_with_tags() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.config.generate_tags_pages = Some(true); | site.config.generate_tags_pages = Some(true); | ||||
@@ -296,7 +296,7 @@ fn can_build_site_with_tags() { | |||||
#[test] | #[test] | ||||
fn can_build_site_and_insert_anchor_links() { | fn can_build_site_and_insert_anchor_links() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
@@ -313,7 +313,7 @@ fn can_build_site_and_insert_anchor_links() { | |||||
#[test] | #[test] | ||||
fn can_build_site_with_pagination_for_section() { | fn can_build_site_with_pagination_for_section() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
@@ -372,7 +372,7 @@ fn can_build_site_with_pagination_for_section() { | |||||
#[test] | #[test] | ||||
fn can_build_site_with_pagination_for_index() { | fn can_build_site_with_pagination_for_index() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
@@ -417,7 +417,7 @@ fn can_build_site_with_pagination_for_index() { | |||||
#[test] | #[test] | ||||
fn can_build_rss_feed() { | fn can_build_rss_feed() { | ||||
let mut path = env::current_dir().unwrap().to_path_buf(); | |||||
let mut path = env::current_dir().unwrap().parent().unwrap().parent().unwrap().to_path_buf(); | |||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
site.load().unwrap(); | site.load().unwrap(); | ||||
@@ -54,11 +54,15 @@ pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn { | |||||
pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn { | pub fn make_get_section(all_sections: &HashMap<PathBuf, Section>) -> GlobalFn { | ||||
let mut sections = HashMap::new(); | let mut sections = HashMap::new(); | ||||
for section in all_sections.values() { | for section in all_sections.values() { | ||||
if section.file.components == vec!["rebuild".to_string()] { | |||||
//println!("Setting sections:\n{:#?}", section.pages[0]); | |||||
} | |||||
sections.insert(section.file.relative.clone(), section.clone()); | sections.insert(section.file.relative.clone(), section.clone()); | ||||
} | } | ||||
Box::new(move |args| -> Result<Value> { | Box::new(move |args| -> Result<Value> { | ||||
let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value"); | let path = required_string_arg!(args.get("path"), "`get_section` requires a `path` argument with a string value"); | ||||
//println!("Found {:#?}", sections.get(&path).unwrap().pages[0]); | |||||
match sections.get(&path) { | match sections.get(&path) { | ||||
Some(p) => Ok(to_value(p).unwrap()), | Some(p) => Ok(to_value(p).unwrap()), | ||||
None => Err(format!("Section `{}` not found.", path).into()) | None => Err(format!("Section `{}` not found.", path).into()) | ||||
@@ -52,7 +52,7 @@ paginate_path = "page" | |||||
# Options are "left", "right" and "none" | # Options are "left", "right" and "none" | ||||
insert_anchor_links = "none" | insert_anchor_links = "none" | ||||
# Whether to render that section or not. | |||||
# Whether to render that section homepage or not. | |||||
# Useful when the section is only there to organize things but is not meant | # Useful when the section is only there to organize things but is not meant | ||||
# to be used directly | # to be used directly | ||||
render = true | render = true | ||||
@@ -51,7 +51,7 @@ Takes a path to a `.md` file and returns the associated page | |||||
Takes a path to a `_index.md` file and returns the associated section | Takes a path to a `_index.md` file and returns the associated section | ||||
```jinja2 | ```jinja2 | ||||
{% set section = get_page(path="blog/_index.md") %} | |||||
{% set section = get_section(path="blog/_index.md") %} | |||||
``` | ``` | ||||
### ` get_url` | ### ` get_url` | ||||
@@ -62,7 +62,6 @@ fn livereload_handler(_: &mut Request) -> IronResult<Response> { | |||||
Ok(Response::with((status::Ok, LIVE_RELOAD.to_string()))) | Ok(Response::with((status::Ok, LIVE_RELOAD.to_string()))) | ||||
} | } | ||||
fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) { | fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) { | ||||
match res { | match res { | ||||
Ok(_) => { | Ok(_) => { | ||||
@@ -16,12 +16,12 @@ extern crate errors; | |||||
extern crate content; | extern crate content; | ||||
extern crate front_matter; | extern crate front_matter; | ||||
extern crate utils; | extern crate utils; | ||||
extern crate rebuild; | |||||
use std::time::Instant; | use std::time::Instant; | ||||
mod cmd; | mod cmd; | ||||
mod console; | mod console; | ||||
mod rebuild; | |||||
mod cli; | mod cli; | ||||
mod prompt; | mod prompt; | ||||
@@ -1,265 +0,0 @@ | |||||
use std::path::{Path, Component}; | |||||
use errors::Result; | |||||
use site::Site; | |||||
use content::{Page, Section}; | |||||
use front_matter::{PageFrontMatter, SectionFrontMatter}; | |||||
/// Finds the section that contains the page given if there is one | |||||
pub fn find_parent_section<'a>(site: &'a Site, page: &Page) -> Option<&'a Section> { | |||||
for section in site.sections.values() { | |||||
if section.is_child_page(&page.file.path) { | |||||
return Some(section) | |||||
} | |||||
} | |||||
None | |||||
} | |||||
#[derive(Debug, Clone, Copy, PartialEq)] | |||||
enum PageChangesNeeded { | |||||
/// Editing `tags` | |||||
Tags, | |||||
/// Editing `categories` | |||||
Categories, | |||||
/// Editing `date` or `order` | |||||
Sort, | |||||
/// Editing anything else | |||||
Render, | |||||
} | |||||
// TODO: seems like editing sort_by/render do weird stuff | |||||
#[derive(Debug, Clone, Copy, PartialEq)] | |||||
enum SectionChangesNeeded { | |||||
/// Editing `sort_by` | |||||
Sort, | |||||
/// Editing `title`, `description`, `extra`, `template` or setting `render` to true | |||||
Render, | |||||
/// Editing `paginate_by`, `paginate_path` or `insert_anchor_links` | |||||
RenderWithPages, | |||||
/// Setting `render` to false | |||||
Delete, | |||||
} | |||||
/// Evaluates all the params in the front matter that changed so we can do the smallest | |||||
/// delta in the serve command | |||||
fn find_section_front_matter_changes(current: &SectionFrontMatter, other: &SectionFrontMatter) -> Vec<SectionChangesNeeded> { | |||||
let mut changes_needed = vec![]; | |||||
if current.sort_by != other.sort_by { | |||||
changes_needed.push(SectionChangesNeeded::Sort); | |||||
} | |||||
if !current.should_render() && other.should_render() { | |||||
changes_needed.push(SectionChangesNeeded::Delete); | |||||
// Nothing else we can do | |||||
return changes_needed; | |||||
} | |||||
if current.paginate_by != other.paginate_by | |||||
|| current.paginate_path != other.paginate_path | |||||
|| current.insert_anchor_links != other.insert_anchor_links { | |||||
changes_needed.push(SectionChangesNeeded::RenderWithPages); | |||||
// Nothing else we can do | |||||
return changes_needed; | |||||
} | |||||
// Any other change will trigger a re-rendering of the section page only | |||||
changes_needed.push(SectionChangesNeeded::Render); | |||||
changes_needed | |||||
} | |||||
/// Evaluates all the params in the front matter that changed so we can do the smallest | |||||
/// delta in the serve command | |||||
fn find_page_front_matter_changes(current: &PageFrontMatter, other: &PageFrontMatter) -> Vec<PageChangesNeeded> { | |||||
let mut changes_needed = vec![]; | |||||
if current.tags != other.tags { | |||||
changes_needed.push(PageChangesNeeded::Tags); | |||||
} | |||||
if current.category != other.category { | |||||
changes_needed.push(PageChangesNeeded::Categories); | |||||
} | |||||
if current.date != other.date || current.order != other.order { | |||||
changes_needed.push(PageChangesNeeded::Sort); | |||||
} | |||||
changes_needed.push(PageChangesNeeded::Render); | |||||
changes_needed | |||||
} | |||||
// What happens when a section or a page is changed | |||||
pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
let is_section = path.file_name().unwrap() == "_index.md"; | |||||
// A page or section got deleted | |||||
if !path.exists() { | |||||
// A folder got deleted, ignore this event | |||||
if !site.sections.contains_key(path) && !site.pages.contains_key(path) { | |||||
return Ok(()); | |||||
} | |||||
if is_section { | |||||
// A section was deleted, many things can be impacted: | |||||
// - the pages of the section are becoming orphans | |||||
// - any page that was referencing the section (index, etc) | |||||
let relative_path = site.sections[path].file.relative.clone(); | |||||
// Remove the link to it and the section itself from the Site | |||||
site.permalinks.remove(&relative_path); | |||||
site.sections.remove(path); | |||||
site.populate_sections(); | |||||
} else { | |||||
// A page was deleted, many things can be impacted: | |||||
// - the section the page is in | |||||
// - any page that was referencing the section (index, etc) | |||||
let relative_path = site.pages[path].file.relative.clone(); | |||||
site.permalinks.remove(&relative_path); | |||||
if let Some(p) = site.pages.remove(path) { | |||||
if p.meta.has_tags() || p.meta.category.is_some() { | |||||
site.populate_tags_and_categories(); | |||||
} | |||||
if find_parent_section(site, &p).is_some() { | |||||
site.populate_sections(); | |||||
} | |||||
}; | |||||
} | |||||
// Ensure we have our fn updated so it doesn't contain the permalinks deleted | |||||
site.register_tera_global_fns(); | |||||
// Deletion is something that doesn't happen all the time so we | |||||
// don't need to optimise it too much | |||||
return site.build(); | |||||
} | |||||
// A section was edited | |||||
if is_section { | |||||
let section = Section::from_file(path, &site.config)?; | |||||
match site.add_section(section, true)? { | |||||
Some(prev) => { | |||||
// Updating a section | |||||
let current_meta = site.sections[path].meta.clone(); | |||||
// Front matter didn't change, only content did | |||||
// so we render only the section page, not its pages | |||||
if current_meta == prev.meta { | |||||
return site.render_section(&site.sections[path], false); | |||||
} | |||||
// Front matter changed | |||||
for changes in find_section_front_matter_changes(¤t_meta, &prev.meta) { | |||||
// Sort always comes first if present so the rendering will be fine | |||||
match changes { | |||||
SectionChangesNeeded::Sort => site.sort_sections_pages(Some(path)), | |||||
SectionChangesNeeded::Render => site.render_section(&site.sections[path], false)?, | |||||
SectionChangesNeeded::RenderWithPages => site.render_section(&site.sections[path], true)?, | |||||
// can't be arsed to make the Delete efficient, it's not a common enough operation | |||||
SectionChangesNeeded::Delete => { | |||||
site.populate_sections(); | |||||
site.build()?; | |||||
}, | |||||
}; | |||||
} | |||||
return Ok(()); | |||||
}, | |||||
None => { | |||||
// New section, only render that one | |||||
site.populate_sections(); | |||||
site.register_tera_global_fns(); | |||||
return site.render_section(&site.sections[path], true); | |||||
} | |||||
}; | |||||
} | |||||
// A page was edited | |||||
let page = Page::from_file(path, &site.config)?; | |||||
match site.add_page(page, true)? { | |||||
Some(prev) => { | |||||
// Updating a page | |||||
let current = site.pages[path].clone(); | |||||
// Front matter didn't change, only content did | |||||
// so we render only the section page, not its content | |||||
if current.meta == prev.meta { | |||||
return site.render_page(¤t); | |||||
} | |||||
// Front matter changed | |||||
for changes in find_page_front_matter_changes(¤t.meta, &prev.meta) { | |||||
// Sort always comes first if present so the rendering will be fine | |||||
match changes { | |||||
PageChangesNeeded::Tags => { | |||||
site.populate_tags_and_categories(); | |||||
site.render_tags()?; | |||||
}, | |||||
PageChangesNeeded::Categories => { | |||||
site.populate_tags_and_categories(); | |||||
site.render_categories()?; | |||||
}, | |||||
PageChangesNeeded::Sort => { | |||||
let section_path = match find_parent_section(site, &site.pages[path]) { | |||||
Some(s) => s.file.path.clone(), | |||||
None => continue // Do nothing if it's an orphan page | |||||
}; | |||||
site.populate_sections(); | |||||
site.sort_sections_pages(Some(§ion_path)); | |||||
site.render_index()?; | |||||
}, | |||||
PageChangesNeeded::Render => { | |||||
site.render_page(&site.pages[path])?; | |||||
}, | |||||
}; | |||||
} | |||||
site.register_tera_global_fns(); | |||||
return Ok(()); | |||||
}, | |||||
None => { | |||||
// It's a new page! | |||||
site.populate_sections(); | |||||
site.populate_tags_and_categories(); | |||||
site.register_tera_global_fns(); | |||||
// No need to optimise that yet, we can revisit if it becomes an issue | |||||
site.build()?; | |||||
} | |||||
} | |||||
Ok(()) | |||||
} | |||||
/// What happens when a template is changed | |||||
pub fn after_template_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
site.tera.full_reload()?; | |||||
let filename = path.file_name().unwrap().to_str().unwrap(); | |||||
match filename { | |||||
"sitemap.xml" => site.render_sitemap(), | |||||
"rss.xml" => site.render_rss_feed(), | |||||
"robots.txt" => site.render_robots(), | |||||
"categories.html" | "category.html" => site.render_categories(), | |||||
"tags.html" | "tag.html" => site.render_tags(), | |||||
"page.html" => { | |||||
site.render_sections()?; | |||||
site.render_orphan_pages() | |||||
}, | |||||
"section.html" => site.render_sections(), | |||||
// Either the index or some unknown template changed | |||||
// We can't really know what this change affects so rebuild all | |||||
// the things | |||||
_ => { | |||||
// If we are updating a shortcode, re-render the markdown of all pages/site | |||||
// because we have no clue which one needs rebuilding | |||||
// TODO: look if there the shortcode is used in the markdown instead of re-rendering | |||||
// everything | |||||
if path.components().collect::<Vec<_>>().contains(&Component::Normal("shortcodes".as_ref())) { | |||||
site.render_markdown()?; | |||||
} | |||||
site.populate_sections(); | |||||
site.render_sections()?; | |||||
site.render_orphan_pages()?; | |||||
site.render_categories()?; | |||||
site.render_tags() | |||||
}, | |||||
} | |||||
} |
@@ -1 +1 @@ | |||||
Subproject commit df5a27523dd37ebe67ba4c7d36ea162dae95b2c3 | |||||
Subproject commit 987eb72681357b7872a46e8409dfb6f43f2fa673 |
@@ -1 +1 @@ | |||||
Subproject commit c29d12d8aceb1a68af4cb6e466199846f41dd2ed | |||||
Subproject commit 0247d1444a66e683bb4005df38218a0fd9576d03 |
@@ -0,0 +1 @@ | |||||
Test site used by some components (`site`, `rebuild`) for integration tests. |
@@ -0,0 +1,5 @@ | |||||
+++ | |||||
paginate_by = 1 | |||||
sort_by = "order" | |||||
template = "rebuild.html" | |||||
+++ |
@@ -0,0 +1,7 @@ | |||||
+++ | |||||
title = "first" | |||||
order = 10 | |||||
date = 2017-01-01 | |||||
+++ | |||||
# A title |
@@ -0,0 +1,7 @@ | |||||
+++ | |||||
title = "second" | |||||
order = 100 | |||||
date = 2016-01-01 | |||||
+++ | |||||
# A title |
@@ -0,0 +1,7 @@ | |||||
{# Testing that global functions/section get reloaded properly #} | |||||
{% set section = get_section(path="rebuild/_index.md") %} | |||||
{% for page in section.pages -%} | |||||
<h1>{{ page.title }}</h1> | |||||
{%- endfor %} |