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::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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
15
src/main.rs
15
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");
|
||||
|
|
83
src/site.rs
83
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<String, Vec<&Page>> = 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<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)?;
|
||||
|
||||
|
|
15
src/utils.rs
15
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<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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue