mdBook/src/config/mod.rs

214 lines
6.4 KiB
Rust
Raw Normal View History

2017-09-30 20:11:24 +08:00
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::Read;
use toml::{self, Value};
use serde::Deserialize;
2017-09-30 20:11:24 +08:00
use errors::*;
2017-05-19 01:32:08 +08:00
pub mod bookconfig;
pub mod htmlconfig;
2017-06-29 12:35:20 +08:00
pub mod playpenconfig;
2017-05-19 01:32:08 +08:00
pub mod tomlconfig;
pub mod jsonconfig;
2017-05-19 01:32:08 +08:00
// Re-export the config structs
pub use self::bookconfig::BookConfig;
pub use self::htmlconfig::HtmlConfig;
2017-06-29 12:35:20 +08:00
pub use self::playpenconfig::PlaypenConfig;
pub use self::tomlconfig::TomlConfig;
2017-09-30 20:11:24 +08:00
/// The overall configuration object for MDBook.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
2017-09-30 20:11:24 +08: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_> {
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 20:11:24 +08:00
}
}
/// Configuration options which are specific to the book and required for
/// loading it from disk.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2017-09-30 20:11:24 +08: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,
}
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 20:11:24 +08: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,
}
#[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 20:11:24 +08: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);
}
}