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
|
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
|
// Construct book
|
||||||
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
|
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
|
||||||
// When append becomes stable, use self.content.append() ...
|
// When append becomes stable, use self.content.append() ...
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub struct HtmlConfig {
|
||||||
destination: PathBuf,
|
destination: PathBuf,
|
||||||
theme: Option<PathBuf>,
|
theme: Option<PathBuf>,
|
||||||
google_analytics: Option<String>,
|
google_analytics: Option<String>,
|
||||||
|
additional_css: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HtmlConfig {
|
impl HtmlConfig {
|
||||||
|
@ -26,6 +27,7 @@ impl HtmlConfig {
|
||||||
destination: root.into().join("book"),
|
destination: root.into().join("book"),
|
||||||
theme: None,
|
theme: None,
|
||||||
google_analytics: None,
|
google_analytics: None,
|
||||||
|
additional_css: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +54,16 @@ impl HtmlConfig {
|
||||||
self.google_analytics = tomlconfig.google_analytics;
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,4 +101,17 @@ impl HtmlConfig {
|
||||||
pub fn get_google_analytics_id(&self) -> Option<String> {
|
pub fn get_google_analytics_id(&self) -> Option<String> {
|
||||||
self.google_analytics.clone()
|
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 destination: Option<PathBuf>,
|
||||||
pub theme: Option<PathBuf>,
|
pub theme: Option<PathBuf>,
|
||||||
pub google_analytics: Option<String>,
|
pub google_analytics: Option<String>,
|
||||||
|
pub additional_css: Option<Vec<PathBuf>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a TomlConfig from a TOML string
|
/// 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-webfont.woff2", theme::FONT_AWESOME_WOFF2)?;
|
||||||
book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)?;
|
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
|
// Copy all remaining files
|
||||||
utils::fs::copy_files_except_ext(
|
utils::fs::copy_files_except_ext(
|
||||||
book.get_source(),
|
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));
|
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![];
|
let mut chapters = vec![];
|
||||||
|
|
||||||
for item in book.iter() {
|
for item in book.iter() {
|
||||||
|
|
|
@ -21,6 +21,11 @@
|
||||||
<link rel="stylesheet" href="highlight.css">
|
<link rel="stylesheet" href="highlight.css">
|
||||||
<link rel="stylesheet" href="tomorrow-night.css">
|
<link rel="stylesheet" href="tomorrow-night.css">
|
||||||
|
|
||||||
|
<!-- Custom theme -->
|
||||||
|
{{#each additional_css}}
|
||||||
|
<link rel="stylesheet" href="{{this}}">
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
<!-- MathJax -->
|
<!-- MathJax -->
|
||||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
<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"));
|
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