Made `mdbook watch` and `mdbook serve` rebuild the book again (#508)

Made `mdbook watch` and `mdbook serve` rebuild the book again
This commit is contained in:
Michael Bryan 2017-12-13 12:04:24 +11:00 committed by GitHub
parent d69bc9c7c3
commit 396426662d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 73 deletions

View File

@ -3,12 +3,11 @@ extern crate staticfile;
extern crate ws; extern crate ws;
use std; use std;
use std::path::PathBuf;
use self::iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response, use self::iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response,
Set}; Set};
use clap::{App, ArgMatches, SubCommand}; use clap::{App, ArgMatches, SubCommand};
use mdbook::MDBook; use mdbook::MDBook;
use mdbook::errors::Result; use mdbook::errors::*;
use {get_book_dir, open}; use {get_book_dir, open};
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
use watch; use watch;
@ -18,28 +17,21 @@ struct ErrorRecover;
// Create clap subcommand arguments // Create clap subcommand arguments
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("serve") SubCommand::with_name("serve")
.about( .about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
"Serve the book at http://localhost:3000. Rebuild and reload on change.",
)
.arg_from_usage( .arg_from_usage(
"[dir] 'A directory for your book{n}(Defaults to \ "[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
Current Directory when omitted)'",
)
.arg_from_usage(
"-d, --dest-dir=[dest-dir] 'The output directory for \
your book{n}(Defaults to ./book when omitted)'",
) )
.arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'") .arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
.arg_from_usage( .arg_from_usage(
"-w, --websocket-port=[ws-port] 'Use another port for the \ "-w, --websocket-port=[ws-port] 'Use another port for the websocket connection \
websocket connection (livereload){n}(Defaults to 3001)'", (livereload){n}(Defaults to 3001)'",
) )
.arg_from_usage( .arg_from_usage(
"-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'", "-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'",
) )
.arg_from_usage( .arg_from_usage(
"-a, --address=[address] 'Address that the browser can reach the \ "-a, --address=[address] 'Address that the browser can reach the websocket server \
websocket server from{n}(Defaults to the interface address)'", from{n}(Defaults to the interface address)'",
) )
.arg_from_usage("-o, --open 'Open the book server in a web browser'") .arg_from_usage("-o, --open 'Open the book server in a web browser'")
} }
@ -51,10 +43,6 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?; let mut book = MDBook::load(&book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = PathBuf::from(dest_dir);
}
let port = args.value_of("port").unwrap_or("3000"); let port = args.value_of("port").unwrap_or("3000");
let ws_port = args.value_of("websocket-port").unwrap_or("3001"); let ws_port = args.value_of("websocket-port").unwrap_or("3001");
let interface = args.value_of("interface").unwrap_or("localhost"); let interface = args.value_of("interface").unwrap_or("localhost");
@ -80,18 +68,19 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
}} }}
</script> </script>
"#, "#,
public_address, public_address, ws_port, RELOAD_COMMAND
ws_port,
RELOAD_COMMAND
)); ));
book.build()?; book.build()?;
let mut chain = Chain::new(staticfile::Static::new(book.get_destination())); let mut chain = Chain::new(staticfile::Static::new(book.get_destination()));
chain.link_after(ErrorRecover); chain.link_after(ErrorRecover);
let _iron = Iron::new(chain).http(&*address).unwrap(); let _iron = Iron::new(chain)
.http(&*address)
.chain_err(|| "Unable to launch the server")?;
let ws_server = ws::WebSocket::new(|_| |_| Ok(())).unwrap(); let ws_server =
ws::WebSocket::new(|_| |_| Ok(())).chain_err(|| "Unable to start the websocket")?;
let broadcaster = ws_server.broadcaster(); let broadcaster = ws_server.broadcaster();
@ -107,9 +96,9 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
} }
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
watch::trigger_on_change(&mut book, move |path, book| { watch::trigger_on_change(&mut book, move |path, book_dir| {
println!("File changed: {:?}\nBuilding book...\n", path); println!("File changed: {:?}\nBuilding book...\n", path);
match book.build() { match MDBook::load(&book_dir).and_then(|mut b| b.build()) {
Err(e) => println!("Error while building: {:?}", e), Err(e) => println!("Error while building: {:?}", e),
_ => broadcaster.send(RELOAD_COMMAND).unwrap(), _ => broadcaster.send(RELOAD_COMMAND).unwrap(),
} }

View File

@ -1,6 +1,6 @@
extern crate notify; extern crate notify;
use std::path::{Path, PathBuf}; use std::path::Path;
use self::notify::Watcher; use self::notify::Watcher;
use std::time::Duration; use std::time::Duration;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
@ -15,12 +15,7 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
.about("Watch the files for changes") .about("Watch the files for changes")
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'") .arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.arg_from_usage( .arg_from_usage(
"-d, --dest-dir=[dest-dir] 'The output directory for \ "[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'",
your book{n}(Defaults to ./book when omitted)'",
)
.arg_from_usage(
"[dir] 'A directory for your book{n}(Defaults to \
Current Directory when omitted)'",
) )
} }
@ -29,30 +24,28 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
let book_dir = get_book_dir(args); let book_dir = get_book_dir(args);
let mut book = MDBook::load(&book_dir)?; let mut book = MDBook::load(&book_dir)?;
if let Some(dest_dir) = args.value_of("dest-dir") {
book.config.build.build_dir = PathBuf::from(dest_dir);
}
if args.is_present("open") { if args.is_present("open") {
book.build()?; book.build()?;
open(book.get_destination().join("index.html")); open(book.get_destination().join("index.html"));
} }
trigger_on_change(&mut book, |path, book| { trigger_on_change(&book, |path, book_dir| {
println!("File changed: {:?}\nBuilding book...\n", path); println!("File changed: {:?}\nBuilding book...\n", path);
if let Err(e) = book.build() { let result = MDBook::load(&book_dir).and_then(|mut b| b.build());
println!("Error while building: {:?}", e);
if let Err(e) = result {
println!("Error while building: {}", e);
} }
println!(""); println!();
}); });
Ok(()) Ok(())
} }
// Calls the closure when a book source file is changed. This is blocking! /// Calls the closure when a book source file is changed, blocking indefinitely.
pub fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> () pub fn trigger_on_change<F>(book: &MDBook, closure: F)
where where
F: Fn(&Path, &mut MDBook) -> (), F: Fn(&Path, &Path),
{ {
use self::notify::RecursiveMode::*; use self::notify::RecursiveMode::*;
use self::notify::DebouncedEvent::*; use self::notify::DebouncedEvent::*;
@ -64,48 +57,29 @@ where
Ok(w) => w, Ok(w) => w,
Err(e) => { Err(e) => {
println!("Error while trying to watch the files:\n\n\t{:?}", e); println!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(0) ::std::process::exit(1)
} }
}; };
// Add the source directory to the watcher // Add the source directory to the watcher
if let Err(e) = watcher.watch(book.source_dir(), Recursive) { if let Err(e) = watcher.watch(book.source_dir(), Recursive) {
println!("Error while watching {:?}:\n {:?}", book.source_dir(), e); println!("Error while watching {:?}:\n {:?}", book.source_dir(), e);
::std::process::exit(0); ::std::process::exit(1);
}; };
// Add the theme directory to the watcher let _ = watcher.watch(book.theme_dir(), Recursive);
watcher.watch(book.theme_dir(), Recursive)
.unwrap_or_default();
// Add the book.{json,toml} file to the watcher if it exists, because it's not // Add the book.toml file to the watcher if it exists
// located in the source directory let _ = watcher.watch(book.root.join("book.toml"), NonRecursive);
if watcher.watch(book.root.join("book.json"), NonRecursive)
.is_err()
{
// do nothing if book.json is not found
}
if watcher.watch(book.root.join("book.toml"), NonRecursive)
.is_err()
{
// do nothing if book.toml is not found
}
println!("\nListening for changes...\n"); println!("\nListening for changes...\n");
loop { for event in rx.recv() {
match rx.recv() { match event {
Ok(event) => { Create(path) | Write(path) | Remove(path) | Rename(_, path) => {
match event { closure(&path, &book.root);
Create(path) | Write(path) | Remove(path) | Rename(_, path) => {
closure(&path, book);
}
_ => {}
}
}
Err(e) => {
println!("An error occured: {:?}", e);
} }
_ => {}
} }
} }
} }