From 70383d0a256e3875d757af6bfae1a0b16dfa5646 Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Fri, 19 May 2017 00:56:37 +0200 Subject: [PATCH] New config structs supports json again (the old style) for a little deprecation period --- src/book/bookconfig_test.rs | 371 ------------------- src/book/mod.rs | 14 +- src/config/bookconfig.rs | 74 ++-- src/config/htmlconfig.rs | 4 + src/config/jsonconfig.rs | 43 +++ src/config/mod.rs | 2 + src/config/tomlconfig.rs | 1 + src/lib.rs | 11 +- src/renderer/html_handlebars/hbs_renderer.rs | 11 +- src/theme/mod.rs | 8 +- tests/jsonconfig.rs | 87 +++++ tests/{config.rs => tomlconfig.rs} | 16 +- 12 files changed, 225 insertions(+), 417 deletions(-) delete mode 100644 src/book/bookconfig_test.rs create mode 100644 src/config/jsonconfig.rs create mode 100644 tests/jsonconfig.rs rename tests/{config.rs => tomlconfig.rs} (82%) diff --git a/src/book/bookconfig_test.rs b/src/book/bookconfig_test.rs deleted file mode 100644 index b3a24385..00000000 --- a/src/book/bookconfig_test.rs +++ /dev/null @@ -1,371 +0,0 @@ -#![cfg(test)] - -use std::path::Path; -use serde_json; -use book::bookconfig::*; - -#[test] -fn it_parses_json_config() { - let text = r#" -{ - "title": "mdBook Documentation", - "description": "Create book from markdown files. Like Gitbook but implemented in Rust", - "author": "Mathieu David" -}"#; - - // TODO don't require path argument, take pwd - let mut config = BookConfig::new(Path::new(".")); - - config.parse_from_json_string(&text.to_string()); - - let mut expected = BookConfig::new(Path::new(".")); - expected.title = "mdBook Documentation".to_string(); - expected.author = "Mathieu David".to_string(); - expected.description = "Create book from markdown files. Like Gitbook but implemented in Rust".to_string(); - - assert_eq!(format!("{:#?}", config), format!("{:#?}", expected)); -} - -#[test] -fn it_parses_toml_config() { - let text = r#" -title = "mdBook Documentation" -description = "Create book from markdown files. Like Gitbook but implemented in Rust" -author = "Mathieu David" -"#; - - // TODO don't require path argument, take pwd - let mut config = BookConfig::new(Path::new(".")); - - config.parse_from_toml_string(&text.to_string()); - - let mut expected = BookConfig::new(Path::new(".")); - expected.title = "mdBook Documentation".to_string(); - expected.author = "Mathieu David".to_string(); - expected.description = "Create book from markdown files. Like Gitbook but implemented in Rust".to_string(); - - assert_eq!(format!("{:#?}", config), format!("{:#?}", expected)); -} - -#[test] -fn it_parses_json_nested_array_to_toml() { - - // Example from: - // toml-0.2.1/tests/valid/arrays-nested.json - - let text = r#" -{ - "nest": { - "type": "array", - "value": [ - {"type": "array", "value": [ - {"type": "string", "value": "a"} - ]}, - {"type": "array", "value": [ - {"type": "string", "value": "b"} - ]} - ] - } -}"#; - - let c: serde_json::Value = serde_json::from_str(&text).unwrap(); - - let result = json_object_to_btreemap(&c.as_object().unwrap()); - - let expected = r#"{ - "nest": Table( - { - "type": String( - "array" - ), - "value": Array( - [ - Table( - { - "type": String( - "array" - ), - "value": Array( - [ - Table( - { - "type": String( - "string" - ), - "value": String( - "a" - ) - } - ) - ] - ) - } - ), - Table( - { - "type": String( - "array" - ), - "value": Array( - [ - Table( - { - "type": String( - "string" - ), - "value": String( - "b" - ) - } - ) - ] - ) - } - ) - ] - ) - } - ) -}"#; - - assert_eq!(format!("{:#?}", result), expected); -} - - -#[test] -fn it_parses_json_arrays_to_toml() { - - // Example from: - // toml-0.2.1/tests/valid/arrays.json - - let text = r#" -{ - "ints": { - "type": "array", - "value": [ - {"type": "integer", "value": "1"}, - {"type": "integer", "value": "2"}, - {"type": "integer", "value": "3"} - ] - }, - "floats": { - "type": "array", - "value": [ - {"type": "float", "value": "1.1"}, - {"type": "float", "value": "2.1"}, - {"type": "float", "value": "3.1"} - ] - }, - "strings": { - "type": "array", - "value": [ - {"type": "string", "value": "a"}, - {"type": "string", "value": "b"}, - {"type": "string", "value": "c"} - ] - }, - "dates": { - "type": "array", - "value": [ - {"type": "datetime", "value": "1987-07-05T17:45:00Z"}, - {"type": "datetime", "value": "1979-05-27T07:32:00Z"}, - {"type": "datetime", "value": "2006-06-01T11:00:00Z"} - ] - } -}"#; - - let c: serde_json::Value = serde_json::from_str(&text).unwrap(); - - let result = json_object_to_btreemap(&c.as_object().unwrap()); - - let expected = r#"{ - "dates": Table( - { - "type": String( - "array" - ), - "value": Array( - [ - Table( - { - "type": String( - "datetime" - ), - "value": String( - "1987-07-05T17:45:00Z" - ) - } - ), - Table( - { - "type": String( - "datetime" - ), - "value": String( - "1979-05-27T07:32:00Z" - ) - } - ), - Table( - { - "type": String( - "datetime" - ), - "value": String( - "2006-06-01T11:00:00Z" - ) - } - ) - ] - ) - } - ), - "floats": Table( - { - "type": String( - "array" - ), - "value": Array( - [ - Table( - { - "type": String( - "float" - ), - "value": String( - "1.1" - ) - } - ), - Table( - { - "type": String( - "float" - ), - "value": String( - "2.1" - ) - } - ), - Table( - { - "type": String( - "float" - ), - "value": String( - "3.1" - ) - } - ) - ] - ) - } - ), - "ints": Table( - { - "type": String( - "array" - ), - "value": Array( - [ - Table( - { - "type": String( - "integer" - ), - "value": String( - "1" - ) - } - ), - Table( - { - "type": String( - "integer" - ), - "value": String( - "2" - ) - } - ), - Table( - { - "type": String( - "integer" - ), - "value": String( - "3" - ) - } - ) - ] - ) - } - ), - "strings": Table( - { - "type": String( - "array" - ), - "value": Array( - [ - Table( - { - "type": String( - "string" - ), - "value": String( - "a" - ) - } - ), - Table( - { - "type": String( - "string" - ), - "value": String( - "b" - ) - } - ), - Table( - { - "type": String( - "string" - ), - "value": String( - "c" - ) - } - ) - ] - ) - } - ) -}"#; - - assert_eq!(format!("{:#?}", result), expected); -} - -#[test] -fn it_fetches_google_analytics_from_toml() { - let text = r#" -title = "mdBook Documentation" -description = "Create book from markdown files. Like Gitbook but implemented in Rust" -author = "Mathieu David" -google_analytics_id = "123456" -"#; - - let mut config = BookConfig::new(Path::new(".")); - - config.parse_from_toml_string(&text.to_string()); - - let mut expected = BookConfig::new(Path::new(".")); - expected.title = "mdBook Documentation".to_string(); - expected.author = "Mathieu David".to_string(); - expected.description = "Create book from markdown files. Like Gitbook but implemented in Rust".to_string(); - expected.google_analytics = Some("123456".to_string()); - - assert_eq!(format!("{:#?}", config), format!("{:#?}", expected)); -} diff --git a/src/book/mod.rs b/src/book/mod.rs index c23687d4..9125dc68 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -1,7 +1,4 @@ pub mod bookitem; -pub mod bookconfig; - -pub mod bookconfig_test; pub use self::bookitem::{BookItem, BookItems}; @@ -18,6 +15,7 @@ use renderer::{Renderer, HtmlHandlebars}; use config::{BookConfig, HtmlConfig}; use config::tomlconfig::TomlConfig; +use config::jsonconfig::JsonConfig; pub struct MDBook { @@ -45,7 +43,7 @@ impl MDBook { /// # use mdbook::MDBook; /// # use std::path::Path; /// # fn main() { - /// let book = MDBook::new("root_dir"); + /// let book = MDBook::new(Path::new("root_dir")); /// # } /// ``` /// @@ -331,7 +329,13 @@ impl MDBook { let parsed_config = TomlConfig::from_toml(&content)?; self.config.fill_from_tomlconfig(parsed_config); } else if json.exists() { - unimplemented!(); + 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); } Ok(self) diff --git a/src/config/bookconfig.rs b/src/config/bookconfig.rs index 9f445b2b..94efe9c5 100644 --- a/src/config/bookconfig.rs +++ b/src/config/bookconfig.rs @@ -2,6 +2,7 @@ use std::path::{PathBuf, Path}; use super::HtmlConfig; use super::tomlconfig::TomlConfig; +use super::jsonconfig::JsonConfig; /// Configuration struct containing all the configuration options available in mdBook. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -80,32 +81,7 @@ impl BookConfig { pub fn from_tomlconfig>(root: T, tomlconfig: TomlConfig) -> Self { let root = root.into(); let mut config = BookConfig::new(&root); - - if let Some(s) = tomlconfig.source { - config.set_source(s); - } - - if let Some(t) = tomlconfig.title { - config.set_title(t); - } - - if let Some(d) = tomlconfig.description { - config.set_description(d); - } - - if let Some(a) = tomlconfig.authors { - config.set_authors(a); - } - - if let Some(a) = tomlconfig.author { - config.set_authors(vec![a]); - } - - if let Some(tomlhtmlconfig) = tomlconfig.output.and_then(|o| o.html) { - let mut htmlconfig = config.get_mut_html_config().expect("We just created a new config and it creates a default HtmlConfig"); - htmlconfig.fill_from_tomlconfig(&root, tomlhtmlconfig); - } - + config.fill_from_tomlconfig(tomlconfig); config } @@ -141,6 +117,52 @@ impl BookConfig { self } + /// The JSON configuration file is **deprecated** and should not be used anymore. + /// Please, migrate to the TOML configuration file. + pub fn from_jsonconfig>(root: T, jsonconfig: JsonConfig) -> Self { + let root = root.into(); + let mut config = BookConfig::new(&root); + config.fill_from_jsonconfig(jsonconfig); + config + } + + /// The JSON configuration file is **deprecated** and should not be used anymore. + /// Please, migrate to the TOML configuration file. + pub fn fill_from_jsonconfig(&mut self, jsonconfig: JsonConfig) -> &mut Self { + + if let Some(s) = jsonconfig.src { + self.set_source(s); + } + + if let Some(t) = jsonconfig.title { + self.set_title(t); + } + + if let Some(d) = jsonconfig.description { + self.set_description(d); + } + + if let Some(a) = jsonconfig.author { + self.set_authors(vec![a]); + } + + if let Some(d) = jsonconfig.dest { + let root = self.get_root().to_owned(); + if let Some(htmlconfig) = self.get_mut_html_config() { + htmlconfig.set_destination(&root, &d); + } + } + + if let Some(d) = jsonconfig.theme_path { + let root = self.get_root().to_owned(); + if let Some(htmlconfig) = self.get_mut_html_config() { + htmlconfig.set_theme(&root, &d); + } + } + + self + } + pub fn set_root>(&mut self, root: T) -> &mut Self { self.root = root.into(); self diff --git a/src/config/htmlconfig.rs b/src/config/htmlconfig.rs index 93bbc522..030782d1 100644 --- a/src/config/htmlconfig.rs +++ b/src/config/htmlconfig.rs @@ -85,4 +85,8 @@ impl HtmlConfig { self } + + pub fn get_google_analytics_id(&self) -> Option { + self.google_analytics.clone() + } } diff --git a/src/config/jsonconfig.rs b/src/config/jsonconfig.rs new file mode 100644 index 00000000..9d1e2f3f --- /dev/null +++ b/src/config/jsonconfig.rs @@ -0,0 +1,43 @@ +extern crate serde_json; +use std::path::PathBuf; + +/// The JSON configuration is **deprecated** and will be removed in the near future. +/// Please migrate to the TOML configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct JsonConfig { + pub src: Option, + pub dest: Option, + + pub title: Option, + pub author: Option, + pub description: Option, + + pub theme_path: Option, + pub google_analytics: Option, +} + + +/// Returns a JsonConfig from a JSON string +/// +/// ``` +/// # use mdbook::config::jsonconfig::JsonConfig; +/// # use std::path::PathBuf; +/// let json = r#"{ +/// "title": "Some title", +/// "dest": "htmlbook" +/// }"#; +/// +/// let config = JsonConfig::from_json(&json).expect("Should parse correctly"); +/// assert_eq!(config.title, Some(String::from("Some title"))); +/// assert_eq!(config.dest, Some(PathBuf::from("htmlbook"))); +/// ``` +impl JsonConfig { + pub fn from_json(input: &str) -> Result { + let config: JsonConfig = serde_json::from_str(input) + .map_err(|e| format!("Could not parse JSON: {}", e))?; + + return Ok(config); + } +} + + diff --git a/src/config/mod.rs b/src/config/mod.rs index ead9dcaa..90a8e2e4 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,9 @@ pub mod bookconfig; pub mod htmlconfig; pub mod tomlconfig; +pub mod jsonconfig; // Re-export the config structs pub use self::bookconfig::BookConfig; pub use self::htmlconfig::HtmlConfig; +pub use self::tomlconfig::TomlConfig; diff --git a/src/config/tomlconfig.rs b/src/config/tomlconfig.rs index cb5dad1b..ebb43068 100644 --- a/src/config/tomlconfig.rs +++ b/src/config/tomlconfig.rs @@ -18,6 +18,7 @@ pub struct TomlOutputConfig { pub html: Option, } +#[serde(rename_all = "kebab-case")] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct TomlHtmlConfig { pub destination: Option, diff --git a/src/lib.rs b/src/lib.rs index ce6d4103..1b31b88d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,12 +24,13 @@ //! use std::path::Path; //! //! fn main() { -//! let mut book = MDBook::new(Path::new("my-book")) // Path to root -//! .set_src(Path::new("src")) // Path from root to source directory -//! .set_dest(Path::new("book")) // Path from root to output directory -//! .read_config(); // Parse book.json file for configuration +//! let mut book = MDBook::new(Path::new("my-book")) // Path to root +//! .with_source(Path::new("src")) // Path from root to source directory +//! .with_destination(Path::new("book")) // Path from root to output directory +//! .read_config() // Parse book.json file for configuration +//! .expect("I don't handle the error for the configuration file, but you should!"); //! -//! book.build().unwrap(); // Render the book +//! book.build().unwrap(); // Render the book //! } //! ``` //! diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 07dd8c4a..16f1ccd9 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -33,7 +33,7 @@ impl Renderer for HtmlHandlebars { let mut handlebars = Handlebars::new(); // Load theme - let theme = theme::Theme::new(book.get_theme_path().expect("If the HTML renderer is called, one would assume the HtmlConfig is set...")); + let theme = theme::Theme::new(book.get_theme_path()); // Register template debug!("[*]: Register handlebars template"); @@ -53,7 +53,7 @@ impl Renderer for HtmlHandlebars { // Check if dest directory exists debug!("[*]: Check if destination directory exists"); - if fs::create_dir_all(book.get_destination().expect("If the HTML renderer is called, one would assume the HtmlConfig is set...")).is_err() { + if fs::create_dir_all(book.get_destination().expect("If the HTML renderer is called, one would assume the HtmlConfig is set... (2)")).is_err() { return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Unexpected error when constructing destination path"))); } @@ -119,7 +119,7 @@ impl Renderer for HtmlHandlebars { let _source = File::open( book.get_destination() - .expect("If the HTML renderer is called, one would assume the HtmlConfig is set...") + .expect("If the HTML renderer is called, one would assume the HtmlConfig is set... (3)") .join(&ch.path.with_extension("html")) )?.read_to_string(&mut content); @@ -136,7 +136,7 @@ impl Renderer for HtmlHandlebars { info!("[*] Creating index.html from {:?} ✓", book.get_destination() - .expect("If the HTML renderer is called, one would assume the HtmlConfig is set...") + .expect("If the HTML renderer is called, one would assume the HtmlConfig is set... (4)") .join(&ch.path.with_extension("html")) ); index = false; @@ -191,10 +191,9 @@ impl Renderer for HtmlHandlebars { utils::fs::copy_files_except_ext( book.get_source(), book.get_destination() - .expect("If the HTML renderer is called, one would assume the HtmlConfig is set..."), true, &["md"] + .expect("If the HTML renderer is called, one would assume the HtmlConfig is set... (5)"), true, &["md"] )?; - Ok(()) } } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 3570cb24..0ac4ec42 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::PathBuf; use std::fs::File; use std::io::Read; @@ -42,7 +42,7 @@ pub struct Theme { } impl Theme { - pub fn new(src: &Path) -> Self { + pub fn new(src: Option<&PathBuf>) -> Self { // Default theme let mut theme = Theme { @@ -58,10 +58,12 @@ impl Theme { }; // Check if the given path exists - if !src.exists() || !src.is_dir() { + if src.is_none() || !src.unwrap().exists() || !src.unwrap().is_dir() { return theme; } + let src = src.unwrap(); + // Check for individual files if they exist // index.hbs diff --git a/tests/jsonconfig.rs b/tests/jsonconfig.rs new file mode 100644 index 00000000..ac82bcfd --- /dev/null +++ b/tests/jsonconfig.rs @@ -0,0 +1,87 @@ +extern crate mdbook; +use mdbook::config::BookConfig; +use mdbook::config::jsonconfig::JsonConfig; + +use std::path::PathBuf; + +// Tests that the `title` key is correcly parsed in the TOML config +#[test] +fn from_json_source() { + let json = r#"{ + "src": "source" + }"#; + + let parsed = JsonConfig::from_json(&json).expect("This should parse"); + let config = BookConfig::from_jsonconfig("root", parsed); + + assert_eq!(config.get_source(), PathBuf::from("root/source")); +} + +// Tests that the `title` key is correcly parsed in the TOML config +#[test] +fn from_json_title() { + let json = r#"{ + "title": "Some title" + }"#; + + let parsed = JsonConfig::from_json(&json).expect("This should parse"); + let config = BookConfig::from_jsonconfig("root", parsed); + + assert_eq!(config.get_title(), "Some title"); +} + +// Tests that the `description` key is correcly parsed in the TOML config +#[test] +fn from_json_description() { + let json = r#"{ + "description": "This is a description" + }"#; + + let parsed = JsonConfig::from_json(&json).expect("This should parse"); + let config = BookConfig::from_jsonconfig("root", parsed); + + assert_eq!(config.get_description(), "This is a description"); +} + +// Tests that the `author` key is correcly parsed in the TOML config +#[test] +fn from_json_author() { + let json = r#"{ + "author": "John Doe" + }"#; + + let parsed = JsonConfig::from_json(&json).expect("This should parse"); + let config = BookConfig::from_jsonconfig("root", parsed); + + assert_eq!(config.get_authors(), &[String::from("John Doe")]); +} + +// Tests that the `output.html.destination` key is correcly parsed in the TOML config +#[test] +fn from_json_destination() { + let json = r#"{ + "dest": "htmlbook" + }"#; + + let parsed = JsonConfig::from_json(&json).expect("This should parse"); + let config = BookConfig::from_jsonconfig("root", parsed); + + let htmlconfig = config.get_html_config().expect("There should be an HtmlConfig"); + + assert_eq!(htmlconfig.get_destination(), PathBuf::from("root/htmlbook")); +} + +// Tests that the `output.html.theme` key is correcly parsed in the TOML config +#[test] +fn from_json_output_html_theme() { + let json = r#"{ + "theme_path": "theme" + }"#; + + let parsed = JsonConfig::from_json(&json).expect("This should parse"); + let config = BookConfig::from_jsonconfig("root", parsed); + + let htmlconfig = config.get_html_config().expect("There should be an HtmlConfig"); + + assert_eq!(htmlconfig.get_theme().expect("the theme key was provided"), &PathBuf::from("root/theme")); +} \ No newline at end of file diff --git a/tests/config.rs b/tests/tomlconfig.rs similarity index 82% rename from tests/config.rs rename to tests/tomlconfig.rs index 3f6e3143..73e472a3 100644 --- a/tests/config.rs +++ b/tests/tomlconfig.rs @@ -84,5 +84,19 @@ fn from_toml_output_html_theme() { let htmlconfig = config.get_html_config().expect("There should be an HtmlConfig"); - assert_eq!(htmlconfig.get_theme().expect("the theme key was provided"), &PathBuf::from("root/src/theme")); + assert_eq!(htmlconfig.get_theme().expect("the theme key was provided"), &PathBuf::from("root/theme")); +} + +// Tests that the `output.html.google-analytics` key is correcly parsed in the TOML config +#[test] +fn from_toml_output_html_google_analytics() { + let toml = r#"[output.html] + google-analytics = "123456""#; + + 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_google_analytics_id().expect("the google-analytics key was provided"), String::from("123456")); } \ No newline at end of file