Refactor serve/rebuilding a bit
This commit is contained in:
parent
4406b16007
commit
a57baf2934
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -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");
|
||||||
|
|
83
src/site.rs
83
src/site.rs
|
@ -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(¤t_path)?;
|
create_directory(¤t_path)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +191,7 @@ impl Site {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the folder exists
|
// Make sure the folder exists
|
||||||
create_dir(¤t_path)?;
|
create_directory(¤t_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)?;
|
||||||
|
|
||||||
|
|
15
src/utils.rs
15
src/utils.rs
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue