2015-08-04 01:25:41 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
2015-07-07 02:56:19 +02:00
|
|
|
use std::fs::{self, File, metadata};
|
2015-08-01 14:12:55 +02:00
|
|
|
use std::io::Write;
|
2015-07-19 00:08:38 +02:00
|
|
|
use std::error::Error;
|
2015-07-07 02:56:19 +02:00
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
use {BookConfig, BookItem};
|
|
|
|
|
use book::BookItems;
|
2015-07-18 00:04:20 +02:00
|
|
|
use parse;
|
2015-07-19 00:08:38 +02:00
|
|
|
use renderer::Renderer;
|
|
|
|
|
use renderer::HtmlHandlebars;
|
2015-07-08 15:17:11 +02:00
|
|
|
|
2015-07-07 02:56:19 +02:00
|
|
|
pub struct MDBook {
|
2015-07-08 15:17:11 +02:00
|
|
|
config: BookConfig,
|
2015-08-05 22:35:26 +02:00
|
|
|
pub root: PathBuf,
|
2015-07-19 00:08:38 +02:00
|
|
|
pub content: Vec<BookItem>,
|
|
|
|
|
renderer: Box<Renderer>,
|
2015-07-07 02:56:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MDBook {
|
2015-07-07 12:36:11 +02:00
|
|
|
|
2015-08-06 21:10:59 +02:00
|
|
|
/// Create a new `MDBook` struct with root directory `root`
|
|
|
|
|
///
|
|
|
|
|
/// - The default source directory is set to `root/src`
|
|
|
|
|
/// - The default output directory is set to `root/book`
|
|
|
|
|
///
|
|
|
|
|
/// They can both be changed by using [`set_src()`](#method.set_src) and [`set_dest()`](#method.set_dest)
|
|
|
|
|
|
|
|
|
|
pub fn new(root: &Path) -> MDBook {
|
2015-07-08 15:17:11 +02:00
|
|
|
|
|
|
|
|
// Hacky way to check if the path exists... Until PathExt moves to stable
|
2015-08-06 21:10:59 +02:00
|
|
|
match metadata(root) {
|
2015-07-08 15:17:11 +02:00
|
|
|
Err(_) => panic!("Directory does not exist"),
|
|
|
|
|
Ok(f) => {
|
|
|
|
|
if !f.is_dir() {
|
|
|
|
|
panic!("Is not a directory");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-07 02:56:19 +02:00
|
|
|
MDBook {
|
2015-08-06 21:10:59 +02:00
|
|
|
root: root.to_path_buf(),
|
2015-07-16 19:26:16 +02:00
|
|
|
content: vec![],
|
2015-07-16 18:20:36 +02:00
|
|
|
config: BookConfig::new()
|
2015-08-06 21:10:59 +02:00
|
|
|
.set_src(&root.join("src"))
|
|
|
|
|
.set_dest(&root.join("book"))
|
2015-07-19 00:08:38 +02:00
|
|
|
.to_owned(),
|
|
|
|
|
renderer: Box::new(HtmlHandlebars::new()),
|
2015-07-07 02:56:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 21:10:59 +02:00
|
|
|
/// Returns a flat depth-first iterator over the elements of the book in the form of a tuple:
|
|
|
|
|
/// `(section: String, bookitem: &BookItem)`
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # extern crate mdbook;
|
|
|
|
|
/// # use mdbook::MDBook;
|
|
|
|
|
/// # use std::path::Path;
|
|
|
|
|
/// # fn main() {
|
|
|
|
|
/// # let mut book = MDBook::new(Path::new("mybook"));
|
|
|
|
|
/// for (section, element) in book.iter() {
|
|
|
|
|
/// println!("{} {}", section, element.name);
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// // would print something like this:
|
|
|
|
|
/// // 1. Chapter 1
|
|
|
|
|
/// // 1.1 Sub Chapter
|
|
|
|
|
/// // 1.2 Sub Chapter
|
|
|
|
|
/// // 2. Chapter 2
|
|
|
|
|
/// //
|
|
|
|
|
/// // etc.
|
|
|
|
|
/// # }
|
|
|
|
|
/// ```
|
|
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
pub fn iter(&self) -> BookItems {
|
|
|
|
|
BookItems {
|
|
|
|
|
items: &self.content[..],
|
|
|
|
|
current_index: 0,
|
|
|
|
|
stack: Vec::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 21:10:59 +02:00
|
|
|
/// `init()` creates some boilerplate files and directories to get you started with your book.
|
|
|
|
|
///
|
|
|
|
|
/// ```text
|
|
|
|
|
/// book-test/
|
|
|
|
|
/// ├── book
|
|
|
|
|
/// └── src
|
|
|
|
|
/// ├── chapter_1.md
|
|
|
|
|
/// └── SUMMARY.md
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// It uses the paths given as source and output directories and adds a `SUMMARY.md` and a
|
|
|
|
|
/// `chapter_1.md` to the source directory.
|
|
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
pub fn init(&self) -> Result<(), Box<Error>> {
|
2015-07-07 02:56:19 +02:00
|
|
|
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[fn]: init");
|
|
|
|
|
|
2015-07-16 18:20:36 +02:00
|
|
|
let dest = self.config.dest();
|
|
|
|
|
let src = self.config.src();
|
2015-07-07 02:56:19 +02:00
|
|
|
|
|
|
|
|
// Hacky way to check if the directory exists... Until PathExt moves to stable
|
|
|
|
|
match metadata(&dest) {
|
|
|
|
|
Err(_) => {
|
2015-07-07 11:46:49 +02:00
|
|
|
// There is a very high chance that the error is due to the fact that
|
|
|
|
|
// the directory / file does not exist
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[*]: {:?} does not exist, trying to create directory", dest);
|
2015-07-07 11:46:49 +02:00
|
|
|
fs::create_dir(&dest).unwrap();
|
|
|
|
|
},
|
2015-07-07 02:56:19 +02:00
|
|
|
Ok(_) => { /* If there is no error, the directory / file does exist */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hacky way to check if the directory exists... Until PathExt moves to stable
|
|
|
|
|
match metadata(&src) {
|
|
|
|
|
Err(_) => {
|
2015-07-07 11:46:49 +02:00
|
|
|
// There is a very high chance that the error is due to the fact that
|
|
|
|
|
// the directory / file does not exist
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[*]: {:?} does not exist, trying to create directory", src);
|
2015-07-07 11:46:49 +02:00
|
|
|
fs::create_dir(&src).unwrap();
|
|
|
|
|
},
|
2015-07-07 02:56:19 +02:00
|
|
|
Ok(_) => { /* If there is no error, the directory / file does exist */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hacky way to check if the directory exists... Until PathExt moves to stable
|
2015-07-07 11:46:49 +02:00
|
|
|
let summary = match metadata(&src.join("SUMMARY.md")) {
|
2015-07-07 02:56:19 +02:00
|
|
|
Err(_) => {
|
2015-07-07 11:46:49 +02:00
|
|
|
// There is a very high chance that the error is due to the fact that
|
|
|
|
|
// the directory / file does not exist
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", src.join("SUMMARY.md"));
|
2015-07-18 00:04:20 +02:00
|
|
|
Ok(File::create(&src.join("SUMMARY.md")).unwrap())
|
2015-07-07 11:46:49 +02:00
|
|
|
},
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
/* If there is no error, the directory / file does exist */
|
2015-07-18 00:04:20 +02:00
|
|
|
Err("SUMMARY.md does already exist")
|
2015-07-07 11:46:49 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Ok(mut f) = summary {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[*]: Writing to SUMMARY.md");
|
|
|
|
|
|
2015-07-07 12:36:11 +02:00
|
|
|
try!(writeln!(f, "# Summary"));
|
|
|
|
|
try!(writeln!(f, ""));
|
2015-07-18 00:14:04 +02:00
|
|
|
try!(writeln!(f, "- [Chapter 1](./chapter_1.md)"));
|
2015-07-07 11:46:49 +02:00
|
|
|
|
|
|
|
|
let mut chapter_1 = File::create(&src.join("chapter_1.md")).unwrap();
|
2015-07-07 12:36:11 +02:00
|
|
|
try!(writeln!(chapter_1, "# Chapter 1"));
|
2015-07-07 02:56:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 21:10:59 +02:00
|
|
|
/// The `build()` method is the one where everything happens. First it parses `SUMMARY.md` to
|
|
|
|
|
/// construct the book's structure in the form of a `Vec<BookItem>` and then calls `render()`
|
|
|
|
|
/// method of the current renderer.
|
|
|
|
|
///
|
|
|
|
|
/// It is the renderer who generates all the output files.
|
|
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
pub fn build(&mut self) -> Result<(), Box<Error>> {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[fn]: build");
|
2015-07-08 15:17:11 +02:00
|
|
|
|
2015-07-18 00:04:20 +02:00
|
|
|
try!(self.parse_summary());
|
2015-07-08 15:17:11 +02:00
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
try!(self.renderer.render(
|
|
|
|
|
self.iter(),
|
|
|
|
|
&self.config,
|
|
|
|
|
));
|
|
|
|
|
|
2015-07-07 12:36:11 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-16 19:26:16 +02:00
|
|
|
|
2015-08-06 21:10:59 +02:00
|
|
|
/// Parses the `book.json` file (if it exists) to extract the configuration parameters.
|
|
|
|
|
/// The `book.json` file should be in the root directory of the book.
|
|
|
|
|
/// The root directory is the one specified when creating a new `MDBook`
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// # extern crate mdbook;
|
|
|
|
|
/// # use mdbook::MDBook;
|
|
|
|
|
/// # use std::path::Path;
|
|
|
|
|
/// # fn main() {
|
|
|
|
|
/// let mut book = MDBook::new(Path::new("root_dir"));
|
|
|
|
|
/// # }
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// In this example, `root_dir` will be the root directory of our book and is specified in function
|
|
|
|
|
/// of the current working directory by using a relative path instead of an absolute path.
|
|
|
|
|
|
2015-08-03 01:37:13 +02:00
|
|
|
pub fn read_config(mut self) -> Self {
|
2015-08-04 01:25:41 +02:00
|
|
|
self.config.read_config(&self.root);
|
2015-08-03 01:37:13 +02:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-06 21:10:59 +02:00
|
|
|
/// You can change the default renderer to another one by using this method. The only requirement
|
|
|
|
|
/// is for your renderer to implement the [Renderer trait](../../renderer/renderer/trait.Renderer.html)
|
|
|
|
|
///
|
|
|
|
|
/// ```no_run
|
|
|
|
|
/// extern crate mdbook;
|
|
|
|
|
/// use mdbook::MDBook;
|
|
|
|
|
/// use mdbook::renderer::HtmlHandlebars;
|
|
|
|
|
/// # use std::path::Path;
|
|
|
|
|
///
|
|
|
|
|
/// fn main() {
|
|
|
|
|
/// let mut book = MDBook::new(Path::new("mybook"))
|
|
|
|
|
/// .set_renderer(Box::new(HtmlHandlebars::new()));
|
|
|
|
|
///
|
|
|
|
|
/// // In this example we replace the default renderer by the default renderer...
|
|
|
|
|
/// // Don't forget to put your renderer in a Box
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// **note:** Don't forget to put your renderer in a `Box` before passing it to `set_renderer()`
|
|
|
|
|
|
2015-08-04 20:51:01 +02:00
|
|
|
pub fn set_renderer(mut self, renderer: Box<Renderer>) -> Self {
|
|
|
|
|
self.renderer = renderer;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
pub fn set_dest(mut self, dest: &Path) -> Self {
|
2015-08-06 14:27:07 +02:00
|
|
|
self.config.set_dest(&self.root.join(dest));
|
2015-07-07 02:56:19 +02:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
pub fn set_src(mut self, src: &Path) -> Self {
|
2015-08-06 14:27:07 +02:00
|
|
|
self.config.set_src(&self.root.join(src));
|
2015-07-07 02:56:19 +02:00
|
|
|
self
|
|
|
|
|
}
|
2015-07-07 12:36:11 +02:00
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
pub fn set_title(mut self, title: &str) -> Self {
|
2015-07-29 00:57:47 +02:00
|
|
|
self.config.title = title.to_owned();
|
2015-07-16 19:26:16 +02:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-19 00:08:38 +02:00
|
|
|
pub fn set_author(mut self, author: &str) -> Self {
|
2015-07-29 00:57:47 +02:00
|
|
|
self.config.author = author.to_owned();
|
2015-07-16 19:26:16 +02:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-18 00:04:20 +02:00
|
|
|
|
|
|
|
|
// Construct book
|
2015-07-19 00:08:38 +02:00
|
|
|
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
|
2015-07-18 00:04:20 +02:00
|
|
|
|
2015-07-28 21:01:13 +02:00
|
|
|
// When append becomes stable, use self.content.append() ...
|
2015-07-18 00:04:20 +02:00
|
|
|
let book_items = try!(parse::construct_bookitems(&self.config.src().join("SUMMARY.md")));
|
|
|
|
|
|
|
|
|
|
for item in book_items {
|
|
|
|
|
self.content.push(item)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-07 02:56:19 +02:00
|
|
|
}
|