catch up with changes in master

This commit is contained in:
Gambhiro 2017-01-19 17:08:07 +00:00
parent ca718b3d78
commit c2ff0c6b15
64 changed files with 362 additions and 231 deletions

View File

@ -28,11 +28,12 @@ glob = "0.2"
log = "0.3"
env_logger = "0.3"
toml = { version = "0.2", features = ["serde"] }
open = "1.1"
phf = "0.7"
includedir = "0.2"
# Watch feature
notify = { version = "2.5.5", optional = true }
notify = { version = "3.0", optional = true }
time = { version = "0.1.34", optional = true }
crossbeam = { version = "0.2.8", optional = true }

View File

@ -21,7 +21,7 @@
</tr>
</table>
mdBook is a utility to create modern online books from markdown files.
mdBook is a utility to create modern online books from Markdown files.
**This project is still evolving.**
See [#90](https://github.com/azerupi/mdBook/issues/90)
@ -29,7 +29,7 @@ See [#90](https://github.com/azerupi/mdBook/issues/90)
## What does it look like?
The [**Documentation**](http://azerupi.github.io/mdBook/) for mdBook has been written in markdown and is using mdBook to generate the online book-like website you can read. The documentation uses the latest version on github and showcases the available features.
The [**Documentation**](http://azerupi.github.io/mdBook/) for mdBook has been written in Markdown and is using mdBook to generate the online book-like website you can read. The documentation uses the latest version on GitHub and showcases the available features.
## Installation
@ -47,12 +47,12 @@ There are multiple ways to install mdBook.
This will download and compile mdBook for you, the only thing left to do is to add the Cargo bin directory to your `PATH`.
3. **From Git**
The version published to Crates.io will ever so slightly be behind the version hosted here on Github. If you need the latest version you can build the git version of mdBook yourself. Cargo makes this ***super easy***!
The version published to crates.io will ever so slightly be behind the version hosted here on GitHub. If you need the latest version you can build the git version of mdBook yourself. Cargo makes this ***super easy***!
```
cargo install --git https://github.com/azerupi/mdBook.git
```
Again, make sure to add the Cargo bin directory to your `PATH`
Again, make sure to add the Cargo bin directory to your `PATH`.
4. **For Contributions**
If you want to contribute to mdBook you will have to clone the repository on your local machine:
@ -74,7 +74,7 @@ There are multiple ways to install mdBook.
mdBook will primaraly be used as a command line tool, even though it exposes all its functionality as a Rust crate for integration in other projects.
Here are the main commands you will want to run, for a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/).
Here are the main commands you will want to run. For a more exhaustive explanation, check out the [documentation](http://azerupi.github.io/mdBook/).
- `mdbook init`
@ -106,7 +106,7 @@ Here are the main commands you will want to run, for a more exhaustive explanati
### As a library
Aside from the command line interface, this crate can also be used as a library. This means that you could integrate it in an existing project, like a web-app for example. Since the command line interface is just a wrapper around the library functionality, when you use this crate as a library you have full access to all the functionality of the command line interface with and easy to use API and more!
Aside from the command line interface, this crate can also be used as a library. This means that you could integrate it in an existing project, like a web-app for example. Since the command line interface is just a wrapper around the library functionality, when you use this crate as a library you have full access to all the functionality of the command line interface with an easy to use API and more!
See the [Documentation](http://azerupi.github.io/mdBook/lib/lib.html) and the [API docs](http://azerupi.github.io/mdBook/mdbook/index.html) for more information.
@ -123,4 +123,4 @@ You can pick any issue you want to work on. Usually it's a good idea to ask if s
## License
All the code is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE](LICENSE) file
All the code is released under the ***Mozilla Public License v2.0***, for more information take a look at the [LICENSE](LICENSE) file.

View File

@ -21,6 +21,15 @@ current working directory.
mdbook build path/to/book
```
#### --open
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser after building it.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-------------------
***note:*** *make sure to run the build command in the root directory and not in the source directory*

View File

@ -22,7 +22,7 @@ configuration files, etc.
- The `book` directory is where your book is rendered. All the output is ready to be uploaded
to a server to be seen by your audience.
- The `SUMMARY.md` file is the most important file, it's the skeleton of your book and is discussed in more detail in another [chapter](../format/summary.html).
- The `SUMMARY.md` file is the most important file, it's the skeleton of your book and is discussed in more detail in another [chapter](format/summary.html).
#### Tip & Trick: Hidden Feature
When a `SUMMARY.md` file already exists, the `init` command will first parse it and generate the missing files according to the paths used in the `SUMMARY.md`. This allows you to think and create the whole structure of your book and then let mdBook generate it for you.

View File

@ -26,8 +26,15 @@ mdbook server path/to/book -p 8000 -i 127.0.0.1 -a 192.168.1.100
If you were to want live reloading for this you would need to proxy the websocket calls through nginx as well from `192.168.1.100:<WS_PORT>` to `127.0.0.1:<WS_PORT>`. The `-w` flag allows for the websocket port to be configured.
#### --open
When you use the `--open` (`-o`) option, mdbook will open the book in your
your default web browser after starting the server.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-----
***note:*** *the `serve` command has not gotten a lot of testing yet, there could be some rough edges. If you discover a problem, please report it [on Github](https://github.com/azerupi/mdBook/issues)*
***note***:

View File

@ -12,6 +12,14 @@ current working directory.
mdbook watch path/to/book
```
#### --open
When you use the `--open` (`-o`) option, mdbook will open the rendered book in
your default web browser.
#### --dest-dir
The `--dest-dir` (`-d`) option allows you to change the output directory for your book.
-----

View File

@ -20,8 +20,9 @@ Here is a list of the properties that are exposed:
- ***language*** Language of the book in the form `en`. To use in <code class="language-html">\<html lang="{{ language }}"></code> for example.
At the moment it is hardcoded.
- ***title*** Title of the book, as specified in `book.toml`
- ***path*** Relative path to the original markdown file from the source directory
- ***chapter_title*** Title of the current chapter, as listed in `SUMMARY.md`
- ***content*** This is the rendered markdown.
- ***path_to_root*** This is a path containing exclusively `../`'s that points to the root of the book from the current file.
Since the original directory structure is maintained, it is useful to prepend relative links with this `path_to_root`.

View File

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 348 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

2
data/book-init/book.toml Normal file
View File

@ -0,0 +1,2 @@
title = "An Empty Canvas"
author = "The Author"

View File

@ -0,0 +1,8 @@
# Title
[Introduction](introduction.md)
- [First Chapter](first-chapter.md)
- [Second Chapter]()
[Glossary](glossary.md)

View File

@ -0,0 +1,8 @@
# First Chapter
> first cicada &ndash;
> for a quick song sighted
> on the post
>
> *Issa, 1816*

View File

@ -0,0 +1,7 @@
# Glossary
**ci·ca·da** (sĭ-kādə, -kä-) *n. pl.* ci·ca·das or ci·ca·dae (-dē)
Any of various insects chiefly of the family Cicadidae, having a broad head,
membranous wings, and in the male a pair of resonating organs that produce a
characteristic high-pitched, droning sound.

View File

@ -0,0 +1 @@
# Introduction

View File

@ -5,6 +5,7 @@ extern crate clap;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate open;
// Dependencies for the Watch feature
#[cfg(feature = "watch")]
@ -23,7 +24,9 @@ extern crate staticfile;
extern crate ws;
use std::env;
use std::fs;
use std::error::Error;
use std::ffi::OsStr;
use std::io::{self, Write, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::Command;
@ -34,6 +37,8 @@ use clap::{App, ArgMatches, SubCommand, AppSettings};
#[cfg(feature = "watch")]
use notify::Watcher;
#[cfg(feature = "watch")]
use std::time::Duration;
#[cfg(feature = "watch")]
use std::sync::mpsc::channel;
use mdbook::MDBook;
@ -57,22 +62,28 @@ fn main() {
.subcommand(SubCommand::with_name("init")
.about("Create boilerplate structure and files in the directory")
// the {n} denotes a newline which will properly aligned in all help messages
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
.arg_from_usage("--copy-assets 'Copies the default assets (css, layout template, etc.) into your project folder'")
.arg_from_usage("--force 'skip confirmation prompts'"))
.subcommand(SubCommand::with_name("build")
.about("Build the book from the markdown files")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
.subcommand(SubCommand::with_name("watch")
.about("Watch the files for changes")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'"))
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'")
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
.subcommand(SubCommand::with_name("serve")
.about("Serve the book at http://localhost:3000. Rebuild and reload on change.")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when ommitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'")
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
.arg_from_usage("-p, --port=[port] 'Use another port{n}(Defaults to 3000)'")
.arg_from_usage("-w, --websocket-port=[ws-port] 'Use another port for the websocket connection (livereload){n}(Defaults to 3001)'")
.arg_from_usage("-i, --interface=[interface] 'Interface to listen on{n}(Defaults to localhost)'")
.arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface addres)'"))
.arg_from_usage("-a, --address=[address] 'Address that the browser can reach the websocket server from{n}(Defaults to the interface addres)'")
.arg_from_usage("-o, --open 'Open the compiled book in a web browser'"))
.subcommand(SubCommand::with_name("test")
.about("Test that code samples compile"))
.get_matches();
@ -106,10 +117,36 @@ fn confirm() -> bool {
}
}
// Init command implementation
/// Init command implementation
///
/// It creates some boilerplate files and directories to get you started with your book.
///
/// ```text
/// thebook/
/// ├── book.toml
/// └── src
/// ├── SUMMARY.md
/// ├── first-chapter.md
/// ├── glossary.md
/// └── introduction.md
/// ```
///
/// It copies the embedded starter book as stored in data/book-init.
fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
debug!("[fn]: init");
let book_dir = get_book_dir(args);
if !book_dir.exists() {
fs::create_dir_all(&book_dir).unwrap();
info!("{:?} created", &book_dir);
}
try!(utils::fs::copy_data("data/book-init/*",
"data/book-init/",
vec![],
&book_dir));
let mut book_project = MDBook::new(&book_dir);
book_project.read_config();
@ -134,8 +171,8 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
}
// Copy the assets
try!(utils::fs::copy_data("data/**/*",
"data/",
try!(utils::fs::copy_data("data/assets/**/*",
"data/assets/",
vec![],
&book_project.get_project_root().join("assets")));
@ -154,6 +191,7 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
println!("\nAll done, no errors...");
debug!("[*]: init done");
Ok(())
}
@ -161,9 +199,18 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut dest_base: Option<PathBuf> = None;
if let Some(p) = args.value_of("dest-dir") {
dest_base = Some(PathBuf::from(p));
}
// TODO select render format intent when we acutally have different renderers
let renderer = HtmlHandlebars::new();
try!(renderer.build(&book_dir));
let book_project: MDBook = try!(renderer.build(&book_dir, &dest_base));
if args.is_present("open") {
open(book_project.get_dest_base().join("index.html"));
}
Ok(())
}
@ -172,22 +219,28 @@ fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
#[cfg(feature = "watch")]
fn watch(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir);
book.read_config();
// |event, book|
trigger_on_change(&mut book, |event, _| {
if let Some(path) = event.path {
let mut dest_base: Option<PathBuf> = None;
if let Some(p) = args.value_of("dest-dir") {
dest_base = Some(PathBuf::from(p));
}
let renderer = HtmlHandlebars::new();
let mut book_project: MDBook = try!(renderer.build(&book_dir, &dest_base));
if args.is_present("open") {
open(book_project.get_dest_base().join("index.html"));
}
trigger_on_change(&mut book_project, |path, _| {
println!("File changed: {:?}\nBuilding book...\n", path);
// TODO select render format intent when we acutally have different renderers
let renderer = HtmlHandlebars::new();
match renderer.build(&book_dir) {
match renderer.build(&book_dir, &dest_base) {
Err(e) => println!("Error while building: {:?}", e),
_ => {},
}
println!("");
}
});
println!("watch");
@ -200,8 +253,20 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
const RELOAD_COMMAND: &'static str = "reload";
let book_dir = get_book_dir(args);
let mut dest_base: Option<PathBuf> = None;
if let Some(p) = args.value_of("dest-dir") {
dest_base = Some(PathBuf::from(p));
}
let mut book = MDBook::new(&book_dir);
book.read_config();
if let Some(p) = dest_base {
book.set_dest_base(&p);
}
book.parse_books();
book.link_translations();
@ -209,6 +274,7 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
let ws_port = args.value_of("ws-port").unwrap_or("3001");
let interface = args.value_of("interface").unwrap_or("localhost");
let public_address = args.value_of("address").unwrap_or(interface);
let open_browser = args.is_present("open");
let address = format!("{}:{}", interface, port);
let ws_address = format!("{}:{}", interface, ws_port);
@ -250,8 +316,11 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
println!("\nServing on {}", address);
trigger_on_change(&mut book, move |event, book| {
if let Some(path) = event.path {
if open_browser {
open(format!("http://{}", address));
}
trigger_on_change(&mut book, move |path, book| {
println!("File changed: {:?}\nBuilding book...\n", path);
let renderer = HtmlHandlebars::new();
match renderer.render(&book) {
@ -259,7 +328,6 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
_ => broadcaster.send(RELOAD_COMMAND).unwrap(),
}
println!("");
}
});
Ok(())
@ -319,61 +387,64 @@ fn get_book_dir(args: &ArgMatches) -> PathBuf {
}
}
fn open<P: AsRef<OsStr>>(path: P) {
if let Err(e) = open::that(path) {
println!("Error opening web browser: {}", e);
}
}
// Calls the closure when a book source file is changed. This is blocking!
#[cfg(feature = "watch")]
fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
where F: Fn(notify::Event, &mut MDBook) -> ()
where F: Fn(&Path, &mut MDBook) -> ()
{
use notify::RecursiveMode::*;
use notify::DebouncedEvent::*;
// Create a channel to receive the events.
let (tx, rx) = channel();
let w: Result<notify::RecommendedWatcher, notify::Error> = notify::Watcher::new(tx);
match w {
Ok(mut watcher) => {
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.get_src_base()) {
println!("Error while watching {:?}:\n {:?}", book.get_src_base(), e);
::std::process::exit(0);
let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
Ok(w) => w,
Err(e) => {
println!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(2);
}
};
// Add the book.toml or book.json file to the watcher if it exists,
// because it's not located in the source directory
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.get_src_base(), Recursive) {
println!("Error while watching {:?}:\n {:?}", book.get_src_base(), e);
::std::process::exit(2);
};
if let Err(_) = watcher.watch(book.get_project_root().join("book.toml")) {
// do nothing if book.toml is not found
}
if let Err(_) = watcher.watch(book.get_project_root().join("book.json")) {
// Add the book.{json,toml} file to the watcher if it exists, because it's not
// located in the source directory
if let Err(_) = watcher.watch(book.get_project_root().join("book.json"), NonRecursive) {
// do nothing if book.json is not found
}
let mut previous_time = time::get_time();
if let Err(_) = watcher.watch(book.get_project_root().join("book.toml"), NonRecursive) {
// do nothing if book.toml is not found
}
println!("\nListening for changes...\n");
loop {
match rx.recv() {
Ok(event) => {
// Skip the event if an event has already been issued in the last second
let time = time::get_time();
if time - previous_time < time::Duration::seconds(1) {
continue;
} else {
previous_time = time;
Ok(event) => match event {
NoticeWrite(path) |
NoticeRemove(path) |
Create(path) |
Write(path) |
Remove(path) |
Rename(_, path) => {
closure(&path, book);
}
closure(event, book);
_ => {}
},
Err(e) => {
println!("An error occured: {:?}", e);
},
}
}
},
Err(e) => {
println!("Error while trying to watch the files:\n\n\t{:?}", e);
::std::process::exit(0);
},
}
}

View File

@ -1,6 +1,4 @@
use std::fs::File;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use book::bookconfig::BookConfig;
use book::toc::{TocItem, TocContent};

View File

@ -1,13 +1,8 @@
extern crate toml;
use std::process::exit;
use std::fs::File;
use std::io::Read;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::collections::BTreeMap;
use std::str::FromStr;
use serde_json;
use utils;

View File

@ -4,10 +4,9 @@ extern crate toml;
use regex::Regex;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::fs::File;
use std::error::Error;
use std::io::{self, Read};
use std::io::Read;
use std::collections::BTreeMap;
use utils;
@ -130,7 +129,7 @@ impl Chapter {
debug!("[*] Creating: {:?}", src_path);
match create_with_str(src_path, &format!("# {}", self.title)) {
Ok(_) => { return Ok(self); },
Err(e) => {
Err(_) => {
return Err(format!("Could not create: {:?}", src_path));
},
}
@ -140,8 +139,12 @@ impl Chapter {
match File::open(src_path) {
Err(e) => { return Err(format!("Read error: {:?}", e)); },
Ok(mut f) => {
// TODO try! here and return error
f.read_to_string(&mut text);
match f.read_to_string(&mut text) {
Ok(_) => {},
Err(e) => {
return Err(format!("Error: {:#?}", e));
},
}
self.content = Some(utils::strip_toml_header(&text));
}
}

View File

@ -9,8 +9,6 @@ pub mod toc;
pub mod chapter;
pub use self::book::Book;
use renderer::{Renderer, HtmlHandlebars};
use self::chapter::TranslationLink;
use self::toc::{TocItem, TocContent};
use utils;
@ -19,9 +17,6 @@ use std::env;
use std::process::exit;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::io::Read;
use std::error::Error;
use std::collections::{HashMap, BTreeMap};
#[derive(Debug, Clone)]
@ -142,34 +137,6 @@ impl MDBook {
MDBook::default().set_project_root(project_root).clone()
}
/// `init()` creates some boilerplate files and directories to get you started with your book.
///
/// ```text
/// book-example/
/// ├── 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.
pub fn init(&mut self) -> Result<(), Box<Error>> {
debug!("[fn]: init");
if !self.project_root.exists() {
fs::create_dir_all(&self.project_root).unwrap();
info!("{:?} created", &self.project_root);
}
// Read book.toml if exists and populate .translations
self.read_config();
debug!("[*]: init done");
Ok(())
}
/// Parses the `book.toml` file (if it exists) to extract the configuration parameters.
/// The `book.toml` file should be in the root directory of the book project.
/// The project root directory is the one specified when creating a new `MDBook`
@ -186,7 +153,6 @@ impl MDBook {
/// In this example, `project_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.
pub fn read_config(&mut self) -> &mut Self {
debug!("[fn]: read_config");
// exit(2) is a clear indication for the user that something is wrong
@ -194,7 +160,7 @@ impl MDBook {
// Read book.toml or book.json if exists to a BTreeMap
if Path::new(self.project_root.join("book.toml").as_os_str()).exists() {
if self.project_root.join("book.toml").exists() {
debug!("[*]: Reading config");
let text = match utils::fs::file_to_string(&self.project_root.join("book.toml")) {
@ -213,7 +179,7 @@ impl MDBook {
}
}
} else if Path::new(self.project_root.join("book.json").as_os_str()).exists() {
} else if self.project_root.join("book.json").exists() {
debug!("[*]: Reading config");
let text = match utils::fs::file_to_string(&self.project_root.join("book.json")) {
@ -444,6 +410,8 @@ impl MDBook {
/// prepare a Vec of default links to point to the index.html of each translation
pub fn translation_index_links(&self) -> Option<Vec<TranslationLink>> {
debug!("[fn] translation_index_links()");
let mut default_links: Vec<TranslationLink> = vec![];
let mut keys = self.translations.keys()
@ -578,6 +546,22 @@ impl MDBook {
} else {
self.src_base = path.to_owned();
}
let a = self.translations.clone();
let keys = a.keys();
let is_multilang: bool = keys.clone().count() > 1;
for key in keys {
if let Some(mut book) = self.translations.get_mut(key) {
if is_multilang {
book.config.src = self.src_base.join(key);
book.config.is_multilang = is_multilang;
} else {
book.config.src = self.src_base.to_owned();
}
}
}
self
}
@ -591,6 +575,22 @@ impl MDBook {
} else {
self.dest_base = path.to_owned();
}
let a = self.translations.clone();
let keys = a.keys();
let is_multilang: bool = keys.clone().count() > 1;
for key in keys {
if let Some(mut book) = self.translations.get_mut(key) {
if is_multilang {
book.config.dest = self.dest_base.join(key);
book.config.is_multilang = is_multilang;
} else {
book.config.dest = self.dest_base.to_owned();
}
}
}
self
}

View File

@ -29,7 +29,7 @@
//! fn main() {
//! let path = PathBuf::from("my-book"); // Path to the book project's root
//! let renderer = HtmlHandlebars::new();
//! try!(renderer.build(&path)); // Build the book
//! try!(renderer.build(&path, &None)); // Build the book
//! }
//! ```
//!

View File

@ -4,15 +4,12 @@ use book::{MDBook, Book};
use book::chapter::{Chapter, TranslationLink};
use book::toc::{TocItem, TocContent};
use utils;
use FILES;
use std::process::exit;
use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use std::fs::{self, File};
use std::path::PathBuf;
use std::fs;
use std::error::Error;
use std::io::{self, Read, Write};
use std::collections::BTreeMap;
use std::io::{self, Write};
use handlebars::Handlebars;
@ -30,13 +27,17 @@ impl HtmlHandlebars {
impl Renderer for HtmlHandlebars {
/// Prepares the project and calls `render()`.
fn build(&self, project_root: &PathBuf) -> Result<(), Box<Error>> {
fn build(&self, project_root: &PathBuf, dest_base: &Option<PathBuf>) -> Result<MDBook, Box<Error>> {
debug!("[fn]: build");
let mut book_project = MDBook::new(&project_root);
book_project.read_config();
if let Some(p) = dest_base.clone() {
book_project.set_dest_base(&p);
}
if !book_project.get_src_base().exists() {
println!("Source folder doesn't exist: {:?}", book_project.get_src_base());
exit(2);
@ -55,11 +56,12 @@ impl Renderer for HtmlHandlebars {
}
}
Ok(())
Ok(book_project)
}
/// Renders the chapters and copies static assets.
fn render(&self, book_project: &MDBook) -> Result<(), Box<Error>> {
debug!("[fn]: render");
debug!("[*]: Check if book's base output folder exists");
if let Err(_) = fs::create_dir_all(&book_project.get_dest_base()) {
@ -82,11 +84,15 @@ impl Renderer for HtmlHandlebars {
let c = a.join("_*");
let exclude_glob = c.to_str().unwrap();
// anyone wants to catch errors?
utils::fs::copy_files(include_glob,
// Ignoring all errors. Should try to see which types are worth returning.
match utils::fs::copy_files(include_glob,
base,
vec![exclude_glob],
&book_project.get_dest_base());
&book_project.get_dest_base()) {
Ok(_) => {},
Err(_) => {},
}
}
// Copy template's static assets
@ -118,20 +124,23 @@ impl Renderer for HtmlHandlebars {
// )
// }
// anyone wants to catch errors?
utils::fs::copy_files(include_glob,
// Ignoring all errors. Should try to see which types are worth returning.
match utils::fs::copy_files(include_glob,
base,
vec![exclude_glob],
&book_project.get_dest_base());
&book_project.get_dest_base()) {
Ok(_) => {},
Err(_) => {},
}
} else {
try!(utils::fs::copy_data("data/_html-template/**/*",
"data/_html-template/",
vec!["data/_html-template/_*"],
try!(utils::fs::copy_data("data/assets/_html-template/**/*",
"data/assets/_html-template/",
vec!["data/assets/_html-template/_*"],
&book_project.get_dest_base()));
}
debug!("[fn]: render");
debug!("[*]: start rendering");
let mut handlebars = Handlebars::new();
let translation_indexes = book_project.translation_index_links();
@ -159,7 +168,7 @@ impl Renderer for HtmlHandlebars {
let s = if let Some(p) = first_path_that_exists(&search_paths) {
try!(utils::fs::file_to_string(&p))
} else {
try!(utils::fs::get_data_file("data/_html-template/_layouts/page.hbs"))
try!(utils::fs::get_data_file("data/assets/_html-template/_layouts/page.hbs"))
};
// Register template
@ -194,7 +203,7 @@ impl Renderer for HtmlHandlebars {
}
// Render the chapters of each book
for (key, book) in &book_project.translations {
for (_, book) in &book_project.translations {
// Check if book's dest directory exists

View File

@ -1,5 +1,4 @@
use std::path::Path;
use std::collections::{VecDeque, BTreeMap};
use std::collections::VecDeque;
use serde_json;
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper, Context};

View File

@ -1,4 +1,3 @@
use std::path::Path;
use std::collections::{VecDeque, BTreeMap};
use serde_json;

View File

@ -6,20 +6,27 @@ use book::MDBook;
use std::error::Error;
use std::path::PathBuf;
// TODO refactor dest_base out of the .build() call. It's only here b/c to
// influence the build output with the --dest-dir CLI arg. It is a good
// thing though that .build() encapsulates the steps to prepare the MDBook
// struct for .render(). Maybe give it the CLI args and process them within
// .build().
pub trait Renderer {
/// When the output format is determined (by a CLI argument for example),
/// call `.build()` of the selected Renderer implementation.
///
/// Constructs an `MDBook` struct given the path of the book project,
/// preparing the project and calling `render()`, doing what is necessary
/// for the particular output format.
/// optionally using a custom output folder (such as when given with
/// `--dest-dir` CLI argument). It prepares the project and calls
/// `render()`, doing what is necessary for the particular output format.
///
/// This involves parsing config options from `book.toml` and parsing the
/// `SUMMARY.md` of each translation to a nested `Vec<TocItem>`.
///
/// Finally it calls `render()` to process the chapters and static assets.
fn build(&self, project_root: &PathBuf) -> Result<(), Box<Error>>;
fn build(&self, project_root: &PathBuf, dest_base: &Option<PathBuf>) -> Result<MDBook, Box<Error>>;
/// Responsible for rendering the chapters and copying static assets.
fn render(&self, book_project: &MDBook) -> Result<(), Box<Error>>;

View File

@ -1,7 +1,6 @@
#[cfg(test)]
#![cfg(test)]
use std::path::PathBuf;
use book::bookconfig::BookConfig;
#[test]

View File

@ -1,7 +1,6 @@
#[cfg(test)]
#![cfg(test)]
use std::path::PathBuf;
use book::bookconfig::Author;
use book::chapter::Chapter;
@ -11,7 +10,12 @@ fn it_parses_when_exists() {
let path = PathBuf::from("at-the-mountains-of-madness.md");
let mut result = Chapter::new("Mountains".to_string(), path.clone());
result.parse_or_create_using(&src_path);
match result.parse_or_create_using(&src_path) {
Ok(_) => {},
Err(e) => {
println!("Error: {:#?}", e);
}
}
let mut expected = Chapter::new("Mountains".to_string(), path.clone());

View File

@ -1,11 +1,10 @@
#[cfg(test)]
#![cfg(test)]
extern crate tempdir;
use std;
use std::fs::{self, File};
use std::io::Read;
use std::path::Path;
use utils;
use utils::fs::copy_files_except_ext;
@ -15,18 +14,21 @@ fn it_copies_data_file() {
let dest_base = std::env::temp_dir().join("in tangles of old alleys");
let dest_path = dest_base.join("book.css");
utils::fs::copy_data_file("data/_html-template/css/books.css", &dest_path);
match utils::fs::copy_data_file("data/assets/_html-template/css/books.css", &dest_path) {
Ok(_) => {},
Err(e) => { println!("{:#?}", e); }
}
let mut file = match File::open(&dest_path) {
Ok(f) => f,
Err(e) => {
Err(_) => {
println!("Failed to open {:?}", dest_path);
return;
},
};
let mut content = String::new();
if let Err(e) = file.read_to_string(&mut content) {
if let Err(_) = file.read_to_string(&mut content) {
println!("Failed to read {:?}", dest_path);
return;
}
@ -34,7 +36,10 @@ fn it_copies_data_file() {
assert!(content.as_str().contains("Open Sans"));
if dest_base.exists() {
fs::remove_dir_all(dest_base);
match fs::remove_dir_all(dest_base) {
Ok(_) => {},
Err(e) => { println!("{:#?}", e); },
}
}
}
@ -42,9 +47,9 @@ fn it_copies_data_file() {
fn it_copies_data_by_pattern() {
let dest_base = std::env::temp_dir().join("near the quays");
if let Err(e) = utils::fs::copy_data("data/_html-template/**/*",
"data/_html-template/",
vec!["data/_html-template/_*"],
if let Err(e) = utils::fs::copy_data("data/assets/_html-template/**/*",
"data/assets/_html-template/",
vec!["data/assets/_html-template/_*"],
&dest_base) {
println!("Error: {:#?}", e);
return;
@ -54,7 +59,10 @@ fn it_copies_data_by_pattern() {
assert!(!dest_base.join("_layouts").exists());
if dest_base.exists() {
fs::remove_dir_all(dest_base);
match fs::remove_dir_all(dest_base) {
Ok(_) => {},
Err(e) => { println!("{:#?}", e); },
}
}
}
@ -72,14 +80,20 @@ fn it_doesnt_delete_toplevel_dotfiles() {
Ok(_) => {},
}
utils::fs::clean_output_dir(&dest_base);
match utils::fs::clean_output_dir(&dest_base) {
Ok(_) => {},
Err(e) => { println!("{:#?}", e); }
}
assert!(dest_base.exists());
assert!(dest_base.join(".dotfile").exists());
assert!(!dest_base.join("door.html").exists());
if dest_base.exists() {
fs::remove_dir_all(dest_base);
match fs::remove_dir_all(dest_base) {
Ok(_) => {},
Err(e) => { println!("{:#?}", e); },
}
}
}

View File

@ -1,4 +1,4 @@
#[cfg(test)]
#![cfg(test)]
use std::path::{Path, PathBuf};
@ -11,7 +11,7 @@ fn it_renders_multilanguage_book() {
let path = PathBuf::from(".").join("src").join("tests").join("book-wonderland-multilang");
let renderer = HtmlHandlebars::new();
if let Err(e) = renderer.build(&path) {
if let Err(e) = renderer.build(&path, &None) {
panic!("{:#?}", e);
}
@ -20,8 +20,8 @@ fn it_renders_multilanguage_book() {
proj.parse_books();
let mut book_path: &Path = proj.translations.get("en").unwrap().config.get_dest();
let mut chapter_path: PathBuf = PathBuf::from("".to_string());
let mut s: String = String::new();
let mut chapter_path: PathBuf;
let mut s: String;
// Test if index.html in the project dest folder is the main book's first chapter

View File

@ -1,4 +1,4 @@
#[cfg(test)]
#![cfg(test)]
use std::path::{Path, PathBuf};
@ -11,7 +11,7 @@ fn it_renders_html_from_minimal_book() {
let path = PathBuf::from(".").join("src").join("tests").join("book-minimal");
let renderer = HtmlHandlebars::new();
if let Err(e) = renderer.build(&path) {
if let Err(e) = renderer.build(&path, &None) {
println!("{:#?}", e);
}
@ -20,8 +20,8 @@ fn it_renders_html_from_minimal_book() {
proj.parse_books();
let book_path: &Path = proj.translations.get("en").unwrap().config.get_dest();
let mut chapter_path: PathBuf = PathBuf::from("".to_string());
let mut s: String = String::new();
let mut chapter_path: PathBuf;
let mut s: String;
// Test if "Library of Babel" was rendered
@ -54,7 +54,7 @@ fn it_copies_local_assets_when_found() {
let path = PathBuf::from(".").join("src").join("tests").join("book-minimal-with-assets");
let renderer = HtmlHandlebars::new();
if let Err(e) = renderer.build(&path) {
if let Err(e) = renderer.build(&path, &None) {
println!("{:#?}", e);
}
@ -64,13 +64,11 @@ fn it_copies_local_assets_when_found() {
let book_path: &Path = proj.translations.get("en").unwrap().config.get_dest();
let mut chapter_path: PathBuf = PathBuf::from("".to_string());
let mut s: String = String::new();
// Test if "Library of Babel" was rendered
chapter_path = book_path.join("fictions").join("babel").with_extension("html");
s = utils::fs::file_to_string(&chapter_path).unwrap();
let chapter_path = book_path.join("fictions").join("babel").with_extension("html");
let s = utils::fs::file_to_string(&chapter_path).unwrap();
assert!(s.contains("The Library of Babel"));
assert_eq!(book_path.join("css").join("book.css").exists(), true);

View File

@ -1,18 +1,16 @@
#[cfg(test)]
#![cfg(test)]
extern crate toml;
use std::process::exit;
use std::path::PathBuf;
use serde_json;
use utils;
use book::MDBook;
use book::book::Book;
use book::bookconfig::{BookConfig, Author, Language};
use book::chapter::Chapter;
use book::toc::{TocItem, TocContent};
use book::toc::TocItem;
#[test]
fn it_parses_simple_json_config() {

View File

@ -1,6 +1,4 @@
#[cfg(test)]
use std::path::PathBuf;
#![cfg(test)]
use parse::summary::parse_level;

View File

@ -1,6 +1,5 @@
#[cfg(test)]
#![cfg(test)]
use book::chapter::Chapter;
use book::toc::{TocItem, TocContent, flat_toc, toc_node_count_id};
use parse::summary::parse_level;

View File

@ -1,9 +1,6 @@
#[cfg(test)]
use std::collections::BTreeMap;
#![cfg(test)]
use serde_json;
use utils::*;
#[test]

View File

@ -61,17 +61,17 @@ pub fn copy_data_file(src_path: &str, dest_path: &Path) -> Result<(), Box<Error>
/// `include_base` will be removed from the source path. This way the path
/// relative to the `dest_path` can be controlled.
///
/// The following will copy all files under "data/_html-template/", excluding
/// folders that start with "_", take the "data/_html-template/" part off the
/// The following will copy all files under "data/assets/_html-template/", excluding
/// folders that start with "_", take the "data/assets/_html-template/" part off the
/// source path, and write the entries to "assets" folder.
///
/// I.e. "data/_html-template/css/book.css" will be written to
/// I.e. "data/assets/_html-template/css/book.css" will be written to
/// "assets/css/book.css".
///
/// ```ignore
/// utils::fs::copy_data("data/_html-template/**/*",
/// "data/_html-template/",
/// vec!["data/_html-template/_*"],
/// utils::fs::copy_data("data/assets/_html-template/**/*",
/// "data/assets/_html-template/",
/// vec!["data/assets/_html-template/_*"],
/// &Path::new("assets"));
/// ```
pub fn copy_data(include_glob: &str,
@ -221,7 +221,7 @@ pub fn path_to_root(path: &Path) -> String {
/// This function creates a file and returns it. But before creating the file it checks every
/// directory in the path to see if it exists, and if it does not it will be created.
pub fn create_file(path: &Path) -> Result<File, Box<Error>> {
pub fn create_file(path: &Path) -> io::Result<File> {
debug!("[fn]: create_file");
// Construct path
@ -232,15 +232,7 @@ pub fn create_file(path: &Path) -> Result<File, Box<Error>> {
}
debug!("[*]: Create file: {:?}", path);
let f = match File::create(path) {
Ok(f) => f,
Err(e) => {
debug!("File::create: {}", e);
return Err(Box::new(io::Error::new(io::ErrorKind::Other, format!("{}", e))));
},
};
Ok(f)
File::create(path)
}
/// A cleaning operation intended to be used on the output directory of a book

View File

@ -4,7 +4,6 @@ extern crate toml;
use regex::Regex;
use std::str::FromStr;
use std::error::Error;
use std::collections::BTreeMap;
use serde_json;