From b75243f1f59390d3d55f2e3655e5e3708cac6568 Mon Sep 17 00:00:00 2001 From: Jesse Stricker Date: Thu, 29 Dec 2016 16:25:51 +0100 Subject: [PATCH 01/17] Fix some minor typos --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3a553f0f..6ee7e65d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ -mdBook is a utility to create modern online books from markdown files. +mdBook is a utility to create modern online books from Markdown files. **This project is still evolving.** See [#90](https://github.com/azerupi/mdBook/issues/90) @@ -29,7 +29,7 @@ See [#90](https://github.com/azerupi/mdBook/issues/90) ## What does it look like? -The [**Documentation**](http://azerupi.github.io/mdBook/) for mdBook has been written in markdown and is using mdBook to generate the online book-like website you can read. The documentation uses the latest version on github and showcases the available features. +The [**Documentation**](http://azerupi.github.io/mdBook/) for mdBook has been written in Markdown and is using mdBook to generate the online book-like website you can read. The documentation uses the latest version on GitHub and showcases the available features. ## Installation @@ -47,12 +47,12 @@ There are multiple ways to install mdBook. This will download and compile mdBook for you, the only thing left to do is to add the Cargo bin directory to your `PATH`. 3. **From Git** - The version published to Crates.io will ever so slightly be behind the version hosted here on Github. If you need the latest version you can build the git version of mdBook yourself. Cargo makes this ***super easy***! + The version published to crates.io will ever so slightly be behind the version hosted here on GitHub. If you need the latest version you can build the git version of mdBook yourself. Cargo makes this ***super easy***! ``` cargo install --git https://github.com/azerupi/mdBook.git ``` - Again, make sure to add the Cargo bin directory to your `PATH` + Again, make sure to add the Cargo bin directory to your `PATH`. 4. **For Contributions** If you want to contribute to mdBook you will have to clone the repository on your local machine: @@ -66,7 +66,7 @@ There are multiple ways to install mdBook. cargo build ``` - the resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe` + The resulting binary can be found in `mdBook/target/debug/` under the name `mdBook` or `mdBook.exe`. @@ -74,7 +74,7 @@ There are multiple ways to install mdBook. mdBook will primaraly be used as a command line tool, even though it exposes all its functionality as a Rust crate for integration in other projects. -Here are the main commands you will want to run, for a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/). +Here are the main commands you will want to run. For a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/). - `mdbook init` @@ -106,7 +106,7 @@ Here are the main commands you will want to run, for a more exhaustive explanati ### As a library -Aside from the command line interface, this crate can also be used as a library. This means that you could integrate it in an existing project, like a web-app for example. Since the command line interface is just a wrapper around the library functionality, when you use this crate as a library you have full access to all the functionality of the command line interface with and easy to use API and more! +Aside from the command line interface, this crate can also be used as a library. This means that you could integrate it in an existing project, like a web-app for example. Since the command line interface is just a wrapper around the library functionality, when you use this crate as a library you have full access to all the functionality of the command line interface with an easy to use API and more! See the [Documentation](http://azerupi.github.io/mdBook/lib/lib.html) and the [API docs](http://azerupi.github.io/mdBook/mdbook/index.html) for more information. @@ -123,4 +123,4 @@ You can pick any issue you want to work on. Usually it's a good idea to ask if s ## License -All the code is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE](LICENSE) file +All the code is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE](LICENSE) file. From b1e384b03b22e6d53497acb0f7582a2ba576a09f Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sat, 31 Dec 2016 09:20:54 -0800 Subject: [PATCH 02/17] Fix a broken link in the documentation This fixes a broken link on http://azerupi.github.io/mdBook/cli/init.html The `..` is redundant because the document's base URI is set to `path_to_root`. It breaks if the base URI is not at the server root. --- book-example/src/cli/init.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book-example/src/cli/init.md b/book-example/src/cli/init.md index a782369b..43d1ae02 100644 --- a/book-example/src/cli/init.md +++ b/book-example/src/cli/init.md @@ -22,7 +22,7 @@ configuration files, etc. - The `book` directory is where your book is rendered. All the output is ready to be uploaded to a server to be seen by your audience. -- The `SUMMARY.md` file is the most important file, it's the skeleton of your book and is discussed in more detail in another [chapter](../format/summary.html). +- The `SUMMARY.md` file is the most important file, it's the skeleton of your book and is discussed in more detail in another [chapter](format/summary.html). #### Tip & Trick: Hidden Feature When a `SUMMARY.md` file already exists, the `init` command will first parse it and generate the missing files according to the paths used in the `SUMMARY.md`. This allows you to think and create the whole structure of your book and then let mdBook generate it for you. From 3a0cfc87df51f9d1d6b94391aa9096d301611e01 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sat, 31 Dec 2016 10:34:15 -0800 Subject: [PATCH 03/17] Add current chapter title to handlebars context --- book-example/src/format/theme/index-hbs.md | 1 + src/renderer/html_handlebars/hbs_renderer.rs | 5 ++++- src/theme/index.hbs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/book-example/src/format/theme/index-hbs.md b/book-example/src/format/theme/index-hbs.md index e509565a..d2d75fa1 100644 --- a/book-example/src/format/theme/index-hbs.md +++ b/book-example/src/format/theme/index-hbs.md @@ -20,6 +20,7 @@ Here is a list of the properties that are exposed: - ***language*** Language of the book in the form `en`. To use in \ for example. At the moment it is hardcoded. - ***title*** Title of the book, as specified in `book.toml` +- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md` - ***path*** Relative path to the original markdown file from the source directory - ***content*** This is the rendered markdown. diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 27fb214e..0e4ef8df 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -93,11 +93,14 @@ impl Renderer for HtmlHandlebars { }, } - // Remove content from previous file and render content for this one data.remove("content"); data.insert("content".to_owned(), content.to_json()); + // Remove chapter title from previous file and add title for this one + data.remove("chapter_title"); + data.insert("chapter_title".to_owned(), ch.name.to_json()); + // Remove path to root from previous file and render content for this one data.remove("path_to_root"); data.insert("path_to_root".to_owned(), utils::fs::path_to_root(&ch.path).to_json()); diff --git a/src/theme/index.hbs b/src/theme/index.hbs index 5f9d94d7..48098e05 100644 --- a/src/theme/index.hbs +++ b/src/theme/index.hbs @@ -2,7 +2,7 @@ - {{ title }} + {{ chapter_title }} - {{ title }} From 712adcf737c300109cfa99c70168037c3c541520 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sat, 31 Dec 2016 10:36:19 -0800 Subject: [PATCH 04/17] Fix cfg attribute in bookconfig_test --- src/book/bookconfig_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/book/bookconfig_test.rs b/src/book/bookconfig_test.rs index 34122628..61cfa6a9 100644 --- a/src/book/bookconfig_test.rs +++ b/src/book/bookconfig_test.rs @@ -1,4 +1,4 @@ -#[cfg(test)] +#![cfg(test)] use std::path::Path; use serde_json; From 0f0750df52c5629b8827be8e04f0366f56b0898a Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sat, 31 Dec 2016 10:39:48 -0800 Subject: [PATCH 05/17] Fix unreachable code warning in parse::summary::parse_level --- src/parse/summary.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse/summary.rs b/src/parse/summary.rs index a4c87a9c..5b73e448 100644 --- a/src/parse/summary.rs +++ b/src/parse/summary.rs @@ -37,7 +37,7 @@ fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec Date: Sat, 31 Dec 2016 18:33:17 -0800 Subject: [PATCH 06/17] Code cleanup: Remove unnecessary .remove() calls `BTreeMap::insert` will replace any existing value, so there's no need to remove the old value first. --- src/renderer/html_handlebars/hbs_renderer.rs | 25 ++++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 0e4ef8df..bcb55522 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -81,8 +81,7 @@ impl Renderer for HtmlHandlebars { content = utils::render_markdown(&content); print_content.push_str(&content); - // Remove content from previous file and render content for this one - data.remove("path"); + // Update the context with data for this file match ch.path.to_str() { Some(p) => { data.insert("path".to_owned(), p.to_json()); @@ -92,20 +91,11 @@ impl Renderer for HtmlHandlebars { "Could not convert path to str"))) }, } - - // Remove content from previous file and render content for this one - data.remove("content"); data.insert("content".to_owned(), content.to_json()); - - // Remove chapter title from previous file and add title for this one - data.remove("chapter_title"); data.insert("chapter_title".to_owned(), ch.name.to_json()); - - // Remove path to root from previous file and render content for this one - data.remove("path_to_root"); data.insert("path_to_root".to_owned(), utils::fs::path_to_root(&ch.path).to_json()); - // Rendere the handlebars template with the data + // Render the handlebars template with the data debug!("[*]: Render template"); let rendered = try!(handlebars.render("index", &data)); @@ -147,19 +137,12 @@ impl Renderer for HtmlHandlebars { // Print version - // Remove content from previous file and render content for this one - data.remove("path"); + // Update the context with data for this file data.insert("path".to_owned(), "print.md".to_json()); - - // Remove content from previous file and render content for this one - data.remove("content"); data.insert("content".to_owned(), print_content.to_json()); - - // Remove path to root from previous file and render content for this one - data.remove("path_to_root"); data.insert("path_to_root".to_owned(), utils::fs::path_to_root(Path::new("print.md")).to_json()); - // Rendere the handlebars template with the data + // Render the handlebars template with the data debug!("[*]: Render template"); let rendered = try!(handlebars.render("index", &data)); let mut file = try!(utils::fs::create_file(&book.get_dest().join("print").with_extension("html"))); From 6b2572e78d14fdeebe06812f671016d8aa5fe95c Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sat, 31 Dec 2016 18:41:59 -0800 Subject: [PATCH 07/17] Simplify some as_str error handling code --- src/renderer/html_handlebars/hbs_renderer.rs | 30 ++++++-------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index bcb55522..84be4f51 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -82,15 +82,9 @@ impl Renderer for HtmlHandlebars { print_content.push_str(&content); // Update the context with data for this file - match ch.path.to_str() { - Some(p) => { - data.insert("path".to_owned(), p.to_json()); - }, - None => { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, - "Could not convert path to str"))) - }, - } + let path = ch.path.to_str().ok_or(io::Error::new(io::ErrorKind::Other, + "Could not convert path to str"))?; + data.insert("path".to_owned(), path.to_json()); data.insert("content".to_owned(), content.to_json()); data.insert("chapter_title".to_owned(), ch.name.to_json()); data.insert("path_to_root".to_owned(), utils::fs::path_to_root(&ch.path).to_json()); @@ -285,22 +279,16 @@ fn make_data(book: &MDBook) -> Result match *item { BookItem::Affix(ref ch) => { chapter.insert("name".to_owned(), ch.name.to_json()); - match ch.path.to_str() { - Some(p) => { - chapter.insert("path".to_owned(), p.to_json()); - }, - None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))), - } + let path = ch.path.to_str().ok_or(io::Error::new(io::ErrorKind::Other, + "Could not convert path to str"))?; + chapter.insert("path".to_owned(), path.to_json()); }, BookItem::Chapter(ref s, ref ch) => { chapter.insert("section".to_owned(), s.to_json()); chapter.insert("name".to_owned(), ch.name.to_json()); - match ch.path.to_str() { - Some(p) => { - chapter.insert("path".to_owned(), p.to_json()); - }, - None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))), - } + let path = ch.path.to_str().ok_or(io::Error::new(io::ErrorKind::Other, + "Could not convert path to str"))?; + chapter.insert("path".to_owned(), path.to_json()); }, BookItem::Spacer => { chapter.insert("spacer".to_owned(), "_spacer_".to_json()); From 894a03655e7c9d6ac8f9c168f57b8239d8323f20 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sat, 31 Dec 2016 18:53:19 -0800 Subject: [PATCH 08/17] Simplify error handling in utils::fs --- src/utils/fs.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 858f1e83..d40e621b 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -69,7 +69,7 @@ pub fn path_to_root(path: &Path) -> String { /// This function creates a file and returns it. But before creating the file it checks every /// directory in the path to see if it exists, and if it does not it will be created. -pub fn create_file(path: &Path) -> Result> { +pub fn create_file(path: &Path) -> io::Result { debug!("[fn]: create_file"); // Construct path @@ -80,15 +80,7 @@ pub fn create_file(path: &Path) -> Result> { } debug!("[*]: Create file: {:?}", path); - let f = match File::create(path) { - Ok(f) => f, - Err(e) => { - debug!("File::create: {}", e); - return Err(Box::new(io::Error::new(io::ErrorKind::Other, format!("{}", e)))); - }, - }; - - Ok(f) + File::create(path) } /// Removes all the content of a directory but not the directory itself From f2b87f7944645a6c81e78d91277cb11eab40bcba Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sat, 31 Dec 2016 22:27:38 -0800 Subject: [PATCH 09/17] Factor common io error handling out of renderer --- src/book/mod.rs | 10 ++ src/renderer/html_handlebars/hbs_renderer.rs | 135 +++---------------- 2 files changed, 30 insertions(+), 115 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index 25d59ca1..3332c371 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -273,6 +273,16 @@ impl MDBook { Ok(()) } + pub fn write_file>(&self, filename: P, content: &[u8]) -> Result<(), Box> { + let path = self.get_dest().join(filename); + try!(utils::fs::create_file(&path).and_then(|mut file| { + file.write_all(content) + }).map_err(|e| { + io::Error::new(io::ErrorKind::Other, format!("Could not create {}: {}", path.display(), e)) + })); + Ok(()) + } + /// Parses the `book.json` file (if it exists) to extract the configuration parameters. /// The `book.json` file should be in the root directory of the book. /// The root directory is the one specified when creating a new `MDBook` diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 84be4f51..68f6c475 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -7,7 +7,7 @@ use {utils, theme}; use std::path::{Path, PathBuf}; use std::fs::{self, File}; use std::error::Error; -use std::io::{self, Read, Write}; +use std::io::{self, Read}; use std::collections::BTreeMap; use handlebars::Handlebars; @@ -93,19 +93,15 @@ impl Renderer for HtmlHandlebars { debug!("[*]: Render template"); let rendered = try!(handlebars.render("index", &data)); - debug!("[*]: Create file {:?}", &book.get_dest().join(&ch.path).with_extension("html")); // Write to file - let mut file = - try!(utils::fs::create_file(&book.get_dest().join(&ch.path).with_extension("html"))); - info!("[*] Creating {:?} ✓", &book.get_dest().join(&ch.path).with_extension("html")); - - try!(file.write_all(&rendered.into_bytes())); + let filename = Path::new(&ch.path).with_extension("html"); + info!("[*] Creating {:?} ✓", filename.display()); + try!(book.write_file(filename, &rendered.into_bytes())); // Create an index.html from the first element in SUMMARY.md if index { debug!("[*]: index.html"); - let mut index_file = try!(File::create(book.get_dest().join("index.html"))); let mut content = String::new(); let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html")))) .read_to_string(&mut content); @@ -117,7 +113,7 @@ impl Renderer for HtmlHandlebars { .collect::>() .join("\n"); - try!(index_file.write_all(content.as_bytes())); + try!(book.write_file("index.html", content.as_bytes())); info!("[*] Creating index.html from {:?} ✓", book.get_dest().join(&ch.path.with_extension("html"))); @@ -139,117 +135,26 @@ impl Renderer for HtmlHandlebars { // Render the handlebars template with the data debug!("[*]: Render template"); let rendered = try!(handlebars.render("index", &data)); - let mut file = try!(utils::fs::create_file(&book.get_dest().join("print").with_extension("html"))); - try!(file.write_all(&rendered.into_bytes())); + try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes())); info!("[*] Creating print.html ✓"); // Copy static files (js, css, images, ...) debug!("[*] Copy static files"); - // JavaScript - let mut js_file = if let Ok(f) = File::create(book.get_dest().join("book.js")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create book.js"))); - }; - try!(js_file.write_all(&theme.js)); - - // Css - let mut css_file = if let Ok(f) = File::create(book.get_dest().join("book.css")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create book.css"))); - }; - try!(css_file.write_all(&theme.css)); - - // Favicon - let mut favicon_file = if let Ok(f) = File::create(book.get_dest().join("favicon.png")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create favicon.png"))); - }; - try!(favicon_file.write_all(&theme.favicon)); - - // JQuery local fallback - let mut jquery = if let Ok(f) = File::create(book.get_dest().join("jquery.js")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create jquery.js"))); - }; - try!(jquery.write_all(&theme.jquery)); - - // syntax highlighting - let mut highlight_css = if let Ok(f) = File::create(book.get_dest().join("highlight.css")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create highlight.css"))); - }; - try!(highlight_css.write_all(&theme.highlight_css)); - - let mut tomorrow_night_css = if let Ok(f) = File::create(book.get_dest().join("tomorrow-night.css")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create tomorrow-night.css"))); - }; - try!(tomorrow_night_css.write_all(&theme.tomorrow_night_css)); - - let mut highlight_js = if let Ok(f) = File::create(book.get_dest().join("highlight.js")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create highlight.js"))); - }; - try!(highlight_js.write_all(&theme.highlight_js)); - - // Font Awesome local fallback - let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest() - .join("_FontAwesome/css/font-awesome.css")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create font-awesome.css"))); - }; - try!(font_awesome.write_all(theme::FONT_AWESOME)); - let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest() - .join("_FontAwesome/fonts/fontawesome-webfont.eot")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.eot"))); - }; - try!(font_awesome.write_all(theme::FONT_AWESOME_EOT)); - let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest() - .join("_FontAwesome/fonts/fontawesome-webfont.svg")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.svg"))); - }; - try!(font_awesome.write_all(theme::FONT_AWESOME_SVG)); - let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest() - .join("_FontAwesome/fonts/fontawesome-webfont.ttf")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.ttf"))); - }; - try!(font_awesome.write_all(theme::FONT_AWESOME_TTF)); - let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest() - .join("_FontAwesome/fonts/fontawesome-webfont.woff")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.woff"))); - }; - try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF)); - let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest() - .join("_FontAwesome/fonts/fontawesome-webfont.woff2")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create fontawesome-webfont.woff2"))); - }; - try!(font_awesome.write_all(theme::FONT_AWESOME_WOFF2)); - let mut font_awesome = if let Ok(f) = utils::fs::create_file(&book.get_dest() - .join("_FontAwesome/fonts/FontAwesome.ttf")) { - f - } else { - return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not create FontAwesome.ttf"))); - }; - try!(font_awesome.write_all(theme::FONT_AWESOME_TTF)); + try!(book.write_file("book.js", &theme.js)); + try!(book.write_file("book.css", &theme.css)); + try!(book.write_file("favicon.png", &theme.favicon)); + try!(book.write_file("jquery.js", &theme.jquery)); + try!(book.write_file("highlight.css", &theme.highlight_css)); + try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css)); + try!(book.write_file("highlight.js", &theme.highlight_js)); + try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME)); + try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT)); + try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG)); + try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF)); + try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF)); + try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2)); + try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)); // Copy all remaining files try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"])); From 21bc3d47c8d8d705e58ce3b4ae120f5f4a77111c Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sun, 1 Jan 2017 09:42:47 -0800 Subject: [PATCH 10/17] Add a CLI option to open a web browser --- Cargo.toml | 1 + book-example/src/cli/build.md | 5 +++++ book-example/src/cli/serve.md | 5 +++++ book-example/src/cli/watch.md | 4 ++++ src/bin/mdbook.rs | 27 ++++++++++++++++++++++++++- 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b203f8fc..f44b31d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ pulldown-cmark = "0.0.8" log = "0.3" env_logger = "0.3" toml = { version = "0.2", features = ["serde"] } +open = "1.1" # Watch feature notify = { version = "2.5.5", optional = true } diff --git a/book-example/src/cli/build.md b/book-example/src/cli/build.md index 0d17e60d..9fe42be3 100644 --- a/book-example/src/cli/build.md +++ b/book-example/src/cli/build.md @@ -21,6 +21,11 @@ current working directory. mdbook build path/to/book ``` +#### --open + +When you use the `--open` (`-o`) option, mdbook will open the rendered book in +your default web browser after building it. + ------------------- ***note:*** *make sure to run the build command in the root directory and not in the source directory* diff --git a/book-example/src/cli/serve.md b/book-example/src/cli/serve.md index 2357dc89..39f1c6b8 100644 --- a/book-example/src/cli/serve.md +++ b/book-example/src/cli/serve.md @@ -26,6 +26,11 @@ mdbook server path/to/book -p 8000 -i 127.0.0.1 -a 192.168.1.100 If you were to want live reloading for this you would need to proxy the websocket calls through nginx as well from `192.168.1.100:` to `127.0.0.1:`. The `-w` flag allows for the websocket port to be configured. +#### --open + +When you use the `--open` (`-o`) option, mdbook will open the book in your +your default web browser after starting the server. + ----- ***note:*** *the `serve` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/azerupi/mdBook/issues)* diff --git a/book-example/src/cli/watch.md b/book-example/src/cli/watch.md index ad130c78..79117b3f 100644 --- a/book-example/src/cli/watch.md +++ b/book-example/src/cli/watch.md @@ -12,6 +12,10 @@ current working directory. mdbook watch path/to/book ``` +#### --open + +When you use the `--open` (`-o`) option, mdbook will open the rendered book in +your default web browser. ----- diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 2d2bb0b0..3a82bf3e 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -5,6 +5,7 @@ extern crate clap; #[macro_use] extern crate log; extern crate env_logger; +extern crate open; // Dependencies for the Watch feature #[cfg(feature = "watch")] @@ -24,6 +25,7 @@ extern crate ws; use std::env; use std::error::Error; +use std::ffi::OsStr; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -59,9 +61,11 @@ fn main() { .arg_from_usage("--force 'skip confirmation prompts'")) .subcommand(SubCommand::with_name("build") .about("Build the book from the markdown files") + .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")) .subcommand(SubCommand::with_name("watch") .about("Watch the files for changes") + .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")) .subcommand(SubCommand::with_name("serve") .about("Serve the book at http://localhost:3000. Rebuild and reload on change.") @@ -69,7 +73,8 @@ fn main() { .arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'") .arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'") .arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'") - .arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface addres)'")) + .arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface address)'") + .arg_from_usage("-o, --open 'Open the book server in a web browser'")) .subcommand(SubCommand::with_name("test") .about("Test that code samples compile")) .get_matches(); @@ -163,6 +168,10 @@ fn build(args: &ArgMatches) -> Result<(), Box> { try!(book.build()); + if args.is_present("open") { + open(book.get_dest().join("index.html")); + } + Ok(()) } @@ -173,6 +182,11 @@ fn watch(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); let mut book = MDBook::new(&book_dir).read_config(); + if args.is_present("open") { + try!(book.build()); + open(book.get_dest().join("index.html")); + } + trigger_on_change(&mut book, |event, book| { if let Some(path) = event.path { println!("File changed: {:?}\nBuilding book...\n", path); @@ -199,6 +213,7 @@ fn serve(args: &ArgMatches) -> Result<(), Box> { let ws_port = args.value_of("ws-port").unwrap_or("3001"); let interface = args.value_of("interface").unwrap_or("localhost"); let public_address = args.value_of("address").unwrap_or(interface); + let open_browser = args.is_present("open"); let address = format!("{}:{}", interface, port); let ws_address = format!("{}:{}", interface, ws_port); @@ -239,6 +254,10 @@ fn serve(args: &ArgMatches) -> Result<(), Box> { println!("\nServing on {}", address); + if open_browser { + open(format!("http://{}", address)); + } + trigger_on_change(&mut book, move |event, book| { if let Some(path) = event.path { println!("File changed: {:?}\nBuilding book...\n", path); @@ -278,6 +297,12 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf { } } +fn open>(path: P) { + if let Err(e) = open::that(path) { + println!("Error opening web browser: {}", e); + } +} + // Calls the closure when a book source file is changed. This is blocking! #[cfg(feature = "watch")] From 09729aaca53c591014bdb5222ac3d9e16133c571 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sun, 1 Jan 2017 16:02:48 -0800 Subject: [PATCH 11/17] Clean up some Path code in bookconfig --- src/book/bookconfig.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/book/bookconfig.rs b/src/book/bookconfig.rs index 50bcb76d..63c668d1 100644 --- a/src/book/bookconfig.rs +++ b/src/book/bookconfig.rs @@ -62,13 +62,13 @@ impl BookConfig { // Read book.toml or book.json if exists - if Path::new(root.join("book.toml").as_os_str()).exists() { + if root.join("book.toml").exists() { debug!("[*]: Reading config"); let data = read_file(root.join("book.toml")); self.parse_from_toml_string(&data); - } else if Path::new(root.join("book.json").as_os_str()).exists() { + } else if root.join("book.json").exists() { debug!("[*]: Reading config"); let data = read_file(root.join("book.json")); From 1ac26023603e26401123fa352ccd74ce46b93f77 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sun, 1 Jan 2017 15:59:22 -0800 Subject: [PATCH 12/17] Update to notify 3.0 notify now does its own event debouncing, so it's no longer necessary for mdbook to do this manually. --- Cargo.toml | 2 +- src/bin/mdbook.rs | 110 ++++++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f44b31d4..cc2f11ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ toml = { version = "0.2", features = ["serde"] } open = "1.1" # Watch feature -notify = { version = "2.5.5", optional = true } +notify = { version = "3.0", optional = true } time = { version = "0.1.34", optional = true } crossbeam = { version = "0.2.8", optional = true } diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 3a82bf3e..27d5313d 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -35,6 +35,8 @@ use clap::{App, ArgMatches, SubCommand, AppSettings}; #[cfg(feature = "watch")] use notify::Watcher; #[cfg(feature = "watch")] +use std::time::Duration; +#[cfg(feature = "watch")] use std::sync::mpsc::channel; @@ -187,15 +189,13 @@ fn watch(args: &ArgMatches) -> Result<(), Box> { open(book.get_dest().join("index.html")); } - trigger_on_change(&mut book, |event, book| { - if let Some(path) = event.path { - println!("File changed: {:?}\nBuilding book...\n", path); - match book.build() { - Err(e) => println!("Error while building: {:?}", e), - _ => {}, - } - println!(""); + trigger_on_change(&mut book, |path, book| { + println!("File changed: {:?}\nBuilding book...\n", path); + match book.build() { + Err(e) => println!("Error while building: {:?}", e), + _ => {}, } + println!(""); }); Ok(()) @@ -258,15 +258,13 @@ fn serve(args: &ArgMatches) -> Result<(), Box> { open(format!("http://{}", address)); } - trigger_on_change(&mut book, move |event, book| { - if let Some(path) = event.path { - println!("File changed: {:?}\nBuilding book...\n", path); - match book.build() { - Err(e) => println!("Error while building: {:?}", e), - _ => broadcaster.send(RELOAD_COMMAND).unwrap(), - } - println!(""); + trigger_on_change(&mut book, move |path, book| { + println!("File changed: {:?}\nBuilding book...\n", path); + match book.build() { + Err(e) => println!("Error while building: {:?}", e), + _ => broadcaster.send(RELOAD_COMMAND).unwrap(), } + println!(""); }); Ok(()) @@ -307,53 +305,51 @@ fn open>(path: P) { // Calls the closure when a book source file is changed. This is blocking! #[cfg(feature = "watch")] fn trigger_on_change(book: &mut MDBook, closure: F) -> () - where F: Fn(notify::Event, &mut MDBook) -> () + where F: Fn(&Path, &mut MDBook) -> () { + use notify::RecursiveMode::*; + use notify::DebouncedEvent::*; + // Create a channel to receive the events. let (tx, rx) = channel(); - let w: Result = notify::Watcher::new(tx); - - match w { - Ok(mut watcher) => { - // Add the source directory to the watcher - if let Err(e) = watcher.watch(book.get_src()) { - println!("Error while watching {:?}:\n {:?}", book.get_src(), e); - ::std::process::exit(0); - }; - - // Add the book.json file to the watcher if it exists, because it's not - // located in the source directory - if let Err(_) = watcher.watch(book.get_root().join("book.json")) { - // do nothing if book.json is not found - } - - let mut previous_time = time::get_time(); - - println!("\nListening for changes...\n"); - - loop { - match rx.recv() { - Ok(event) => { - // Skip the event if an event has already been issued in the last second - let time = time::get_time(); - if time - previous_time < time::Duration::seconds(1) { - continue; - } else { - previous_time = time; - } - - closure(event, book); - }, - Err(e) => { - println!("An error occured: {:?}", e); - }, - } - } - }, + let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) { + Ok(w) => w, Err(e) => { println!("Error while trying to watch the files:\n\n\t{:?}", e); ::std::process::exit(0); - }, + } + }; + + // Add the source directory to the watcher + if let Err(e) = watcher.watch(book.get_src(), Recursive) { + println!("Error while watching {:?}:\n {:?}", book.get_src(), e); + ::std::process::exit(0); + }; + + // Add the book.{json,toml} file to the watcher if it exists, because it's not + // located in the source directory + if let Err(_) = watcher.watch(book.get_root().join("book.json"), NonRecursive) { + // do nothing if book.json is not found + } + println!("\nListening for changes...\n"); + + loop { + match rx.recv() { + Ok(event) => match event { + NoticeWrite(path) | + NoticeRemove(path) | + Create(path) | + Write(path) | + Remove(path) | + Rename(_, path) => { + closure(&path, book); + } + _ => {} + }, + Err(e) => { + println!("An error occured: {:?}", e); + }, + } } } From c7b4147ba73830c7532e69fad98a9f22d5151102 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Sun, 1 Jan 2017 16:00:21 -0800 Subject: [PATCH 13/17] Watch both book.json and book.toml --- src/bin/mdbook.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 27d5313d..33b22913 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -332,6 +332,10 @@ fn trigger_on_change(book: &mut MDBook, closure: F) -> () if let Err(_) = watcher.watch(book.get_root().join("book.json"), NonRecursive) { // do nothing if book.json is not found } + if let Err(_) = watcher.watch(book.get_root().join("book.toml"), NonRecursive) { + // do nothing if book.toml is not found + } + println!("\nListening for changes...\n"); loop { From 1afa2debc19a1c1c3f0be45d0b2ec030328028f6 Mon Sep 17 00:00:00 2001 From: Pete Hayes Date: Thu, 12 Jan 2017 12:23:39 +0000 Subject: [PATCH 14/17] Fix spelling of omitted --- src/bin/mdbook.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 33b22913..a2e7bef3 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -58,20 +58,20 @@ fn main() { .subcommand(SubCommand::with_name("init") .about("Create boilerplate structure and files in the directory") // the {n} denotes a newline which will properly aligned in all help messages - .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'") + .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'") .arg_from_usage("--theme 'Copies the default theme into your source folder'") .arg_from_usage("--force 'skip confirmation prompts'")) .subcommand(SubCommand::with_name("build") .about("Build the book from the markdown files") .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") - .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")) + .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")) .subcommand(SubCommand::with_name("watch") .about("Watch the files for changes") .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") - .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")) + .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")) .subcommand(SubCommand::with_name("serve") .about("Serve the book at http://localhost:3000. Rebuild and reload on change.") - .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'") + .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'") .arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'") .arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'") .arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'") From 4b31ae67898217a47948be9df3e4e6b0146248e7 Mon Sep 17 00:00:00 2001 From: Pete Hayes Date: Thu, 12 Jan 2017 12:26:22 +0000 Subject: [PATCH 15/17] Add --dest-dir arg to build, watch and serve subcommands --- src/bin/mdbook.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index a2e7bef3..29f6d806 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -64,14 +64,17 @@ fn main() { .subcommand(SubCommand::with_name("build") .about("Build the book from the markdown files") .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'") .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")) .subcommand(SubCommand::with_name("watch") .about("Watch the files for changes") .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'") .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")) .subcommand(SubCommand::with_name("serve") .about("Serve the book at http://localhost:3000. Rebuild and reload on change.") .arg_from_usage("[dir] 'A directory for your book{n}(Defaults to 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("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'") .arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'") @@ -166,7 +169,12 @@ fn init(args: &ArgMatches) -> Result<(), Box> { // 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(); + let book = MDBook::new(&book_dir).read_config(); + + let mut book = match args.value_of("dest-dir") { + Some(dest_dir) => book.set_dest(Path::new(dest_dir)), + None => book + }; try!(book.build()); @@ -182,7 +190,12 @@ fn build(args: &ArgMatches) -> Result<(), Box> { #[cfg(feature = "watch")] fn watch(args: &ArgMatches) -> Result<(), Box> { let book_dir = get_book_dir(args); - let mut book = MDBook::new(&book_dir).read_config(); + let book = MDBook::new(&book_dir).read_config(); + + let mut book = match args.value_of("dest-dir") { + Some(dest_dir) => book.set_dest(Path::new(dest_dir)), + None => book + }; if args.is_present("open") { try!(book.build()); @@ -208,7 +221,13 @@ fn serve(args: &ArgMatches) -> Result<(), Box> { const RELOAD_COMMAND: &'static str = "reload"; let book_dir = get_book_dir(args); - let mut book = MDBook::new(&book_dir).read_config(); + let book = MDBook::new(&book_dir).read_config(); + + let mut book = match args.value_of("dest-dir") { + Some(dest_dir) => book.set_dest(Path::new(dest_dir)), + None => book + }; + let port = args.value_of("port").unwrap_or("3000"); let ws_port = args.value_of("ws-port").unwrap_or("3001"); let interface = args.value_of("interface").unwrap_or("localhost"); From ac6f15cb277c893bb7868a3023b1b48120a32078 Mon Sep 17 00:00:00 2001 From: Pete Hayes Date: Tue, 17 Jan 2017 00:11:39 +0000 Subject: [PATCH 16/17] Add docs for --dest-dir option --- book-example/src/cli/build.md | 4 ++++ book-example/src/cli/serve.md | 6 +++++- book-example/src/cli/watch.md | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/book-example/src/cli/build.md b/book-example/src/cli/build.md index 9fe42be3..0c296541 100644 --- a/book-example/src/cli/build.md +++ b/book-example/src/cli/build.md @@ -26,6 +26,10 @@ mdbook build path/to/book When you use the `--open` (`-o`) option, mdbook will open the rendered book in your default web browser after building it. +#### --dest-dir + +The `--dest-dir` (`-d`) option allows you to change the output directory for your book. + ------------------- ***note:*** *make sure to run the build command in the root directory and not in the source directory* diff --git a/book-example/src/cli/serve.md b/book-example/src/cli/serve.md index 39f1c6b8..24221c8e 100644 --- a/book-example/src/cli/serve.md +++ b/book-example/src/cli/serve.md @@ -31,8 +31,12 @@ If you were to want live reloading for this you would need to proxy the websocke When you use the `--open` (`-o`) option, mdbook will open the book in your your default web browser after starting the server. +#### --dest-dir + +The `--dest-dir` (`-d`) option allows you to change the output directory for your book. + ----- ***note:*** *the `serve` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/azerupi/mdBook/issues)* -***note***: +***note***: diff --git a/book-example/src/cli/watch.md b/book-example/src/cli/watch.md index 79117b3f..8b110476 100644 --- a/book-example/src/cli/watch.md +++ b/book-example/src/cli/watch.md @@ -17,6 +17,10 @@ mdbook watch path/to/book When you use the `--open` (`-o`) option, mdbook will open the rendered book in your default web browser. +#### --dest-dir + +The `--dest-dir` (`-d`) option allows you to change the output directory for your book. + ----- ***note:*** *the `watch` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/azerupi/mdBook/issues)* From 32814f6f71eaa02fab107243fb8f109d8b5b9db7 Mon Sep 17 00:00:00 2001 From: Pete Hayes Date: Tue, 17 Jan 2017 00:15:18 +0000 Subject: [PATCH 17/17] Remove blank ***note*** section --- book-example/src/cli/serve.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/book-example/src/cli/serve.md b/book-example/src/cli/serve.md index 24221c8e..e4578c99 100644 --- a/book-example/src/cli/serve.md +++ b/book-example/src/cli/serve.md @@ -38,5 +38,3 @@ The `--dest-dir` (`-d`) option allows you to change the output directory for you ----- ***note:*** *the `serve` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/azerupi/mdBook/issues)* - -***note***: