First big step for the html renderer, it reads an handlebars template and creates the files from SUMMARY.md respecting the source folder structure
This commit is contained in:
parent
35be20da8b
commit
4d4f35ecba
|
@ -5,3 +5,5 @@ authors = ["Mathieu David <mathieudavid@mathieudavid.org>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "*"
|
getopts = "*"
|
||||||
|
handlebars = "*"
|
||||||
|
rustc-serialize = "*"
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct BookConfig {
|
pub struct BookConfig {
|
||||||
|
title: String,
|
||||||
|
author: String,
|
||||||
dest: PathBuf,
|
dest: PathBuf,
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
indent_spaces: i32,
|
indent_spaces: i32,
|
||||||
|
@ -11,6 +14,8 @@ pub struct BookConfig {
|
||||||
impl BookConfig {
|
impl BookConfig {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
BookConfig {
|
BookConfig {
|
||||||
|
title: String::new(),
|
||||||
|
author: String::new(),
|
||||||
dest: PathBuf::from("book"),
|
dest: PathBuf::from("book"),
|
||||||
src: PathBuf::from("src"),
|
src: PathBuf::from("src"),
|
||||||
indent_spaces: 4,
|
indent_spaces: 4,
|
||||||
|
@ -18,21 +23,31 @@ impl BookConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dest(&self) -> PathBuf {
|
pub fn dest(&self) -> &Path {
|
||||||
self.dest.clone()
|
&self.dest
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dest(mut self, dest: PathBuf) -> Self {
|
pub fn set_dest(&mut self, dest: &Path) -> &mut Self {
|
||||||
self.dest = dest;
|
self.dest = dest.to_owned();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn src(&self) -> PathBuf {
|
pub fn src(&self) -> &Path {
|
||||||
self.src.clone()
|
&self.src
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_src(mut self, src: PathBuf) -> Self {
|
pub fn set_src(&mut self, src: &Path) -> &mut Self {
|
||||||
self.src = src;
|
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
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 struct BookItem {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
@ -7,12 +12,14 @@ pub struct BookItem {
|
||||||
spacer: bool,
|
spacer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ItemType {
|
#[derive(Debug, Clone)]
|
||||||
Pre,
|
pub struct BookItems<'a> {
|
||||||
Chapter,
|
pub items: &'a [BookItem],
|
||||||
Post
|
pub current_index: usize,
|
||||||
|
pub stack: Vec<(&'a [BookItem], usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl BookItem {
|
impl BookItem {
|
||||||
|
|
||||||
pub fn new(name: String, path: PathBuf) -> Self {
|
pub fn new(name: String, path: PathBuf) -> Self {
|
||||||
|
@ -33,9 +40,53 @@ impl BookItem {
|
||||||
spacer: true,
|
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<String, Json> = 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
use std::path::PathBuf;
|
use std::path::Path;
|
||||||
use std::fs::{self, File, metadata};
|
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 {BookConfig, BookItem};
|
||||||
use book::bookitem::BookItem;
|
use book::BookItems;
|
||||||
use parse;
|
use parse;
|
||||||
|
use renderer::Renderer;
|
||||||
|
use renderer::HtmlHandlebars;
|
||||||
|
|
||||||
pub struct MDBook {
|
pub struct MDBook {
|
||||||
title: String,
|
|
||||||
author: String,
|
|
||||||
config: BookConfig,
|
config: BookConfig,
|
||||||
pub content: Vec<BookItem>
|
pub content: Vec<BookItem>,
|
||||||
|
renderer: Box<Renderer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MDBook {
|
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
|
// Hacky way to check if the path exists... Until PathExt moves to stable
|
||||||
match metadata(path) {
|
match metadata(path) {
|
||||||
|
@ -28,16 +30,24 @@ impl MDBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
MDBook {
|
MDBook {
|
||||||
title: String::from(""),
|
|
||||||
author: String::from(""),
|
|
||||||
content: vec![],
|
content: vec![],
|
||||||
config: BookConfig::new()
|
config: BookConfig::new()
|
||||||
.set_src(path.join("src"))
|
.set_src(&path.join("src"))
|
||||||
.set_dest(path.join("book")),
|
.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<Error>> {
|
||||||
|
|
||||||
let dest = self.config.dest();
|
let dest = self.config.dest();
|
||||||
let src = self.config.src();
|
let src = self.config.src();
|
||||||
|
@ -87,38 +97,43 @@ impl MDBook {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&mut self) -> Result<()> {
|
pub fn build(&mut self) -> Result<(), Box<Error>> {
|
||||||
|
|
||||||
try!(self.parse_summary());
|
try!(self.parse_summary());
|
||||||
|
|
||||||
|
try!(self.renderer.render(
|
||||||
|
self.iter(),
|
||||||
|
&self.config,
|
||||||
|
));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Builder functions
|
// Builder functions
|
||||||
pub fn set_dest(mut self, dest: PathBuf) -> Self {
|
pub fn set_dest(mut self, dest: &Path) -> Self {
|
||||||
self.config = self.config.set_dest(dest);
|
self.config.set_dest(dest);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_src(mut self, src: PathBuf) -> Self {
|
pub fn set_src(mut self, src: &Path) -> Self {
|
||||||
self.config = self.config.set_src(src);
|
self.config.set_src(src);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_title(mut self, title: String) -> Self {
|
pub fn set_title(mut self, title: &str) -> Self {
|
||||||
self.title = title;
|
self.config.set_title(title);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_author(mut self, author: String) -> Self {
|
pub fn set_author(mut self, author: &str) -> Self {
|
||||||
self.author = author;
|
self.config.set_author(author);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Construct book
|
// Construct book
|
||||||
fn parse_summary(&mut self) -> Result<()> {
|
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
|
||||||
|
|
||||||
// When append becomes stale, use self.content.append() ...
|
// When append becomes stale, use self.content.append() ...
|
||||||
let book_items = try!(parse::construct_bookitems(&self.config.src().join("SUMMARY.md")));
|
let book_items = try!(parse::construct_bookitems(&self.config.src().join("SUMMARY.md")));
|
||||||
|
@ -127,11 +142,6 @@ impl MDBook {
|
||||||
self.content.push(item)
|
self.content.push(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug
|
|
||||||
for item in &self.content {
|
|
||||||
println!("name: \"{}\" path: {:?}", item.name, item.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,6 @@ pub mod mdbook;
|
||||||
pub mod bookitem;
|
pub mod bookitem;
|
||||||
mod bookconfig;
|
mod bookconfig;
|
||||||
|
|
||||||
|
pub use self::bookitem::{BookItem, BookItems};
|
||||||
use self::bookconfig::BookConfig;
|
pub use self::bookconfig::BookConfig;
|
||||||
use self::bookitem::BookItem;
|
pub use self::mdbook::MDBook;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
mod book;
|
mod book;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
pub mod renderer;
|
||||||
|
pub mod theme;
|
||||||
|
|
||||||
pub use book::mdbook::MDBook;
|
pub use book::MDBook;
|
||||||
use book::bookitem::BookItem;
|
pub use book::BookItem;
|
||||||
|
pub use book::BookConfig;
|
||||||
|
|
|
@ -82,7 +82,7 @@ fn parse_line(l: &str) -> Option<BookItem> {
|
||||||
let mut name;
|
let mut name;
|
||||||
let mut path;
|
let mut path;
|
||||||
// Remove leading and trailing spaces or tabs
|
// 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) {
|
if let Some(c) = line.chars().nth(0) {
|
||||||
match c {
|
match c {
|
||||||
|
|
|
@ -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<Error>> {
|
||||||
|
|
||||||
|
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<String, Box<Error>> {
|
||||||
|
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<File, Box<Error>> {
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub use self::renderer::Renderer;
|
||||||
|
pub use self::html_handlebars::HtmlHandlebars;
|
||||||
|
|
||||||
|
pub mod renderer;
|
||||||
|
pub mod html_handlebars;
|
|
@ -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<Error>>;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="{{ language }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||||
|
<meta name="description" content="{% block description %}{% endblock %}">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="book.css" media="screen" title="no title" charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<script src="book.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
pub fn get_index_hbs() -> &'static str {
|
||||||
|
let index = include_str!("index.hbs");
|
||||||
|
index
|
||||||
|
}
|
Loading…
Reference in New Issue