From 170bf8b1eb22aacfbfff5b5f524cd61ee22fcd59 Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Thu, 18 May 2017 19:32:08 +0200 Subject: [PATCH] New configuration struct + tests #285 --- Cargo.toml | 1 + src/config/bookconfig.rs | 179 +++++++++++++++++++++++++++++++++++++++ src/config/htmlconfig.rs | 64 ++++++++++++++ src/config/mod.rs | 7 ++ src/config/tomlconfig.rs | 50 +++++++++++ src/lib.rs | 7 +- tests/config.rs | 88 +++++++++++++++++++ 7 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 src/config/bookconfig.rs create mode 100644 src/config/htmlconfig.rs create mode 100644 src/config/mod.rs create mode 100644 src/config/tomlconfig.rs create mode 100644 tests/config.rs diff --git a/Cargo.toml b/Cargo.toml index 97c2be12..098566da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ exclude = [ clap = "2.24" handlebars = "0.26" serde = "1.0" +serde_derive = "1.0" serde_json = "1.0" pulldown-cmark = "0.0.14" log = "0.3" diff --git a/src/config/bookconfig.rs b/src/config/bookconfig.rs new file mode 100644 index 00000000..2c2673a0 --- /dev/null +++ b/src/config/bookconfig.rs @@ -0,0 +1,179 @@ +use std::path::{PathBuf, Path}; + +use super::HtmlConfig; +use super::tomlconfig::TomlConfig; + +/// Configuration struct containing all the configuration options available in mdBook. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct BookConfig { + root: PathBuf, + source: PathBuf, + + title: String, + authors: Vec, + description: String, + + multilingual: bool, + indent_spaces: i32, + + html_config: Option, +} + +impl BookConfig { + /// Creates a new `BookConfig` struct with as root path the path given as parameter. + /// The source directory is `root/src` and the destination for the rendered book is `root/book`. + /// + /// ``` + /// # use std::path::PathBuf; + /// # use mdbook::config::{BookConfig, HtmlConfig}; + /// # + /// let root = PathBuf::from("directory/to/my/book"); + /// let config = BookConfig::new(&root); + /// + /// assert_eq!(config.get_root(), &root); + /// assert_eq!(config.get_source(), PathBuf::from("directory/to/my/book/src")); + /// assert_eq!(config.get_html_config(), Some(&HtmlConfig::new(PathBuf::from("directory/to/my/book")))); + /// ``` + pub fn new>(root: T) -> Self { + let root: PathBuf = root.into(); + let htmlconfig = HtmlConfig::new(&root); + + BookConfig { + root: root.clone(), + source: root.join("src"), + + title: String::new(), + authors: Vec::new(), + description: String::new(), + + multilingual: false, + indent_spaces: 4, + + html_config: Some(htmlconfig), + } + } + + /// Builder method to set the source directory + pub fn with_source>(mut self, source: T) -> Self { + self.source = source.into(); + self + } + + /// Builder method to set the book's title + pub fn with_title>(mut self, title: T) -> Self { + self.title = title.into(); + self + } + + /// Builder method to set the book's description + pub fn with_description>(mut self, description: T) -> Self { + self.description = description.into(); + self + } + + /// Builder method to set the book's authors + pub fn with_authors>>(mut self, authors: T) -> Self { + self.authors = authors.into(); + self + } + + 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 source = config.get_source().to_owned(); + 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, &source, tomlhtmlconfig); + } + + config + } + + pub fn set_root>(&mut self, root: T) -> &mut Self { + self.root = root.into(); + self + } + + pub fn get_root(&self) -> &Path { + &self.root + } + + pub fn set_source>(&mut self, source: T) -> &mut Self { + let mut source = source.into(); + + // If the source path is relative, start with the root path + if source.is_relative() { + source = self.root.join(source); + } + + self.source = source; + self + } + + pub fn get_source(&self) -> &Path { + &self.source + } + + pub fn set_title>(&mut self, title: T) -> &mut Self { + self.title = title.into(); + self + } + + pub fn get_title(&self) -> &str { + &self.title + } + + pub fn set_description>(&mut self, description: T) -> &mut Self { + self.description = description.into(); + self + } + + pub fn get_description(&self) -> &str { + &self.description + } + + pub fn set_authors>>(&mut self, authors: T) -> &mut Self { + self.authors = authors.into(); + self + } + + /// Returns the authors of the book as specified in the configuration file + pub fn get_authors(&self) -> &[String] { + self.authors.as_slice() + } + + pub fn set_html_config(&mut self, htmlconfig: HtmlConfig) -> &mut Self { + self.html_config = Some(htmlconfig); + self + } + + /// Returns the configuration for the HTML renderer or None of there isn't any + pub fn get_html_config(&self) -> Option<&HtmlConfig> { + self.html_config.as_ref() + } + + pub fn get_mut_html_config(&mut self) -> Option<&mut HtmlConfig> { + self.html_config.as_mut() + } +} diff --git a/src/config/htmlconfig.rs b/src/config/htmlconfig.rs new file mode 100644 index 00000000..5f8d40ae --- /dev/null +++ b/src/config/htmlconfig.rs @@ -0,0 +1,64 @@ +use std::path::{PathBuf, Path}; + +use super::tomlconfig::TomlHtmlConfig; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct HtmlConfig { + destination: PathBuf, + theme: Option, + google_analytics: Option, +} + +impl HtmlConfig { + /// Creates a new `HtmlConfig` struct containing the configuration parameters for the HTML renderer. + /// + /// ``` + /// # use std::path::PathBuf; + /// # use mdbook::config::HtmlConfig; + /// # + /// let output = PathBuf::from("root/book"); + /// let config = HtmlConfig::new(PathBuf::from("root")); + /// + /// assert_eq!(config.get_destination(), &output); + /// ``` + pub fn new>(root: T) -> Self { + HtmlConfig { + destination: root.into().join("book"), + theme: None, + google_analytics: None, + } + } + + pub fn fill_from_tomlconfig>(&mut self, root: T, source: T, tomlconfig: TomlHtmlConfig) -> &mut Self { + if let Some(d) = tomlconfig.destination { + if d.is_relative() { + self.destination = root.into().join(d); + } else { + self.destination = d; + } + } + + if let Some(t) = tomlconfig.theme { + if t.is_relative() { + self.theme = Some(source.into().join(t)); + } else { + self.theme = Some(t); + } + } + + if tomlconfig.google_analytics.is_some() { + self.google_analytics = tomlconfig.google_analytics; + } + + self + } + + pub fn get_destination(&self) -> &Path { + &self.destination + } + + // FIXME: How to get a `Option<&Path>` ? + pub fn get_theme(&self) -> Option<&PathBuf> { + self.theme.as_ref() + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..ead9dcaa --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,7 @@ +pub mod bookconfig; +pub mod htmlconfig; +pub mod tomlconfig; + +// Re-export the config structs +pub use self::bookconfig::BookConfig; +pub use self::htmlconfig::HtmlConfig; diff --git a/src/config/tomlconfig.rs b/src/config/tomlconfig.rs new file mode 100644 index 00000000..cb5dad1b --- /dev/null +++ b/src/config/tomlconfig.rs @@ -0,0 +1,50 @@ +extern crate toml; +use std::path::PathBuf; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TomlConfig { + pub source: Option, + + pub title: Option, + pub author: Option, + pub authors: Option>, + pub description: Option, + + pub output: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TomlOutputConfig { + pub html: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct TomlHtmlConfig { + pub destination: Option, + pub theme: Option, + pub google_analytics: Option, +} + +/// Returns a TomlConfig from a TOML string +/// +/// ``` +/// # use mdbook::config::tomlconfig::TomlConfig; +/// # use std::path::PathBuf; +/// let toml = r#"title="Some title" +/// [output.html] +/// destination = "htmlbook" "#; +/// +/// let config = TomlConfig::from_toml(&toml).expect("Should parse correctly"); +/// assert_eq!(config.title, Some(String::from("Some title"))); +/// assert_eq!(config.output.unwrap().html.unwrap().destination, Some(PathBuf::from("htmlbook"))); +/// ``` +impl TomlConfig { + pub fn from_toml(input: &str) -> Result { + let config: TomlConfig = toml::from_str(input) + .map_err(|e| format!("Could not parse TOML: {}", e))?; + + return Ok(config); + } +} + + diff --git a/src/lib.rs b/src/lib.rs index 303bb23b..882e22c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,9 +69,11 @@ //! //! Make sure to take a look at it. -extern crate serde; #[macro_use] -extern crate serde_json; +extern crate serde_derive; +extern crate serde; +#[macro_use] extern crate serde_json; + extern crate handlebars; extern crate pulldown_cmark; extern crate regex; @@ -79,6 +81,7 @@ extern crate regex; #[macro_use] extern crate log; pub mod book; +pub mod config; mod parse; pub mod renderer; pub mod theme; diff --git a/tests/config.rs b/tests/config.rs new file mode 100644 index 00000000..3f6e3143 --- /dev/null +++ b/tests/config.rs @@ -0,0 +1,88 @@ +extern crate mdbook; +use mdbook::config::BookConfig; +use mdbook::config::tomlconfig::TomlConfig; + +use std::path::PathBuf; + +// Tests that the `title` key is correcly parsed in the TOML config +#[test] +fn from_toml_source() { + let toml = r#"source = "source""#; + + let parsed = TomlConfig::from_toml(&toml).expect("This should parse"); + let config = BookConfig::from_tomlconfig("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_toml_title() { + let toml = r#"title = "Some title""#; + + let parsed = TomlConfig::from_toml(&toml).expect("This should parse"); + let config = BookConfig::from_tomlconfig("root", parsed); + + assert_eq!(config.get_title(), "Some title"); +} + +// Tests that the `description` key is correcly parsed in the TOML config +#[test] +fn from_toml_description() { + let toml = r#"description = "This is a description""#; + + let parsed = TomlConfig::from_toml(&toml).expect("This should parse"); + let config = BookConfig::from_tomlconfig("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_toml_author() { + let toml = r#"author = "John Doe""#; + + let parsed = TomlConfig::from_toml(&toml).expect("This should parse"); + let config = BookConfig::from_tomlconfig("root", parsed); + + assert_eq!(config.get_authors(), &[String::from("John Doe")]); +} + +// Tests that the `authors` key is correcly parsed in the TOML config +#[test] +fn from_toml_authors() { + let toml = r#"authors = ["John Doe", "Jane Doe"]"#; + + let parsed = TomlConfig::from_toml(&toml).expect("This should parse"); + let config = BookConfig::from_tomlconfig("root", parsed); + + assert_eq!(config.get_authors(), &[String::from("John Doe"), String::from("Jane Doe")]); +} + +// Tests that the `output.html.destination` key is correcly parsed in the TOML config +#[test] +fn from_toml_output_html_destination() { + let toml = r#"[output.html] + destination = "htmlbook""#; + + 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_destination(), PathBuf::from("root/htmlbook")); +} + +// Tests that the `output.html.theme` key is correcly parsed in the TOML config +#[test] +fn from_toml_output_html_theme() { + let toml = r#"[output.html] + theme = "theme""#; + + 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_theme().expect("the theme key was provided"), &PathBuf::from("root/src/theme")); +} \ No newline at end of file