From 6059883229b499e45a6de187ff6a7f4fd77685af Mon Sep 17 00:00:00 2001 From: Michael Bryan Date: Sat, 30 Sep 2017 20:11:24 +0800 Subject: [PATCH 01/16] Added some basic configuration objects --- src/config/mod.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 99 insertions(+) diff --git a/src/config/mod.rs b/src/config/mod.rs index 412bcc54a2..be74b7fc5a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,11 @@ +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; +use std::fs::File; +use std::io::Read; +use toml::{self, Value}; + +use errors::*; + pub mod bookconfig; pub mod htmlconfig; pub mod playpenconfig; @@ -9,3 +17,93 @@ pub use self::bookconfig::BookConfig; pub use self::htmlconfig::HtmlConfig; pub use self::playpenconfig::PlaypenConfig; pub use self::tomlconfig::TomlConfig; + + +/// The overall configuration object for MDBook. +#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct Config { + /// Metadata about the book. + pub book: BookConfig_, + /// Arbitrary information which renderers can use during the rendering + /// stage. + pub output: BTreeMap, + /// Information for use by preprocessors. + pub preprocess: BTreeMap, + /// Information for use by postprocessors. + pub postprocess: BTreeMap, +} + +impl Config { + /// Load a `Config` from some string. + pub fn from_str(src: &str) -> Result { + toml::from_str(src).chain_err(|| Error::from("Invalid configuration file")) + } + + /// Load the configuration file from disk. + pub fn from_disk>(config_file: P) -> Result { + let mut buffer = String::new(); + File::open(config_file) + .chain_err(|| "Unable to open the configuration file")? + .read_to_string(&mut buffer) + .chain_err(|| "Couldn't read the file")?; + + Config::from_str(&buffer) + } + + /// Convenience method for getting the html renderer's configuration. + /// + /// # Note + /// + /// This is for compatibility only. It will be removed completely once the + /// rendering and plugin system is established. + pub fn html_config(&self) -> Option { + self.output + .get("html") + .and_then(|value| HtmlConfig_::from_toml(value).ok()) + } +} + + +/// Configuration options which are specific to the book and required for +/// loading it from disk. +#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "kebab-case")] +pub struct BookConfig_ { + /// The book's title. + pub title: Option, + /// The book's authors. + pub authors: Vec, + /// An optional description for the book. + pub description: Option, + /// Location of the book source, relative to the book's root directory. + pub src: PathBuf, + /// Where to put built artefacts, relative to the book's root directory. + pub build_dir: PathBuf, + /// Does this book support more than one language? + pub multilingual: bool, +} + +#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(default, rename_all = "kebab-case")] +pub struct HtmlConfig_ { + pub theme: Option, + pub curly_quotes: bool, + pub mathjax_support: bool, + pub google_analytics: Option, + pub additional_css: Vec, + pub additional_js: Vec, + pub playpen: Playpen, +} + +impl HtmlConfig_ { + pub fn from_toml(value: &Value) -> Result { + value + .clone() + .try_into() + .chain_err(|| "Unable to deserialize the HTML config") + } +} + +#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct Playpen; diff --git a/src/lib.rs b/src/lib.rs index cec4617d71..ad5cdc08af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,6 +86,7 @@ extern crate serde_derive; #[macro_use] extern crate serde_json; extern crate tempdir; +extern crate toml; mod parse; mod preprocess; From 1d22a9a040901e215c4fab73675992defcd91bbc Mon Sep 17 00:00:00 2001 From: Michael Bryan Date: Sat, 30 Sep 2017 20:34:27 +0800 Subject: [PATCH 02/16] Added some basic deserializing tests and helpers --- src/config/mod.rs | 134 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 15 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index be74b7fc5a..14662095ae 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use std::fs::File; use std::io::Read; use toml::{self, Value}; +use serde::Deserialize; use errors::*; @@ -20,7 +21,7 @@ pub use self::tomlconfig::TomlConfig; /// The overall configuration object for MDBook. -#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(default)] pub struct Config { /// Metadata about the book. @@ -58,16 +59,42 @@ impl Config { /// This is for compatibility only. It will be removed completely once the /// rendering and plugin system is established. pub fn html_config(&self) -> Option { - self.output - .get("html") - .and_then(|value| HtmlConfig_::from_toml(value).ok()) + self.try_get_output("html").ok() + } + + /// Try to get an output and deserialize it as a `T`. + pub fn try_get_output<'de, T: Deserialize<'de>, S: AsRef>(&self, name: S) -> Result { + get_deserialized(name, &self.output) + } + + /// Try to get the configuration for a preprocessor, deserializing it as a + /// `T`. + pub fn try_get_preprocessor<'de, T: Deserialize<'de>, S: AsRef>(&self, name: S) -> Result { + get_deserialized(name, &self.preprocess) + } + + /// Try to get the configuration for a postprocessor, deserializing it as a + /// `T`. + pub fn try_get_postprocessor<'de, T: Deserialize<'de>, S: AsRef>(&self, name: S) -> Result { + get_deserialized(name, &self.postprocess) + } +} + +/// Convenience function to load a value from some table then deserialize it. +fn get_deserialized<'de, T: Deserialize<'de>, S: AsRef>(name: S, table: &BTreeMap) -> Result { + match table.get(name.as_ref()) { + Some(output) => output + .clone() + .try_into() + .chain_err(|| "Couldn't deserialize the value"), + None => bail!("Key Not Found"), } } /// Configuration options which are specific to the book and required for /// loading it from disk. -#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] pub struct BookConfig_ { /// The book's title. @@ -84,7 +111,20 @@ pub struct BookConfig_ { pub multilingual: bool, } -#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] +impl Default for BookConfig_ { + fn default() -> BookConfig_ { + BookConfig_ { + title: None, + authors: Vec::new(), + description: None, + src: PathBuf::from("src"), + build_dir: PathBuf::from("build"), + multilingual: false, + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] pub struct HtmlConfig_ { pub theme: Option, @@ -96,14 +136,78 @@ pub struct HtmlConfig_ { pub playpen: Playpen, } -impl HtmlConfig_ { - pub fn from_toml(value: &Value) -> Result { - value - .clone() - .try_into() - .chain_err(|| "Unable to deserialize the HTML config") +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +pub struct Playpen; + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn load_a_complex_config_file() { + let src = r#" + [book] + title = "Some Book" + authors = ["Michael-F-Bryan "] + description = "A completely useless book" + multilingual = true + src = "source" + build-dir = "outputs" + + [output.html] + curly-quotes = true + google-analytics = "123456" + additional-css = ["./foo/bar/baz.css"] + "#; + + let book_should_be = BookConfig_ { + title: Some(String::from("Some Book")), + authors: vec![String::from("Michael-F-Bryan ")], + description: Some(String::from("A completely useless book")), + multilingual: true, + src: PathBuf::from("source"), + build_dir: PathBuf::from("outputs"), + ..Default::default() + }; + let html_should_be = HtmlConfig_ { + curly_quotes: true, + google_analytics: Some(String::from("123456")), + additional_css: vec![PathBuf::from("./foo/bar/baz.css")], + ..Default::default() + }; + + let got = Config::from_str(src).unwrap(); + + assert_eq!(got.book, book_should_be); + assert_eq!(got.html_config().unwrap(), html_should_be); } -} -#[derive(Clone, Default, PartialEq, Serialize, Deserialize)] -pub struct Playpen; + #[test] + fn load_arbitrary_output_type() { + #[derive(Debug, Deserialize, PartialEq)] + struct RandomOutput { + foo: u32, + bar: String, + baz: Vec, + } + + let src = r#" + [output.random] + foo = 5 + bar = "Hello World" + baz = [true, true, false] + "#; + + let should_be = RandomOutput { + foo: 5, + bar: String::from("Hello World"), + baz: vec![true, true, false], + }; + + let cfg = Config::from_str(src).unwrap(); + let got: RandomOutput = cfg.try_get_output("random").unwrap(); + + assert_eq!(got, should_be); + } +} From 8d7970b32d8245de31b8d544373c6d8a5f8d8cc7 Mon Sep 17 00:00:00 2001 From: Michael Bryan Date: Sat, 30 Sep 2017 21:04:05 +0800 Subject: [PATCH 03/16] Changed to the new config types --- src/config/mod.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 14662095ae..585bd7f20e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,17 +7,17 @@ use serde::Deserialize; use errors::*; -pub mod bookconfig; -pub mod htmlconfig; -pub mod playpenconfig; -pub mod tomlconfig; -pub mod jsonconfig; +// pub mod bookconfig; +// pub mod htmlconfig; +// pub mod playpenconfig; +// pub mod tomlconfig; +// pub mod jsonconfig; // Re-export the config structs -pub use self::bookconfig::BookConfig; -pub use self::htmlconfig::HtmlConfig; -pub use self::playpenconfig::PlaypenConfig; -pub use self::tomlconfig::TomlConfig; +// pub use self::bookconfig::BookConfig; +// pub use self::htmlconfig::HtmlConfig; +// pub use self::playpenconfig::PlaypenConfig; +// pub use self::tomlconfig::TomlConfig; /// The overall configuration object for MDBook. @@ -25,7 +25,7 @@ pub use self::tomlconfig::TomlConfig; #[serde(default)] pub struct Config { /// Metadata about the book. - pub book: BookConfig_, + pub book: BookConfig, /// Arbitrary information which renderers can use during the rendering /// stage. pub output: BTreeMap, @@ -58,7 +58,7 @@ impl Config { /// /// This is for compatibility only. It will be removed completely once the /// rendering and plugin system is established. - pub fn html_config(&self) -> Option { + pub fn html_config(&self) -> Option { self.try_get_output("html").ok() } @@ -96,7 +96,7 @@ fn get_deserialized<'de, T: Deserialize<'de>, S: AsRef>(name: S, table: &BT /// loading it from disk. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] -pub struct BookConfig_ { +pub struct BookConfig { /// The book's title. pub title: Option, /// The book's authors. @@ -111,9 +111,9 @@ pub struct BookConfig_ { pub multilingual: bool, } -impl Default for BookConfig_ { - fn default() -> BookConfig_ { - BookConfig_ { +impl Default for BookConfig { + fn default() -> BookConfig { + BookConfig { title: None, authors: Vec::new(), description: None, @@ -126,7 +126,7 @@ impl Default for BookConfig_ { #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case")] -pub struct HtmlConfig_ { +pub struct HtmlConfig { pub theme: Option, pub curly_quotes: bool, pub mathjax_support: bool, From c056b5cbd0efd2a0e103ccf93aaec5a6d27f9b24 Mon Sep 17 00:00:00 2001 From: Michael Bryan Date: Sat, 30 Sep 2017 21:13:00 +0800 Subject: [PATCH 04/16] Removed old configs from MDBook --- src/book/mod.rs | 253 ++++++++++++++---------------------------------- 1 file changed, 70 insertions(+), 183 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index 82699a7a7a..920c5cda03 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -13,13 +13,11 @@ use renderer::{HtmlHandlebars, Renderer}; use preprocess; use errors::*; -use config::BookConfig; -use config::tomlconfig::TomlConfig; -use config::htmlconfig::HtmlConfig; -use config::jsonconfig::JsonConfig; +use config::Config; pub struct MDBook { - config: BookConfig, + pub root: PathBuf, + config: Config, pub content: Vec, renderer: Box, @@ -66,7 +64,8 @@ impl MDBook { } MDBook { - config: BookConfig::new(root), + root: root, + config: Config::default(), content: vec![], renderer: Box::new(HtmlHandlebars::new()), @@ -131,26 +130,26 @@ impl MDBook { pub fn init(&mut self) -> Result<()> { debug!("[fn]: init"); - if !self.config.get_root().exists() { - fs::create_dir_all(&self.config.get_root()).unwrap(); - info!("{:?} created", &self.config.get_root()); + if !self.root.exists() { + fs::create_dir_all(&self.root).unwrap(); + info!("{:?} created", self.root.display()); } { - if !self.get_destination().exists() { - debug!("[*]: {:?} does not exist, trying to create directory", - self.get_destination()); - fs::create_dir_all(self.get_destination())?; + let dest = &self.config.book.build_dir; + if !dest.exists() { + debug!("[*]: {} does not exist, trying to create directory", dest.display()); + fs::create_dir_all(dest)?; } - if !self.config.get_source().exists() { - debug!("[*]: {:?} does not exist, trying to create directory", - self.config.get_source()); - fs::create_dir_all(self.config.get_source())?; + let src = self.get_source(); + if !src.exists() { + debug!("[*]: {} does not exist, trying to create directory", src.display()); + fs::create_dir_all(src)?; } - let summary = self.config.get_source().join("SUMMARY.md"); + let summary = src.join("SUMMARY.md"); if !summary.exists() { // Summary does not exist, create it @@ -177,12 +176,13 @@ impl MDBook { BookItem::Chapter(_, ref ch) | BookItem::Affix(ref ch) => ch, }; if !ch.path.as_os_str().is_empty() { - let path = self.config.get_source().join(&ch.path); + let path = self.config.book.src.join(&ch.path); if !path.exists() { if !self.create_missing { - return Err(format!("'{}' referenced from SUMMARY.md does not exist.", - path.to_string_lossy()).into()); + return Err( + format!("'{}' referenced from SUMMARY.md does not exist.", path.to_string_lossy()).into(), + ); } debug!("[*]: {:?} does not exist, trying to create file", path); ::std::fs::create_dir_all(path.parent().unwrap())?; @@ -201,22 +201,22 @@ impl MDBook { pub fn create_gitignore(&self) { let gitignore = self.get_gitignore(); - let destination = self.config.get_html_config().get_destination(); + let destination = self.get_destination(); - // Check that the gitignore does not extist and - // that the destination path begins with the root path - // We assume tha if it does begin with the root path it is contained within. This assumption - // will not hold true for paths containing double dots to go back up - // e.g. `root/../destination` - if !gitignore.exists() && destination.starts_with(self.config.get_root()) { - let relative = destination.strip_prefix(self.config.get_root()) - .expect("Could not strip the root prefix, path is not \ - relative to root") - .to_str() - .expect("Could not convert to &str"); + // Check that the gitignore does not extist and that the destination path + // begins with the root path + // We assume tha if it does begin with the root path it is contained within. + // This assumption + // will not hold true for paths containing double dots to go back up e.g. + // `root/../destination` + if !gitignore.exists() && destination.starts_with(&self.root) { + let relative = destination + .strip_prefix(&self.root) + .expect("Could not strip the root prefix, path is not relative to root") + .to_str() + .expect("Could not convert to &str"); - debug!("[*]: {:?} does not exist, trying to create .gitignore", - gitignore); + debug!("[*]: {:?} does not exist, trying to create .gitignore", gitignore); let mut f = File::create(&gitignore).expect("Could not create file."); @@ -238,20 +238,21 @@ impl MDBook { self.init()?; // Clean output directory - utils::fs::remove_dir_content(self.config.get_html_config().get_destination())?; + utils::fs::remove_dir_content(&self.config.book.build_dir)?; self.renderer.render(self) } pub fn get_gitignore(&self) -> PathBuf { - self.config.get_root().join(".gitignore") + self.root.join(".gitignore") } pub fn copy_theme(&self) -> Result<()> { debug!("[fn]: copy_theme"); - let themedir = self.config.get_html_config().get_theme(); + let themedir = self.theme_dir(); + if !themedir.exists() { debug!("[*]: {:?} does not exist, trying to create directory", themedir); @@ -259,27 +260,27 @@ impl MDBook { } // index.hbs - let mut index = File::create(&themedir.join("index.hbs"))?; + let mut index = File::create(themedir.join("index.hbs"))?; index.write_all(theme::INDEX)?; // book.css - let mut css = File::create(&themedir.join("book.css"))?; + let mut css = File::create(themedir.join("book.css"))?; css.write_all(theme::CSS)?; // favicon.png - let mut favicon = File::create(&themedir.join("favicon.png"))?; + let mut favicon = File::create(themedir.join("favicon.png"))?; favicon.write_all(theme::FAVICON)?; // book.js - let mut js = File::create(&themedir.join("book.js"))?; + let mut js = File::create(themedir.join("book.js"))?; js.write_all(theme::JS)?; // highlight.css - let mut highlight_css = File::create(&themedir.join("highlight.css"))?; + let mut highlight_css = File::create(themedir.join("highlight.css"))?; highlight_css.write_all(theme::HIGHLIGHT_CSS)?; // highlight.js - let mut highlight_js = File::create(&themedir.join("highlight.js"))?; + let mut highlight_js = File::create(themedir.join("highlight.js"))?; highlight_js.write_all(theme::HIGHLIGHT_JS)?; Ok(()) @@ -298,25 +299,9 @@ impl MDBook { /// The root directory is the one specified when creating a new `MDBook` pub fn read_config(mut self) -> Result { - let toml = self.get_root().join("book.toml"); - let json = self.get_root().join("book.json"); - - if toml.exists() { - let mut file = File::open(toml)?; - let mut content = String::new(); - file.read_to_string(&mut content)?; - - let parsed_config = TomlConfig::from_toml(&content)?; - self.config.fill_from_tomlconfig(parsed_config); - } else if json.exists() { - warn!("The JSON configuration file is deprecated, please use the TOML configuration."); - let mut file = File::open(json)?; - let mut content = String::new(); - file.read_to_string(&mut content)?; - - let parsed_config = JsonConfig::from_json(&content)?; - self.config.fill_from_jsonconfig(parsed_config); - } + let config_path = self.root.join("book.toml"); + debug!("[*] Loading the config from {}", config_path.display()); + self.config = Config::from_disk(&config_path)?; Ok(self) } @@ -353,10 +338,11 @@ impl MDBook { pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> { // read in the chapters self.parse_summary().chain_err(|| "Couldn't parse summary")?; - let library_args: Vec<&str> = (0..library_paths.len()).map(|_| "-L") - .zip(library_paths.into_iter()) - .flat_map(|x| vec![x.0, x.1]) - .collect(); + let library_args: Vec<&str> = (0..library_paths.len()) + .map(|_| "-L") + .zip(library_paths.into_iter()) + .flat_map(|x| vec![x.0, x.1]) + .collect(); let temp_dir = TempDir::new("mdbook")?; for item in self.iter() { if let BookItem::Chapter(_, ref ch) = *item { @@ -369,7 +355,7 @@ impl MDBook { let content = preprocess::links::replace_all(&content, base)?; println!("[*]: Testing file: {:?}", path); - //write preprocessed file to tempdir + // write preprocessed file to tempdir let path = temp_dir.path().join(&ch.path); let mut tmpf = utils::fs::create_file(&path)?; tmpf.write_all(content.as_bytes())?; @@ -389,127 +375,28 @@ impl MDBook { Ok(()) } - pub fn get_root(&self) -> &Path { - self.config.get_root() - } - - - pub fn with_destination>(mut self, destination: T) -> Self { - let root = self.config.get_root().to_owned(); - self.config - .get_mut_html_config() - .set_destination(&root, &destination.into()); - self - } - - - pub fn get_destination(&self) -> &Path { - self.config.get_html_config().get_destination() - } - - pub fn with_source>(mut self, source: T) -> Self { - self.config.set_source(source); - self - } - - pub fn get_source(&self) -> &Path { - self.config.get_source() - } - - pub fn with_title>(mut self, title: T) -> Self { - self.config.set_title(title); - self - } - - pub fn get_title(&self) -> &str { - self.config.get_title() - } - - pub fn with_description>(mut self, description: T) -> Self { - self.config.set_description(description); - self - } - - pub fn get_description(&self) -> &str { - self.config.get_description() - } - - pub fn set_livereload(&mut self, livereload: String) -> &mut Self { - self.livereload = Some(livereload); - self - } - - pub fn unset_livereload(&mut self) -> &Self { - self.livereload = None; - self - } - - pub fn get_livereload(&self) -> Option<&String> { - self.livereload.as_ref() - } - - pub fn with_theme_path>(mut self, theme_path: T) -> Self { - let root = self.config.get_root().to_owned(); - self.config - .get_mut_html_config() - .set_theme(&root, &theme_path.into()); - self - } - - pub fn get_theme_path(&self) -> &Path { - self.config.get_html_config().get_theme() - } - - pub fn with_curly_quotes(mut self, curly_quotes: bool) -> Self { - self.config - .get_mut_html_config() - .set_curly_quotes(curly_quotes); - self - } - - pub fn get_curly_quotes(&self) -> bool { - self.config.get_html_config().get_curly_quotes() - } - - pub fn with_mathjax_support(mut self, mathjax_support: bool) -> Self { - self.config - .get_mut_html_config() - .set_mathjax_support(mathjax_support); - self - } - - pub fn get_mathjax_support(&self) -> bool { - self.config.get_html_config().get_mathjax_support() - } - - pub fn get_google_analytics_id(&self) -> Option { - self.config.get_html_config().get_google_analytics_id() - } - - pub fn has_additional_js(&self) -> bool { - self.config.get_html_config().has_additional_js() - } - - pub fn get_additional_js(&self) -> &[PathBuf] { - self.config.get_html_config().get_additional_js() + // Construct book + fn parse_summary(&mut self) -> Result<()> { + // When append becomes stable, use self.content.append() ... + let summary = self.get_source().join("SUMMARY.md"); + self.content = parse::construct_bookitems(&summary)?; + Ok(()) } - pub fn has_additional_css(&self) -> bool { - self.config.get_html_config().has_additional_css() + fn get_destination(&self) -> PathBuf { + self.root.join(&self.config.book.build_dir) } - pub fn get_additional_css(&self) -> &[PathBuf] { - self.config.get_html_config().get_additional_css() + fn get_source(&self) -> PathBuf { + self.root.join(&self.config.book.src) } - pub fn get_html_config(&self) -> &HtmlConfig { - self.config.get_html_config() - } + fn theme_dir(&self) -> PathBuf { + let relative_to_book = match self.config.html_config().and_then(|h| h.theme) { + Some(ref d) => d, + None => Path::new("theme"), + }; - // Construct book - 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(()) + self.root.join(relative_to_book) } } From b74c2c18efdbad302ef5c1365bc36dfff0f8b462 Mon Sep 17 00:00:00 2001 From: Michael Bryan Date: Sat, 30 Sep 2017 21:36:03 +0800 Subject: [PATCH 05/16] Removed all references to old the configuration from the html renderer --- src/book/mod.rs | 22 +++--- src/config/mod.rs | 5 +- src/renderer/html_handlebars/hbs_renderer.rs | 81 ++++++++++++-------- 3 files changed, 63 insertions(+), 45 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index 920c5cda03..edd268dcc0 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -17,12 +17,12 @@ use config::Config; pub struct MDBook { pub root: PathBuf, - config: Config, + pub config: Config, pub content: Vec, renderer: Box, - livereload: Option, + pub livereload: Option, /// Should `mdbook build` create files referenced from SUMMARY.md if they /// don't exist @@ -146,7 +146,7 @@ impl MDBook { let src = self.get_source(); if !src.exists() { debug!("[*]: {} does not exist, trying to create directory", src.display()); - fs::create_dir_all(src)?; + fs::create_dir_all(&src)?; } let summary = src.join("SUMMARY.md"); @@ -383,20 +383,18 @@ impl MDBook { Ok(()) } - fn get_destination(&self) -> PathBuf { + pub fn get_destination(&self) -> PathBuf { self.root.join(&self.config.book.build_dir) } - fn get_source(&self) -> PathBuf { + pub fn get_source(&self) -> PathBuf { self.root.join(&self.config.book.src) } - fn theme_dir(&self) -> PathBuf { - let relative_to_book = match self.config.html_config().and_then(|h| h.theme) { - Some(ref d) => d, - None => Path::new("theme"), - }; - - self.root.join(relative_to_book) + pub fn theme_dir(&self) -> PathBuf { + match self.config.html_config().and_then(|h| h.theme) { + Some(d) => self.root.join(d), + None => self.root.join("theme"), + } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 585bd7f20e..6d5bb97f2f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -137,7 +137,10 @@ pub struct HtmlConfig { } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] -pub struct Playpen; +pub struct Playpen { + pub editor: PathBuf, + pub editable: bool, +} #[cfg(test)] diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 20323eecf8..55be312270 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -3,9 +3,9 @@ use preprocess; use renderer::Renderer; use book::MDBook; use book::bookitem::{BookItem, Chapter}; -use config::PlaypenConfig; -use {theme, utils}; -use theme::{playpen_editor, Theme}; +use config::{Config, Playpen, HtmlConfig}; +use {utils, theme}; +use theme::{Theme, playpen_editor}; use errors::*; use regex::{Captures, Regex}; @@ -45,7 +45,7 @@ impl HtmlHandlebars { // Parse and expand links let content = preprocess::links::replace_all(&content, base)?; - let content = utils::render_markdown(&content, ctx.book.get_curly_quotes()); + let content = utils::render_markdown(&content, ctx.html_config.curly_quotes); print_content.push_str(&content); // Update the context with data for this file @@ -138,7 +138,7 @@ impl HtmlHandlebars { rendered } - fn copy_static_files(&self, book: &MDBook, theme: &Theme) -> Result<()> { + fn copy_static_files(&self, book: &MDBook, theme: &Theme, html_config: &HtmlConfig) -> Result<()> { book.write_file("book.js", &theme.js)?; book.write_file("book.css", &theme.css)?; book.write_file("favicon.png", &theme.favicon)?; @@ -163,12 +163,12 @@ impl HtmlHandlebars { book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)?; - let playpen_config = book.get_html_config().get_playpen_config(); + let playpen_config = &html_config.playpen; // Ace is a very large dependency, so only load it when requested - if playpen_config.is_editable() { + if playpen_config.editable { // Load the editor - let editor = playpen_editor::PlaypenEditor::new(playpen_config.get_editor()); + let editor = playpen_editor::PlaypenEditor::new(&playpen_config.editor); book.write_file("editor.js", &editor.js)?; book.write_file("ace.js", &editor.ace_js)?; book.write_file("mode-rust.js", &editor.mode_rust_js)?; @@ -186,7 +186,7 @@ impl HtmlHandlebars { let mut f = File::open(custom_file)?; f.read_to_end(&mut data)?; - let name = match custom_file.strip_prefix(book.get_root()) { + let name = match custom_file.strip_prefix(&book.root) { Ok(p) => p.to_str().expect("Could not convert to str"), Err(_) => { custom_file.file_name() @@ -224,9 +224,11 @@ 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<()> { - let custom_files = book.get_additional_css() + let html = book.config.html_config().unwrap_or_default(); + + let custom_files = html.additional_css .iter() - .chain(book.get_additional_js().iter()); + .chain(html.additional_js.iter()); for custom_file in custom_files { self.write_custom_file(custom_file, book)?; @@ -239,10 +241,17 @@ impl HtmlHandlebars { impl Renderer for HtmlHandlebars { fn render(&self, book: &MDBook) -> Result<()> { + let html_config = book.config.html_config().unwrap_or_default(); + debug!("[fn]: render"); let mut handlebars = Handlebars::new(); - let theme = theme::Theme::new(book.get_theme_path()); + let theme_dir = match html_config.theme { + Some(ref theme) => theme, + None => Path::new("theme"), + }; + + let theme = theme::Theme::new(theme_dir); debug!("[*]: Register handlebars template"); handlebars.register_template_string("index", String::from_utf8(theme.index.clone())?)?; @@ -250,12 +259,13 @@ impl Renderer for HtmlHandlebars { debug!("[*]: Register handlebars helpers"); self.register_hbs_helpers(&mut handlebars); - let mut data = make_data(book)?; + let mut data = make_data(book, &book.config)?; // Print version let mut print_content = String::new(); - let destination = book.get_destination(); + // TODO: The Renderer trait should really pass in where it wants us to build to... + let destination = book.root.join(&book.config.book.build_dir); debug!("[*]: Check if destination directory exists"); if fs::create_dir_all(&destination).is_err() { @@ -269,13 +279,16 @@ impl Renderer for HtmlHandlebars { destination: destination.to_path_buf(), data: data.clone(), is_index: i == 0, + html_config: html_config.clone(), }; self.render_item(item, ctx, &mut print_content)?; } // Print version self.configure_print_version(&mut data, &print_content); - data.insert("title".to_owned(), json!(book.get_title())); + if let Some(ref title) = book.config.book.title { + data.insert("title".to_owned(), json!(title)); + } // Render the handlebars template with the data debug!("[*]: Render template"); @@ -292,42 +305,44 @@ impl Renderer for HtmlHandlebars { // Copy static files (js, css, images, ...) debug!("[*] Copy static files"); - self.copy_static_files(book, &theme)?; + self.copy_static_files(book, &theme, &html_config)?; self.copy_additional_css_and_js(book)?; // Copy all remaining files - utils::fs::copy_files_except_ext(book.get_source(), destination, true, &["md"])?; + let src = book.get_source(); + utils::fs::copy_files_except_ext(&src, &destination, true, &["md"])?; Ok(()) } } -fn make_data(book: &MDBook) -> Result> { +fn make_data(book: &MDBook, config: &Config) -> Result> { debug!("[fn]: make_data"); + let html = config.html_config().unwrap_or_default(); let mut data = serde_json::Map::new(); data.insert("language".to_owned(), json!("en")); - data.insert("book_title".to_owned(), json!(book.get_title())); - data.insert("description".to_owned(), json!(book.get_description())); + data.insert("book_title".to_owned(), json!(config.book.title.clone().unwrap_or_default())); + data.insert("description".to_owned(), json!(config.book.description.clone().unwrap_or_default())); data.insert("favicon".to_owned(), json!("favicon.png")); - if let Some(livereload) = book.get_livereload() { + if let Some(ref livereload) = book.livereload { data.insert("livereload".to_owned(), json!(livereload)); } // Add google analytics tag - if let Some(ref ga) = book.get_google_analytics_id() { + if let Some(ref ga) = book.config.html_config().and_then(|html| html.google_analytics) { data.insert("google_analytics".to_owned(), json!(ga)); } - if book.get_mathjax_support() { + if html.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() { + if !html.additional_css.is_empty() { let mut css = Vec::new(); - for style in book.get_additional_css() { - match style.strip_prefix(book.get_root()) { + for style in &html.additional_css { + match style.strip_prefix(&book.root) { Ok(p) => css.push(p.to_str().expect("Could not convert to str")), Err(_) => { css.push(style.file_name() @@ -341,10 +356,10 @@ fn make_data(book: &MDBook) -> Result } // Add check to see if there is an additional script - if book.has_additional_js() { + if !html.additional_js.is_empty() { let mut js = Vec::new(); - for script in book.get_additional_js() { - match script.strip_prefix(book.get_root()) { + for script in &html.additional_js { + match script.strip_prefix(&book.root) { Ok(p) => js.push(p.to_str().expect("Could not convert to str")), Err(_) => { js.push(script.file_name() @@ -357,7 +372,7 @@ fn make_data(book: &MDBook) -> Result data.insert("additional_js".to_owned(), json!(js)); } - if book.get_html_config().get_playpen_config().is_editable() { + if html.playpen.editable { data.insert("playpens_editable".to_owned(), json!(true)); data.insert("editor_js".to_owned(), json!("editor.js")); data.insert("ace_js".to_owned(), json!("ace.js")); @@ -519,8 +534,9 @@ fn fix_code_blocks(html: &str) -> String { .into_owned() } -fn add_playpen_pre(html: &str, playpen_config: &PlaypenConfig) -> String { +fn add_playpen_pre(html: &str, playpen_config: &Playpen) -> String { let regex = Regex::new(r##"((?s)]?class="([^"]+)".*?>(.*?))"##).unwrap(); +<<<<<<< HEAD regex.replace_all(html, |caps: &Captures| { let text = &caps[1]; let classes = &caps[2]; @@ -530,7 +546,7 @@ fn add_playpen_pre(html: &str, playpen_config: &PlaypenConfig) -> String { classes.contains("mdbook-runnable") { // wrap the contents in an external pre block - if playpen_config.is_editable() && classes.contains("editable") || + if playpen_config.editable && classes.contains("editable") || text.contains("fn main") || text.contains("quick_main!") { format!("
{}
", text) @@ -583,6 +599,7 @@ struct RenderItemContext<'a> { destination: PathBuf, data: serde_json::Map, is_index: bool, + html_config: HtmlConfig, } pub fn normalize_path(path: &str) -> String { From ddb0834da80f184e50eecf446eae6d56b7e48195 Mon Sep 17 00:00:00 2001 From: Michael Bryan Date: Sat, 30 Sep 2017 21:44:25 +0800 Subject: [PATCH 06/16] Upgraded binaries to new configuration API --- src/bin/build.rs | 20 ++++++-------------- src/bin/init.rs | 2 +- src/bin/serve.rs | 20 +++++--------------- src/bin/watch.rs | 18 ++++-------------- 4 files changed, 16 insertions(+), 44 deletions(-) diff --git a/src/bin/build.rs b/src/bin/build.rs index 7ad127da29..f3b595e142 100644 --- a/src/bin/build.rs +++ b/src/bin/build.rs @@ -1,4 +1,5 @@ -use clap::{App, ArgMatches, SubCommand}; +use std::path::PathBuf; +use clap::{ArgMatches, SubCommand, App}; use mdbook::MDBook; use mdbook::errors::Result; use {get_book_dir, open}; @@ -15,10 +16,6 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { .arg_from_usage( "--no-create 'Will not create non-existent files linked from SUMMARY.md'", ) - .arg_from_usage( - "--curly-quotes 'Convert straight quotes to curly quotes, except for those \ - that occur in code blocks and code spans'", - ) .arg_from_usage( "[dir] 'A directory for your book{n}(Defaults to Current Directory \ when omitted)'", @@ -28,21 +25,16 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { // Build command implementation pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); - let book = MDBook::new(&book_dir).read_config()?; + let mut book = MDBook::new(&book_dir).read_config()?; - let mut book = match args.value_of("dest-dir") { - Some(dest_dir) => book.with_destination(dest_dir), - None => book, - }; + if let Some(dest_dir) = args.value_of("dest-dir") { + book.config.book.build_dir = PathBuf::from(dest_dir); + } if args.is_present("no-create") { book.create_missing = false; } - if args.is_present("curly-quotes") { - book = book.with_curly_quotes(true); - } - book.build()?; if args.is_present("open") { diff --git a/src/bin/init.rs b/src/bin/init.rs index 83431211de..83ac54be61 100644 --- a/src/bin/init.rs +++ b/src/bin/init.rs @@ -47,7 +47,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { } // Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root` - let is_dest_inside_root = book.get_destination().starts_with(book.get_root()); + let is_dest_inside_root = book.get_destination().starts_with(&book.root); if !args.is_present("force") && is_dest_inside_root { println!("\nDo you want a .gitignore to be created? (y/n)"); diff --git a/src/bin/serve.rs b/src/bin/serve.rs index c4f3a2222c..853fa237b1 100644 --- a/src/bin/serve.rs +++ b/src/bin/serve.rs @@ -3,7 +3,7 @@ extern crate staticfile; extern crate ws; use std; -use std::path::Path; +use std::path::{Path, PathBuf}; use self::iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response, Set}; use clap::{App, ArgMatches, SubCommand}; @@ -29,10 +29,6 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { "-d, --dest-dir=[dest-dir] 'The output directory for \ your book{n}(Defaults to ./book when omitted)'", ) - .arg_from_usage( - "--curly-quotes 'Convert straight quotes to curly quotes, except \ - for those that occur in code blocks and code spans'", - ) .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 \ @@ -53,15 +49,10 @@ pub fn execute(args: &ArgMatches) -> Result<()> { const RELOAD_COMMAND: &'static str = "reload"; let book_dir = get_book_dir(args); - let book = MDBook::new(&book_dir).read_config()?; - - let mut book = match args.value_of("dest-dir") { - Some(dest_dir) => book.with_destination(Path::new(dest_dir)), - None => book, - }; + let mut book = MDBook::new(&book_dir).read_config()?; - if args.is_present("curly-quotes") { - book = book.with_curly_quotes(true); + if let Some(dest_dir) = args.value_of("dest-dir") { + book.config.book.build_dir = PathBuf::from(dest_dir); } let port = args.value_of("port").unwrap_or("3000"); @@ -73,8 +64,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> { let address = format!("{}:{}", interface, port); let ws_address = format!("{}:{}", interface, ws_port); - book.set_livereload(format!( - r#" + book.livereload = Some(format!(r#"