From a57baf29344d91a1db40cb87db5ecf56badaa09e Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Fri, 10 Mar 2017 20:39:58 +0900 Subject: [PATCH] Refactor serve/rebuilding a bit --- src/cmd/serve.rs | 81 ++++++++++++++++++++++++++-------------------- src/config.rs | 1 + src/main.rs | 15 ++++++--- src/site.rs | 83 +++++++++++++++++++++++++++--------------------- src/utils.rs | 15 +++++++-- 5 files changed, 117 insertions(+), 78 deletions(-) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index e36f1dd7..6211ce42 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -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 { } +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); } _ => {} } diff --git a/src/config.rs b/src/config.rs index bd9b20c4..ec26c617 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, /// Description of the site diff --git a/src/main.rs b/src/main.rs index 8a4f0fae..fe017a04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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"); diff --git a/src/site.rs b/src/site.rs index 8a36809d..c3b8d48c 100644 --- a/src/site.rs +++ b/src/site.rs @@ -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> = HashMap::new(); @@ -175,7 +181,7 @@ impl Site { current_path.push(section); if !current_path.exists() { - create_dir(¤t_path)?; + create_directory(¤t_path)?; } } @@ -185,7 +191,7 @@ impl Site { } // Make sure the folder exists - create_dir(¤t_path)?; + create_directory(¤t_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>) -> 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)?; diff --git a/src/utils.rs b/src/utils.rs index 3e0a09f5..23f509a1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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>(path: P, content: &str) -> Result<()> { @@ -10,3 +10,14 @@ pub fn create_file>(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>(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(()) +}