Replace the old book structure with the new one

This commit is contained in:
Mathieu David 2017-05-18 23:52:38 +02:00
parent 170bf8b1eb
commit d3ae2eda56
6 changed files with 238 additions and 159 deletions

View File

@ -129,7 +129,7 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
// Skip this if `--force` is present
if !args.is_present("force") {
// Print warning
print!("\nCopying the default theme to {:?}", book.get_src());
print!("\nCopying the default theme to {:?}", book.get_source());
println!("could potentially overwrite files already present in that directory.");
print!("\nAre you sure you want to continue? (y/n) ");
@ -148,7 +148,9 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
}
// Because of `src/book/mdbook.rs#L37-L39`, `dest` will always start with `root`
let is_dest_inside_root = book.get_dest().starts_with(book.get_root());
let is_dest_inside_root = book.get_destination()
.map(|p| p.starts_with(book.get_root()))
.unwrap_or(false);
if !args.is_present("force") && is_dest_inside_root {
println!("\nDo you want a .gitignore to be created? (y/n)");
@ -168,10 +170,10 @@ fn init(args: &ArgMatches) -> Result<(), Box<Error>> {
// Build command implementation
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let book = MDBook::new(&book_dir).read_config();
let book = MDBook::new(&book_dir).read_config()?;
let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
Some(dest_dir) => book.with_destination(Path::new(dest_dir)),
None => book,
};
@ -181,8 +183,10 @@ fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
book.build()?;
if let Some(d) = book.get_destination() {
if args.is_present("open") {
open(book.get_dest().join("index.html"));
open(d.join("index.html"));
}
}
Ok(())
@ -193,16 +197,18 @@ 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 book = MDBook::new(&book_dir).read_config();
let book = MDBook::new(&book_dir).read_config()?;
let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
Some(dest_dir) => book.with_destination(Path::new(dest_dir)),
None => book,
};
if args.is_present("open") {
book.build()?;
open(book.get_dest().join("index.html"));
if let Some(d) = book.get_destination() {
open(d.join("index.html"));
}
}
trigger_on_change(&mut book, |path, book| {
@ -223,13 +229,18 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
const RELOAD_COMMAND: &'static str = "reload";
let book_dir = get_book_dir(args);
let book = MDBook::new(&book_dir).read_config();
let book = MDBook::new(&book_dir).read_config()?;
let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
Some(dest_dir) => book.with_destination(Path::new(dest_dir)),
None => book,
};
if let None = book.get_destination() {
println!("The HTML renderer is not set up, impossible to serve the files.");
std::process::exit(2);
}
let port = args.value_of("port").unwrap_or("3000");
let ws_port = args.value_of("websocket-port").unwrap_or("3001");
let interface = args.value_of("interface").unwrap_or("localhost");
@ -260,7 +271,7 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
book.build()?;
let staticfile = staticfile::Static::new(book.get_dest());
let staticfile = staticfile::Static::new(book.get_destination().expect("destination is present, checked before"));
let iron = iron::Iron::new(staticfile);
let _iron = iron.http(&*address).unwrap();
@ -292,7 +303,7 @@ fn serve(args: &ArgMatches) -> Result<(), Box<Error>> {
fn test(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config();
let mut book = MDBook::new(&book_dir).read_config()?;
book.test()?;
@ -341,8 +352,8 @@ fn trigger_on_change<F>(book: &mut MDBook, closure: F) -> ()
};
// Add the source directory to the watcher
if let Err(e) = watcher.watch(book.get_src(), Recursive) {
println!("Error while watching {:?}:\n {:?}", book.get_src(), e);
if let Err(e) = watcher.watch(book.get_source(), Recursive) {
println!("Error while watching {:?}:\n {:?}", book.get_source(), e);
::std::process::exit(0);
};

View File

@ -4,29 +4,24 @@ pub mod bookconfig;
pub mod bookconfig_test;
pub use self::bookitem::{BookItem, BookItems};
pub use self::bookconfig::BookConfig;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io;
use std::io::Write;
use std::io::{Read, Write};
use std::io::ErrorKind;
use std::process::Command;
use {theme, parse, utils};
use renderer::{Renderer, HtmlHandlebars};
use config::{BookConfig, HtmlConfig};
use config::tomlconfig::TomlConfig;
pub struct MDBook {
root: PathBuf,
dest: PathBuf,
src: PathBuf,
theme_path: PathBuf,
pub title: String,
pub author: String,
pub description: String,
config: BookConfig,
pub content: Vec<BookItem>,
renderer: Box<Renderer>,
@ -50,7 +45,7 @@ impl MDBook {
/// # use mdbook::MDBook;
/// # use std::path::Path;
/// # fn main() {
/// let book = MDBook::new(Path::new("root_dir"));
/// let book = MDBook::new("root_dir");
/// # }
/// ```
///
@ -75,14 +70,7 @@ impl MDBook {
}
MDBook {
root: root.to_owned(),
dest: root.join("book"),
src: root.join("src"),
theme_path: root.join("theme"),
title: String::new(),
author: String::new(),
description: String::new(),
config: BookConfig::new(root),
content: vec![],
renderer: Box::new(HtmlHandlebars::new()),
@ -149,31 +137,33 @@ impl MDBook {
debug!("[fn]: init");
if !self.root.exists() {
fs::create_dir_all(&self.root).unwrap();
info!("{:?} created", &self.root);
if !self.config.get_root().exists() {
fs::create_dir_all(&self.config.get_root()).unwrap();
info!("{:?} created", &self.config.get_root());
}
{
if !self.dest.exists() {
debug!("[*]: {:?} does not exist, trying to create directory", self.dest);
fs::create_dir_all(&self.dest)?;
if let Some(htmlconfig) = self.config.get_html_config() {
if !htmlconfig.get_destination().exists() {
debug!("[*]: {:?} does not exist, trying to create directory", htmlconfig.get_destination());
fs::create_dir_all(htmlconfig.get_destination())?;
}
}
if !self.src.exists() {
debug!("[*]: {:?} does not exist, trying to create directory", self.src);
fs::create_dir_all(&self.src)?;
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.src.join("SUMMARY.md");
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", self.src.join("SUMMARY.md"));
let mut f = File::create(&self.src.join("SUMMARY.md"))?;
debug!("[*]: {:?} does not exist, trying to create SUMMARY.md", &summary);
let mut f = File::create(&summary)?;
debug!("[*]: Writing to SUMMARY.md");
@ -195,7 +185,7 @@ impl MDBook {
BookItem::Affix(ref ch) => ch,
};
if !ch.path.as_os_str().is_empty() {
let path = self.src.join(&ch.path);
let path = self.config.get_source().join(&ch.path);
if !path.exists() {
if !self.create_missing {
@ -219,22 +209,23 @@ impl MDBook {
pub fn create_gitignore(&self) {
let gitignore = self.get_gitignore();
if !gitignore.exists() {
// Gitignore does not exist, create it
// If the HTML renderer is not set, return
if self.config.get_html_config().is_none() { return; }
// Because of `src/book/mdbook.rs#L37-L39`,
// `dest` will always start with `root`.
// If it is not, `strip_prefix` will return an Error.
if !self.get_dest().starts_with(&self.root) {
return;
}
let destination = self.config.get_html_config()
.expect("The HtmlConfig does exist, checked just before")
.get_destination();
let relative = self.get_dest()
.strip_prefix(&self.root)
.expect("Destination is not relative to root.");
let relative = relative
// 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`
if !gitignore.exists() && destination.starts_with(self.config.get_root()) {
let relative = destination
.strip_prefix(self.config.get_root())
.expect("Could not strip the root prefix, path is not relative to root")
.to_str()
.expect("Path could not be yielded into a string slice.");
.expect("Could not convert to &str");
debug!("[*]: {:?} does not exist, trying to create .gitignore", gitignore);
@ -258,7 +249,9 @@ impl MDBook {
self.init()?;
// Clean output directory
utils::fs::remove_dir_content(&self.dest)?;
if let Some(htmlconfig) = self.config.get_html_config() {
utils::fs::remove_dir_content(htmlconfig.get_destination())?;
}
self.renderer.render(&self)?;
@ -267,51 +260,56 @@ impl MDBook {
pub fn get_gitignore(&self) -> PathBuf {
self.root.join(".gitignore")
self.config.get_root().join(".gitignore")
}
pub fn copy_theme(&self) -> Result<(), Box<Error>> {
debug!("[fn]: copy_theme");
let theme_dir = self.src.join("theme");
if let Some(themedir) = self.config.get_html_config().and_then(HtmlConfig::get_theme) {
if !theme_dir.exists() {
debug!("[*]: {:?} does not exist, trying to create directory", theme_dir);
fs::create_dir(&theme_dir)?;
if !themedir.exists() {
debug!("[*]: {:?} does not exist, trying to create directory", themedir);
fs::create_dir(&themedir)?;
}
// index.hbs
let mut index = File::create(&theme_dir.join("index.hbs"))?;
let mut index = File::create(&themedir.join("index.hbs"))?;
index.write_all(theme::INDEX)?;
// book.css
let mut css = File::create(&theme_dir.join("book.css"))?;
let mut css = File::create(&themedir.join("book.css"))?;
css.write_all(theme::CSS)?;
// favicon.png
let mut favicon = File::create(&theme_dir.join("favicon.png"))?;
let mut favicon = File::create(&themedir.join("favicon.png"))?;
favicon.write_all(theme::FAVICON)?;
// book.js
let mut js = File::create(&theme_dir.join("book.js"))?;
let mut js = File::create(&themedir.join("book.js"))?;
js.write_all(theme::JS)?;
// highlight.css
let mut highlight_css = File::create(&theme_dir.join("highlight.css"))?;
let mut highlight_css = File::create(&themedir.join("highlight.css"))?;
highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
// highlight.js
let mut highlight_js = File::create(&theme_dir.join("highlight.js"))?;
let mut highlight_js = File::create(&themedir.join("highlight.js"))?;
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
}
Ok(())
}
pub fn write_file<P: AsRef<Path>>(&self, filename: P, content: &[u8]) -> Result<(), Box<Error>> {
let path = self.get_dest().join(filename);
let path = self.get_destination()
.ok_or(String::from("HtmlConfig not set, could not find a destination"))?
.join(filename);
utils::fs::create_file(&path)
.and_then(|mut file| file.write_all(content))
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Could not create {}: {}", path.display(), e)))?;
Ok(())
}
@ -320,21 +318,23 @@ impl MDBook {
/// 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`
pub fn read_config(mut self) -> Self {
pub fn read_config(mut self) -> Result<Self, Box<Error>> {
let config = BookConfig::new(&self.root)
.read_config(&self.root)
.to_owned();
let toml = self.get_root().join("book.toml");
let json = self.get_root().join("book.json");
self.title = config.title;
self.description = config.description;
self.author = config.author;
if toml.exists() {
let mut file = File::open(toml)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
self.dest = config.dest;
self.src = config.src;
self.theme_path = config.theme_path;
let parsed_config = TomlConfig::from_toml(&content)?;
self.config.fill_from_tomlconfig(parsed_config);
} else if json.exists() {
unimplemented!();
}
self
Ok(self)
}
/// You can change the default renderer to another one
@ -374,7 +374,7 @@ impl MDBook {
if let BookItem::Chapter(_, ref ch) = *item {
if ch.path != PathBuf::new() {
let path = self.get_src().join(&ch.path);
let path = self.get_source().join(&ch.path);
println!("[*]: Testing file: {:?}", path);
@ -395,52 +395,49 @@ impl MDBook {
}
pub fn get_root(&self) -> &Path {
&self.root
self.config.get_root()
}
pub fn set_dest(mut self, dest: &Path) -> Self {
// Handle absolute and relative paths
if dest.is_absolute() {
self.dest = dest.to_owned();
pub fn with_destination<T: Into<PathBuf>>(mut self, destination: T) -> Self {
let root = self.config.get_root().to_owned();
if let Some(htmlconfig) = self.config.get_mut_html_config() {
htmlconfig.set_destination(&root, &destination.into());
} else {
let dest = self.root.join(dest).to_owned();
self.dest = dest;
error!("There is no HTML renderer set...");
}
self
}
pub fn get_dest(&self) -> &Path {
&self.dest
pub fn get_destination(&self) -> Option<&Path> {
if let Some(htmlconfig) = self.config.get_html_config() {
return Some(htmlconfig.get_destination());
}
pub fn set_src(mut self, src: &Path) -> Self {
// Handle absolute and relative paths
if src.is_absolute() {
self.src = src.to_owned();
} else {
let src = self.root.join(src).to_owned();
self.src = src;
None
}
pub fn with_source<T: Into<PathBuf>>(mut self, source: T) -> Self {
self.config.set_source(source);
self
}
pub fn get_src(&self) -> &Path {
&self.src
pub fn get_source(&self) -> &Path {
self.config.get_source()
}
pub fn set_title(mut self, title: &str) -> Self {
self.title = title.to_owned();
pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
self.config.set_title(title);
self
}
pub fn get_title(&self) -> &str {
&self.title
self.config.get_title()
}
/*
pub fn set_author(mut self, author: &str) -> Self {
self.author = author.to_owned();
self
@ -449,14 +446,14 @@ impl MDBook {
pub fn get_author(&self) -> &str {
&self.author
}
pub fn set_description(mut self, description: &str) -> Self {
self.description = description.to_owned();
*/
pub fn with_description<T: Into<String>>(mut self, description: T) -> Self {
self.config.set_description(description);
self
}
pub fn get_description(&self) -> &str {
&self.description
self.config.get_description()
}
pub fn set_livereload(&mut self, livereload: String) -> &mut Self {
@ -473,23 +470,28 @@ impl MDBook {
self.livereload.as_ref()
}
pub fn set_theme_path(mut self, theme_path: &Path) -> Self {
self.theme_path = if theme_path.is_absolute() {
theme_path.to_owned()
pub fn with_theme_path<T: Into<PathBuf>>(mut self, theme_path: T) -> Self {
let root = self.config.get_root().to_owned();
if let Some(htmlconfig) = self.config.get_mut_html_config() {
htmlconfig.set_theme(&root, &theme_path.into());
} else {
self.root.join(theme_path).to_owned()
};
error!("There is no HTML renderer set...");
}
self
}
pub fn get_theme_path(&self) -> &Path {
&self.theme_path
pub fn get_theme_path(&self) -> Option<&PathBuf> {
if let Some(htmlconfig) = self.config.get_html_config() {
return htmlconfig.get_theme();
}
None
}
// Construct book
fn parse_summary(&mut self) -> Result<(), Box<Error>> {
// When append becomes stable, use self.content.append() ...
self.content = parse::construct_bookitems(&self.src.join("SUMMARY.md"))?;
self.content = parse::construct_bookitems(&self.get_source().join("SUMMARY.md"))?;
Ok(())
}
}

View File

@ -102,14 +102,45 @@ impl BookConfig {
}
if let Some(tomlhtmlconfig) = tomlconfig.output.and_then(|o| o.html) {
let source = config.get_source().to_owned();
let mut htmlconfig = config.get_mut_html_config().expect("We just created a new config and it creates a default HtmlConfig");
htmlconfig.fill_from_tomlconfig(&root, &source, tomlhtmlconfig);
htmlconfig.fill_from_tomlconfig(&root, tomlhtmlconfig);
}
config
}
pub fn fill_from_tomlconfig(&mut self, tomlconfig: TomlConfig) -> &mut Self {
if let Some(s) = tomlconfig.source {
self.set_source(s);
}
if let Some(t) = tomlconfig.title {
self.set_title(t);
}
if let Some(d) = tomlconfig.description {
self.set_description(d);
}
if let Some(a) = tomlconfig.authors {
self.set_authors(a);
}
if let Some(a) = tomlconfig.author {
self.set_authors(vec![a]);
}
if let Some(tomlhtmlconfig) = tomlconfig.output.and_then(|o| o.html) {
let root = self.root.clone();
if let Some(htmlconfig) = self.get_mut_html_config() {
htmlconfig.fill_from_tomlconfig(root, tomlhtmlconfig);
}
}
self
}
pub fn set_root<T: Into<PathBuf>>(&mut self, root: T) -> &mut Self {
self.root = root.into();
self

View File

@ -29,10 +29,12 @@ impl HtmlConfig {
}
}
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self, root: T, source: T, tomlconfig: TomlHtmlConfig) -> &mut Self {
pub fn fill_from_tomlconfig<T: Into<PathBuf>>(&mut self, root: T, tomlconfig: TomlHtmlConfig) -> &mut Self {
let root = root.into();
if let Some(d) = tomlconfig.destination {
if d.is_relative() {
self.destination = root.into().join(d);
self.destination = root.join(d);
} else {
self.destination = d;
}
@ -40,7 +42,7 @@ impl HtmlConfig {
if let Some(t) = tomlconfig.theme {
if t.is_relative() {
self.theme = Some(source.into().join(t));
self.theme = Some(root.join(t));
} else {
self.theme = Some(t);
}
@ -53,6 +55,17 @@ impl HtmlConfig {
self
}
pub fn set_destination<T: Into<PathBuf>>(&mut self, root: T, destination: T) -> &mut Self {
let d = destination.into();
if d.is_relative() {
self.destination = root.into().join(d);
} else {
self.destination = d;
}
self
}
pub fn get_destination(&self) -> &Path {
&self.destination
}
@ -61,4 +74,15 @@ impl HtmlConfig {
pub fn get_theme(&self) -> Option<&PathBuf> {
self.theme.as_ref()
}
pub fn set_theme<T: Into<PathBuf>>(&mut self, root: T, theme: T) -> &mut Self {
let d = theme.into();
if d.is_relative() {
self.theme = Some(root.into().join(d));
} else {
self.theme = Some(d);
}
self
}
}

View File

@ -89,5 +89,4 @@ pub mod utils;
pub use book::MDBook;
pub use book::BookItem;
pub use book::BookConfig;
pub use renderer::Renderer;

View File

@ -33,7 +33,7 @@ impl Renderer for HtmlHandlebars {
let mut handlebars = Handlebars::new();
// Load theme
let theme = theme::Theme::new(book.get_theme_path());
let theme = theme::Theme::new(book.get_theme_path().expect("If the HTML renderer is called, one would assume the HtmlConfig is set..."));
// Register template
debug!("[*]: Register handlebars template");
@ -53,7 +53,7 @@ impl Renderer for HtmlHandlebars {
// Check if dest directory exists
debug!("[*]: Check if destination directory exists");
if fs::create_dir_all(book.get_dest()).is_err() {
if fs::create_dir_all(book.get_destination().expect("If the HTML renderer is called, one would assume the HtmlConfig is set...")).is_err() {
return Err(Box::new(io::Error::new(io::ErrorKind::Other,
"Unexpected error when constructing destination path")));
}
@ -67,7 +67,7 @@ impl Renderer for HtmlHandlebars {
BookItem::Affix(ref ch) => {
if ch.path != PathBuf::new() {
let path = book.get_src().join(&ch.path);
let path = book.get_source().join(&ch.path);
debug!("[*]: Opening file: {:?}", path);
let mut f = File::open(&path)?;
@ -116,8 +116,12 @@ impl Renderer for HtmlHandlebars {
debug!("[*]: index.html");
let mut content = String::new();
let _source = File::open(book.get_dest().join(&ch.path.with_extension("html")))?
.read_to_string(&mut content);
let _source = File::open(
book.get_destination()
.expect("If the HTML renderer is called, one would assume the HtmlConfig is set...")
.join(&ch.path.with_extension("html"))
)?.read_to_string(&mut content);
// This could cause a problem when someone displays
// code containing <base href=...>
@ -131,7 +135,10 @@ impl Renderer for HtmlHandlebars {
book.write_file("index.html", content.as_bytes())?;
info!("[*] Creating index.html from {:?} ✓",
book.get_dest().join(&ch.path.with_extension("html")));
book.get_destination()
.expect("If the HTML renderer is called, one would assume the HtmlConfig is set...")
.join(&ch.path.with_extension("html"))
);
index = false;
}
}
@ -181,7 +188,12 @@ impl Renderer for HtmlHandlebars {
book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF)?;
// Copy all remaining files
utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"])?;
utils::fs::copy_files_except_ext(
book.get_source(),
book.get_destination()
.expect("If the HTML renderer is called, one would assume the HtmlConfig is set..."), true, &["md"]
)?;
Ok(())
}