Refactor serve/rebuilding a bit

This commit is contained in:
Vincent Prouillet 2017-03-10 20:39:58 +09:00
parent 4406b16007
commit a57baf2934
5 changed files with 117 additions and 78 deletions

View file

@ -4,16 +4,17 @@ use std::sync::mpsc::channel;
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use std::thread; use std::thread;
use chrono::prelude::*;
use iron::{Iron, Request, IronResult, Response, status}; use iron::{Iron, Request, IronResult, Response, status};
use mount::Mount; use mount::Mount;
use staticfile::Static; use staticfile::Static;
use notify::{Watcher, RecursiveMode, watcher}; use notify::{Watcher, RecursiveMode, watcher};
use ws::{WebSocket}; use ws::{WebSocket, Sender};
use gutenberg::Site; use gutenberg::Site;
use gutenberg::errors::{Result}; use gutenberg::errors::{Result};
use ::time_elapsed; use ::report_elapsed_time;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -31,10 +32,38 @@ fn livereload_handler(_: &mut Request) -> IronResult<Response> {
} }
fn rebuild_done_handling(broadcaster: &Sender, res: Result<()>, reload_path: &str) {
match res {
Ok(_) => {
broadcaster.send(format!(r#"
{{
"command": "reload",
"path": "{}",
"originalPath": "",
"liveCSS": true,
"liveImg": true,
"protocol": ["http://livereload.com/protocols/official-7"]
}}"#, reload_path)
).unwrap();
},
Err(e) => {
println!("Failed to build the site");
println!("Error: {}", e);
for e in e.iter().skip(1) {
println!("Reason: {}", e)
}
}
}
}
// Most of it taken from mdbook // Most of it taken from mdbook
pub fn serve(interface: &str, port: &str) -> Result<()> { pub fn serve(interface: &str, port: &str) -> Result<()> {
println!("Building site...");
let start = Instant::now();
let mut site = Site::new(true)?; let mut site = Site::new(true)?;
site.build()?; site.build()?;
report_elapsed_time(start);
let address = format!("{}:{}", interface, port); let address = format!("{}:{}", interface, port);
let ws_address = format!("{}:{}", interface, "1112"); let ws_address = format!("{}:{}", interface, "1112");
@ -67,7 +96,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> {
watcher.watch("templates/", RecursiveMode::Recursive).unwrap(); watcher.watch("templates/", RecursiveMode::Recursive).unwrap();
let pwd = format!("{}", env::current_dir().unwrap().display()); let pwd = format!("{}", env::current_dir().unwrap().display());
println!("Listening for changes in {}/{{content, static, templates}}", pwd); println!("Listening for changes in {}/{{content, static, templates}}", pwd);
println!("Press CTRL+C to stop"); println!("Press CTRL+C to stop\n");
use notify::DebouncedEvent::*; use notify::DebouncedEvent::*;
@ -85,41 +114,23 @@ pub fn serve(interface: &str, port: &str) -> Result<()> {
continue; continue;
} }
println!("Change detected, rebuilding site"); println!("Change detected @ {}", Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
let what_changed = detect_change_kind(&pwd, &path); let start = Instant::now();
let mut reload_path = String::new(); match detect_change_kind(&pwd, &path) {
match what_changed { (ChangeKind::Content, _) => {
(ChangeKind::Content, _) => println!("Content changed {}", path.display()), println!("-> Content changed {}", path.display());
(ChangeKind::Templates, _) => println!("Template changed {}", path.display()), rebuild_done_handling(&broadcaster, site.rebuild(), "");
},
(ChangeKind::Templates, _) => {
println!("-> Template changed {}", path.display());
rebuild_done_handling(&broadcaster, site.rebuild_after_template_change(), "");
},
(ChangeKind::StaticFiles, p) => { (ChangeKind::StaticFiles, p) => {
reload_path = p; println!("-> Static file changes detected {}", path.display());
println!("Static file changes detected {}", path.display()); rebuild_done_handling(&broadcaster, site.copy_static_directory(), &p);
}, },
}; };
println!("Reloading {}", reload_path); report_elapsed_time(start);
let start = Instant::now();
match site.rebuild() {
Ok(_) => {
println!("Done in {:.1}s.\n", time_elapsed(start));
broadcaster.send(format!(r#"
{{
"command": "reload",
"path": "{}",
"originalPath": "",
"liveCSS": true,
"liveImg": true,
"protocol": ["http://livereload.com/protocols/official-7"]
}}"#, reload_path)).unwrap();
},
Err(e) => {
println!("Failed to build the site");
println!("Error: {}", e);
for e in e.iter().skip(1) {
println!("Reason: {}", e)
}
}
}
} }
_ => {} _ => {}
} }

View file

@ -14,6 +14,7 @@ pub struct Config {
pub title: String, pub title: String,
/// Base URL of the site /// Base URL of the site
pub base_url: String, pub base_url: String,
/// Whether to highlight all code blocks found in markdown files. Defaults to false /// Whether to highlight all code blocks found in markdown files. Defaults to false
pub highlight_code: Option<bool>, pub highlight_code: Option<bool>,
/// Description of the site /// Description of the site

View file

@ -19,9 +19,15 @@ mod cmd;
// Print the time elapsed rounded to 1 decimal // Print the time elapsed rounded to 1 decimal
fn time_elapsed(instant: Instant) -> f64 { fn report_elapsed_time(instant: Instant) {
let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64 / 1000.0; let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64;
(duration_ms * 10.0).round() / 10.0
if duration_ms < 1000.0 {
println!("Done in {}ms.\n", duration_ms);
} else {
let duration_sec = duration_ms / 1000.0;
println!("Done in {:.1}s.\n", ((duration_sec * 10.0).round() / 10.0));
}
} }
@ -58,10 +64,11 @@ fn main() {
}; };
}, },
("build", Some(_)) => { ("build", Some(_)) => {
println!("Building site");
let start = Instant::now(); let start = Instant::now();
match cmd::build() { match cmd::build() {
Ok(()) => { Ok(()) => {
println!("Site built in {:.1}s.", time_elapsed(start)); report_elapsed_time(start);
}, },
Err(e) => { Err(e) => {
println!("Failed to build the site"); println!("Failed to build the site");

View file

@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::fs::{create_dir, remove_dir_all, copy, remove_file}; use std::fs::{remove_dir_all, copy, remove_file};
use std::path::Path; use std::path::Path;
use glob::glob; use glob::glob;
@ -11,7 +11,7 @@ use walkdir::WalkDir;
use errors::{Result, ResultExt}; use errors::{Result, ResultExt};
use config::{Config, get_config}; use config::{Config, get_config};
use page::Page; use page::Page;
use utils::create_file; use utils::{create_file, create_directory};
lazy_static! { lazy_static! {
@ -108,10 +108,6 @@ impl Site {
html html
} }
/// Reload the Tera templates
pub fn reload_templates(&mut self) -> Result<()> {
Ok(())
}
/// Copy the content of the `static` folder into the `public` folder /// Copy the content of the `static` folder into the `public` folder
/// ///
@ -131,7 +127,7 @@ impl Site {
if entry.path().is_dir() { if entry.path().is_dir() {
if !target_path.exists() { if !target_path.exists() {
create_dir(&target_path)?; create_directory(&target_path)?;
} }
} else { } else {
if target_path.exists() { if target_path.exists() {
@ -143,24 +139,34 @@ impl Site {
Ok(()) Ok(())
} }
/// Re-parse and re-generate the site /// Deletes the `public` directory if it exists
/// Very dumb for now, ideally it would only rebuild what changed pub fn clean(&self) -> Result<()> {
pub fn rebuild(&mut self) -> Result<()> {
self.parse_site()?;
self.templates.full_reload()?;
self.build()
}
/// Builds the site to the `public` directory after deleting it
pub fn build(&self) -> Result<()> {
if Path::new("public").exists() { if Path::new("public").exists() {
// Delete current `public` directory so we can start fresh // Delete current `public` directory so we can start fresh
remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?; remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?;
} }
// Start from scratch Ok(())
create_dir("public")?; }
/// Re-parse and re-generate the site
/// Very dumb for now, ideally it would only rebuild what changed
pub fn rebuild(&mut self) -> Result<()> {
self.parse_site()?;
self.build()
}
pub fn rebuild_after_template_change(&mut self) -> Result<()> {
self.templates.full_reload()?;
println!("Reloaded templates");
self.build_pages()
}
pub fn build_pages(&self) -> Result<()> {
let public = Path::new("public"); let public = Path::new("public");
if !public.exists() {
create_directory(&public)?;
}
let mut pages = vec![]; let mut pages = vec![];
let mut category_pages: HashMap<String, Vec<&Page>> = HashMap::new(); let mut category_pages: HashMap<String, Vec<&Page>> = HashMap::new();
@ -175,7 +181,7 @@ impl Site {
current_path.push(section); current_path.push(section);
if !current_path.exists() { if !current_path.exists() {
create_dir(&current_path)?; create_directory(&current_path)?;
} }
} }
@ -185,7 +191,7 @@ impl Site {
} }
// Make sure the folder exists // Make sure the folder exists
create_dir(&current_path)?; create_directory(&current_path)?;
// Finally, create a index.html file there with the page rendered // Finally, create a index.html file there with the page rendered
let output = page.render_html(&self.templates, &self.config)?; let output = page.render_html(&self.templates, &self.config)?;
create_file(current_path.join("index.html"), &self.inject_livereload(output))?; create_file(current_path.join("index.html"), &self.inject_livereload(output))?;
@ -205,9 +211,6 @@ impl Site {
self.render_categories_and_tags(RenderList::Categories, &category_pages)?; self.render_categories_and_tags(RenderList::Categories, &category_pages)?;
self.render_categories_and_tags(RenderList::Tags, &tag_pages)?; self.render_categories_and_tags(RenderList::Tags, &tag_pages)?;
self.render_sitemap()?;
self.render_rss_feed()?;
// And finally the index page // And finally the index page
let mut context = Context::new(); let mut context = Context::new();
pages.sort_by(|a, b| a.partial_cmp(b).unwrap()); pages.sort_by(|a, b| a.partial_cmp(b).unwrap());
@ -216,10 +219,18 @@ impl Site {
let index = self.templates.render("index.html", &context)?; let index = self.templates.render("index.html", &context)?;
create_file(public.join("index.html"), &self.inject_livereload(index))?; create_file(public.join("index.html"), &self.inject_livereload(index))?;
self.copy_static_directory()?;
Ok(()) Ok(())
} }
/// Builds the site to the `public` directory after deleting it
pub fn build(&self) -> Result<()> {
self.clean()?;
self.build_pages()?;
self.render_sitemap()?;
self.render_rss_feed()?;
self.copy_static_directory()
}
/// Render the /{categories, list} pages and each individual category/tag page /// Render the /{categories, list} pages and each individual category/tag page
fn render_categories_and_tags(&self, kind: RenderList, container: &HashMap<String, Vec<&Page>>) -> Result<()> { fn render_categories_and_tags(&self, kind: RenderList, container: &HashMap<String, Vec<&Page>>) -> Result<()> {
if container.is_empty() { if container.is_empty() {
@ -235,7 +246,7 @@ impl Site {
let public = Path::new("public"); let public = Path::new("public");
let mut output_path = public.to_path_buf(); let mut output_path = public.to_path_buf();
output_path.push(name); output_path.push(name);
create_dir(&output_path)?; create_directory(&output_path)?;
// First we render the list of categories/tags page // First we render the list of categories/tags page
let mut sorted_container = vec![]; let mut sorted_container = vec![];
@ -262,7 +273,7 @@ impl Site {
context.add("config", &self.config); context.add("config", &self.config);
let single_output = self.templates.render(single_tpl_name, &context)?; let single_output = self.templates.render(single_tpl_name, &context)?;
create_dir(&output_path.join(&slug))?; create_directory(&output_path.join(&slug))?;
create_file( create_file(
output_path.join(&slug).join("index.html"), output_path.join(&slug).join("index.html"),
&self.inject_livereload(single_output) &self.inject_livereload(single_output)
@ -283,14 +294,6 @@ impl Site {
Ok(()) Ok(())
} }
fn get_rss_feed_url(&self) -> String {
if self.config.base_url.ends_with("/") {
format!("{}{}", self.config.base_url, "feed.xml")
} else {
format!("{}/{}", self.config.base_url, "feed.xml")
}
}
fn render_rss_feed(&self) -> Result<()> { fn render_rss_feed(&self) -> Result<()> {
let mut context = Context::new(); let mut context = Context::new();
let mut pages = self.pages.values() let mut pages = self.pages.values()
@ -306,7 +309,13 @@ impl Site {
context.add("pages", &pages); context.add("pages", &pages);
context.add("last_build_date", &pages[0].meta.date); context.add("last_build_date", &pages[0].meta.date);
context.add("config", &self.config); context.add("config", &self.config);
context.add("feed_url", &self.get_rss_feed_url());
let rss_feed_url = if self.config.base_url.ends_with("/") {
format!("{}{}", self.config.base_url, "feed.xml")
} else {
format!("{}/{}", self.config.base_url, "feed.xml")
};
context.add("feed_url", &rss_feed_url);
let sitemap = self.templates.render("rss.xml", &context)?; let sitemap = self.templates.render("rss.xml", &context)?;

View file

@ -1,8 +1,8 @@
use std::io::prelude::*; use std::io::prelude::*;
use std::fs::{File}; use std::fs::{File, create_dir};
use std::path::Path; use std::path::Path;
use errors::Result; use errors::{Result, ResultExt};
pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> { pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
@ -10,3 +10,14 @@ pub fn create_file<P: AsRef<Path>>(path: P, content: &str) -> Result<()> {
file.write_all(content.as_bytes())?; file.write_all(content.as_bytes())?;
Ok(()) Ok(())
} }
/// Very similar to create_dir from the std except it checks if the folder
/// exists before creating it
pub fn create_directory<P: AsRef<Path>>(path: P) -> Result<()> {
let path = path.as_ref();
if !path.exists() {
create_dir(path)
.chain_err(|| format!("Was not able to create folder {}", path.display()))?;
}
Ok(())
}