From cdbb2ee5fd91610b720459a344381d9b6ead052d Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Mon, 9 Nov 2015 14:31:00 +0100 Subject: [PATCH] Watch builds are now spawned in new threads (using crossbeam) and there is a timelock, preventing multiple builds being triggered in less than a second --- Cargo.toml | 16 ++++++--- book-example/src/SUMMARY.md | 2 +- book-example/src/cli/watch.md | 13 ++++++++ src/bin/mdbook.rs | 61 ++++++++++++++++++++++++++--------- 4 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 book-example/src/cli/watch.md diff --git a/Cargo.toml b/Cargo.toml index 769873aa..fd5299c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,20 +19,28 @@ clap = "*" handlebars = "*" rustc-serialize = "*" pulldown-cmark = "*" +crossbeam = "^0.1.5" +# Watch feature +[dependencies.notify] +notify = "^2.4.1" +optional = true + +[dependencies.time] +time = "^0.1.33" +optional = true + +# Tests [dev-dependencies] tempdir = "*" -[dependencies.notify] -notify = "*" -optional = true [features] default = ["output", "watch"] debug = [] output = [] regenerate-css = [] -watch = ["notify"] +watch = ["notify", "time"] [[bin]] doc = false diff --git a/book-example/src/SUMMARY.md b/book-example/src/SUMMARY.md index 18eb90e0..584fbb27 100644 --- a/book-example/src/SUMMARY.md +++ b/book-example/src/SUMMARY.md @@ -4,7 +4,7 @@ - [Command Line Tool](cli/cli-tool.md) - [init](cli/init.md) - [build](cli/build.md) - - [watch]() + - [watch](cli/watch.md) - [Format](format/format.md) - [SUMMARY.md](format/summary.md) - [Configuration](format/config.md) diff --git a/book-example/src/cli/watch.md b/book-example/src/cli/watch.md new file mode 100644 index 00000000..7e945450 --- /dev/null +++ b/book-example/src/cli/watch.md @@ -0,0 +1,13 @@ +# The watch command + +The watch command is useful when you want your book to be rendered on every file change. +You could issue `mdbook build` everytime you change a file. But using `mdbook watch` once will watch your files and will trigger a build whenever you modify a file. + +#### Specify a directory + +Like `init` and `build`, `watch` can take a directory as argument to use instead of the +current working directory. + +``` +mdbook watch path/to/book +``` diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 0e63b5f1..71cbf1d9 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -1,8 +1,14 @@ extern crate mdbook; #[macro_use] extern crate clap; +extern crate crossbeam; + +// Dependencies for the Watch feature #[cfg(feature = "watch")] extern crate notify; +#[cfg(feature = "watch")] +extern crate time; + use std::env; use std::error::Error; @@ -11,11 +17,13 @@ use std::path::{Path, PathBuf}; use clap::{App, ArgMatches, SubCommand}; +// Uses for the Watch feature #[cfg(feature = "watch")] use notify::Watcher; #[cfg(feature = "watch")] use std::sync::mpsc::channel; + use mdbook::MDBook; const NAME: &'static str = "mdbook"; @@ -57,6 +65,8 @@ fn main() { } } + +// Simple function that user comfirmation fn confirm() -> bool { io::stdout().flush().unwrap(); let mut s = String::new(); @@ -67,6 +77,8 @@ fn confirm() -> bool { } } + +// Init command implementation fn init(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); @@ -104,6 +116,8 @@ fn init(args: &ArgMatches) -> Result<(), Box> { Ok(()) } + +// Build command implementation fn build(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); let mut book = MDBook::new(&book_dir).read_config(); @@ -113,6 +127,8 @@ fn build(args: &ArgMatches) -> Result<(), Box> { Ok(()) } + +// Watch command implementation #[cfg(feature = "watch")] fn watch(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); @@ -127,25 +143,39 @@ fn watch(args: &ArgMatches) -> Result<(), Box> { Ok(mut watcher) => { watcher.watch(book.get_src()).unwrap(); + watcher.watch(book_dir.join("book.json")).unwrap(); - loop { - match rx.recv() { - Ok(event) => { - if let Some(path) = event.path { - println!("File changed: {:?}\nBuilding book...\n", path); - try!(build(args)); - println!(""); - // Hack to prevent receiving the event 4 times, probably a bug in notify - return watch(args); - } else { - continue; + let previous_time = time::get_time().sec; + + crossbeam::scope(|scope| { + loop { + match rx.recv() { + Ok(event) => { + + // Skip the event if an event has already been issued in the last second + if time::get_time().sec - previous_time < 1 { continue } + + if let Some(path) = event.path { + // Trigger the build process in a new thread (to keep receiving events) + scope.spawn(move || { + println!("File changed: {:?}\nBuilding book...\n", path); + match build(args) { + Err(e) => println!("Error while building: {:?}", e), + _ => {} + } + println!(""); + }); + + } else { + continue; + } + }, + Err(e) => { + println!("An error occured: {:?}", e); } - }, - Err(e) => { - println!("An error occured: {:?}", e); } } - } + }); }, Err(e) => { @@ -158,6 +188,7 @@ fn watch(args: &ArgMatches) -> Result<(), Box> { } +// Helper function that returns the right path if either a relative or absolute path is passed fn get_book_dir(args: &ArgMatches) -> PathBuf { if let Some(dir) = args.value_of("dir") { // Check if path is relative from current dir, or absolute...