2017-09-30 12:11:24 +00:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Read;
|
|
|
|
use toml::{self, Value};
|
2017-09-30 12:34:27 +00:00
|
|
|
use serde::Deserialize;
|
2017-09-30 12:11:24 +00:00
|
|
|
|
|
|
|
use errors::*;
|
|
|
|
|
2017-05-18 17:32:08 +00:00
|
|
|
pub mod bookconfig;
|
|
|
|
pub mod htmlconfig;
|
2017-06-29 04:35:20 +00:00
|
|
|
pub mod playpenconfig;
|
2017-05-18 17:32:08 +00:00
|
|
|
pub mod tomlconfig;
|
2017-05-18 22:56:37 +00:00
|
|
|
pub mod jsonconfig;
|
2017-05-18 17:32:08 +00:00
|
|
|
|
|
|
|
// Re-export the config structs
|
|
|
|
pub use self::bookconfig::BookConfig;
|
|
|
|
pub use self::htmlconfig::HtmlConfig;
|
2017-06-29 04:35:20 +00:00
|
|
|
pub use self::playpenconfig::PlaypenConfig;
|
2017-05-18 22:56:37 +00:00
|
|
|
pub use self::tomlconfig::TomlConfig;
|
2017-09-30 12:11:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
/// The overall configuration object for MDBook.
|
2017-09-30 12:34:27 +00:00
|
|
|
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
2017-09-30 12:11:24 +00:00
|
|
|
#[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<String, Value>,
|
|
|
|
/// Information for use by preprocessors.
|
|
|
|
pub preprocess: BTreeMap<String, Value>,
|
|
|
|
/// Information for use by postprocessors.
|
|
|
|
pub postprocess: BTreeMap<String, Value>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
|
|
|
/// Load a `Config` from some string.
|
|
|
|
pub fn from_str(src: &str) -> Result<Config> {
|
|
|
|
toml::from_str(src).chain_err(|| Error::from("Invalid configuration file"))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Load the configuration file from disk.
|
|
|
|
pub fn from_disk<P: AsRef<Path>>(config_file: P) -> Result<Config> {
|
|
|
|
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<HtmlConfig_> {
|
2017-09-30 12:34:27 +00:00
|
|
|
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<str>>(&self, name: S) -> Result<T> {
|
|
|
|
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<str>>(&self, name: S) -> Result<T> {
|
|
|
|
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<str>>(&self, name: S) -> Result<T> {
|
|
|
|
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<str>>(name: S, table: &BTreeMap<String, Value>) -> Result<T> {
|
|
|
|
match table.get(name.as_ref()) {
|
|
|
|
Some(output) => output
|
|
|
|
.clone()
|
|
|
|
.try_into()
|
|
|
|
.chain_err(|| "Couldn't deserialize the value"),
|
|
|
|
None => bail!("Key Not Found"),
|
2017-09-30 12:11:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Configuration options which are specific to the book and required for
|
|
|
|
/// loading it from disk.
|
2017-09-30 12:34:27 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
2017-09-30 12:11:24 +00:00
|
|
|
#[serde(default, rename_all = "kebab-case")]
|
|
|
|
pub struct BookConfig_ {
|
|
|
|
/// The book's title.
|
|
|
|
pub title: Option<String>,
|
|
|
|
/// The book's authors.
|
|
|
|
pub authors: Vec<String>,
|
|
|
|
/// An optional description for the book.
|
|
|
|
pub description: Option<String>,
|
|
|
|
/// 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,
|
|
|
|
}
|
|
|
|
|
2017-09-30 12:34:27 +00:00
|
|
|
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)]
|
2017-09-30 12:11:24 +00:00
|
|
|
#[serde(default, rename_all = "kebab-case")]
|
|
|
|
pub struct HtmlConfig_ {
|
|
|
|
pub theme: Option<PathBuf>,
|
|
|
|
pub curly_quotes: bool,
|
|
|
|
pub mathjax_support: bool,
|
|
|
|
pub google_analytics: Option<String>,
|
|
|
|
pub additional_css: Vec<PathBuf>,
|
|
|
|
pub additional_js: Vec<PathBuf>,
|
|
|
|
pub playpen: Playpen,
|
|
|
|
}
|
|
|
|
|
2017-09-30 12:34:27 +00:00
|
|
|
#[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 <michaelfbryan@gmail.com>"]
|
|
|
|
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 <michaelfbryan@gmail.com>")],
|
|
|
|
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);
|
2017-09-30 12:11:24 +00:00
|
|
|
}
|
|
|
|
|
2017-09-30 12:34:27 +00:00
|
|
|
#[test]
|
|
|
|
fn load_arbitrary_output_type() {
|
|
|
|
#[derive(Debug, Deserialize, PartialEq)]
|
|
|
|
struct RandomOutput {
|
|
|
|
foo: u32,
|
|
|
|
bar: String,
|
|
|
|
baz: Vec<bool>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|