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

This commit is contained in:
Mathieu David 2015-11-09 14:31:00 +01:00
parent 522eef9296
commit cdbb2ee5fd
4 changed files with 72 additions and 20 deletions

View File

@ -19,20 +19,28 @@ clap = "*"
handlebars = "*" handlebars = "*"
rustc-serialize = "*" rustc-serialize = "*"
pulldown-cmark = "*" 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] [dev-dependencies]
tempdir = "*" tempdir = "*"
[dependencies.notify]
notify = "*"
optional = true
[features] [features]
default = ["output", "watch"] default = ["output", "watch"]
debug = [] debug = []
output = [] output = []
regenerate-css = [] regenerate-css = []
watch = ["notify"] watch = ["notify", "time"]
[[bin]] [[bin]]
doc = false doc = false

View File

@ -4,7 +4,7 @@
- [Command Line Tool](cli/cli-tool.md) - [Command Line Tool](cli/cli-tool.md)
- [init](cli/init.md) - [init](cli/init.md)
- [build](cli/build.md) - [build](cli/build.md)
- [watch]() - [watch](cli/watch.md)
- [Format](format/format.md) - [Format](format/format.md)
- [SUMMARY.md](format/summary.md) - [SUMMARY.md](format/summary.md)
- [Configuration](format/config.md) - [Configuration](format/config.md)

View File

@ -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
```

View File

@ -1,8 +1,14 @@
extern crate mdbook; extern crate mdbook;
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
extern crate crossbeam;
// Dependencies for the Watch feature
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
extern crate notify; extern crate notify;
#[cfg(feature = "watch")]
extern crate time;
use std::env; use std::env;
use std::error::Error; use std::error::Error;
@ -11,11 +17,13 @@ use std::path::{Path, PathBuf};
use clap::{App, ArgMatches, SubCommand}; use clap::{App, ArgMatches, SubCommand};
// Uses for the Watch feature
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
use notify::Watcher; use notify::Watcher;
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use mdbook::MDBook; use mdbook::MDBook;
const NAME: &'static str = "mdbook"; const NAME: &'static str = "mdbook";
@ -57,6 +65,8 @@ fn main() {
} }
} }
// Simple function that user comfirmation
fn confirm() -> bool { fn confirm() -> bool {
io::stdout().flush().unwrap(); io::stdout().flush().unwrap();
let mut s = String::new(); let mut s = String::new();
@ -67,6 +77,8 @@ fn confirm() -> bool {
} }
} }
// Init command implementation
fn init(args: &ArgMatches) -> Result<(), Box<Error>> { fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
@ -104,6 +116,8 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
Ok(()) Ok(())
} }
// Build command implementation
fn build(args: &ArgMatches) -> Result<(), Box<Error>> { fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config(); let mut book = MDBook::new(&book_dir).read_config();
@ -113,6 +127,8 @@ fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
Ok(()) Ok(())
} }
// Watch command implementation
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
fn watch(args: &ArgMatches) -> Result<(), Box<Error>> { fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
@ -127,16 +143,29 @@ fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
Ok(mut watcher) => { Ok(mut watcher) => {
watcher.watch(book.get_src()).unwrap(); watcher.watch(book.get_src()).unwrap();
watcher.watch(book_dir.join("book.json")).unwrap();
let previous_time = time::get_time().sec;
crossbeam::scope(|scope| {
loop { loop {
match rx.recv() { match rx.recv() {
Ok(event) => { 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 { 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); println!("File changed: {:?}\nBuilding book...\n", path);
try!(build(args)); match build(args) {
Err(e) => println!("Error while building: {:?}", e),
_ => {}
}
println!(""); println!("");
// Hack to prevent receiving the event 4 times, probably a bug in notify });
return watch(args);
} else { } else {
continue; continue;
} }
@ -146,6 +175,7 @@ fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
} }
} }
} }
});
}, },
Err(e) => { Err(e) => {
@ -158,6 +188,7 @@ fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
} }
// Helper function that returns the right path if either a relative or absolute path is passed
fn get_book_dir(args: &ArgMatches) -> PathBuf { fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(dir) = args.value_of("dir") { if let Some(dir) = args.value_of("dir") {
// Check if path is relative from current dir, or absolute... // Check if path is relative from current dir, or absolute...