mirror of
https://github.com/rust-lang/mdBook
synced 2024-12-14 06:42:35 +00:00
Big refactoring, now using enum for different book items (Chapter, Affix, Spacer, ...) Closes #9
This commit is contained in:
parent
6962731474
commit
a050d9c4ad
7 changed files with 300 additions and 158 deletions
|
@ -11,3 +11,5 @@
|
|||
- [index.hbs](format/theme/index-hbs.md)
|
||||
- [Syntax highlighting](format/theme/syntax-highlighting.md)
|
||||
- [Rust Library](lib/lib.md)
|
||||
-----------
|
||||
[Contributors](misc/contributors.md)
|
||||
|
|
|
@ -5,11 +5,17 @@ use std::path::PathBuf;
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BookItem {
|
||||
pub enum BookItem {
|
||||
Chapter(String, Chapter), // String = section
|
||||
Affix(Chapter),
|
||||
Spacer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Chapter {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub sub_items: Vec<BookItem>,
|
||||
spacer: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -20,30 +26,21 @@ pub struct BookItems<'a> {
|
|||
}
|
||||
|
||||
|
||||
impl BookItem {
|
||||
impl Chapter {
|
||||
|
||||
pub fn new(name: String, path: PathBuf) -> Self {
|
||||
|
||||
BookItem {
|
||||
Chapter {
|
||||
name: name,
|
||||
path: path,
|
||||
sub_items: vec![],
|
||||
spacer: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn _spacer() -> Self {
|
||||
BookItem {
|
||||
name: String::from("SPACER"),
|
||||
path: PathBuf::new(),
|
||||
sub_items: vec![],
|
||||
spacer: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl ToJson for BookItem {
|
||||
impl ToJson for Chapter {
|
||||
|
||||
fn to_json(&self) -> Json {
|
||||
let mut m: BTreeMap<String, Json> = BTreeMap::new();
|
||||
m.insert("name".to_string(), self.name.to_json());
|
||||
|
@ -59,9 +56,9 @@ impl ToJson for BookItem {
|
|||
// 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);
|
||||
type Item = &'a BookItem;
|
||||
|
||||
fn next(&mut self) -> Option<(String, &'a BookItem)> {
|
||||
fn next(&mut self) -> Option<&'a BookItem> {
|
||||
loop {
|
||||
if self.current_index >= self.items.len() {
|
||||
match self.stack.pop() {
|
||||
|
@ -74,18 +71,18 @@ impl<'a> Iterator for BookItems<'a> {
|
|||
} 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('.');
|
||||
|
||||
match cur {
|
||||
&BookItem::Chapter(_, ref ch) | &BookItem::Affix(ref ch) => {
|
||||
self.stack.push((self.items, self.current_index));
|
||||
self.items = &cur.sub_items[..];
|
||||
self.items = &ch.sub_items[..];
|
||||
self.current_index = 0;
|
||||
return Some((section, cur))
|
||||
},
|
||||
&BookItem::Spacer => {
|
||||
self.current_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(cur)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,21 +126,29 @@ impl MDBook {
|
|||
// parse SUMMARY.md, and create the missing item related file
|
||||
try!(self.parse_summary());
|
||||
|
||||
for (_, item) in self.iter() {
|
||||
if item.path != PathBuf::new() {
|
||||
let path = self.config.get_src().join(&item.path);
|
||||
debug!("[*]: constructing paths for missing files");
|
||||
for item in self.iter() {
|
||||
debug!("[*]: item: {:?}", item);
|
||||
match item {
|
||||
&BookItem::Spacer => continue,
|
||||
&BookItem::Chapter(_, ref ch) | &BookItem::Affix(ref ch) => {
|
||||
if ch.path != PathBuf::new() {
|
||||
let path = self.config.get_src().join(&ch.path);
|
||||
|
||||
if !path.exists() {
|
||||
debug!("[*]: {:?} does not exist, trying to create file", path);
|
||||
try!(::std::fs::create_dir_all(path.parent().unwrap()));
|
||||
let mut f = try!(File::create(path));
|
||||
|
||||
debug!("[*]: Writing to {:?}", path);
|
||||
try!(writeln!(f, "# {}", item.name));
|
||||
//debug!("[*]: Writing to {:?}", path);
|
||||
try!(writeln!(f, "# {}", ch.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("[*]: init done");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -300,14 +308,8 @@ impl MDBook {
|
|||
|
||||
// Construct book
|
||||
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
|
||||
|
||||
// When append becomes stable, use self.content.append() ...
|
||||
let book_items = try!(parse::construct_bookitems(&self.config.get_src().join("SUMMARY.md")));
|
||||
|
||||
for item in book_items {
|
||||
self.content.push(item)
|
||||
}
|
||||
|
||||
self.content = try!(parse::construct_bookitems(&self.config.get_src().join("SUMMARY.md")));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Result, Error, ErrorKind};
|
||||
use book::bookitem::BookItem;
|
||||
use book::bookitem::{BookItem, Chapter};
|
||||
|
||||
pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
|
||||
debug!("[fn]: construct_bookitems");
|
||||
|
@ -9,36 +9,106 @@ pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
|
|||
try!(try!(File::open(path)).read_to_string(&mut summary));
|
||||
|
||||
debug!("[*]: Parse SUMMARY.md");
|
||||
let top_items = try!(parse_level(&mut summary.split('\n').collect(), 0));
|
||||
|
||||
let top_items = try!(parse_level(&mut summary.split('\n').collect(), 0, vec![0]));
|
||||
debug!("[*]: Done parsing SUMMARY.md");
|
||||
Ok(top_items)
|
||||
}
|
||||
|
||||
fn parse_level(summary: &mut Vec<&str>, current_level: i32) -> Result<Vec<BookItem>> {
|
||||
fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32>) -> Result<Vec<BookItem>> {
|
||||
debug!("[fn]: parse_level");
|
||||
let mut items: Vec<BookItem> = vec![];
|
||||
|
||||
loop {
|
||||
if summary.len() <= 0 { break }
|
||||
|
||||
// Construct the book recursively
|
||||
while summary.len() > 0 {
|
||||
let item: BookItem;
|
||||
// Indentation level of the line to parse
|
||||
let level = try!(level(summary[0], 4));
|
||||
|
||||
if current_level > level { break }
|
||||
else if current_level < level {
|
||||
items.last_mut().unwrap().sub_items = try!(parse_level(summary, level))
|
||||
}
|
||||
else {
|
||||
// Do the thing
|
||||
if let Some(item) = parse_line(summary[0].clone()) {
|
||||
items.push(item);
|
||||
}
|
||||
summary.remove(0);
|
||||
}
|
||||
// if level < current_level we remove the last digit of section, exit the current function,
|
||||
// and return the parsed level to the calling function.
|
||||
if level < current_level { break }
|
||||
|
||||
// if level > current_level we call ourselves to go one level deeper
|
||||
if level > current_level {
|
||||
// Level can not be root level !!
|
||||
// Add a sub-number to section
|
||||
section.push(1);
|
||||
let last = items.pop().expect("There should be at least one item since this can't be the root level");
|
||||
|
||||
item = if let BookItem::Chapter(ref s, ref ch) = last {
|
||||
let mut ch = ch.clone();
|
||||
ch.sub_items = try!(parse_level(summary, level, section.clone()));
|
||||
items.push(BookItem::Chapter(s.clone(), ch));
|
||||
|
||||
// Remove the last number from the section, because we got back to our level..
|
||||
section.pop();
|
||||
continue
|
||||
} else {
|
||||
return Err(Error::new( ErrorKind::Other, format!(
|
||||
"Your summary.md is messed up\n\n
|
||||
Prefix, Suffix and Spacer elements can only exist on the root level.\n
|
||||
Prefix elements can only exist before any chapter and there can be no chapters after suffix elements."
|
||||
)))
|
||||
};
|
||||
|
||||
} else {
|
||||
// level and current_level are the same, parse the line
|
||||
item = if let Some(parsed_item) = parse_line(summary[0]) {
|
||||
|
||||
// Eliminate possible errors and set section to -1 after suffix
|
||||
match parsed_item {
|
||||
// error if level != 0 and BookItem is != Chapter
|
||||
BookItem::Affix(_) | BookItem::Spacer if level > 0 => {
|
||||
return Err(Error::new( ErrorKind::Other, format!(
|
||||
"Your summary.md is messed up\n\n
|
||||
Prefix, Suffix and Spacer elements can only exist on the root level.\n
|
||||
Prefix elements can only exist before any chapter and there can be no chapters after suffix elements."
|
||||
)))
|
||||
},
|
||||
|
||||
// error if BookItem == Chapter and section == -1
|
||||
BookItem::Chapter(_, _) if section[0] == -1 => {
|
||||
return Err(Error::new( ErrorKind::Other, format!(
|
||||
"Your summary.md is messed up\n\n
|
||||
Prefix, Suffix and Spacer elements can only exist on the root level.\n
|
||||
Prefix elements can only exist before any chapter and there can be no chapters after suffix elements."
|
||||
)))
|
||||
},
|
||||
|
||||
// Set section = -1 after suffix
|
||||
BookItem::Affix(_) if section[0] > 0 => {
|
||||
section[0] = -1;
|
||||
}
|
||||
|
||||
_ => {},
|
||||
}
|
||||
|
||||
match parsed_item {
|
||||
BookItem::Chapter(_, ch) => {
|
||||
// Increment section
|
||||
let len = section.len() -1;
|
||||
section[len] += 1;
|
||||
let s = section.iter().fold("".to_owned(), |s, i| s + &i.to_string() + ".");
|
||||
BookItem::Chapter(s, ch)
|
||||
}
|
||||
_ => parsed_item
|
||||
}
|
||||
|
||||
} else {
|
||||
// If parse_line does not return Some(_) continue...
|
||||
summary.remove(0);
|
||||
continue;
|
||||
};
|
||||
}
|
||||
|
||||
summary.remove(0);
|
||||
items.push(item)
|
||||
}
|
||||
debug!("[*]: Level: {:?}", items);
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
|
||||
fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
|
||||
debug!("[fn]: level");
|
||||
let mut spaces = 0;
|
||||
|
@ -74,16 +144,42 @@ fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
|
|||
|
||||
fn parse_line(l: &str) -> Option<BookItem> {
|
||||
debug!("[fn]: parse_line");
|
||||
let mut name;
|
||||
let mut path;
|
||||
|
||||
// Remove leading and trailing spaces or tabs
|
||||
let line = l.trim_matches(|c: char| { c == ' ' || c == '\t' });
|
||||
|
||||
// Spacers are "------"
|
||||
if line.starts_with("--") {
|
||||
debug!("[*]: Line is spacer");
|
||||
return Some(BookItem::Spacer)
|
||||
}
|
||||
|
||||
if let Some(c) = line.chars().nth(0) {
|
||||
match c {
|
||||
// List item
|
||||
'-' | '*' => {
|
||||
debug!("[*]: Line is list element");
|
||||
|
||||
if let Some((name, path)) = read_link(line) {
|
||||
return Some(BookItem::Chapter("0".to_owned(), Chapter::new(name, path)))
|
||||
} else { return None }
|
||||
},
|
||||
// Non-list element
|
||||
'[' => {
|
||||
debug!("[*]: Line is a link element");
|
||||
|
||||
if let Some((name, path)) = read_link(line) {
|
||||
return Some(BookItem::Affix(Chapter::new(name, path)))
|
||||
} else { return None }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn read_link(line: &str) -> Option<(String, PathBuf)> {
|
||||
let mut start_delimitor;
|
||||
let mut end_delimitor;
|
||||
|
||||
|
@ -103,7 +199,7 @@ fn parse_line(l: &str) -> Option<BookItem> {
|
|||
return None
|
||||
}
|
||||
|
||||
name = line[start_delimitor + 1 .. end_delimitor].to_string();
|
||||
let name = line[start_delimitor + 1 .. end_delimitor].to_string();
|
||||
|
||||
start_delimitor = end_delimitor + 1;
|
||||
if let Some(i) = line[start_delimitor..].find(')') {
|
||||
|
@ -114,13 +210,7 @@ fn parse_line(l: &str) -> Option<BookItem> {
|
|||
return None
|
||||
}
|
||||
|
||||
path = PathBuf::from(line[start_delimitor + 1 .. end_delimitor].to_string());
|
||||
let path = PathBuf::from(line[start_delimitor + 1 .. end_delimitor].to_string());
|
||||
|
||||
return Some(BookItem::new(name, path))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Some((name, path))
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ extern crate pulldown_cmark;
|
|||
use renderer::html_handlebars::helpers;
|
||||
use renderer::Renderer;
|
||||
use book::MDBook;
|
||||
use book::bookitem::BookItem;
|
||||
use {utils, theme};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -57,11 +58,13 @@ impl Renderer for HtmlHandlebars {
|
|||
|
||||
// Render a file for every entry in the book
|
||||
let mut index = true;
|
||||
for (_, item) in book.iter() {
|
||||
for item in book.iter() {
|
||||
|
||||
if item.path != PathBuf::new() {
|
||||
match item {
|
||||
&BookItem::Chapter(_, ref ch) | &BookItem::Affix(ref ch) => {
|
||||
if ch.path != PathBuf::new() {
|
||||
|
||||
let path = book.get_src().join(&item.path);
|
||||
let path = book.get_src().join(&ch.path);
|
||||
|
||||
debug!("[*]: Opening file: {:?}", path);
|
||||
let mut f = try!(File::open(&path));
|
||||
|
@ -76,7 +79,7 @@ impl Renderer for HtmlHandlebars {
|
|||
|
||||
// Remove content from previous file and render content for this one
|
||||
data.remove("path");
|
||||
match item.path.to_str() {
|
||||
match ch.path.to_str() {
|
||||
Some(p) => { data.insert("path".to_string(), p.to_json()); },
|
||||
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))),
|
||||
}
|
||||
|
@ -88,16 +91,16 @@ impl Renderer for HtmlHandlebars {
|
|||
|
||||
// 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_to_root(&item.path).to_json());
|
||||
data.insert("path_to_root".to_string(), utils::path_to_root(&ch.path).to_json());
|
||||
|
||||
// Rendere the handlebars template with the data
|
||||
debug!("[*]: Render template");
|
||||
let rendered = try!(handlebars.render("index", &data));
|
||||
|
||||
debug!("[*]: Create file {:?}", &book.get_dest().join(&item.path).with_extension("html"));
|
||||
debug!("[*]: Create file {:?}", &book.get_dest().join(&ch.path).with_extension("html"));
|
||||
// Write to file
|
||||
let mut file = try!(utils::create_file(&book.get_dest().join(&item.path).with_extension("html")));
|
||||
output!("[*] Creating {:?} ✓", &book.get_dest().join(&item.path).with_extension("html"));
|
||||
let mut file = try!(utils::create_file(&book.get_dest().join(&ch.path).with_extension("html")));
|
||||
output!("[*] Creating {:?} ✓", &book.get_dest().join(&ch.path).with_extension("html"));
|
||||
|
||||
try!(file.write_all(&rendered.into_bytes()));
|
||||
|
||||
|
@ -105,18 +108,21 @@ impl Renderer for HtmlHandlebars {
|
|||
if index {
|
||||
debug!("[*]: index.html");
|
||||
try!(fs::copy(
|
||||
book.get_dest().join(&item.path.with_extension("html")),
|
||||
book.get_dest().join(&ch.path.with_extension("html")),
|
||||
book.get_dest().join("index.html")
|
||||
));
|
||||
|
||||
output!(
|
||||
"[*] Creating index.html from {:?} ✓",
|
||||
book.get_dest().join(&item.path.with_extension("html"))
|
||||
book.get_dest().join(&ch.path.with_extension("html"))
|
||||
);
|
||||
index = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Print version
|
||||
|
||||
|
@ -169,14 +175,31 @@ fn make_data(book: &MDBook) -> Result<BTreeMap<String,Json>, Box<Error>> {
|
|||
|
||||
let mut chapters = vec![];
|
||||
|
||||
for (section, item) in book.iter() {
|
||||
for item in book.iter() {
|
||||
// Create the data to inject in the template
|
||||
let mut chapter = BTreeMap::new();
|
||||
chapter.insert("section".to_string(), section.to_json());
|
||||
chapter.insert("name".to_string(), item.name.to_json());
|
||||
match item.path.to_str() {
|
||||
|
||||
match item {
|
||||
&BookItem::Affix(ref ch) => {
|
||||
chapter.insert("name".to_string(), ch.name.to_json());
|
||||
match ch.path.to_str() {
|
||||
Some(p) => { chapter.insert("path".to_string(), p.to_json()); },
|
||||
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))),
|
||||
}
|
||||
},
|
||||
&BookItem::Chapter(ref s, ref ch) => {
|
||||
chapter.insert("section".to_string(), s.to_json());
|
||||
chapter.insert("name".to_string(), ch.name.to_json());
|
||||
match ch.path.to_str() {
|
||||
Some(p) => { chapter.insert("path".to_string(), p.to_json()); },
|
||||
None => return Err(Box::new(io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))),
|
||||
}
|
||||
},
|
||||
&BookItem::Spacer => {
|
||||
chapter.insert("spacer".to_string(), "_spacer_".to_json());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
chapters.push(chapter);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,14 @@ impl HelperDef for RenderToc {
|
|||
|
||||
for item in decoded {
|
||||
|
||||
let level = item.get("section").expect("Error: section should be Some(_)").len() / 2;
|
||||
// Spacer
|
||||
if let Some(_) = item.get("spacer") {
|
||||
try!(rc.writer.write("<li class=\"spacer\"></li>".as_bytes()));
|
||||
continue
|
||||
}
|
||||
|
||||
let level = if let Some(s) = item.get("section") { s.len() / 2 } else { 1 };
|
||||
|
||||
if level > current_level {
|
||||
try!(rc.writer.write("<li>".as_bytes()));
|
||||
try!(rc.writer.write("<ul class=\"section\">".as_bytes()));
|
||||
|
@ -42,7 +49,11 @@ impl HelperDef for RenderToc {
|
|||
try!(rc.writer.write("<li>".as_bytes()));
|
||||
}
|
||||
else {
|
||||
try!(rc.writer.write("<li>".as_bytes()));
|
||||
try!(rc.writer.write("<li".as_bytes()));
|
||||
if let None = item.get("section") {
|
||||
try!(rc.writer.write(" class=\"affix\"".as_bytes()));
|
||||
}
|
||||
try!(rc.writer.write(">".as_bytes()));
|
||||
}
|
||||
|
||||
// Link
|
||||
|
@ -74,10 +85,16 @@ impl HelperDef for RenderToc {
|
|||
false
|
||||
};
|
||||
|
||||
// Section does not necessarily exist
|
||||
if let Some(section) = item.get("section") {
|
||||
try!(rc.writer.write("<strong>".as_bytes()));
|
||||
try!(rc.writer.write(item.get("section").expect("Error: section should be Some(_)").as_bytes()));
|
||||
try!(rc.writer.write(section.as_bytes()));
|
||||
try!(rc.writer.write("</strong> ".as_bytes()));
|
||||
try!(rc.writer.write(item.get("name").expect("Error: name should be Some(_)").as_bytes()));
|
||||
}
|
||||
|
||||
if let Some(name) = item.get("name") {
|
||||
try!(rc.writer.write(name.as_bytes()));
|
||||
}
|
||||
|
||||
if path_exists {
|
||||
try!(rc.writer.write("</a>".as_bytes()));
|
||||
|
|
|
@ -102,6 +102,17 @@ html, body {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.chapter .affix {
|
||||
|
||||
}
|
||||
|
||||
.chapter .spacer {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: #f4f4f4;
|
||||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
.menu-bar {
|
||||
position: relative;
|
||||
height: 50px;
|
||||
|
|
Loading…
Reference in a new issue