Merge pull request #222 from Keats/rebuild-tests
Move test_site and turn rebuild.rs into a component
This commit is contained in:
commit
094dfb4f2f
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -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
|
||||||
|
|
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -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(),
|
||||||
|
|
15
components/rebuild/Cargo.toml
Normal file
15
components/rebuild/Cargo.toml
Normal file
|
@ -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"
|
371
components/rebuild/src/lib.rs
Normal file
371
components/rebuild/src/lib.rs
Normal file
|
@ -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]);
|
||||||
|
}
|
||||||
|
}
|
126
components/rebuild/tests/rebuild.rs
Normal file
126
components/rebuild/tests/rebuild.rs
Normal file
|
@ -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;
|
||||||
|
|
||||||
|
|
265
src/rebuild.rs
265
src/rebuild.rs
|
@ -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
|
Binary file not shown.
Binary file not shown.
1
test_site/README.md
Normal file
1
test_site/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Test site used by some components (`site`, `rebuild`) for integration tests.
|
5
test_site/content/rebuild/_index.md
Normal file
5
test_site/content/rebuild/_index.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
paginate_by = 1
|
||||||
|
sort_by = "order"
|
||||||
|
template = "rebuild.html"
|
||||||
|
+++
|
7
test_site/content/rebuild/first.md
Normal file
7
test_site/content/rebuild/first.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+++
|
||||||
|
title = "first"
|
||||||
|
order = 10
|
||||||
|
date = 2017-01-01
|
||||||
|
+++
|
||||||
|
|
||||||
|
# A title
|
7
test_site/content/rebuild/second.md
Normal file
7
test_site/content/rebuild/second.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+++
|
||||||
|
title = "second"
|
||||||
|
order = 100
|
||||||
|
date = 2016-01-01
|
||||||
|
+++
|
||||||
|
|
||||||
|
# A title
|
7
test_site/templates/rebuild.html
Normal file
7
test_site/templates/rebuild.html
Normal file
|
@ -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 %}
|
Loading…
Reference in a new issue