Integrated the new Book structure into MDBook
This commit is contained in:
parent
01301d9951
commit
65a60bf81f
|
@ -28,6 +28,7 @@ env_logger = "0.4.0"
|
|||
toml = { version = "0.4", features = ["serde"] }
|
||||
open = "1.1"
|
||||
regex = "0.2.1"
|
||||
tempdir = "0.3.4"
|
||||
|
||||
# Watch feature
|
||||
notify = { version = "4.0", optional = true }
|
||||
|
@ -41,7 +42,7 @@ ws = { version = "0.7", optional = true}
|
|||
|
||||
# Tests
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3.4"
|
||||
pretty_assertions = "0.2.1"
|
||||
|
||||
[build-dependencies]
|
||||
error-chain = "0.10"
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
use serde::{Serialize, Serializer};
|
||||
use serde::ser::SerializeStruct;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BookItems<'a> {
|
||||
pub items: &'a [BookItem],
|
||||
pub current_index: usize,
|
||||
pub stack: Vec<(&'a [BookItem], usize)>,
|
||||
}
|
||||
|
||||
|
||||
impl Chapter {
|
||||
pub fn new(name: String, path: PathBuf) -> Self {
|
||||
|
||||
Chapter {
|
||||
name: name,
|
||||
path: path,
|
||||
sub_items: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Serialize for Chapter {
|
||||
fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
let mut struct_ = serializer.serialize_struct("Chapter", 2)?;
|
||||
struct_.serialize_field("name", &self.name)?;
|
||||
struct_.serialize_field("path", &self.path)?;
|
||||
struct_.end()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Shamelessly copied from Rustbook
|
||||
// (https://github.com/rust-lang/rust/blob/master/src/rustbook/book.rs)
|
||||
impl<'a> Iterator for BookItems<'a> {
|
||||
type Item = &'a BookItem;
|
||||
|
||||
fn next(&mut self) -> Option<&'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[self.current_index];
|
||||
|
||||
match *cur {
|
||||
BookItem::Chapter(_, ref ch) |
|
||||
BookItem::Affix(ref ch) => {
|
||||
self.stack.push((self.items, self.current_index));
|
||||
self.items = &ch.sub_items[..];
|
||||
self.current_index = 0;
|
||||
},
|
||||
BookItem::Spacer => {
|
||||
self.current_index += 1;
|
||||
},
|
||||
}
|
||||
|
||||
return Some(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
202
src/book/mod.rs
202
src/book/mod.rs
|
@ -1,25 +1,25 @@
|
|||
pub mod bookitem;
|
||||
|
||||
pub use self::bookitem::{BookItem, BookItems};
|
||||
// pub use self::bookitem::{BookItem, BookItems};
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::process::Command;
|
||||
|
||||
use {theme, parse, utils};
|
||||
use {theme, utils};
|
||||
use renderer::{Renderer, HtmlHandlebars};
|
||||
use tempdir::TempDir;
|
||||
use errors::*;
|
||||
|
||||
use config::BookConfig;
|
||||
use config::tomlconfig::TomlConfig;
|
||||
use config::jsonconfig::JsonConfig;
|
||||
use loader::{self, Book, BookItem, BookItems, Chapter};
|
||||
|
||||
|
||||
pub struct MDBook {
|
||||
config: BookConfig,
|
||||
|
||||
pub content: Vec<BookItem>,
|
||||
book: Book,
|
||||
renderer: Box<Renderer>,
|
||||
|
||||
livereload: Option<String>,
|
||||
|
@ -57,22 +57,22 @@ impl MDBook {
|
|||
/// They can both be changed by using [`set_src()`](#method.set_src) and
|
||||
/// [`set_dest()`](#method.set_dest)
|
||||
|
||||
pub fn new<P: Into<PathBuf>>(root: P) -> MDBook {
|
||||
pub fn new<P: AsRef<Path>>(root: P) -> Result<MDBook> {
|
||||
|
||||
let root = root.into();
|
||||
let root = root.as_ref();
|
||||
if !root.exists() || !root.is_dir() {
|
||||
warn!("{:?} No directory with that name", root);
|
||||
bail!("{:?} No directory with that name", root);
|
||||
}
|
||||
|
||||
MDBook {
|
||||
let book = loader::load_book(root.join("src"))?;
|
||||
|
||||
Ok(MDBook {
|
||||
config: BookConfig::new(root),
|
||||
|
||||
content: vec![],
|
||||
book: book,
|
||||
renderer: Box::new(HtmlHandlebars::new()),
|
||||
|
||||
livereload: None,
|
||||
create_missing: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a flat depth-first iterator over the elements of the book,
|
||||
|
@ -105,11 +105,7 @@ impl MDBook {
|
|||
/// ```
|
||||
|
||||
pub fn iter(&self) -> BookItems {
|
||||
BookItems {
|
||||
items: &self.content[..],
|
||||
current_index: 0,
|
||||
stack: Vec::new(),
|
||||
}
|
||||
self.book.iter()
|
||||
}
|
||||
|
||||
/// `init()` creates some boilerplate files and directories
|
||||
|
@ -127,86 +123,51 @@ impl MDBook {
|
|||
/// and adds a `SUMMARY.md` and a
|
||||
/// `chapter_1.md` to the source directory.
|
||||
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
pub fn init<P: AsRef<Path>>(root: P) -> Result<MDBook> {
|
||||
let root = root.as_ref();
|
||||
|
||||
debug!("[fn]: init");
|
||||
|
||||
if !self.config.get_root().exists() {
|
||||
fs::create_dir_all(&self.config.get_root()).unwrap();
|
||||
info!("{:?} created", &self.config.get_root());
|
||||
if !root.exists() {
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
info!("{} created", root.display());
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
if !self.get_destination().exists() {
|
||||
debug!("[*]: {:?} does not exist, trying to create directory", self.get_destination());
|
||||
fs::create_dir_all(self.get_destination())?;
|
||||
}
|
||||
|
||||
|
||||
if !self.config.get_source().exists() {
|
||||
debug!("[*]: {:?} does not exist, trying to create directory", self.config.get_source());
|
||||
fs::create_dir_all(self.config.get_source())?;
|
||||
}
|
||||
|
||||
let summary = self.config.get_source().join("SUMMARY.md");
|
||||
|
||||
if !summary.exists() {
|
||||
|
||||
// Summary does not exist, create it
|
||||
debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", &summary);
|
||||
let mut f = File::create(&summary)?;
|
||||
|
||||
debug!("[*]: Writing to SUMMARY.md");
|
||||
|
||||
writeln!(f, "# Summary")?;
|
||||
writeln!(f, "")?;
|
||||
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
||||
for dir in &["book", "src"] {
|
||||
let dir_name = root.join(dir);
|
||||
if !dir_name.exists() {
|
||||
debug!("[*]: {} does not exist, trying to create directory", dir_name.display());
|
||||
fs::create_dir_all(dir_name)?;
|
||||
}
|
||||
}
|
||||
|
||||
// parse SUMMARY.md, and create the missing item related file
|
||||
self.parse_summary()?;
|
||||
debug!("[*]: Creating SUMMARY.md");
|
||||
let mut summary = File::create(root.join("src").join("SUMMARY.md"))?;
|
||||
writeln!(summary, "# Summary")?;
|
||||
writeln!(summary, "")?;
|
||||
writeln!(summary, "- [Chapter 1](./chapter_1.md")?;
|
||||
|
||||
debug!("[*]: constructing paths for missing files");
|
||||
for item in self.iter() {
|
||||
debug!("[*]: item: {:?}", item);
|
||||
let ch = match *item {
|
||||
BookItem::Spacer => continue,
|
||||
BookItem::Chapter(_, ref ch) |
|
||||
BookItem::Affix(ref ch) => ch,
|
||||
};
|
||||
if !ch.path.as_os_str().is_empty() {
|
||||
let path = self.config.get_source().join(&ch.path);
|
||||
|
||||
if !path.exists() {
|
||||
if !self.create_missing {
|
||||
return Err(format!("'{}' referenced from SUMMARY.md does not exist.", path.to_string_lossy())
|
||||
.into());
|
||||
}
|
||||
debug!("[*]: {:?} does not exist, trying to create file", path);
|
||||
::std::fs::create_dir_all(path.parent().unwrap())?;
|
||||
let mut f = File::create(path)?;
|
||||
|
||||
// debug!("[*]: Writing to {:?}", path);
|
||||
writeln!(f, "# {}", ch.name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("[*]: Creating a chapter");
|
||||
let mut chapter_1 = File::create(root.join("src").join("chapter_1.md"))?;
|
||||
writeln!(chapter_1, "# Chapter 1")?;
|
||||
writeln!(chapter_1, "")?;
|
||||
writeln!(chapter_1, "TODO: Create some content.")?;
|
||||
|
||||
debug!("[*]: init done");
|
||||
Ok(())
|
||||
|
||||
MDBook::new(root)
|
||||
}
|
||||
|
||||
pub fn create_gitignore(&self) {
|
||||
let gitignore = self.get_gitignore();
|
||||
|
||||
let destination = self.config.get_html_config()
|
||||
.get_destination();
|
||||
let destination = self.config.get_html_config().get_destination();
|
||||
|
||||
// Check that the gitignore does not extist and that the destination path begins with the root path
|
||||
// We assume tha if it does begin with the root path it is contained within. This assumption
|
||||
// will not hold true for paths containing double dots to go back up e.g. `root/../destination`
|
||||
// Check that the gitignore does not exist and that the destination
|
||||
// path begins with the root path We assume tha if it does begin with
|
||||
// the root path it is contained within. This assumption will not hold
|
||||
// true for paths containing double dots to go back up e.g.
|
||||
// `root/../destination`.
|
||||
if !gitignore.exists() && destination.starts_with(self.config.get_root()) {
|
||||
|
||||
let relative = destination
|
||||
|
@ -234,8 +195,6 @@ impl MDBook {
|
|||
pub fn build(&mut self) -> Result<()> {
|
||||
debug!("[fn]: build");
|
||||
|
||||
self.init()?;
|
||||
|
||||
// Clean output directory
|
||||
utils::fs::remove_dir_content(self.config.get_html_config().get_destination())?;
|
||||
|
||||
|
@ -284,12 +243,13 @@ impl MDBook {
|
|||
}
|
||||
|
||||
pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<()> {
|
||||
let path = self.get_destination()
|
||||
.join(filename);
|
||||
let path = self.get_destination().join(filename);
|
||||
|
||||
utils::fs::create_file(&path)?
|
||||
.write_all(content)
|
||||
.map_err(|e| e.into())
|
||||
utils::fs::create_file(&path)?.write_all(content).map_err(
|
||||
|e| {
|
||||
e.into()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses the `book.json` file (if it exists) to extract
|
||||
|
@ -375,6 +335,7 @@ impl MDBook {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -385,15 +346,16 @@ impl MDBook {
|
|||
|
||||
pub fn with_destination<T: Into<PathBuf>>(mut self, destination: T) -> Self {
|
||||
let root = self.config.get_root().to_owned();
|
||||
self.config.get_mut_html_config()
|
||||
.set_destination(&root, &destination.into());
|
||||
self.config.get_mut_html_config().set_destination(
|
||||
&root,
|
||||
&destination.into(),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn get_destination(&self) -> &Path {
|
||||
self.config.get_html_config()
|
||||
.get_destination()
|
||||
self.config.get_html_config().get_destination()
|
||||
}
|
||||
|
||||
pub fn with_source<T: Into<PathBuf>>(mut self, source: T) -> Self {
|
||||
|
@ -439,67 +401,69 @@ impl MDBook {
|
|||
|
||||
pub fn with_theme_path<T: Into<PathBuf>>(mut self, theme_path: T) -> Self {
|
||||
let root = self.config.get_root().to_owned();
|
||||
self.config.get_mut_html_config()
|
||||
.set_theme(&root, &theme_path.into());
|
||||
self.config.get_mut_html_config().set_theme(
|
||||
&root,
|
||||
&theme_path.into(),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_theme_path(&self) -> &Path {
|
||||
self.config.get_html_config()
|
||||
.get_theme()
|
||||
self.config.get_html_config().get_theme()
|
||||
}
|
||||
|
||||
pub fn with_curly_quotes(mut self, curly_quotes: bool) -> Self {
|
||||
self.config.get_mut_html_config()
|
||||
.set_curly_quotes(curly_quotes);
|
||||
self.config.get_mut_html_config().set_curly_quotes(
|
||||
curly_quotes,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_curly_quotes(&self) -> bool {
|
||||
self.config.get_html_config()
|
||||
.get_curly_quotes()
|
||||
self.config.get_html_config().get_curly_quotes()
|
||||
}
|
||||
|
||||
pub fn with_mathjax_support(mut self, mathjax_support: bool) -> Self {
|
||||
self.config.get_mut_html_config()
|
||||
.set_mathjax_support(mathjax_support);
|
||||
self.config.get_mut_html_config().set_mathjax_support(
|
||||
mathjax_support,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_mathjax_support(&self) -> bool {
|
||||
self.config.get_html_config()
|
||||
.get_mathjax_support()
|
||||
self.config.get_html_config().get_mathjax_support()
|
||||
}
|
||||
|
||||
pub fn get_google_analytics_id(&self) -> Option<String> {
|
||||
self.config.get_html_config()
|
||||
.get_google_analytics_id()
|
||||
self.config.get_html_config().get_google_analytics_id()
|
||||
}
|
||||
|
||||
pub fn has_additional_js(&self) -> bool {
|
||||
self.config.get_html_config()
|
||||
.has_additional_js()
|
||||
self.config.get_html_config().has_additional_js()
|
||||
}
|
||||
|
||||
pub fn get_additional_js(&self) -> &[PathBuf] {
|
||||
self.config.get_html_config()
|
||||
.get_additional_js()
|
||||
self.config.get_html_config().get_additional_js()
|
||||
}
|
||||
|
||||
pub fn has_additional_css(&self) -> bool {
|
||||
self.config.get_html_config()
|
||||
.has_additional_css()
|
||||
self.config.get_html_config().has_additional_css()
|
||||
}
|
||||
|
||||
pub fn get_additional_css(&self) -> &[PathBuf] {
|
||||
self.config.get_html_config()
|
||||
.get_additional_css()
|
||||
self.config.get_html_config().get_additional_css()
|
||||
}
|
||||
}
|
||||
|
||||
fn test_chapter(ch: &Chapter, tmp: &TempDir) -> Result<()> {
|
||||
let path = tmp.path().join(&ch.name);
|
||||
File::create(&path)?.write_all(ch.content.as_bytes())?;
|
||||
|
||||
let output = Command::new("rustdoc").arg(&path).arg("--test").output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(ErrorKind::Subprocess("Rustdoc returned an error".to_string(), output));
|
||||
}
|
||||
|
||||
// Construct book
|
||||
fn parse_summary(&mut self) -> Result<()> {
|
||||
// When append becomes stable, use self.content.append() ...
|
||||
self.content = parse::construct_bookitems(&self.get_source().join("SUMMARY.md"))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,6 +87,8 @@ extern crate serde;
|
|||
extern crate serde_json;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
extern crate tempdir;
|
||||
|
||||
mod parse;
|
||||
|
@ -99,7 +101,7 @@ pub mod utils;
|
|||
pub mod loader;
|
||||
|
||||
pub use book::MDBook;
|
||||
pub use book::BookItem;
|
||||
pub use loader::{Book, BookItem};
|
||||
pub use renderer::Renderer;
|
||||
|
||||
/// The error types used through out this crate.
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::path::Path;
|
|||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::fmt;
|
||||
|
||||
use loader::summary::{Summary, Link, SummaryItem, SectionNumber};
|
||||
use errors::*;
|
||||
|
@ -60,6 +61,28 @@ impl Chapter {
|
|||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get this chapter's location in the book structure, relative to the
|
||||
/// root.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This **may not** be the same as the source file's location on disk!
|
||||
/// Rather, it reflects the chapter's location in the `Book` tree
|
||||
/// structure.
|
||||
pub fn path(&self) -> &Path {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Chapter {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(ref number) = self.number {
|
||||
write!(f, "{}) ", number)?;
|
||||
}
|
||||
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the provided `Summary` to load a `Book` from disk.
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pub use self::summary::construct_bookitems;
|
||||
|
||||
pub mod summary;
|
|
@ -1,231 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Result, Error, ErrorKind};
|
||||
use book::bookitem::{BookItem, Chapter};
|
||||
|
||||
pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
|
||||
debug!("[fn]: construct_bookitems");
|
||||
let mut summary = String::new();
|
||||
File::open(path)?.read_to_string(&mut summary)?;
|
||||
|
||||
debug!("[*]: Parse SUMMARY.md");
|
||||
let top_items = 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, mut section: Vec<i32>) -> Result<Vec<BookItem>> {
|
||||
debug!("[fn]: parse_level");
|
||||
let mut items: Vec<BookItem> = vec![];
|
||||
|
||||
// Construct the book recursively
|
||||
while !summary.is_empty() {
|
||||
let item: BookItem;
|
||||
// Indentation level of the line to parse
|
||||
let level = level(summary[0], 4)?;
|
||||
|
||||
// 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(0);
|
||||
let last = items
|
||||
.pop()
|
||||
.expect("There should be at least one item since this can't be the root level");
|
||||
|
||||
if let BookItem::Chapter(ref s, ref ch) = last {
|
||||
let mut ch = ch.clone();
|
||||
ch.sub_items = 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,
|
||||
"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,
|
||||
"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,
|
||||
"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;
|
||||
let mut level = 0;
|
||||
|
||||
for ch in line.chars() {
|
||||
match ch {
|
||||
' ' => spaces += 1,
|
||||
'\t' => level += 1,
|
||||
_ => break,
|
||||
}
|
||||
if spaces >= spaces_in_tab {
|
||||
level += 1;
|
||||
spaces = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are spaces left, there is an indentation error
|
||||
if spaces > 0 {
|
||||
debug!("[SUMMARY.md]:");
|
||||
debug!("\t[line]: {}", line);
|
||||
debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces", spaces_in_tab);
|
||||
return Err(Error::new(ErrorKind::Other, format!("Indentation error on line:\n\n{}", line)));
|
||||
}
|
||||
|
||||
Ok(level)
|
||||
}
|
||||
|
||||
|
||||
fn parse_line(l: &str) -> Option<BookItem> {
|
||||
debug!("[fn]: parse_line");
|
||||
|
||||
// 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;
|
||||
|
||||
// In the future, support for list item that is not a link
|
||||
// Not sure if I should error on line I can't parse or just ignore them...
|
||||
if let Some(i) = line.find('[') {
|
||||
start_delimitor = i;
|
||||
} else {
|
||||
debug!("[*]: '[' not found, this line is not a link. Ignoring...");
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(i) = line[start_delimitor..].find("](") {
|
||||
end_delimitor = start_delimitor + i;
|
||||
} else {
|
||||
debug!("[*]: '](' not found, this line is not a link. Ignoring...");
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = line[start_delimitor + 1..end_delimitor].to_owned();
|
||||
|
||||
start_delimitor = end_delimitor + 1;
|
||||
if let Some(i) = line[start_delimitor..].find(')') {
|
||||
end_delimitor = start_delimitor + i;
|
||||
} else {
|
||||
debug!("[*]: ')' not found, this line is not a link. Ignoring...");
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = PathBuf::from(line[start_delimitor + 1..end_delimitor].to_owned());
|
||||
|
||||
Some((name, path))
|
||||
}
|
|
@ -2,7 +2,7 @@ use renderer::html_handlebars::helpers;
|
|||
use preprocess;
|
||||
use renderer::Renderer;
|
||||
use book::MDBook;
|
||||
use book::bookitem::{BookItem, Chapter};
|
||||
use loader::{BookItem, Chapter};
|
||||
use utils;
|
||||
use theme::{self, Theme};
|
||||
use errors::*;
|
||||
|
@ -28,8 +28,7 @@ impl HtmlHandlebars {
|
|||
HtmlHandlebars
|
||||
}
|
||||
|
||||
fn render_item(&self, item: &BookItem, mut ctx: RenderItemContext, print_content: &mut String)
|
||||
-> Result<()> {
|
||||
fn render_item(&self, item: &BookItem, mut ctx: RenderItemContext, print_content: &mut String) -> Result<()> {
|
||||
// FIXME: This should be made DRY-er and rely less on mutable state
|
||||
match *item {
|
||||
BookItem::Chapter(_, ref ch) |
|
||||
|
@ -92,15 +91,10 @@ impl HtmlHandlebars {
|
|||
fn render_index(&self, book: &MDBook, ch: &Chapter, destination: &Path) -> Result<()> {
|
||||
debug!("[*]: index.html");
|
||||
|
||||
let mut content = String::new();
|
||||
|
||||
File::open(destination.join(&ch.path.with_extension("html")))?
|
||||
.read_to_string(&mut content)?;
|
||||
|
||||
// This could cause a problem when someone displays
|
||||
// code containing <base href=...>
|
||||
// on the front page, however this case should be very very rare...
|
||||
content = content
|
||||
let content = ch.content
|
||||
.lines()
|
||||
.filter(|line| !line.contains("<base href="))
|
||||
.collect::<Vec<&str>>()
|
||||
|
@ -110,8 +104,9 @@ impl HtmlHandlebars {
|
|||
|
||||
info!(
|
||||
"[*] Creating index.html from {:?} ✓",
|
||||
book.get_destination()
|
||||
.join(&ch.path.with_extension("html"))
|
||||
book.get_destination().join(
|
||||
ch.path().with_extension("html"),
|
||||
)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
@ -362,22 +357,18 @@ fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>
|
|||
let mut chapter = BTreeMap::new();
|
||||
|
||||
match *item {
|
||||
BookItem::Affix(ref ch) => {
|
||||
BookItem::Chapter(ref ch) => {
|
||||
if let Some(ref section) = ch.number {
|
||||
chapter.insert("section".to_owned(), json!(section.to_string()));
|
||||
}
|
||||
chapter.insert("name".to_owned(), json!(ch.name));
|
||||
let path = ch.path.to_str().ok_or_else(|| {
|
||||
|
||||
let path = ch.path().to_str().ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str")
|
||||
})?;
|
||||
chapter.insert("path".to_owned(), json!(path));
|
||||
},
|
||||
BookItem::Chapter(ref s, ref ch) => {
|
||||
chapter.insert("section".to_owned(), json!(s));
|
||||
chapter.insert("name".to_owned(), json!(ch.name));
|
||||
let path = ch.path.to_str().ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "Could not convert path to str")
|
||||
})?;
|
||||
chapter.insert("path".to_owned(), json!(path));
|
||||
},
|
||||
BookItem::Spacer => {
|
||||
BookItem::Separator => {
|
||||
chapter.insert("spacer".to_owned(), json!("_spacer_"));
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue