diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs new file mode 100644 index 00000000..32b2ddc6 --- /dev/null +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -0,0 +1,222 @@ +extern crate handlebars; +extern crate rustc_serialize; +extern crate pulldown_cmark; + +use renderer::html_handlebars::helpers; +use renderer::Renderer; +use book::{BookItems, BookConfig}; +use {theme, utils}; + +use std::path::{Path, PathBuf, Component}; +use std::fs::{self, File, metadata}; +use std::error::Error; +use std::io::{self, Read, Write}; +use std::collections::BTreeMap; + +use self::handlebars::{Handlebars, JsonRender}; +use self::rustc_serialize::json::{Json, ToJson}; +use self::pulldown_cmark::{Parser, html}; + +pub struct HtmlHandlebars; + +impl Renderer for HtmlHandlebars { + fn render(&self, book: BookItems, config: &BookConfig) -> Result<(), Box> { + debug!("[fn]: render"); + let mut handlebars = Handlebars::new(); + + // Load template + let t = theme::get_index_hbs(); + + // Register template + debug!("[*]: Register handlebars template"); + try!(handlebars.register_template_string("index", t.to_owned())); + + // Register helpers + debug!("[*]: Register handlebars helpers"); + handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc)); + handlebars.register_helper("previous", Box::new(helpers::navigation::previous)); + handlebars.register_helper("next", Box::new(helpers::navigation::next)); + + let mut data = try!(make_data(book.clone(), config)); + + // Check if dest directory exists + debug!("[*]: Check if destination directory exists"); + match utils::path::create_path(config.dest()) { + Err(_) => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Unexcpected error when constructing destination path"))), + _ => {}, + }; + + // Render a file for every entry in the book + let mut index = true; + for (_, item) in book { + + if item.path != PathBuf::new() { + + let path = config.src().join(&item.path); + + debug!("[*]: Opening file: {:?}", path); + let mut f = try!(File::open(&path)); + let mut content: String = String::new(); + + debug!("[*]: Reading file"); + try!(f.read_to_string(&mut content)); + + // Render markdown using the pulldown-cmark crate + content = render_html(&content); + + // Remove content from previous file and render content for this one + data.remove("path"); + data.insert("path".to_string(), item.path.to_str().unwrap().to_json()); + + // Remove content from previous file and render content for this one + data.remove("content"); + data.insert("content".to_string(), content.to_json()); + + // Remove path to root from previous file and render content for this one + data.remove("path_to_root"); + data.insert("path_to_root".to_string(), utils::path::path_to_root(&item.path).to_json()); + + // Rendere the handlebars template with the data + debug!("[*]: Render template"); + let rendered = try!(handlebars.render("index", &data)); + + debug!("[*] Write to file"); + // Write to file + let mut file = try!(create_file(config.dest(), &item.path)); + try!(file.write_all(&rendered.into_bytes())); + + // Create an index.html from the first element in SUMMARY.md + if index { + debug!("[*] index.html"); + try!(fs::copy( + config.dest().join(&item.path.with_extension("html")), + config.dest().join("index.html") + )); + + println!( + "[*] Creating index.html from {:?} ✓", + config.dest().join(&item.path.with_extension("html")) + ); + index = false; + } + } + } + + // Copy static files (js, css, images, ...) + + debug!("[*] Copy static files"); + // JavaScript + let mut js_file = try!(File::create(config.dest().join("book.js"))); + try!(js_file.write_all(theme::get_js())); + + // Css + let mut css_file = try!(File::create(config.dest().join("book.css"))); + try!(css_file.write_all(theme::get_css())); + + Ok(()) + } +} + +impl HtmlHandlebars { + pub fn new() -> Self { + HtmlHandlebars + } + + /*fn _load_template(&self, path: &Path) -> Result> { + let mut file = try!(File::open(path)); + let mut s = String::new(); + try!(file.read_to_string(&mut s)); + Ok(s) + }*/ +} + +fn create_file(working_directory: &Path, path: &Path) -> Result> { + + debug!("[fn]: create_file"); + + debug!("[*]: extract filename"); + // Extract filename + let mut file_name; + if let Some(name) = path.file_stem() { + file_name = String::from(name.to_str().unwrap()); + } + else { return Err(Box::new(io::Error::new(io::ErrorKind::Other, "No filename"))) } + + file_name.push_str(".html"); + + // Delete filename from path + let mut path = path.to_path_buf(); + path.pop(); + + // Create directories if they do not exist + let mut constructed_path = PathBuf::from(working_directory); + + for component in path.components() { + + let mut dir; + match component { + Component::Normal(_) => { dir = PathBuf::from(component.as_os_str()); }, + _ => continue, + } + + constructed_path.push(&dir); + + // Check if path exists + match metadata(&constructed_path) { + // Any way to combine the Err and first Ok branch ?? + Err(_) => { + debug!("[*]: Create {:?}", constructed_path); + try!(fs::create_dir(&constructed_path)) + }, + Ok(f) => { + if !f.is_dir() { + debug!("[*]: Create {:?}", constructed_path); + try!(fs::create_dir(&constructed_path)) + } else { + debug!("[*]: Directory exists: {:?}", constructed_path); + continue + } + }, + } + + } + debug!("[*]: Create {:?}", constructed_path.join(&file_name)); + let file = try!(File::create( + constructed_path.join(&file_name) + )); + println!("[*] Create file: {:?} ✓", constructed_path.join(&file_name)); + + Ok(file) +} + + +fn make_data(book: BookItems, config: &BookConfig) -> Result, Box> { + debug!("[fn]: make_data"); + + let mut data = BTreeMap::new(); + data.insert("language".to_string(), "en".to_json()); + data.insert("title".to_string(), config.title.to_json()); + + let mut chapters = vec![]; + + for (section, item) in book { + let mut chapter = BTreeMap::new(); + chapter.insert("section".to_string(), section.to_json()); + chapter.insert("name".to_string(), item.name.to_json()); + chapter.insert("path".to_string(), item.path.to_str().unwrap().to_json()); + + chapters.push(chapter); + } + + data.insert("chapters".to_string(), chapters.to_json()); + + debug!("[*]: JSON constructed"); + Ok(data) +} + +fn render_html(text: &str) -> String { + let mut s = String::with_capacity(text.len() * 3 / 2); + let p = Parser::new(&text); + html::push_html(&mut s, p); + s +} diff --git a/src/renderer/html_handlebars/helpers/mod.rs b/src/renderer/html_handlebars/helpers/mod.rs new file mode 100644 index 00000000..62fc6149 --- /dev/null +++ b/src/renderer/html_handlebars/helpers/mod.rs @@ -0,0 +1,2 @@ +pub mod navigation; +pub mod toc; diff --git a/src/renderer/html_handlebars/hbs_navigation_helper.rs b/src/renderer/html_handlebars/helpers/navigation.rs similarity index 100% rename from src/renderer/html_handlebars/hbs_navigation_helper.rs rename to src/renderer/html_handlebars/helpers/navigation.rs diff --git a/src/renderer/html_handlebars/hbs_toc_helper.rs b/src/renderer/html_handlebars/helpers/toc.rs similarity index 100% rename from src/renderer/html_handlebars/hbs_toc_helper.rs rename to src/renderer/html_handlebars/helpers/toc.rs diff --git a/src/renderer/html_handlebars/mod.rs b/src/renderer/html_handlebars/mod.rs index 8273a317..f1df6d8d 100644 --- a/src/renderer/html_handlebars/mod.rs +++ b/src/renderer/html_handlebars/mod.rs @@ -1,225 +1,4 @@ -extern crate handlebars; -extern crate rustc_serialize; -extern crate pulldown_cmark; +pub use self::hbs_renderer::HtmlHandlebars; -mod hbs_toc_helper; -mod hbs_navigation_helper; -use self::hbs_toc_helper::RenderToc; - -use renderer::Renderer; -use book::{BookItems, BookConfig}; -use {theme, utils}; - -use std::path::{Path, PathBuf, Component}; -use std::fs::{self, File, metadata}; -use std::error::Error; -use std::io::{self, Read, Write}; -use std::collections::BTreeMap; - -use self::handlebars::{Handlebars, JsonRender}; -use self::rustc_serialize::json::{Json, ToJson}; -use self::pulldown_cmark::{Parser, html}; - -pub struct HtmlHandlebars; - -impl Renderer for HtmlHandlebars { - fn render(&self, book: BookItems, config: &BookConfig) -> Result<(), Box> { - debug!("[fn]: render"); - let mut handlebars = Handlebars::new(); - - // Load template - let t = theme::get_index_hbs(); - - // Register template - debug!("[*]: Register handlebars template"); - try!(handlebars.register_template_string("index", t.to_owned())); - - // Register helpers - debug!("[*]: Register handlebars helpers"); - handlebars.register_helper("toc", Box::new(RenderToc)); - handlebars.register_helper("previous", Box::new(hbs_navigation_helper::previous)); - handlebars.register_helper("next", Box::new(hbs_navigation_helper::next)); - - let mut data = try!(make_data(book.clone(), config)); - - // Check if dest directory exists - debug!("[*]: Check if destination directory exists"); - match utils::path::create_path(config.dest()) { - Err(_) => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Unexcpected error when constructing destination path"))), - _ => {}, - }; - - // Render a file for every entry in the book - let mut index = true; - for (_, item) in book { - - if item.path != PathBuf::new() { - - let path = config.src().join(&item.path); - - debug!("[*]: Opening file: {:?}", path); - let mut f = try!(File::open(&path)); - let mut content: String = String::new(); - - debug!("[*]: Reading file"); - try!(f.read_to_string(&mut content)); - - // Render markdown using the pulldown-cmark crate - content = render_html(&content); - - // Remove content from previous file and render content for this one - data.remove("path"); - data.insert("path".to_string(), item.path.to_str().unwrap().to_json()); - - // Remove content from previous file and render content for this one - data.remove("content"); - data.insert("content".to_string(), content.to_json()); - - // Remove path to root from previous file and render content for this one - data.remove("path_to_root"); - data.insert("path_to_root".to_string(), utils::path::path_to_root(&item.path).to_json()); - - // Rendere the handlebars template with the data - debug!("[*]: Render template"); - let rendered = try!(handlebars.render("index", &data)); - - debug!("[*] Write to file"); - // Write to file - let mut file = try!(create_file(config.dest(), &item.path)); - try!(file.write_all(&rendered.into_bytes())); - - // Create an index.html from the first element in SUMMARY.md - if index { - debug!("[*] index.html"); - try!(fs::copy( - config.dest().join(&item.path.with_extension("html")), - config.dest().join("index.html") - )); - - println!( - "[*] Creating index.html from {:?} ✓", - config.dest().join(&item.path.with_extension("html")) - ); - index = false; - } - } - } - - // Copy static files (js, css, images, ...) - - debug!("[*] Copy static files"); - // JavaScript - let mut js_file = try!(File::create(config.dest().join("book.js"))); - try!(js_file.write_all(theme::get_js())); - - // Css - let mut css_file = try!(File::create(config.dest().join("book.css"))); - try!(css_file.write_all(theme::get_css())); - - Ok(()) - } -} - -impl HtmlHandlebars { - pub fn new() -> Self { - HtmlHandlebars - } - - /*fn _load_template(&self, path: &Path) -> Result> { - let mut file = try!(File::open(path)); - let mut s = String::new(); - try!(file.read_to_string(&mut s)); - Ok(s) - }*/ -} - -fn create_file(working_directory: &Path, path: &Path) -> Result> { - - debug!("[fn]: create_file"); - - debug!("[*]: extract filename"); - // Extract filename - let mut file_name; - if let Some(name) = path.file_stem() { - file_name = String::from(name.to_str().unwrap()); - } - else { return Err(Box::new(io::Error::new(io::ErrorKind::Other, "No filename"))) } - - file_name.push_str(".html"); - - // Delete filename from path - let mut path = path.to_path_buf(); - path.pop(); - - // Create directories if they do not exist - let mut constructed_path = PathBuf::from(working_directory); - - for component in path.components() { - - let mut dir; - match component { - Component::Normal(_) => { dir = PathBuf::from(component.as_os_str()); }, - _ => continue, - } - - constructed_path.push(&dir); - - // Check if path exists - match metadata(&constructed_path) { - // Any way to combine the Err and first Ok branch ?? - Err(_) => { - debug!("[*]: Create {:?}", constructed_path); - try!(fs::create_dir(&constructed_path)) - }, - Ok(f) => { - if !f.is_dir() { - debug!("[*]: Create {:?}", constructed_path); - try!(fs::create_dir(&constructed_path)) - } else { - debug!("[*]: Directory exists: {:?}", constructed_path); - continue - } - }, - } - - } - debug!("[*]: Create {:?}", constructed_path.join(&file_name)); - let file = try!(File::create( - constructed_path.join(&file_name) - )); - println!("[*] Create file: {:?} ✓", constructed_path.join(&file_name)); - - Ok(file) -} - - -fn make_data(book: BookItems, config: &BookConfig) -> Result, Box> { - debug!("[fn]: make_data"); - - let mut data = BTreeMap::new(); - data.insert("language".to_string(), "en".to_json()); - data.insert("title".to_string(), config.title.to_json()); - - let mut chapters = vec![]; - - for (section, item) in book { - let mut chapter = BTreeMap::new(); - chapter.insert("section".to_string(), section.to_json()); - chapter.insert("name".to_string(), item.name.to_json()); - chapter.insert("path".to_string(), item.path.to_str().unwrap().to_json()); - - chapters.push(chapter); - } - - data.insert("chapters".to_string(), chapters.to_json()); - - debug!("[*]: JSON constructed"); - Ok(data) -} - -fn render_html(text: &str) -> String { - let mut s = String::with_capacity(text.len() * 3 / 2); - let p = Parser::new(&text); - html::push_html(&mut s, p); - s -} +mod hbs_renderer; +mod helpers;