Browse Source

Move test_site and turn rebuild.rs into a component

index-subcmd
Vincent Prouillet 6 years ago
parent
commit
16f658c70c
64 changed files with 585 additions and 287 deletions
  1. +20
    -0
      Cargo.lock
  2. +2
    -0
      Cargo.toml
  3. +1
    -1
      components/content/src/page.rs
  4. +15
    -0
      components/rebuild/Cargo.toml
  5. +371
    -0
      components/rebuild/src/lib.rs
  6. +126
    -0
      components/rebuild/tests/rebuild.rs
  7. +0
    -1
      components/site/Cargo.toml
  8. +4
    -4
      components/site/src/lib.rs
  9. +12
    -12
      components/site/tests/site.rs
  10. +4
    -0
      components/templates/src/global_fns.rs
  11. +1
    -1
      docs/content/documentation/content/section.md
  12. +1
    -1
      docs/content/documentation/templates/overview.md
  13. +0
    -1
      src/cmd/serve.rs
  14. +1
    -1
      src/main.rs
  15. +0
    -265
      src/rebuild.rs
  16. +1
    -0
      test_site/README.md
  17. +0
    -0
      test_site/config.staging.toml
  18. +0
    -0
      test_site/config.toml
  19. +0
    -0
      test_site/content/hello.md
  20. +0
    -0
      test_site/content/paginated/_index.md
  21. +0
    -0
      test_site/content/posts/_index.md
  22. +0
    -0
      test_site/content/posts/draft.md
  23. +0
    -0
      test_site/content/posts/fixed-slug.md
  24. +0
    -0
      test_site/content/posts/fixed-url.md
  25. +0
    -0
      test_site/content/posts/no-section/simple.md
  26. +0
    -0
      test_site/content/posts/python.md
  27. +0
    -0
      test_site/content/posts/simple.md
  28. +0
    -0
      test_site/content/posts/tutorials/_index.md
  29. +0
    -0
      test_site/content/posts/tutorials/devops/_index.md
  30. +0
    -0
      test_site/content/posts/tutorials/devops/docker.md
  31. +0
    -0
      test_site/content/posts/tutorials/devops/nix.md
  32. +0
    -0
      test_site/content/posts/tutorials/programming/_index.md
  33. +0
    -0
      test_site/content/posts/tutorials/programming/python.md
  34. +0
    -0
      test_site/content/posts/tutorials/programming/rust.md
  35. +0
    -0
      test_site/content/posts/with-assets/index.md
  36. +0
    -0
      test_site/content/posts/with-assets/with.js
  37. +5
    -0
      test_site/content/rebuild/_index.md
  38. +7
    -0
      test_site/content/rebuild/first.md
  39. +7
    -0
      test_site/content/rebuild/second.md
  40. +0
    -0
      test_site/sass/_included.scss
  41. +0
    -0
      test_site/sass/blog.scss
  42. +0
    -0
      test_site/static/scripts/hello.js
  43. +0
    -0
      test_site/static/site.css
  44. +0
    -0
      test_site/templates/categories.html
  45. +0
    -0
      test_site/templates/category.html
  46. +0
    -0
      test_site/templates/index.html
  47. +0
    -0
      test_site/templates/index_paginated.html
  48. +0
    -0
      test_site/templates/page.html
  49. +7
    -0
      test_site/templates/rebuild.html
  50. +0
    -0
      test_site/templates/section.html
  51. +0
    -0
      test_site/templates/section_paginated.html
  52. +0
    -0
      test_site/templates/shortcodes/basic.html
  53. +0
    -0
      test_site/templates/shortcodes/pirate.html
  54. +0
    -0
      test_site/templates/tag.html
  55. +0
    -0
      test_site/templates/tags.html
  56. +0
    -0
      test_site/themes/sample/sass/sample.scss
  57. +0
    -0
      test_site/themes/sample/static/some.js
  58. +0
    -0
      test_site/themes/sample/templates/category.html
  59. +0
    -0
      test_site/themes/sample/templates/child.html
  60. +0
    -0
      test_site/themes/sample/templates/included.html
  61. +0
    -0
      test_site/themes/sample/templates/index.html
  62. +0
    -0
      test_site/themes/sample/templates/macros.html
  63. +0
    -0
      test_site/themes/sample/templates/using-macros.html
  64. +0
    -0
      test_site/themes/sample/theme.toml

+ 20
- 0
Cargo.lock View File

@@ -282,6 +282,11 @@ dependencies = [
"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]]
name = "fsevent"
version = "0.2.17"
@@ -342,6 +347,7 @@ dependencies = [
"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)",
"notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rebuild 0.1.0",
"site 0.1.0",
"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)",
@@ -864,6 +870,19 @@ dependencies = [
"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]]
name = "redox_syscall"
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 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 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-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"


+ 2
- 0
Cargo.toml View File

@@ -36,6 +36,7 @@ errors = { path = "components/errors" }
content = { path = "components/content" }
front_matter = { path = "components/front_matter" }
utils = { path = "components/utils" }
rebuild = { path = "components/rebuild" }

[workspace]
members = [
@@ -45,6 +46,7 @@ members = [
"components/front_matter",
"components/highlighting",
"components/pagination",
"components/rebuild",
"components/rendering",
"components/site",
"components/taxonomies",


+ 1
- 1
components/content/src/page.rs View File

@@ -59,7 +59,7 @@ impl Page {

Page {
file: FileInfo::new_page(file_path),
meta: meta,
meta,
raw_content: "".to_string(),
assets: vec![],
content: "".to_string(),


+ 15
- 0
components/rebuild/Cargo.toml View 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
- 0
components/rebuild/src/lib.rs View 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(&section_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(&current, &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(&current, &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
- 0
components/rebuild/tests/rebuild.rs View 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>"));
}

+ 0
- 1
components/site/Cargo.toml View File

@@ -21,6 +21,5 @@ pagination = { path = "../pagination" }
taxonomies = { path = "../taxonomies" }
content = { path = "../content" }


[dev-dependencies]
tempdir = "0.3"

+ 4
- 4
components/site/src/lib.rs View File

@@ -275,7 +275,7 @@ impl Site {
/// Add a page to the site
/// The `render` parameter is used in the serve command, when rebuilding a 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>> {
let path = page.file.path.clone();
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
@@ -293,7 +293,7 @@ impl Site {
/// Add a section to the site
/// The `render` parameter is used in the serve command, when rebuilding a 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>> {
let path = section.file.path.clone();
self.permalinks.insert(section.file.relative.clone(), section.permalink.clone());
@@ -333,11 +333,11 @@ impl Site {
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() {
let parent_section_path = page.file.parent.join("_index.md");
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());
}
}


+ 12
- 12
components/site/tests/site.rs View File

@@ -12,13 +12,13 @@ use site::Site;

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.load().unwrap();

// 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");

// 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()]);

// 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
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);

let posts_section = &site.sections[&posts_path.join("_index.md")];
@@ -91,7 +91,7 @@ macro_rules! file_contains {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.load().unwrap();
@@ -152,7 +152,7 @@ fn can_build_site_without_live_reload() {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.load().unwrap();
@@ -190,7 +190,7 @@ fn can_build_site_with_live_reload() {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.config.generate_categories_pages = Some(true);
@@ -244,7 +244,7 @@ fn can_build_site_with_categories() {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.config.generate_tags_pages = Some(true);
@@ -296,7 +296,7 @@ fn can_build_site_with_tags() {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.load().unwrap();
@@ -313,7 +313,7 @@ fn can_build_site_and_insert_anchor_links() {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.load().unwrap();
@@ -372,7 +372,7 @@ fn can_build_site_with_pagination_for_section() {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.load().unwrap();
@@ -417,7 +417,7 @@ fn can_build_site_with_pagination_for_index() {

#[test]
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");
let mut site = Site::new(&path, "config.toml").unwrap();
site.load().unwrap();


+ 4
- 0
components/templates/src/global_fns.rs View File

@@ -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 {
let mut sections = HashMap::new();
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());
}

Box::new(move |args| -> Result<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) {
Some(p) => Ok(to_value(p).unwrap()),
None => Err(format!("Section `{}` not found.", path).into())


+ 1
- 1
docs/content/documentation/content/section.md View File

@@ -52,7 +52,7 @@ paginate_path = "page"
# Options are "left", "right" and "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
# to be used directly
render = true


+ 1
- 1
docs/content/documentation/templates/overview.md View File

@@ -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

```jinja2
{% set section = get_page(path="blog/_index.md") %}
{% set section = get_section(path="blog/_index.md") %}
```

### ` get_url`


+ 0
- 1
src/cmd/serve.rs View File

@@ -62,7 +62,6 @@ fn livereload_handler(_: &mut Request) -> IronResult<Response> {
Ok(Response::with((status::Ok, LIVE_RELOAD.to_string())))
}


fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) {
match res {
Ok(_) => {


+ 1
- 1
src/main.rs View File

@@ -16,12 +16,12 @@ extern crate errors;
extern crate content;
extern crate front_matter;
extern crate utils;
extern crate rebuild;

use std::time::Instant;

mod cmd;
mod console;
mod rebuild;
mod cli;
mod prompt;



+ 0
- 265
src/rebuild.rs View File

@@ -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(&current_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(&current);
}

// Front matter changed
for changes in find_page_front_matter_changes(&current.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(&section_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
- 0
test_site/README.md View File

@@ -0,0 +1 @@
Test site used by some components (`site`, `rebuild`) for integration tests.

components/site/test_site/config.staging.toml → test_site/config.staging.toml View File


components/site/test_site/config.toml → test_site/config.toml View File


components/site/test_site/content/hello.md → test_site/content/hello.md View File


components/site/test_site/content/paginated/_index.md → test_site/content/paginated/_index.md View File


components/site/test_site/content/posts/_index.md → test_site/content/posts/_index.md View File


components/site/test_site/content/posts/draft.md → test_site/content/posts/draft.md View File


components/site/test_site/content/posts/fixed-slug.md → test_site/content/posts/fixed-slug.md View File


components/site/test_site/content/posts/fixed-url.md → test_site/content/posts/fixed-url.md View File


components/site/test_site/content/posts/no-section/simple.md → test_site/content/posts/no-section/simple.md View File


components/site/test_site/content/posts/python.md → test_site/content/posts/python.md View File


components/site/test_site/content/posts/simple.md → test_site/content/posts/simple.md View File


components/site/test_site/content/posts/tutorials/_index.md → test_site/content/posts/tutorials/_index.md View File


components/site/test_site/content/posts/tutorials/devops/_index.md → test_site/content/posts/tutorials/devops/_index.md View File


components/site/test_site/content/posts/tutorials/devops/docker.md → test_site/content/posts/tutorials/devops/docker.md View File


components/site/test_site/content/posts/tutorials/devops/nix.md → test_site/content/posts/tutorials/devops/nix.md View File


components/site/test_site/content/posts/tutorials/programming/_index.md → test_site/content/posts/tutorials/programming/_index.md View File


components/site/test_site/content/posts/tutorials/programming/python.md → test_site/content/posts/tutorials/programming/python.md View File


components/site/test_site/content/posts/tutorials/programming/rust.md → test_site/content/posts/tutorials/programming/rust.md View File


components/site/test_site/content/posts/with-assets/index.md → test_site/content/posts/with-assets/index.md View File


components/site/test_site/content/posts/with-assets/with.js → test_site/content/posts/with-assets/with.js View File


+ 5
- 0
test_site/content/rebuild/_index.md View File

@@ -0,0 +1,5 @@
+++
paginate_by = 1
sort_by = "order"
template = "rebuild.html"
+++

+ 7
- 0
test_site/content/rebuild/first.md View File

@@ -0,0 +1,7 @@
+++
title = "first"
order = 10
date = 2017-01-01
+++

# A title

+ 7
- 0
test_site/content/rebuild/second.md View File

@@ -0,0 +1,7 @@
+++
title = "second"
order = 100
date = 2016-01-01
+++

# A title

components/site/test_site/sass/_included.scss → test_site/sass/_included.scss View File


components/site/test_site/sass/blog.scss → test_site/sass/blog.scss View File


components/site/test_site/static/scripts/hello.js → test_site/static/scripts/hello.js View File


components/site/test_site/static/site.css → test_site/static/site.css View File


components/site/test_site/templates/categories.html → test_site/templates/categories.html View File


components/site/test_site/templates/category.html → test_site/templates/category.html View File


components/site/test_site/templates/index.html → test_site/templates/index.html View File


components/site/test_site/templates/index_paginated.html → test_site/templates/index_paginated.html View File


components/site/test_site/templates/page.html → test_site/templates/page.html View File


+ 7
- 0
test_site/templates/rebuild.html View 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 %}

components/site/test_site/templates/section.html → test_site/templates/section.html View File


components/site/test_site/templates/section_paginated.html → test_site/templates/section_paginated.html View File


components/site/test_site/templates/shortcodes/basic.html → test_site/templates/shortcodes/basic.html View File


components/site/test_site/templates/shortcodes/pirate.html → test_site/templates/shortcodes/pirate.html View File


components/site/test_site/templates/tag.html → test_site/templates/tag.html View File


components/site/test_site/templates/tags.html → test_site/templates/tags.html View File


components/site/test_site/themes/sample/sass/sample.scss → test_site/themes/sample/sass/sample.scss View File


components/site/test_site/themes/sample/static/some.js → test_site/themes/sample/static/some.js View File


components/site/test_site/themes/sample/templates/category.html → test_site/themes/sample/templates/category.html View File


components/site/test_site/themes/sample/templates/child.html → test_site/themes/sample/templates/child.html View File


components/site/test_site/themes/sample/templates/included.html → test_site/themes/sample/templates/included.html View File


components/site/test_site/themes/sample/templates/index.html → test_site/themes/sample/templates/index.html View File


components/site/test_site/themes/sample/templates/macros.html → test_site/themes/sample/templates/macros.html View File


components/site/test_site/themes/sample/templates/using-macros.html → test_site/themes/sample/templates/using-macros.html View File


components/site/test_site/themes/sample/theme.toml → test_site/themes/sample/theme.toml View File


Loading…
Cancel
Save