@@ -10,6 +10,13 @@ extern crate notify;
1010#[ cfg( feature = "watch" ) ]
1111extern crate time;
1212
13+ // Dependencies for the Serve feature
14+ #[ cfg( feature = "serve" ) ]
15+ extern crate iron;
16+ #[ cfg( feature = "serve" ) ]
17+ extern crate staticfile;
18+ #[ cfg( feature = "serve" ) ]
19+ extern crate ws;
1320
1421use std:: env;
1522use std:: error:: Error ;
@@ -50,6 +57,11 @@ fn main() {
5057 . subcommand ( SubCommand :: with_name ( "watch" )
5158 . about ( "Watch the files for changes" )
5259 . arg_from_usage ( "[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'" ) )
60+ . subcommand ( SubCommand :: with_name ( "serve" )
61+ . about ( "Serve the book at http://localhost:3000. Rebuild and reload on change." )
62+ . arg_from_usage ( "[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'" )
63+ . arg_from_usage ( "-p, --port=[port] 'Use another port{n}(Defaults to 3000)'" )
64+ . arg_from_usage ( "-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'" ) )
5365 . subcommand ( SubCommand :: with_name ( "test" )
5466 . about ( "Test that code samples compile" ) )
5567 . get_matches ( ) ;
@@ -60,6 +72,8 @@ fn main() {
6072 ( "build" , Some ( sub_matches) ) => build ( sub_matches) ,
6173 #[ cfg( feature = "watch" ) ]
6274 ( "watch" , Some ( sub_matches) ) => watch ( sub_matches) ,
75+ #[ cfg( feature = "serve" ) ]
76+ ( "serve" , Some ( sub_matches) ) => serve ( sub_matches) ,
6377 ( "test" , Some ( sub_matches) ) => test ( sub_matches) ,
6478 ( _, _) => unreachable ! ( ) ,
6579 } ;
@@ -148,77 +162,85 @@ fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
148162#[ cfg( feature = "watch" ) ]
149163fn watch ( args : & ArgMatches ) -> Result < ( ) , Box < Error > > {
150164 let book_dir = get_book_dir ( args) ;
151- let book = MDBook :: new ( & book_dir) . read_config ( ) ;
165+ let mut book = MDBook :: new ( & book_dir) . read_config ( ) ;
152166
153- // Create a channel to receive the events.
154- let ( tx, rx) = channel ( ) ;
167+ trigger_on_change ( & mut book, |event, book| {
168+ if let Some ( path) = event. path {
169+ println ! ( "File changed: {:?}\n Building book...\n " , path) ;
170+ match book. build ( ) {
171+ Err ( e) => println ! ( "Error while building: {:?}" , e) ,
172+ _ => { } ,
173+ }
174+ println ! ( "" ) ;
175+ }
176+ } ) ;
155177
156- let w: Result < notify:: RecommendedWatcher , notify:: Error > = notify:: Watcher :: new ( tx) ;
178+ Ok ( ( ) )
179+ }
157180
158- match w {
159- Ok ( mut watcher) => {
160181
161- // Add the source directory to the watcher
162- if let Err ( e) = watcher. watch ( book. get_src ( ) ) {
163- println ! ( "Error while watching {:?}:\n {:?}" , book. get_src( ) , e) ;
164- :: std:: process:: exit ( 0 ) ;
165- } ;
182+ // Watch command implementation
183+ #[ cfg( feature = "serve" ) ]
184+ fn serve ( args : & ArgMatches ) -> Result < ( ) , Box < Error > > {
185+ const RELOAD_COMMAND : & ' static str = "reload" ;
166186
167- // Add the book.json file to the watcher if it exists, because it's not
168- // located in the source directory
169- if let Err ( _) = watcher. watch ( book_dir. join ( "book.json" ) ) {
170- // do nothing if book.json is not found
171- }
187+ let book_dir = get_book_dir ( args) ;
188+ let mut book = MDBook :: new ( & book_dir) . read_config ( ) ;
189+ let port = args. value_of ( "port" ) . unwrap_or ( "3000" ) ;
190+ let ws_port = args. value_of ( "ws-port" ) . unwrap_or ( "3001" ) ;
191+
192+ let address = format ! ( "localhost:{}" , port) ;
193+ let ws_address = format ! ( "localhost:{}" , ws_port) ;
194+
195+ book. set_livereload ( format ! ( r#"
196+ <script type="text/javascript">
197+ var socket = new WebSocket("ws://localhost:{}");
198+ socket.onmessage = function (event) {{
199+ if (event.data === "{}") {{
200+ socket.close();
201+ location.reload(true); // force reload from server (not from cache)
202+ }}
203+ }};
204+
205+ window.onbeforeunload = function() {{
206+ socket.close();
207+ }}
208+ </script>
209+ "# , ws_port, RELOAD_COMMAND ) . to_owned ( ) ) ;
172210
173- let mut previous_time = time :: get_time ( ) ;
211+ try! ( book . build ( ) ) ;
174212
175- crossbeam:: scope ( |scope| {
176- loop {
177- match rx. recv ( ) {
178- Ok ( event) => {
179-
180- // Skip the event if an event has already been issued in the last second
181- let time = time:: get_time ( ) ;
182- if time - previous_time < time:: Duration :: seconds ( 1 ) {
183- continue ;
184- } else {
185- previous_time = time;
186- }
187-
188- if let Some ( path) = event. path {
189- // Trigger the build process in a new thread (to keep receiving events)
190- scope. spawn ( move || {
191- println ! ( "File changed: {:?}\n Building book...\n " , path) ;
192- match build ( args) {
193- Err ( e) => println ! ( "Error while building: {:?}" , e) ,
194- _ => { } ,
195- }
196- println ! ( "" ) ;
197- } ) ;
198-
199- } else {
200- continue ;
201- }
202- } ,
203- Err ( e) => {
204- println ! ( "An error occured: {:?}" , e) ;
205- } ,
206- }
207- }
208- } ) ;
213+ let staticfile = staticfile:: Static :: new ( book. get_dest ( ) ) ;
214+ let iron = iron:: Iron :: new ( staticfile) ;
215+ let _iron = iron. http ( & * address) . unwrap ( ) ;
209216
210- } ,
211- Err ( e) => {
212- println ! ( "Error while trying to watch the files:\n \n \t {:?}" , e) ;
213- :: std:: process:: exit ( 0 ) ;
214- } ,
215- }
217+ let ws_server = ws:: WebSocket :: new ( |_| {
218+ |_| {
219+ Ok ( ( ) )
220+ }
221+ } ) . unwrap ( ) ;
222+
223+ let broadcaster = ws_server. broadcaster ( ) ;
224+
225+ std:: thread:: spawn ( move || {
226+ ws_server. listen ( & * ws_address) . unwrap ( ) ;
227+ } ) ;
228+
229+ trigger_on_change ( & mut book, move |event, book| {
230+ if let Some ( path) = event. path {
231+ println ! ( "File changed: {:?}\n Building book...\n " , path) ;
232+ match book. build ( ) {
233+ Err ( e) => println ! ( "Error while building: {:?}" , e) ,
234+ _ => broadcaster. send ( RELOAD_COMMAND ) . unwrap ( ) ,
235+ }
236+ println ! ( "" ) ;
237+ }
238+ } ) ;
216239
217240 Ok ( ( ) )
218241}
219242
220243
221-
222244fn test ( args : & ArgMatches ) -> Result < ( ) , Box < Error > > {
223245 let book_dir = get_book_dir ( args) ;
224246 let mut book = MDBook :: new ( & book_dir) . read_config ( ) ;
@@ -229,7 +251,6 @@ fn test(args: &ArgMatches) -> Result<(), Box<Error>> {
229251}
230252
231253
232-
233254fn get_book_dir ( args : & ArgMatches ) -> PathBuf {
234255 if let Some ( dir) = args. value_of ( "dir" ) {
235256 // Check if path is relative from current dir, or absolute...
@@ -243,3 +264,57 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
243264 env:: current_dir ( ) . unwrap ( )
244265 }
245266}
267+
268+
269+ // Calls the closure when a book source file is changed. This is blocking!
270+ fn trigger_on_change < F > ( book : & mut MDBook , closure : F ) -> ( )
271+ where F : Fn ( notify:: Event , & mut MDBook ) -> ( )
272+ {
273+ // Create a channel to receive the events.
274+ let ( tx, rx) = channel ( ) ;
275+
276+ let w: Result < notify:: RecommendedWatcher , notify:: Error > = notify:: Watcher :: new ( tx) ;
277+
278+ match w {
279+ Ok ( mut watcher) => {
280+ // Add the source directory to the watcher
281+ if let Err ( e) = watcher. watch ( book. get_src ( ) ) {
282+ println ! ( "Error while watching {:?}:\n {:?}" , book. get_src( ) , e) ;
283+ :: std:: process:: exit ( 0 ) ;
284+ } ;
285+
286+ // Add the book.json file to the watcher if it exists, because it's not
287+ // located in the source directory
288+ if let Err ( _) = watcher. watch ( book. get_root ( ) . join ( "book.json" ) ) {
289+ // do nothing if book.json is not found
290+ }
291+
292+ let mut previous_time = time:: get_time ( ) ;
293+
294+ println ! ( "\n Listening for changes...\n " ) ;
295+
296+ loop {
297+ match rx. recv ( ) {
298+ Ok ( event) => {
299+ // Skip the event if an event has already been issued in the last second
300+ let time = time:: get_time ( ) ;
301+ if time - previous_time < time:: Duration :: seconds ( 1 ) {
302+ continue ;
303+ } else {
304+ previous_time = time;
305+ }
306+
307+ closure ( event, book) ;
308+ } ,
309+ Err ( e) => {
310+ println ! ( "An error occured: {:?}" , e) ;
311+ } ,
312+ }
313+ }
314+ } ,
315+ Err ( e) => {
316+ println ! ( "Error while trying to watch the files:\n \n \t {:?}" , e) ;
317+ :: std:: process:: exit ( 0 ) ;
318+ } ,
319+ }
320+ }
0 commit comments