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::thread;
use chrono::prelude::*;
use iron::{Iron, Request, IronResult, Response, status};
use mount::Mount;
use staticfile::Static;
use notify::{Watcher, RecursiveMode, watcher};
use ws::{WebSocket};
use ws::{WebSocket, Sender};
use gutenberg::Site;
use gutenberg::errors::{Result};
use ::time_elapsed;
use ::report_elapsed_time;
#[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
pub fn serve(interface: &str, port: &str) -> Result<()> {
println!("Building site...");
let start = Instant::now();
let mut site = Site::new(true)?;
site.build()?;
report_elapsed_time(start);
let address = format!("{}:{}", interface, port);
let ws_address = format!("{}:{}", interface, "1112");
@ -67,7 +96,7 @@ pub fn serve(interface: &str, port: &str) -> Result<()> {
watcher.watch("templates/", RecursiveMode::Recursive).unwrap();
let pwd = format!("{}", env::current_dir().unwrap().display());
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::*;
@ -85,41 +114,23 @@ pub fn serve(interface: &str, port: &str) -> Result<()> {
continue;
}
println!("Change detected, rebuilding site");
let what_changed = detect_change_kind(&pwd, &path);
let mut reload_path = String::new();
match what_changed {
(ChangeKind::Content, _) => println!("Content changed {}", path.display()),
(ChangeKind::Templates, _) => println!("Template changed {}", path.display()),
println!("Change detected @ {}", Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
let start = Instant::now();
match detect_change_kind(&pwd, &path) {
(ChangeKind::Content, _) => {
println!("-> Content 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) => {
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);
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)
}
}
}
report_elapsed_time(start);
}
_ => {}
}

View file

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

View file

@ -19,9 +19,15 @@ mod cmd;
// Print the time elapsed rounded to 1 decimal
fn time_elapsed(instant: Instant) -> f64 {
let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64 / 1000.0;
(duration_ms * 10.0).round() / 10.0
fn report_elapsed_time(instant: Instant) {
let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64;
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(_)) => {
println!("Building site");
let start = Instant::now();
match cmd::build() {
Ok(()) => {
println!("Site built in {:.1}s.", time_elapsed(start));
report_elapsed_time(start);
},
Err(e) => {
println!("Failed to build the site");

View file

@ -1,6 +1,6 @@
use std::collections::HashMap;
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 glob::glob;
@ -11,7 +11,7 @@ use walkdir::WalkDir;
use errors::{Result, ResultExt};
use config::{Config, get_config};
use page::Page;
use utils::create_file;
use utils::{create_file, create_directory};
lazy_static! {
@ -108,10 +108,6 @@ impl Site {
html
}
/// Reload the Tera templates
pub fn reload_templates(&mut self) -> Result<()> {
Ok(())
}
/// Copy the content of the `static` folder into the `public` folder
///
@ -131,7 +127,7 @@ impl Site {
if entry.path().is_dir() {
if !target_path.exists() {
create_dir(&target_path)?;
create_directory(&target_path)?;
}
} else {
if target_path.exists() {
@ -143,24 +139,34 @@ impl Site {
Ok(())
}
/// 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.templates.full_reload()?;
self.build()
}
/// Builds the site to the `public` directory after deleting it
pub fn build(&self) -> Result<()> {
/// Deletes the `public` directory if it exists
pub fn clean(&self) -> Result<()> {
if Path::new("public").exists() {
// Delete current `public` directory so we can start fresh
remove_dir_all("public").chain_err(|| "Couldn't delete `public` directory")?;
}
// Start from scratch
create_dir("public")?;
Ok(())
}
/// 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");
if !public.exists() {
create_directory(&public)?;
}
let mut pages = vec![];
let mut category_pages: HashMap<String, Vec<&Page>> = HashMap::new();
@ -175,7 +181,7 @@ impl Site {
current_path.push(section);
if !current_path.exists() {
create_dir(&current_path)?;
create_directory(&current_path)?;
}
}
@ -185,7 +191,7 @@ impl Site {
}
// Make sure the folder exists
create_dir(&current_path)?;
create_directory(&current_path)?;
// Finally, create a index.html file there with the page rendered
let output = page.render_html(&self.templates, &self.config)?;
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::Tags, &tag_pages)?;
self.render_sitemap()?;
self.render_rss_feed()?;
// And finally the index page
let mut context = Context::new();
pages.sort_by(|a, b| a.partial_cmp(b).unwrap());
@ -216,10 +219,18 @@ impl Site {
let index = self.templates.render("index.html", &context)?;
create_file(public.join("index.html"), &self.inject_livereload(index))?;
self.copy_static_directory()?;
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
fn render_categories_and_tags(&self, kind: RenderList, container: &HashMap<String, Vec<&Page>>) -> Result<()> {
if container.is_empty() {
@ -235,7 +246,7 @@ impl Site {
let public = Path::new("public");
let mut output_path = public.to_path_buf();
output_path.push(name);
create_dir(&output_path)?;
create_directory(&output_path)?;
// First we render the list of categories/tags page
let mut sorted_container = vec![];
@ -262,7 +273,7 @@ impl Site {
context.add("config", &self.config);
let single_output = self.templates.render(single_tpl_name, &context)?;
create_dir(&output_path.join(&slug))?;
create_directory(&output_path.join(&slug))?;
create_file(
output_path.join(&slug).join("index.html"),
&self.inject_livereload(single_output)
@ -283,14 +294,6 @@ impl Site {
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<()> {
let mut context = Context::new();
let mut pages = self.pages.values()
@ -306,7 +309,13 @@ impl Site {
context.add("pages", &pages);
context.add("last_build_date", &pages[0].meta.date);
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)?;

View file

@ -1,8 +1,8 @@
use std::io::prelude::*;
use std::fs::{File};
use std::fs::{File, create_dir};
use std::path::Path;
use errors::Result;
use errors::{Result, ResultExt};
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())?;
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(())
}