From 4d4f35ecba8ac6dfc047bcea3735f350f2848a82 Mon Sep 17 00:00:00 2001 From: Mathieu David Date: Sun, 19 Jul 2015 00:08:38 +0200 Subject: [PATCH] First big step for the html renderer, it reads an handlebars template and creates the files from SUMMARY.md respecting the source folder structure --- Cargo.toml | 2 + src/book/bookconfig.rs | 33 ++++++--- src/book/bookitem.rs | 71 ++++++++++++++++--- src/book/mdbook.rs | 66 +++++++++-------- src/book/mod.rs | 6 +- src/lib.rs | 7 +- src/parse/summary.rs | 2 +- src/renderer/html_handlebars.rs | 121 ++++++++++++++++++++++++++++++++ src/renderer/mod.rs | 5 ++ src/renderer/renderer.rs | 8 +++ src/theme/index.hbs | 16 +++++ src/theme/mod.rs | 5 ++ 12 files changed, 289 insertions(+), 53 deletions(-) create mode 100644 src/renderer/html_handlebars.rs create mode 100644 src/renderer/mod.rs create mode 100644 src/renderer/renderer.rs create mode 100644 src/theme/index.hbs create mode 100644 src/theme/mod.rs diff --git a/Cargo.toml b/Cargo.toml index aa3b2217..93637fea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ authors = ["Mathieu David "] [dependencies] getopts = "*" +handlebars = "*" +rustc-serialize = "*" diff --git a/src/book/bookconfig.rs b/src/book/bookconfig.rs index 3e8046b1..32c40305 100644 --- a/src/book/bookconfig.rs +++ b/src/book/bookconfig.rs @@ -1,6 +1,9 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +#[derive(Debug, Clone)] pub struct BookConfig { + title: String, + author: String, dest: PathBuf, src: PathBuf, indent_spaces: i32, @@ -11,6 +14,8 @@ pub struct BookConfig { impl BookConfig { pub fn new() -> Self { BookConfig { + title: String::new(), + author: String::new(), dest: PathBuf::from("book"), src: PathBuf::from("src"), indent_spaces: 4, @@ -18,21 +23,31 @@ impl BookConfig { } } - pub fn dest(&self) -> PathBuf { - self.dest.clone() + pub fn dest(&self) -> &Path { + &self.dest } - pub fn set_dest(mut self, dest: PathBuf) -> Self { - self.dest = dest; + pub fn set_dest(&mut self, dest: &Path) -> &mut Self { + self.dest = dest.to_owned(); self } - pub fn src(&self) -> PathBuf { - self.src.clone() + pub fn src(&self) -> &Path { + &self.src } - pub fn set_src(mut self, src: PathBuf) -> Self { - self.src = src; + pub fn set_src(&mut self, src: &Path) -> &mut Self { + self.src = src.to_owned(); + self + } + + pub fn set_title(&mut self, title: &str) -> &mut Self { + self.title = title.to_owned(); + self + } + + pub fn set_author(&mut self, author: &str) -> &mut Self { + self.author = author.to_owned(); self } } diff --git a/src/book/bookitem.rs b/src/book/bookitem.rs index 1082302e..232d9ad9 100644 --- a/src/book/bookitem.rs +++ b/src/book/bookitem.rs @@ -1,5 +1,10 @@ -use std::path::PathBuf; +extern crate rustc_serialize; +use self::rustc_serialize::json::{Json, ToJson}; +use std::path::PathBuf; +use std::collections::BTreeMap; + +#[derive(Debug, Clone)] pub struct BookItem { pub name: String, pub path: PathBuf, @@ -7,12 +12,14 @@ pub struct BookItem { spacer: bool, } -pub enum ItemType { - Pre, - Chapter, - Post +#[derive(Debug, Clone)] +pub struct BookItems<'a> { + pub items: &'a [BookItem], + pub current_index: usize, + pub stack: Vec<(&'a [BookItem], usize)>, } + impl BookItem { pub fn new(name: String, path: PathBuf) -> Self { @@ -33,9 +40,53 @@ impl BookItem { spacer: true, } } - - fn push(&mut self, item: BookItem) { - self.sub_items.push(item); - } - +} + + +impl ToJson for BookItem { + fn to_json(&self) -> Json { + let mut m: BTreeMap = BTreeMap::new(); + m.insert("name".to_string(), self.name.to_json()); + m.insert("path".to_string(),self.path.to_str() + .expect("Json conversion failed for path").to_json() + ); + m.to_json() + } +} + + + +// Shamelessly copied from Rustbook +// (https://github.com/rust-lang/rust/blob/master/src/rustbook/book.rs) +impl<'a> Iterator for BookItems<'a> { + type Item = (String, &'a BookItem); + + fn next(&mut self) -> Option<(String, &'a BookItem)> { + loop { + if self.current_index >= self.items.len() { + match self.stack.pop() { + None => return None, + Some((parent_items, parent_idx)) => { + self.items = parent_items; + self.current_index = parent_idx + 1; + } + } + } else { + let cur = self.items.get(self.current_index).unwrap(); + + let mut section = "".to_string(); + for &(_, idx) in &self.stack { + section.push_str(&(idx + 1).to_string()[..]); + section.push('.'); + } + section.push_str(&(self.current_index + 1).to_string()[..]); + section.push('.'); + + self.stack.push((self.items, self.current_index)); + self.items = &cur.sub_items[..]; + self.current_index = 0; + return Some((section, cur)) + } + } + } } diff --git a/src/book/mdbook.rs b/src/book/mdbook.rs index cdaab1aa..f7fefd9a 100644 --- a/src/book/mdbook.rs +++ b/src/book/mdbook.rs @@ -1,21 +1,23 @@ -use std::path::PathBuf; +use std::path::Path; use std::fs::{self, File, metadata}; -use std::io::{Write, Result}; +use std::io::{self, Write}; +use std::error::Error; -use book::bookconfig::BookConfig; -use book::bookitem::BookItem; +use {BookConfig, BookItem}; +use book::BookItems; use parse; +use renderer::Renderer; +use renderer::HtmlHandlebars; pub struct MDBook { - title: String, - author: String, config: BookConfig, - pub content: Vec + pub content: Vec, + renderer: Box, } impl MDBook { - pub fn new(path: &PathBuf) -> Self { + pub fn new(path: &Path) -> MDBook { // Hacky way to check if the path exists... Until PathExt moves to stable match metadata(path) { @@ -28,16 +30,24 @@ impl MDBook { } MDBook { - title: String::from(""), - author: String::from(""), content: vec![], config: BookConfig::new() - .set_src(path.join("src")) - .set_dest(path.join("book")), + .set_src(&path.join("src")) + .set_dest(&path.join("book")) + .to_owned(), + renderer: Box::new(HtmlHandlebars::new()), } } - pub fn init(&self) -> Result<()> { + pub fn iter(&self) -> BookItems { + BookItems { + items: &self.content[..], + current_index: 0, + stack: Vec::new(), + } + } + + pub fn init(&self) -> Result<(), Box> { let dest = self.config.dest(); let src = self.config.src(); @@ -87,38 +97,43 @@ impl MDBook { return Ok(()); } - pub fn build(&mut self) -> Result<()> { + pub fn build(&mut self) -> Result<(), Box> { try!(self.parse_summary()); + try!(self.renderer.render( + self.iter(), + &self.config, + )); + Ok(()) } // Builder functions - pub fn set_dest(mut self, dest: PathBuf) -> Self { - self.config = self.config.set_dest(dest); + pub fn set_dest(mut self, dest: &Path) -> Self { + self.config.set_dest(dest); self } - pub fn set_src(mut self, src: PathBuf) -> Self { - self.config = self.config.set_src(src); + pub fn set_src(mut self, src: &Path) -> Self { + self.config.set_src(src); self } - pub fn set_title(mut self, title: String) -> Self { - self.title = title; + pub fn set_title(mut self, title: &str) -> Self { + self.config.set_title(title); self } - pub fn set_author(mut self, author: String) -> Self { - self.author = author; + pub fn set_author(mut self, author: &str) -> Self { + self.config.set_author(author); self } // Construct book - fn parse_summary(&mut self) -> Result<()> { + fn parse_summary(&mut self) -> Result<(), Box> { // When append becomes stale, use self.content.append() ... let book_items = try!(parse::construct_bookitems(&self.config.src().join("SUMMARY.md"))); @@ -127,11 +142,6 @@ impl MDBook { self.content.push(item) } - // Debug - for item in &self.content { - println!("name: \"{}\" path: {:?}", item.name, item.path); - } - Ok(()) } diff --git a/src/book/mod.rs b/src/book/mod.rs index 0c1861e1..64a119b0 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -2,6 +2,6 @@ pub mod mdbook; pub mod bookitem; mod bookconfig; - -use self::bookconfig::BookConfig; -use self::bookitem::BookItem; +pub use self::bookitem::{BookItem, BookItems}; +pub use self::bookconfig::BookConfig; +pub use self::mdbook::MDBook; diff --git a/src/lib.rs b/src/lib.rs index 5c96c703..1b421767 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ mod book; mod parse; +pub mod renderer; +pub mod theme; -pub use book::mdbook::MDBook; -use book::bookitem::BookItem; +pub use book::MDBook; +pub use book::BookItem; +pub use book::BookConfig; diff --git a/src/parse/summary.rs b/src/parse/summary.rs index 45425430..ef4ff1db 100644 --- a/src/parse/summary.rs +++ b/src/parse/summary.rs @@ -82,7 +82,7 @@ fn parse_line(l: &str) -> Option { let mut name; let mut path; // Remove leading and trailing spaces or tabs - let mut line = l.trim_matches(|c: char| { c == ' ' || c == '\t' }); + let line = l.trim_matches(|c: char| { c == ' ' || c == '\t' }); if let Some(c) = line.chars().nth(0) { match c { diff --git a/src/renderer/html_handlebars.rs b/src/renderer/html_handlebars.rs new file mode 100644 index 00000000..d1be3696 --- /dev/null +++ b/src/renderer/html_handlebars.rs @@ -0,0 +1,121 @@ +extern crate handlebars; +extern crate rustc_serialize; + +use renderer::Renderer; +use book::{BookItems, BookItem, BookConfig}; +use theme; + +use std::path::{Path, PathBuf, Component}; +use std::fs::{self, File, metadata}; +use std::error::Error; +use std::io::{self, Read, Write}; +use self::handlebars::Handlebars; +use self::rustc_serialize::json::{Json, ToJson}; +use std::collections::BTreeMap; + +pub struct HtmlHandlebars; + +impl Renderer for HtmlHandlebars { + fn render(&self, book: BookItems, config: &BookConfig) -> Result<(), Box> { + + let mut handlebars = Handlebars::new(); + + // Load template + let t = theme::get_index_hbs(); + + // Register template + try!(handlebars.register_template_string("index", t.to_owned())); + + let mut data = BTreeMap::new(); + let mut chapters: Vec<(String, BookItem)> = vec![]; + + // Hacky way to prevent move error... Not optimal + for (section, item) in book.clone() { + chapters.push((section, item.clone())); + } + data.insert("chapters".to_string(), chapters.to_json()); + + for (_, item) in book { + + if item.path != PathBuf::new() { + + let rendered = try!(handlebars.render("index", &data)); + + let mut file = try!(create_file(config.dest(), &item.path)); + try!(file.write_all(&rendered.into_bytes())); + } + } + + 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> { + + println!("create_file:\n\t{:?}\n\t{:?}", working_directory, path); + + // 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); + + println!("constructed path= {:?}\ndir= {:?}", constructed_path, dir.as_os_str()); + + // Check if path exists + match metadata(&constructed_path) { + // Any way to combine the Err and first Ok branch ?? + Err(_) => { + try!(fs::create_dir(&constructed_path)) + }, + Ok(f) => { + if !f.is_dir() { + try!(fs::create_dir(&constructed_path)) + } else { + println!("Exists ??"); + continue + } + }, + } + + } + + let file = try!(File::create( + constructed_path.join(file_name) + )); + + Ok(file) +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs new file mode 100644 index 00000000..1fbb507f --- /dev/null +++ b/src/renderer/mod.rs @@ -0,0 +1,5 @@ +pub use self::renderer::Renderer; +pub use self::html_handlebars::HtmlHandlebars; + +pub mod renderer; +pub mod html_handlebars; diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs new file mode 100644 index 00000000..1165523f --- /dev/null +++ b/src/renderer/renderer.rs @@ -0,0 +1,8 @@ +use book::{BookItem, BookItems}; +use book::BookConfig; + +use std::error::Error; + +pub trait Renderer { + fn render(&self, book: BookItems, config: &BookConfig) -> Result<(), Box>; +} diff --git a/src/theme/index.hbs b/src/theme/index.hbs new file mode 100644 index 00000000..94d78319 --- /dev/null +++ b/src/theme/index.hbs @@ -0,0 +1,16 @@ + + + + + {{ title }} + + + + + + + + + + + diff --git a/src/theme/mod.rs b/src/theme/mod.rs new file mode 100644 index 00000000..e9d674ea --- /dev/null +++ b/src/theme/mod.rs @@ -0,0 +1,5 @@ + +pub fn get_index_hbs() -> &'static str { + let index = include_str!("index.hbs"); + index +}