Merge pull request #377 from Michael-F-Bryan/dry-themes

Make themes module more DRY
This commit is contained in:
Mathieu David 2017-08-03 14:06:45 +02:00 committed by GitHub
commit ef435825b0
3 changed files with 131 additions and 82 deletions

View File

@ -51,7 +51,7 @@
//! # fn main() { //! # fn main() {
//! # let your_renderer = HtmlHandlebars::new(); //! # let your_renderer = HtmlHandlebars::new();
//! # //! #
//! let book = MDBook::new("my-book").set_renderer(Box::new(your_renderer)); //! let book = MDBook::new("my-book").set_renderer(Box::new(your_renderer));
//! # } //! # }
//! ``` //! ```
//! If you make a renderer, you get the book constructed in form of `Vec<BookItems>` and you get //! If you make a renderer, you get the book constructed in form of `Vec<BookItems>` and you get
@ -65,7 +65,9 @@
//! following function [`utils::fs::create_file(path: //! following function [`utils::fs::create_file(path:
//! &Path)`](utils/fs/fn.create_file.html) //! &Path)`](utils/fs/fn.create_file.html)
//! //!
//! This function creates a file and returns it. But before creating the file it checks every directory in the path to see if it exists, and if it does not it will be created. //! This function creates a file and returns it. But before creating the file
//! it checks every directory in the path to see if it exists, and if it does
//! not it will be created.
//! //!
//! Make sure to take a look at it. //! Make sure to take a look at it.
@ -84,6 +86,9 @@ extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_json; extern crate serde_json;
#[cfg(test)]
extern crate tempdir;
mod parse; mod parse;
mod preprocess; mod preprocess;
pub mod book; pub mod book;

View File

@ -2,6 +2,8 @@ use std::path::Path;
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use errors::*;
pub static INDEX: &'static [u8] = include_bytes!("index.hbs"); pub static INDEX: &'static [u8] = include_bytes!("index.hbs");
pub static CSS: &'static [u8] = include_bytes!("book.css"); pub static CSS: &'static [u8] = include_bytes!("book.css");
@ -11,7 +13,7 @@ pub static HIGHLIGHT_JS: &'static [u8] = include_bytes!("highlight.js");
pub static TOMORROW_NIGHT_CSS: &'static [u8] = include_bytes!("tomorrow-night.css"); pub static TOMORROW_NIGHT_CSS: &'static [u8] = include_bytes!("tomorrow-night.css");
pub static HIGHLIGHT_CSS: &'static [u8] = include_bytes!("highlight.css"); pub static HIGHLIGHT_CSS: &'static [u8] = include_bytes!("highlight.css");
pub static AYU_HIGHLIGHT_CSS: &'static [u8] = include_bytes!("ayu-highlight.css"); pub static AYU_HIGHLIGHT_CSS: &'static [u8] = include_bytes!("ayu-highlight.css");
pub static JQUERY: &'static [u8] = include_bytes!("jquery-2.1.4.min.js"); pub static JQUERY: &'static [u8] = include_bytes!("jquery.js");
pub static CLIPBOARD_JS: &'static [u8] = include_bytes!("clipboard.min.js"); pub static CLIPBOARD_JS: &'static [u8] = include_bytes!("clipboard.min.js");
pub static STORE_JS: &'static [u8] = include_bytes!("store.js"); pub static STORE_JS: &'static [u8] = include_bytes!("store.js");
pub static FONT_AWESOME: &'static [u8] = include_bytes!("_FontAwesome/css/font-awesome.min.css"); pub static FONT_AWESOME: &'static [u8] = include_bytes!("_FontAwesome/css/font-awesome.min.css");
@ -22,15 +24,14 @@ pub static FONT_AWESOME_WOFF: &'static [u8] = include_bytes!("_FontAwesome/fonts
pub static FONT_AWESOME_WOFF2: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff2"); pub static FONT_AWESOME_WOFF2: &'static [u8] = include_bytes!("_FontAwesome/fonts/fontawesome-webfont.woff2");
pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf"); pub static FONT_AWESOME_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf");
/// The `Theme` struct should be used instead of the static variables because /// The `Theme` struct should be used instead of the static variables because
/// the `new()` method /// the `new()` method will look if the user has a theme directory in his
/// will look if the user has a theme directory in his source folder and use /// source folder and use the users theme instead of the default.
/// the users theme instead
/// of the default.
/// ///
/// You should exceptionnaly use the static variables only if you need the /// You should only ever use the static variables directly if you want to
/// default theme even if the /// override the user's theme with the defaults.
/// user has specified another theme. #[derive(Debug, PartialEq)]
pub struct Theme { pub struct Theme {
pub index: Vec<u8>, pub index: Vec<u8>,
pub css: Vec<u8>, pub css: Vec<u8>,
@ -46,10 +47,49 @@ pub struct Theme {
} }
impl Theme { impl Theme {
pub fn new(src: &Path) -> Self { pub fn new<P: AsRef<Path>>(theme_dir: P) -> Self {
let theme_dir = theme_dir.as_ref();
let mut theme = Theme::default();
// Default theme // If the theme directory doesn't exist there's no point continuing...
let mut theme = Theme { if !theme_dir.exists() || !theme_dir.is_dir() {
return theme;
}
// Check for individual files, if they exist copy them across
{
let files = vec![
(theme_dir.join("index.hbs"), &mut theme.index),
(theme_dir.join("book.js"), &mut theme.js),
(theme_dir.join("book.css"), &mut theme.css),
(theme_dir.join("favicon.png"), &mut theme.favicon),
(theme_dir.join("highlight.js"), &mut theme.highlight_js),
(theme_dir.join("clipboard.min.js"), &mut theme.clipboard_js),
(theme_dir.join("store.js"), &mut theme.store_js),
(theme_dir.join("highlight.css"), &mut theme.highlight_css),
(theme_dir.join("tomorrow-night.css"), &mut theme.tomorrow_night_css),
(theme_dir.join("ayu-highlight.css"), &mut theme.ayu_highlight_css),
(theme_dir.join("jquery.js"), &mut theme.jquery),
];
for (filename, dest) in files {
if !filename.exists() {
continue;
}
if let Err(e) = load_file_contents(&filename, dest) {
warn!("Couldn't load custom file, {}: {}", filename.display(), e);
}
}
}
theme
}
}
impl Default for Theme {
fn default() -> Theme {
Theme {
index: INDEX.to_owned(), index: INDEX.to_owned(),
css: CSS.to_owned(), css: CSS.to_owned(),
favicon: FAVICON.to_owned(), favicon: FAVICON.to_owned(),
@ -61,75 +101,79 @@ impl Theme {
clipboard_js: CLIPBOARD_JS.to_owned(), clipboard_js: CLIPBOARD_JS.to_owned(),
store_js: STORE_JS.to_owned(), store_js: STORE_JS.to_owned(),
jquery: JQUERY.to_owned(), jquery: JQUERY.to_owned(),
};
// Check if the given path exists
if !src.exists() || !src.is_dir() {
return theme;
} }
}
// Check for individual files if they exist }
// index.hbs /// Checks if a file exists, if so, the destination buffer will be filled with
if let Ok(mut f) = File::open(&src.join("index.hbs")) { /// its contents.
theme.index.clear(); // Reset the value, because read_to_string appends... fn load_file_contents<P: AsRef<Path>>(filename: P, dest: &mut Vec<u8>) -> Result<()> {
let _ = f.read_to_end(&mut theme.index); let filename = filename.as_ref();
}
let mut buffer = Vec::new();
// book.js File::open(filename)?.read_to_end(&mut buffer)?;
if let Ok(mut f) = File::open(&src.join("book.js")) {
theme.js.clear(); // We needed the buffer so we'd only overwrite the existing content if we
let _ = f.read_to_end(&mut theme.js); // could successfully load the file into memory.
} dest.clear();
dest.append(&mut buffer);
// book.css
if let Ok(mut f) = File::open(&src.join("book.css")) { Ok(())
theme.css.clear(); }
let _ = f.read_to_end(&mut theme.css);
}
#[cfg(test)]
// favicon.png mod tests {
if let Ok(mut f) = File::open(&src.join("favicon.png")) { use super::*;
theme.favicon.clear(); use tempdir::TempDir;
let _ = f.read_to_end(&mut theme.favicon); use std::path::PathBuf;
}
#[test]
// highlight.js fn theme_uses_defaults_with_nonexistent_src_dir() {
if let Ok(mut f) = File::open(&src.join("highlight.js")) { let non_existent = PathBuf::from("/non/existent/directory/");
theme.highlight_js.clear(); assert!(!non_existent.exists());
let _ = f.read_to_end(&mut theme.highlight_js);
} let should_be = Theme::default();
let got = Theme::new(&non_existent);
// clipboard.js
if let Ok(mut f) = File::open(&src.join("clipboard.min.js")) { assert_eq!(got, should_be);
theme.clipboard_js.clear(); }
let _ = f.read_to_end(&mut theme.clipboard_js);
} #[test]
fn theme_dir_overrides_defaults() {
// store.js // Get all the non-Rust files in the theme directory
if let Ok(mut f) = File::open(&src.join("store.js")) { let special_files = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
theme.store_js.clear(); .join("src/theme")
let _ = f.read_to_end(&mut theme.store_js); .read_dir()
} .unwrap()
.filter_map(|f| f.ok())
// highlight.css .map(|f| f.path())
if let Ok(mut f) = File::open(&src.join("highlight.css")) { .filter(|p| p.is_file() && !p.ends_with(".rs"));
theme.highlight_css.clear();
let _ = f.read_to_end(&mut theme.highlight_css); let temp = TempDir::new("mdbook").unwrap();
}
// "touch" all of the special files so we have empty copies
// tomorrow-night.css for special_file in special_files {
if let Ok(mut f) = File::open(&src.join("tomorrow-night.css")) { let filename = temp.path().join(special_file.file_name().unwrap());
theme.tomorrow_night_css.clear(); let _ = File::create(&filename);
let _ = f.read_to_end(&mut theme.tomorrow_night_css); }
}
let got = Theme::new(temp.path());
// ayu-highlight.css
if let Ok(mut f) = File::open(&src.join("ayu-highlight.css")) { let empty = Theme {
theme.ayu_highlight_css.clear(); index: Vec::new(),
let _ = f.read_to_end(&mut theme.ayu_highlight_css); css: Vec::new(),
} favicon: Vec::new(),
js: Vec::new(),
theme highlight_css: Vec::new(),
tomorrow_night_css: Vec::new(),
ayu_highlight_css: Vec::new(),
highlight_js: Vec::new(),
clipboard_js: Vec::new(),
store_js: Vec::new(),
jquery: Vec::new(),
};
assert_eq!(got, empty);
} }
} }