Allow an additional custom stylesheets, closes #178
This commit is contained in:
parent
c6bfe0b1d7
commit
bb4ceb481f
|
@ -1,230 +0,0 @@
|
|||
extern crate toml;
|
||||
|
||||
use std::process::exit;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
use serde_json;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BookConfig {
|
||||
root: PathBuf,
|
||||
pub dest: PathBuf,
|
||||
pub src: PathBuf,
|
||||
pub theme_path: PathBuf,
|
||||
|
||||
pub title: String,
|
||||
pub author: String,
|
||||
pub description: String,
|
||||
|
||||
pub indent_spaces: i32,
|
||||
multilingual: bool,
|
||||
pub google_analytics: Option<String>,
|
||||
}
|
||||
|
||||
impl BookConfig {
|
||||
pub fn new(root: &Path) -> Self {
|
||||
BookConfig {
|
||||
root: root.to_owned(),
|
||||
dest: root.join("book"),
|
||||
src: root.join("src"),
|
||||
theme_path: root.join("theme"),
|
||||
|
||||
title: String::new(),
|
||||
author: String::new(),
|
||||
description: String::new(),
|
||||
|
||||
indent_spaces: 4, // indentation used for SUMMARY.md
|
||||
multilingual: false,
|
||||
google_analytics: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_config(&mut self, root: &Path) -> &mut Self {
|
||||
|
||||
debug!("[fn]: read_config");
|
||||
|
||||
let read_file = |path: PathBuf| -> String {
|
||||
let mut data = String::new();
|
||||
let mut f: File = match File::open(&path) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
error!("[*]: Failed to open {:?}", &path);
|
||||
exit(2);
|
||||
},
|
||||
};
|
||||
if f.read_to_string(&mut data).is_err() {
|
||||
error!("[*]: Failed to read {:?}", &path);
|
||||
exit(2);
|
||||
}
|
||||
data
|
||||
};
|
||||
|
||||
// Read book.toml or book.json if exists
|
||||
|
||||
if root.join("book.toml").exists() {
|
||||
|
||||
debug!("[*]: Reading config");
|
||||
let data = read_file(root.join("book.toml"));
|
||||
self.parse_from_toml_string(&data);
|
||||
|
||||
} else if root.join("book.json").exists() {
|
||||
|
||||
debug!("[*]: Reading config");
|
||||
let data = read_file(root.join("book.json"));
|
||||
self.parse_from_json_string(&data);
|
||||
|
||||
} else {
|
||||
debug!("[*]: No book.toml or book.json was found, using defaults.");
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parse_from_toml_string(&mut self, data: &str) -> &mut Self {
|
||||
let config = match toml::from_str(data) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("[*]: Toml parse errors in book.toml: {:?}", e);
|
||||
exit(2);
|
||||
},
|
||||
};
|
||||
|
||||
self.parse_from_btreemap(&config);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Parses the string to JSON and converts it
|
||||
/// to BTreeMap<String, toml::Value>.
|
||||
pub fn parse_from_json_string(&mut self, data: &str) -> &mut Self {
|
||||
|
||||
let c: serde_json::Value = match serde_json::from_str(data) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("[*]: JSON parse errors in book.json: {:?}", e);
|
||||
exit(2);
|
||||
},
|
||||
};
|
||||
|
||||
let config = json_object_to_btreemap(c.as_object().unwrap());
|
||||
self.parse_from_btreemap(&config);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn parse_from_btreemap(&mut self, config: &BTreeMap<String, toml::Value>) -> &mut Self {
|
||||
|
||||
// Title, author, description
|
||||
if let Some(a) = config.get("title") {
|
||||
self.title = a.to_string().replace("\"", "");
|
||||
}
|
||||
if let Some(a) = config.get("author") {
|
||||
self.author = a.to_string().replace("\"", "");
|
||||
}
|
||||
if let Some(a) = config.get("description") {
|
||||
self.description = a.to_string().replace("\"", "");
|
||||
}
|
||||
|
||||
// Destination folder
|
||||
if let Some(a) = config.get("dest") {
|
||||
let mut dest = PathBuf::from(&a.to_string().replace("\"", ""));
|
||||
|
||||
// If path is relative make it absolute from the parent directory of src
|
||||
if dest.is_relative() {
|
||||
dest = self.get_root().join(&dest);
|
||||
}
|
||||
self.set_dest(&dest);
|
||||
}
|
||||
|
||||
// Source folder
|
||||
if let Some(a) = config.get("src") {
|
||||
let mut src = PathBuf::from(&a.to_string().replace("\"", ""));
|
||||
if src.is_relative() {
|
||||
src = self.get_root().join(&src);
|
||||
}
|
||||
self.set_src(&src);
|
||||
}
|
||||
|
||||
// Theme path folder
|
||||
if let Some(a) = config.get("theme_path") {
|
||||
let mut theme_path = PathBuf::from(&a.to_string().replace("\"", ""));
|
||||
if theme_path.is_relative() {
|
||||
theme_path = self.get_root().join(&theme_path);
|
||||
}
|
||||
self.set_theme_path(&theme_path);
|
||||
}
|
||||
|
||||
// Google analytics code
|
||||
if let Some(id) = config.get("google_analytics_id") {
|
||||
self.google_analytics = Some(id.to_string().replace("\"", ""));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_root(&self) -> &Path {
|
||||
&self.root
|
||||
}
|
||||
|
||||
pub fn set_root(&mut self, root: &Path) -> &mut Self {
|
||||
self.root = root.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_dest(&self) -> &Path {
|
||||
&self.dest
|
||||
}
|
||||
|
||||
pub fn set_dest(&mut self, dest: &Path) -> &mut Self {
|
||||
self.dest = dest.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_src(&self) -> &Path {
|
||||
&self.src
|
||||
}
|
||||
|
||||
pub fn set_src(&mut self, src: &Path) -> &mut Self {
|
||||
self.src = src.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_theme_path(&self) -> &Path {
|
||||
&self.theme_path
|
||||
}
|
||||
|
||||
pub fn set_theme_path(&mut self, theme_path: &Path) -> &mut Self {
|
||||
self.theme_path = theme_path.to_owned();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn json_object_to_btreemap(json: &serde_json::Map<String, serde_json::Value>) -> BTreeMap<String, toml::Value> {
|
||||
let mut config: BTreeMap<String, toml::Value> = BTreeMap::new();
|
||||
|
||||
for (key, value) in json.iter() {
|
||||
config.insert(String::from_str(key).unwrap(), json_value_to_toml_value(value.to_owned()));
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
pub fn json_value_to_toml_value(json: serde_json::Value) -> toml::Value {
|
||||
match json {
|
||||
serde_json::Value::Null => toml::Value::String("".to_string()),
|
||||
serde_json::Value::Bool(x) => toml::Value::Boolean(x),
|
||||
serde_json::Value::Number(ref x) if x.is_i64() => toml::Value::Integer(x.as_i64().unwrap()),
|
||||
serde_json::Value::Number(ref x) if x.is_u64() => toml::Value::Integer(x.as_i64().unwrap()),
|
||||
serde_json::Value::Number(x) => toml::Value::Float(x.as_f64().unwrap()),
|
||||
serde_json::Value::String(x) => toml::Value::String(x),
|
||||
serde_json::Value::Array(x) => {
|
||||
toml::Value::Array(x.iter()
|
||||
.map(|v| json_value_to_toml_value(v.to_owned()))
|
||||
.collect())
|
||||
},
|
||||
serde_json::Value::Object(x) => toml::Value::Table(json_object_to_btreemap(&x)),
|
||||
}
|
||||
}
|
|
@ -497,6 +497,22 @@ impl MDBook {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn has_additional_css(&self) -> bool {
|
||||
if let Some(htmlconfig) = self.config.get_html_config() {
|
||||
return htmlconfig.has_additional_css();
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_additional_css(&self) -> &[PathBuf] {
|
||||
if let Some(htmlconfig) = self.config.get_html_config() {
|
||||
return htmlconfig.get_additional_css();
|
||||
}
|
||||
|
||||
&[]
|
||||
}
|
||||
|
||||
// Construct book
|
||||
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
|
||||
// When append becomes stable, use self.content.append() ...
|
||||
|
|
|
@ -7,6 +7,7 @@ pub struct HtmlConfig {
|
|||
destination: PathBuf,
|
||||
theme: Option<PathBuf>,
|
||||
google_analytics: Option<String>,
|
||||
additional_css: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl HtmlConfig {
|
||||
|
@ -26,6 +27,7 @@ impl HtmlConfig {
|
|||
destination: root.into().join("book"),
|
||||
theme: None,
|
||||
google_analytics: None,
|
||||
additional_css: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +54,16 @@ impl HtmlConfig {
|
|||
self.google_analytics = tomlconfig.google_analytics;
|
||||
}
|
||||
|
||||
if let Some(stylepaths) = tomlconfig.additional_css {
|
||||
for path in stylepaths {
|
||||
if path.is_relative() {
|
||||
self.additional_css.push(root.join(path));
|
||||
} else {
|
||||
self.additional_css.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -89,4 +101,17 @@ impl HtmlConfig {
|
|||
pub fn get_google_analytics_id(&self) -> Option<String> {
|
||||
self.google_analytics.clone()
|
||||
}
|
||||
|
||||
pub fn set_google_analytics_id(&mut self, id: Option<String>) -> &mut Self {
|
||||
self.google_analytics = id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_additional_css(&self) -> bool {
|
||||
!self.additional_css.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_additional_css(&self) -> &[PathBuf] {
|
||||
&self.additional_css
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ pub struct TomlHtmlConfig {
|
|||
pub destination: Option<PathBuf>,
|
||||
pub theme: Option<PathBuf>,
|
||||
pub google_analytics: Option<String>,
|
||||
pub additional_css: Option<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
/// Returns a TomlConfig from a TOML string
|
||||
|
|
|
@ -187,6 +187,19 @@ impl Renderer for HtmlHandlebars {
|
|||
book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2)?;
|
||||
book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)?;
|
||||
|
||||
for style in book.get_additional_css() {
|
||||
let mut data = Vec::new();
|
||||
let mut f = File::open(style)?;
|
||||
f.read_to_end(&mut data)?;
|
||||
|
||||
let name = match style.strip_prefix(book.get_root()) {
|
||||
Ok(p) => p.to_str().expect("Could not convert to str"),
|
||||
Err(_) => style.file_name().expect("File has a file name").to_str().expect("Could not convert to str"),
|
||||
};
|
||||
|
||||
book.write_file(name, &data)?;
|
||||
}
|
||||
|
||||
// Copy all remaining files
|
||||
utils::fs::copy_files_except_ext(
|
||||
book.get_source(),
|
||||
|
@ -215,6 +228,18 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
|
|||
data.insert("google_analytics".to_owned(), json!(ga));
|
||||
}
|
||||
|
||||
// Add check to see if there is an additional style
|
||||
if book.has_additional_css() {
|
||||
let mut css = Vec::new();
|
||||
for style in book.get_additional_css() {
|
||||
match style.strip_prefix(book.get_root()) {
|
||||
Ok(p) => css.push(p.to_str().expect("Could not convert to str")),
|
||||
Err(_) => css.push(style.file_name().expect("File has a file name").to_str().expect("Could not convert to str")),
|
||||
}
|
||||
}
|
||||
data.insert("additional_css".to_owned(), json!(css));
|
||||
}
|
||||
|
||||
let mut chapters = vec![];
|
||||
|
||||
for item in book.iter() {
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
|
||||
<!-- Custom theme -->
|
||||
{{#each additional_css}}
|
||||
<link rel="stylesheet" href="{{this}}">
|
||||
{{/each}}
|
||||
|
||||
<!-- MathJax -->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
|
||||
|
|
|
@ -100,3 +100,18 @@ fn from_toml_output_html_google_analytics() {
|
|||
|
||||
assert_eq!(htmlconfig.get_google_analytics_id().expect("the google-analytics key was provided"), String::from("123456"));
|
||||
}
|
||||
|
||||
|
||||
// Tests that the `output.html.additional-css` key is correcly parsed in the TOML config
|
||||
#[test]
|
||||
fn from_toml_output_html_additional_stylesheet() {
|
||||
let toml = r#"[output.html]
|
||||
additional-css = ["custom.css", "two/custom.css"]"#;
|
||||
|
||||
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_additional_css(), &[PathBuf::from("root/custom.css"), PathBuf::from("root/two/custom.css")]);
|
||||
}
|
Loading…
Reference in New Issue