From 2f8d5ce263584fb3f00e0d8a54ece58ebbb74a6c Mon Sep 17 00:00:00 2001 From: Michael Bryan Date: Mon, 10 Jul 2017 19:26:43 +0800 Subject: [PATCH] Removed a lot of the repetition in Theme::new() --- src/lib.rs | 9 +- src/theme/{jquery-2.1.4.min.js => jquery.js} | 0 src/theme/mod.rs | 165 ++++++++++++++----- 3 files changed, 127 insertions(+), 47 deletions(-) rename src/theme/{jquery-2.1.4.min.js => jquery.js} (100%) diff --git a/src/lib.rs b/src/lib.rs index 495b8026..e6c54443 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ //! # fn main() { //! # 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` and you get @@ -65,7 +65,9 @@ //! following function [`utils::fs::create_file(path: //! &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. @@ -84,6 +86,9 @@ extern crate serde; #[macro_use] extern crate serde_json; +#[cfg(test)] +extern crate tempdir; + mod parse; mod preprocess; pub mod book; diff --git a/src/theme/jquery-2.1.4.min.js b/src/theme/jquery.js similarity index 100% rename from src/theme/jquery-2.1.4.min.js rename to src/theme/jquery.js diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 600417f7..d01a2a2f 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -2,6 +2,8 @@ use std::path::Path; use std::fs::File; use std::io::Read; +use errors::*; + pub static INDEX: &'static [u8] = include_bytes!("index.hbs"); 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 HIGHLIGHT_CSS: &'static [u8] = include_bytes!("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 STORE_JS: &'static [u8] = include_bytes!("store.js"); 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_OTF: &'static [u8] = include_bytes!("_FontAwesome/fonts/FontAwesome.otf"); + /// The `Theme` struct should be used instead of the static variables because -/// the `new()` method -/// will look if the user has a theme directory in his source folder and use -/// the users theme instead -/// of the default. +/// the `new()` method will look if the user has a theme directory in his +/// source folder and use the users theme instead of the default. /// -/// You should exceptionnaly use the static variables only if you need the -/// default theme even if the -/// user has specified another theme. +/// You should only ever use the static variables directly if you want to +/// override the user's theme with the defaults. +#[derive(Debug, PartialEq)] pub struct Theme { pub index: Vec, pub css: Vec, @@ -46,10 +47,49 @@ pub struct Theme { } impl Theme { - pub fn new(src: &Path) -> Self { + pub fn new>(theme_dir: P) -> Self { + let theme_dir = theme_dir.as_ref(); + let mut theme = Theme::default(); - // Default theme - let mut theme = Theme { + // If the theme directory doesn't exist there's no point continuing... + 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(), css: CSS.to_owned(), favicon: FAVICON.to_owned(), @@ -61,44 +101,79 @@ impl Theme { clipboard_js: CLIPBOARD_JS.to_owned(), store_js: STORE_JS.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 copy them across - { - let files = vec![ - (src.join("index.hbs"), &mut theme.index), - (src.join("book.js"), &mut theme.js), - (src.join("book.css"), &mut theme.css), - (src.join("favicon.png"), &mut theme.favicon), - (src.join("highlight.js"), &mut theme.highlight_js), - (src.join("clipboard.min.js"), &mut theme.clipboard_js), - (src.join("store.js"), &mut theme.store_js), - (src.join("highlight.css"), &mut theme.highlight_css), - (src.join("tomorrow-night.css"), &mut theme.tomorrow_night_css), - (src.join("ayu-highlight.css"), &mut theme.ayu_highlight_css), - ]; - - for (filename, dest) in files { - load_file_contents(filename, dest); - } - } - - theme } } -fn load_file_contents>(filename: P, dest: &mut Vec) { +/// Checks if a file exists, if so, the destination buffer will be filled with +/// its contents. +fn load_file_contents>(filename: P, dest: &mut Vec) -> Result<()> { let filename = filename.as_ref(); - if let Ok(mut f) = File::open(filename) { - dest.clear(); - if let Err(e) = f.read_to_end(dest) { - warn!("Couldn't load custom file, {}: {}", filename.display(), e); - } + let mut buffer = Vec::new(); + File::open(filename)?.read_to_end(&mut buffer)?; + + // We needed the buffer so we'd only overwrite the existing content if we + // could successfully load the file into memory. + dest.clear(); + dest.append(&mut buffer); + + Ok(()) +} + + +#[cfg(test)] +mod tests { + use super::*; + use tempdir::TempDir; + use std::path::PathBuf; + + #[test] + fn theme_uses_defaults_with_nonexistent_src_dir() { + let non_existent = PathBuf::from("/non/existent/directory/"); + assert!(!non_existent.exists()); + + let should_be = Theme::default(); + let got = Theme::new(&non_existent); + + assert_eq!(got, should_be); } -} \ No newline at end of file + + #[test] + fn theme_dir_overrides_defaults() { + // Get all the non-Rust files in the theme directory + let special_files = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("src/theme") + .read_dir() + .unwrap() + .filter_map(|f| f.ok()) + .map(|f| f.path()) + .filter(|p| p.is_file() && !p.ends_with(".rs")); + + let temp = TempDir::new("mdbook").unwrap(); + + // "touch" all of the special files so we have empty copies + for special_file in special_files { + let filename = temp.path().join(special_file.file_name().unwrap()); + let _ = File::create(&filename); + } + + let got = Theme::new(temp.path()); + + let empty = Theme { + index: Vec::new(), + css: Vec::new(), + favicon: Vec::new(), + js: Vec::new(), + 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); + } +}