2017-11-18 21:22:30 +08:00
|
|
|
use std::fs::{self, File};
|
|
|
|
use std::io::Write;
|
2018-07-24 01:45:01 +08:00
|
|
|
use std::path::PathBuf;
|
2017-11-18 21:22:30 +08:00
|
|
|
use toml;
|
|
|
|
|
2017-11-18 20:41:04 +08:00
|
|
|
use super::MDBook;
|
2018-07-24 01:45:01 +08:00
|
|
|
use config::Config;
|
2017-11-18 20:41:04 +08:00
|
|
|
use errors::*;
|
2018-07-24 01:45:01 +08:00
|
|
|
use theme;
|
2017-11-18 20:41:04 +08:00
|
|
|
|
|
|
|
/// A helper for setting up a new book and its directory structure.
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub struct BookBuilder {
|
|
|
|
root: PathBuf,
|
|
|
|
create_gitignore: bool,
|
|
|
|
config: Config,
|
|
|
|
copy_theme: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BookBuilder {
|
2017-12-11 08:24:43 +08:00
|
|
|
/// Create a new `BookBuilder` which will generate a book in the provided
|
|
|
|
/// root directory.
|
2017-11-18 20:41:04 +08:00
|
|
|
pub fn new<P: Into<PathBuf>>(root: P) -> BookBuilder {
|
|
|
|
BookBuilder {
|
|
|
|
root: root.into(),
|
|
|
|
create_gitignore: false,
|
|
|
|
config: Config::default(),
|
|
|
|
copy_theme: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the `Config` to be used.
|
|
|
|
pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
|
|
|
|
self.config = cfg;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2017-12-11 08:24:43 +08:00
|
|
|
/// Get the config used by the `BookBuilder`.
|
|
|
|
pub fn config(&self) -> &Config {
|
|
|
|
&self.config
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Should the theme be copied into the generated book (so users can tweak
|
|
|
|
/// it)?
|
2017-11-18 20:41:04 +08:00
|
|
|
pub fn copy_theme(&mut self, copy: bool) -> &mut BookBuilder {
|
|
|
|
self.copy_theme = copy;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2017-12-11 08:24:43 +08:00
|
|
|
/// Should we create a `.gitignore` file?
|
2017-11-18 20:41:04 +08:00
|
|
|
pub fn create_gitignore(&mut self, create: bool) -> &mut BookBuilder {
|
|
|
|
self.create_gitignore = create;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2017-12-11 08:24:43 +08:00
|
|
|
/// Generate the actual book. This will:
|
|
|
|
///
|
|
|
|
/// - Create the directory structure.
|
|
|
|
/// - Stub out some dummy chapters and the `SUMMARY.md`.
|
|
|
|
/// - Create a `.gitignore` (if applicable)
|
|
|
|
/// - Create a themes directory and populate it (if applicable)
|
|
|
|
/// - Generate a `book.toml` file,
|
2017-12-11 15:50:31 +08:00
|
|
|
/// - Then load the book so we can build it or run tests.
|
2017-11-18 20:41:04 +08:00
|
|
|
pub fn build(&self) -> Result<MDBook> {
|
2017-11-18 21:22:30 +08:00
|
|
|
info!("Creating a new book with stub content");
|
|
|
|
|
|
|
|
self.create_directory_structure()
|
|
|
|
.chain_err(|| "Unable to create directory structure")?;
|
|
|
|
|
|
|
|
self.create_stub_files()
|
|
|
|
.chain_err(|| "Unable to create stub files")?;
|
|
|
|
|
|
|
|
if self.create_gitignore {
|
|
|
|
self.build_gitignore()
|
|
|
|
.chain_err(|| "Unable to create .gitignore")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.copy_theme {
|
|
|
|
self.copy_across_theme()
|
|
|
|
.chain_err(|| "Unable to copy across the theme")?;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.write_book_toml()?;
|
|
|
|
|
2017-12-11 08:24:43 +08:00
|
|
|
match MDBook::load(&self.root) {
|
|
|
|
Ok(book) => Ok(book),
|
|
|
|
Err(e) => {
|
|
|
|
error!("{}", e);
|
2017-11-18 21:22:30 +08:00
|
|
|
|
2017-12-11 08:24:43 +08:00
|
|
|
panic!(
|
|
|
|
"The BookBuilder should always create a valid book. If you are seeing this it \
|
|
|
|
is a bug and should be reported."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2017-11-18 20:41:04 +08:00
|
|
|
}
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
fn write_book_toml(&self) -> Result<()> {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Writing book.toml");
|
2017-11-18 21:22:30 +08:00
|
|
|
let book_toml = self.root.join("book.toml");
|
2017-12-11 08:24:43 +08:00
|
|
|
let cfg = toml::to_vec(&self.config).chain_err(|| "Unable to serialize the config")?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
|
|
|
File::create(book_toml)
|
|
|
|
.chain_err(|| "Couldn't create book.toml")?
|
|
|
|
.write_all(&cfg)
|
|
|
|
.chain_err(|| "Unable to write config to book.toml")?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn copy_across_theme(&self) -> Result<()> {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Copying theme");
|
2017-11-18 21:22:30 +08:00
|
|
|
|
2018-08-03 09:22:49 +08:00
|
|
|
let themedir = self
|
|
|
|
.config
|
2017-12-11 08:24:43 +08:00
|
|
|
.html_config()
|
2017-11-18 21:22:30 +08:00
|
|
|
.and_then(|html| html.theme)
|
|
|
|
.unwrap_or_else(|| self.config.book.src.join("theme"));
|
|
|
|
let themedir = self.root.join(themedir);
|
|
|
|
|
|
|
|
if !themedir.exists() {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!(
|
|
|
|
"{} does not exist, creating the directory",
|
|
|
|
themedir.display()
|
|
|
|
);
|
2017-12-11 08:24:43 +08:00
|
|
|
fs::create_dir(&themedir)?;
|
2017-11-18 21:22:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut index = File::create(themedir.join("index.hbs"))?;
|
|
|
|
index.write_all(theme::INDEX)?;
|
|
|
|
|
2018-07-26 04:51:09 +08:00
|
|
|
let cssdir = themedir.join("css");
|
|
|
|
fs::create_dir(&cssdir)?;
|
|
|
|
|
|
|
|
let mut general_css = File::create(cssdir.join("general.css"))?;
|
|
|
|
general_css.write_all(theme::GENERAL_CSS)?;
|
|
|
|
|
2019-03-20 22:21:36 +08:00
|
|
|
let mut book_css = File::create(cssdir.join("book.css"))?;
|
|
|
|
book_css.write_all(theme::BOOK_CSS)?;
|
|
|
|
|
2018-07-26 04:51:09 +08:00
|
|
|
let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
|
|
|
|
chrome_css.write_all(theme::CHROME_CSS)?;
|
|
|
|
|
|
|
|
let mut print_css = File::create(cssdir.join("print.css"))?;
|
|
|
|
print_css.write_all(theme::PRINT_CSS)?;
|
|
|
|
|
|
|
|
let mut variables_css = File::create(cssdir.join("variables.css"))?;
|
|
|
|
variables_css.write_all(theme::VARIABLES_CSS)?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
|
|
|
let mut favicon = File::create(themedir.join("favicon.png"))?;
|
|
|
|
favicon.write_all(theme::FAVICON)?;
|
|
|
|
|
|
|
|
let mut js = File::create(themedir.join("book.js"))?;
|
|
|
|
js.write_all(theme::JS)?;
|
|
|
|
|
|
|
|
let mut highlight_css = File::create(themedir.join("highlight.css"))?;
|
|
|
|
highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
|
|
|
|
|
|
|
|
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
|
|
|
|
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build_gitignore(&self) -> Result<()> {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Creating .gitignore");
|
2017-11-18 21:22:30 +08:00
|
|
|
|
2017-12-11 15:50:31 +08:00
|
|
|
let mut f = File::create(self.root.join(".gitignore"))?;
|
|
|
|
|
|
|
|
writeln!(f, "{}", self.config.build.build_dir.display())?;
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_stub_files(&self) -> Result<()> {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Creating example book contents");
|
2017-11-18 21:22:30 +08:00
|
|
|
let src_dir = self.root.join(&self.config.book.src);
|
|
|
|
|
|
|
|
let summary = src_dir.join("SUMMARY.md");
|
|
|
|
let mut f = File::create(&summary).chain_err(|| "Unable to create SUMMARY.md")?;
|
|
|
|
writeln!(f, "# Summary")?;
|
2018-12-04 07:10:09 +08:00
|
|
|
writeln!(f)?;
|
2017-11-18 21:22:30 +08:00
|
|
|
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
|
|
|
|
|
|
|
|
let chapter_1 = src_dir.join("chapter_1.md");
|
|
|
|
let mut f = File::create(&chapter_1).chain_err(|| "Unable to create chapter_1.md")?;
|
|
|
|
writeln!(f, "# Chapter 1")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_directory_structure(&self) -> Result<()> {
|
2018-01-23 01:28:37 +08:00
|
|
|
debug!("Creating directory tree");
|
2017-11-18 21:22:30 +08:00
|
|
|
fs::create_dir_all(&self.root)?;
|
|
|
|
|
|
|
|
let src = self.root.join(&self.config.book.src);
|
|
|
|
fs::create_dir_all(&src)?;
|
|
|
|
|
|
|
|
let build = self.root.join(&self.config.build.build_dir);
|
|
|
|
fs::create_dir_all(&build)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|