Browse Source

Refactor serve/rebuilding a bit

index-subcmd
Vincent Prouillet 7 years ago
parent
commit
a57baf2934
5 changed files with 114 additions and 75 deletions
  1. +46
    -35
      src/cmd/serve.rs
  2. +1
    -0
      src/config.rs
  3. +11
    -4
      src/main.rs
  4. +43
    -34
      src/site.rs
  5. +13
    -2
      src/utils.rs

+ 46
- 35
src/cmd/serve.rs 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);
}
_ => {}
}


+ 1
- 0
src/config.rs 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


+ 11
- 4
src/main.rs 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");


+ 43
- 34
src/site.rs 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(())
}

/// 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")?;
}

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<()> {
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")?;
}
pub fn rebuild_after_template_change(&mut self) -> Result<()> {
self.templates.full_reload()?;
println!("Reloaded templates");
self.build_pages()
}

// Start from scratch
create_dir("public")?;
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)?;



+ 13
- 2
src/utils.rs 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(())
}

Loading…
Cancel
Save