diff --git a/.travis.yml b/.travis.yml index e1d21a08..8b68ee5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,13 @@ matrix: - nodejs - os: linux env: TARGET=x86_64-unknown-linux-musl CHANNEL=stable + dist: trusty + addons: + apt: + packages: &musl_packages + - musl + - musl-dev + - musl-tools # Beta channel - os: osx env: TARGET=i686-apple-darwin CHANNEL=beta @@ -42,6 +49,13 @@ matrix: env: TARGET=x86_64-unknown-linux-gnu CHANNEL=beta - os: linux env: TARGET=x86_64-unknown-linux-musl CHANNEL=beta + dist: trusty + addons: + apt: + packages: &musl_packages + - musl + - musl-dev + - musl-tools # Nightly channel - os: osx env: TARGET=i686-apple-darwin CHANNEL=nightly @@ -56,6 +70,13 @@ matrix: env: TARGET=x86_64-unknown-linux-gnu CHANNEL=nightly - os: linux env: TARGET=x86_64-unknown-linux-musl CHANNEL=nightly + dist: trusty + addons: + apt: + packages: &musl_packages + - musl + - musl-dev + - musl-tools install: - export PATH="$PATH:$HOME/.cargo/bin" diff --git a/Cargo.toml b/Cargo.toml index ced82e51..65ad1e62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ clap = "2.24" handlebars = "0.27" serde = "1.0" serde_derive = "1.0" +error-chain = "0.10.0" serde_json = "1.0" pulldown-cmark = "0.0.14" log = "0.3" diff --git a/src/bin/build.rs b/src/bin/build.rs index a7bdc70c..c05353f9 100644 --- a/src/bin/build.rs +++ b/src/bin/build.rs @@ -1,12 +1,10 @@ -use std::error::Error; - use clap::{ArgMatches, SubCommand, App}; use mdbook::MDBook; - +use mdbook::errors::*; use {get_book_dir, open}; // Build command implementation -pub fn build(args: &ArgMatches) -> Result<(), Box> { +pub fn build(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let book = MDBook::new(&book_dir).read_config()?; diff --git a/src/bin/init.rs b/src/bin/init.rs index f0f51861..75905d42 100644 --- a/src/bin/init.rs +++ b/src/bin/init.rs @@ -1,14 +1,12 @@ use std::io; use std::io::Write; -use std::error::Error; - use clap::{ArgMatches, SubCommand, App}; use mdbook::MDBook; - +use mdbook::errors::*; use get_book_dir; // Init command implementation -pub fn init(args: &ArgMatches) -> Result<(), Box> { +pub fn init(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let mut book = MDBook::new(&book_dir); diff --git a/src/bin/mdbook.rs b/src/bin/mdbook.rs index 961007be..12ce8e6f 100644 --- a/src/bin/mdbook.rs +++ b/src/bin/mdbook.rs @@ -9,7 +9,6 @@ use std::env; use std::ffi::OsStr; use std::io::{self, Write}; use std::path::{Path, PathBuf}; - use clap::{App, ArgMatches, AppSettings}; pub mod build; diff --git a/src/bin/serve.rs b/src/bin/serve.rs index cce336e9..9dabfcc6 100644 --- a/src/bin/serve.rs +++ b/src/bin/serve.rs @@ -4,13 +4,11 @@ extern crate ws; use std; use std::path::Path; -use std::error::Error; use self::iron::{Iron, AfterMiddleware, IronResult, IronError, Request, Response, status, Set, Chain}; use clap::{ArgMatches, SubCommand, App}; use mdbook::MDBook; - +use mdbook::errors::*; use {get_book_dir, open}; - #[cfg(feature = "watch")] use watch; @@ -27,7 +25,7 @@ impl AfterMiddleware for ErrorRecover { } // Watch command implementation -pub fn serve(args: &ArgMatches) -> Result<(), Box> { +pub fn serve(args: &ArgMatches) -> Result<()> { const RELOAD_COMMAND: &'static str = "reload"; let book_dir = get_book_dir(args); diff --git a/src/bin/test.rs b/src/bin/test.rs index 9517ffa5..734e93f4 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -1,12 +1,10 @@ -use std::error::Error; - use clap::{ArgMatches, SubCommand, App}; use mdbook::MDBook; - +use mdbook::errors::*; use get_book_dir; // test command implementation -pub fn test(args: &ArgMatches) -> Result<(), Box> { +pub fn test(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let mut book = MDBook::new(&book_dir).read_config()?; diff --git a/src/bin/watch.rs b/src/bin/watch.rs index ecd6a942..b9702d60 100644 --- a/src/bin/watch.rs +++ b/src/bin/watch.rs @@ -3,18 +3,16 @@ extern crate time; extern crate crossbeam; use std::path::Path; -use std::error::Error; - use self::notify::Watcher; use std::time::Duration; use std::sync::mpsc::channel; use clap::{ArgMatches, SubCommand, App}; use mdbook::MDBook; - +use mdbook::errors::*; use {get_book_dir, open}; // Watch command implementation -pub fn watch(args: &ArgMatches) -> Result<(), Box> { +pub fn watch(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); let book = MDBook::new(&book_dir).read_config()?; diff --git a/src/book/bookitem.rs b/src/book/bookitem.rs index e3e40225..7fe7ab55 100644 --- a/src/book/bookitem.rs +++ b/src/book/bookitem.rs @@ -2,6 +2,7 @@ use serde::{Serialize, Serializer}; use serde::ser::SerializeStruct; use std::path::PathBuf; + #[derive(Debug, Clone)] pub enum BookItem { Chapter(String, Chapter), // String = section @@ -37,7 +38,7 @@ impl Chapter { impl Serialize for Chapter { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> ::std::result::Result where S: Serializer { let mut struct_ = serializer.serialize_struct("Chapter", 2)?; diff --git a/src/book/mod.rs b/src/book/mod.rs index 35ed41f7..e4a7ebd6 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -4,14 +4,12 @@ pub use self::bookitem::{BookItem, BookItems}; use std::path::{Path, PathBuf}; use std::fs::{self, File}; -use std::error::Error; -use std::io; use std::io::{Read, Write}; -use std::io::ErrorKind; use std::process::Command; use {theme, parse, utils}; use renderer::{Renderer, HtmlHandlebars}; +use errors::*; use config::BookConfig; use config::tomlconfig::TomlConfig; @@ -129,7 +127,7 @@ impl MDBook { /// and adds a `SUMMARY.md` and a /// `chapter_1.md` to the source directory. - pub fn init(&mut self) -> Result<(), Box> { + pub fn init(&mut self) -> Result<()> { debug!("[fn]: init"); @@ -239,7 +237,7 @@ impl MDBook { /// method of the current renderer. /// /// It is the renderer who generates all the output files. - pub fn build(&mut self) -> Result<(), Box> { + pub fn build(&mut self) -> Result<()> { debug!("[fn]: build"); self.init()?; @@ -249,9 +247,7 @@ impl MDBook { utils::fs::remove_dir_content(htmlconfig.get_destination())?; } - self.renderer.render(&self)?; - - Ok(()) + self.renderer.render(&self) } @@ -259,7 +255,7 @@ impl MDBook { self.config.get_root().join(".gitignore") } - pub fn copy_theme(&self) -> Result<(), Box> { + pub fn copy_theme(&self) -> Result<()> { debug!("[fn]: copy_theme"); if let Some(htmlconfig) = self.config.get_html_config() { @@ -298,16 +294,14 @@ impl MDBook { Ok(()) } - pub fn write_file>(&self, filename: P, content: &[u8]) -> Result<(), Box> { + pub fn write_file>(&self, filename: P, content: &[u8]) -> Result<()> { let path = self.get_destination() .ok_or(String::from("HtmlConfig not set, could not find a destination"))? .join(filename); - 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(()) + utils::fs::create_file(&path)? + .write_all(content) + .map_err(|e| e.into()) } /// Parses the `book.json` file (if it exists) to extract @@ -315,7 +309,7 @@ impl MDBook { /// 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` - pub fn read_config(mut self) -> Result> { + pub fn read_config(mut self) -> Result { let toml = self.get_root().join("book.toml"); let json = self.get_root().join("book.json"); @@ -369,9 +363,9 @@ impl MDBook { self } - pub fn test(&mut self) -> Result<(), Box> { + pub fn test(&mut self) -> Result<()> { // read in the chapters - self.parse_summary()?; + self.parse_summary().chain_err(|| "Couldn't parse summary")?; for item in self.iter() { if let BookItem::Chapter(_, ref ch) = *item { @@ -381,15 +375,10 @@ impl MDBook { println!("[*]: Testing file: {:?}", path); - let output_result = Command::new("rustdoc").arg(&path).arg("--test").output(); - let output = output_result?; + let output = Command::new("rustdoc").arg(&path).arg("--test").output()?; if !output.status.success() { - return Err(Box::new(io::Error::new(ErrorKind::Other, - format!("{}\n{}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr)))) as - Box); + bail!(ErrorKind::Subprocess("Rustdoc returned an error".to_string(), output)); } } } @@ -498,6 +487,23 @@ impl MDBook { false } + pub fn with_mathjax_support(mut self, mathjax_support: bool) -> Self { + if let Some(htmlconfig) = self.config.get_mut_html_config() { + htmlconfig.set_mathjax_support(mathjax_support); + } else { + error!("There is no HTML renderer set..."); + } + self + } + + pub fn get_mathjax_support(&self) -> bool { + if let Some(htmlconfig) = self.config.get_html_config() { + return htmlconfig.get_mathjax_support(); + } + + false + } + pub fn get_google_analytics_id(&self) -> Option { if let Some(htmlconfig) = self.config.get_html_config() { return htmlconfig.get_google_analytics_id(); @@ -539,7 +545,7 @@ impl MDBook { } // Construct book - fn parse_summary(&mut self) -> Result<(), Box> { + fn parse_summary(&mut self) -> Result<()> { // When append becomes stable, use self.content.append() ... self.content = parse::construct_bookitems(&self.get_source().join("SUMMARY.md"))?; Ok(()) diff --git a/src/config/htmlconfig.rs b/src/config/htmlconfig.rs index 0cb2192b..5d5cac94 100644 --- a/src/config/htmlconfig.rs +++ b/src/config/htmlconfig.rs @@ -7,6 +7,7 @@ pub struct HtmlConfig { destination: PathBuf, theme: PathBuf, curly_quotes: bool, + mathjax_support: bool, google_analytics: Option, additional_css: Vec, additional_js: Vec, @@ -30,6 +31,7 @@ impl HtmlConfig { destination: root.clone().join("book"), theme: root.join("theme"), curly_quotes: false, + mathjax_support: false, google_analytics: None, additional_css: Vec::new(), additional_js: Vec::new(), @@ -51,6 +53,10 @@ impl HtmlConfig { self.curly_quotes = curly_quotes; } + if let Some(mathjax_support) = tomlconfig.mathjax_support { + self.mathjax_support = mathjax_support; + } + if tomlconfig.google_analytics.is_some() { self.google_analytics = tomlconfig.google_analytics; } @@ -116,6 +122,14 @@ impl HtmlConfig { self.curly_quotes = curly_quotes; } + pub fn get_mathjax_support(&self) -> bool { + self.mathjax_support + } + + pub fn set_mathjax_support(&mut self, mathjax_support: bool) { + self.mathjax_support = mathjax_support; + } + pub fn get_google_analytics_id(&self) -> Option { self.google_analytics.clone() } diff --git a/src/config/jsonconfig.rs b/src/config/jsonconfig.rs index f41f8763..904787c0 100644 --- a/src/config/jsonconfig.rs +++ b/src/config/jsonconfig.rs @@ -1,5 +1,6 @@ extern crate serde_json; use std::path::PathBuf; +use errors::*; /// The JSON configuration is **deprecated** and will be removed in the near future. /// Please migrate to the TOML configuration. @@ -32,9 +33,9 @@ pub struct JsonConfig { /// assert_eq!(config.dest, Some(PathBuf::from("htmlbook"))); /// ``` impl JsonConfig { - pub fn from_json(input: &str) -> Result { + pub fn from_json(input: &str) -> Result { let config: JsonConfig = serde_json::from_str(input) - .map_err(|e| format!("Could not parse JSON: {}", e))?; + .chain_err(|| format!("Could not parse JSON"))?; return Ok(config); } diff --git a/src/config/tomlconfig.rs b/src/config/tomlconfig.rs index 90cfa4c6..e75203aa 100644 --- a/src/config/tomlconfig.rs +++ b/src/config/tomlconfig.rs @@ -1,5 +1,6 @@ extern crate toml; use std::path::PathBuf; +use errors::*; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct TomlConfig { @@ -25,6 +26,7 @@ pub struct TomlHtmlConfig { pub theme: Option, pub google_analytics: Option, pub curly_quotes: Option, + pub mathjax_support: Option, pub additional_css: Option>, pub additional_js: Option>, } @@ -43,9 +45,9 @@ pub struct TomlHtmlConfig { /// assert_eq!(config.output.unwrap().html.unwrap().destination, Some(PathBuf::from("htmlbook"))); /// ``` impl TomlConfig { - pub fn from_toml(input: &str) -> Result { + pub fn from_toml(input: &str) -> Result { let config: TomlConfig = toml::from_str(input) - .map_err(|e| format!("Could not parse TOML: {}", e))?; + .chain_err(|| "Could not parse TOML")?; return Ok(config); } diff --git a/src/lib.rs b/src/lib.rs index e91f18a4..aab5ca5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,10 +69,13 @@ //! //! Make sure to take a look at it. +#[macro_use] +extern crate error_chain; #[macro_use] extern crate serde_derive; extern crate serde; -#[macro_use] extern crate serde_json; +#[macro_use] +extern crate serde_json; extern crate handlebars; extern crate pulldown_cmark; @@ -90,3 +93,21 @@ pub mod utils; pub use book::MDBook; pub use book::BookItem; pub use renderer::Renderer; + +/// The error types used through out this crate. +pub mod errors { + error_chain!{ + foreign_links { + Io(::std::io::Error); + HandlebarsRender(::handlebars::RenderError); + HandlebarsTemplate(::handlebars::TemplateError); + Utf8(::std::string::FromUtf8Error); + } + + errors { + Subprocess(message: String, output: ::std::process::Output) { + description("A subprocess failed") + } + } + } +} \ No newline at end of file diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index fd69ee58..9c627553 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -4,12 +4,12 @@ use book::MDBook; use book::bookitem::{BookItem, Chapter}; use utils; use theme::{self, Theme}; +use errors::*; use regex::{Regex, Captures}; use std::ascii::AsciiExt; use std::path::{Path, PathBuf}; use std::fs::{self, File}; -use std::error::Error; use std::io::{self, Read}; use std::collections::BTreeMap; use std::collections::HashMap; @@ -28,7 +28,7 @@ impl HtmlHandlebars { } fn render_item(&self, item: &BookItem, mut ctx: RenderItemContext, print_content: &mut String) - -> Result<(), Box> { + -> Result<()> { // FIXME: This should be made DRY-er and rely less on mutable state match *item { BookItem::Chapter(_, ref ch) | @@ -88,7 +88,7 @@ impl HtmlHandlebars { } /// Create an index.html from the first element in SUMMARY.md - fn render_index(&self, book: &MDBook, ch: &Chapter, destination: &Path) -> Result<(), Box> { + fn render_index(&self, book: &MDBook, ch: &Chapter, destination: &Path) -> Result<()> { debug!("[*]: index.html"); let mut content = String::new(); @@ -129,7 +129,7 @@ impl HtmlHandlebars { rendered } - fn copy_static_files(&self, book: &MDBook, theme: &Theme) -> Result<(), Box> { + fn copy_static_files(&self, book: &MDBook, theme: &Theme) -> Result<()> { book.write_file("book.js", &theme.js)?; book.write_file("book.css", &theme.css)?; book.write_file("favicon.png", &theme.favicon)?; @@ -180,7 +180,7 @@ impl HtmlHandlebars { /// Helper function to write a file to the build directory, normalizing /// the path to be relative to the book root. - fn write_custom_file(&self, custom_file: &Path, book: &MDBook) -> Result<(), Box> { + fn write_custom_file(&self, custom_file: &Path, book: &MDBook) -> Result<()> { let mut data = Vec::new(); let mut f = File::open(custom_file)?; f.read_to_end(&mut data)?; @@ -216,7 +216,7 @@ impl HtmlHandlebars { /// Copy across any additional CSS and JavaScript files which the book /// has been configured to use. - fn copy_additional_css_and_js(&self, book: &MDBook) -> Result<(), Box> { + fn copy_additional_css_and_js(&self, book: &MDBook) -> Result<()> { let custom_files = book.get_additional_css().iter().chain( book.get_additional_js() .iter(), @@ -232,7 +232,7 @@ impl HtmlHandlebars { impl Renderer for HtmlHandlebars { - fn render(&self, book: &MDBook) -> Result<(), Box> { + fn render(&self, book: &MDBook) -> Result<()> { debug!("[fn]: render"); let mut handlebars = Handlebars::new(); @@ -258,9 +258,7 @@ impl Renderer for HtmlHandlebars { debug!("[*]: Check if destination directory exists"); if fs::create_dir_all(&destination).is_err() { - return Err(Box::new( - io::Error::new(io::ErrorKind::Other, "Unexpected error when constructing destination path"), - )); + bail!("Unexpected error when constructing destination path"); } for (i, item) in book.iter().enumerate() { @@ -301,7 +299,7 @@ impl Renderer for HtmlHandlebars { } } -fn make_data(book: &MDBook) -> Result, Box> { +fn make_data(book: &MDBook) -> Result> { debug!("[fn]: make_data"); let mut data = serde_json::Map::new(); @@ -318,6 +316,10 @@ fn make_data(book: &MDBook) -> Result data.insert("google_analytics".to_owned(), json!(ga)); } + if book.get_mathjax_support() { + data.insert("mathjax_support".to_owned(), json!(true)); + } + // Add check to see if there is an additional style if book.has_additional_css() { let mut css = Vec::new(); diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index e76ffb48..fe32b387 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,8 +2,8 @@ pub use self::html_handlebars::HtmlHandlebars; mod html_handlebars; -use std::error::Error; +use errors::*; pub trait Renderer { - fn render(&self, book: &::book::MDBook) -> Result<(), Box>; + fn render(&self, book: &::book::MDBook) -> Result<()>; } diff --git a/src/theme/index.hbs b/src/theme/index.hbs index 467d2fe6..769d1caa 100644 --- a/src/theme/index.hbs +++ b/src/theme/index.hbs @@ -27,8 +27,10 @@ {{/each}} + {{#if mathjax_support}} + {{/if}} diff --git a/src/utils/fs.rs b/src/utils/fs.rs index 43e934c0..da45611d 100644 --- a/src/utils/fs.rs +++ b/src/utils/fs.rs @@ -1,16 +1,16 @@ use std::path::{Path, PathBuf, Component}; -use std::error::Error; -use std::io::{self, Read}; +use errors::*; +use std::io::Read; use std::fs::{self, File}; /// Takes a path to a file and try to read the file into a String -pub fn file_to_string(path: &Path) -> Result> { +pub fn file_to_string(path: &Path) -> Result { let mut file = match File::open(path) { Ok(f) => f, Err(e) => { debug!("[*]: Failed to open {:?}", path); - return Err(Box::new(e)); + bail!(e); }, }; @@ -18,7 +18,7 @@ pub fn file_to_string(path: &Path) -> Result> { if let Err(e) = file.read_to_string(&mut content) { debug!("[*]: Failed to read {:?}", path); - return Err(Box::new(e)); + bail!(e); } Ok(content) @@ -72,7 +72,7 @@ pub fn path_to_root>(path: P) -> String { /// 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) -> io::Result { +pub fn create_file(path: &Path) -> Result { debug!("[fn]: create_file"); // Construct path @@ -83,12 +83,12 @@ pub fn create_file(path: &Path) -> io::Result { } debug!("[*]: Create file: {:?}", path); - File::create(path) + File::create(path).map_err(|e| e.into()) } /// Removes all the content of a directory but not the directory itself -pub fn remove_dir_content(dir: &Path) -> Result<(), Box> { +pub fn remove_dir_content(dir: &Path) -> Result<()> { for item in fs::read_dir(dir)? { if let Ok(item) = item { let item = item.path(); @@ -108,7 +108,7 @@ pub fn remove_dir_content(dir: &Path) -> Result<(), Box> { /// with the extensions given in the `ext_blacklist` array pub fn copy_files_except_ext(from: &Path, to: &Path, recursive: bool, ext_blacklist: &[&str]) - -> Result<(), Box> { + -> Result<()> { debug!("[fn] copy_files_except_ext"); // Check that from and to are different if from == to { diff --git a/tests/config.rs b/tests/config.rs index 9fd95e60..fd9d86cc 100644 --- a/tests/config.rs +++ b/tests/config.rs @@ -15,7 +15,8 @@ fn do_not_overwrite_unspecified_config_values() { let book = MDBook::new(dir.path()) .with_source("bar") - .with_destination("baz"); + .with_destination("baz") + .with_mathjax_support(true); assert_eq!(book.get_root(), dir.path()); assert_eq!(book.get_source(), dir.path().join("bar")); @@ -27,6 +28,7 @@ fn do_not_overwrite_unspecified_config_values() { assert_eq!(book.get_root(), dir.path()); assert_eq!(book.get_source(), dir.path().join("bar")); assert_eq!(book.get_destination().unwrap(), dir.path().join("baz")); + assert_eq!(book.get_mathjax_support(), true); // Try with a partial config file let file_path = dir.path().join("book.toml"); @@ -39,5 +41,6 @@ fn do_not_overwrite_unspecified_config_values() { assert_eq!(book.get_root(), dir.path()); assert_eq!(book.get_source(), dir.path().join("barbaz")); assert_eq!(book.get_destination().unwrap(), dir.path().join("baz")); + assert_eq!(book.get_mathjax_support(), true); } diff --git a/tests/tomlconfig.rs b/tests/tomlconfig.rs index 0ff62ada..a751b359 100644 --- a/tests/tomlconfig.rs +++ b/tests/tomlconfig.rs @@ -101,6 +101,20 @@ fn from_toml_output_html_curly_quotes() { assert_eq!(htmlconfig.get_curly_quotes(), true); } +// Tests that the `output.html.mathjax-support` key is correctly parsed in the TOML config +#[test] +fn from_toml_output_html_mathjax_support() { + let toml = r#"[output.html] + mathjax-support = true"#; + + let parsed = TomlConfig::from_toml(&toml).expect("This should parse"); + let config = BookConfig::from_tomlconfig("root", parsed); + + let htmlconfig = config.get_html_config().expect("There should be an HtmlConfig"); + + assert_eq!(htmlconfig.get_mathjax_support(), true); +} + // Tests that the `output.html.google-analytics` key is correctly parsed in the TOML config #[test] fn from_toml_output_html_google_analytics() {