2025-07-21 21:42:58 -07:00
|
|
|
//! Support for initializing a new book.
|
|
|
|
|
|
2017-11-18 20:41:04 +08:00
|
|
|
use super::MDBook;
|
2025-07-21 11:37:46 -07:00
|
|
|
use anyhow::{Context, Result};
|
2025-07-21 13:26:57 -07:00
|
|
|
use mdbook_core::config::Config;
|
2025-09-20 17:05:33 -07:00
|
|
|
use mdbook_core::utils::fs;
|
2025-07-21 18:32:36 -07:00
|
|
|
use mdbook_html::theme;
|
2025-09-20 17:05:33 -07:00
|
|
|
use std::path::PathBuf;
|
2025-09-12 06:13:45 -07:00
|
|
|
use tracing::{debug, error, info, trace};
|
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 11:24:43 +11: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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-10 14:51:30 -08:00
|
|
|
/// Set the [`Config`] to be used.
|
2017-11-18 20:41:04 +08:00
|
|
|
pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
|
|
|
|
|
self.config = cfg;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-11 11:24:43 +11: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 11:24:43 +11: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 11:24:43 +11: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 18:50:31 +11: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()
|
2020-05-20 14:32:00 -07:00
|
|
|
.with_context(|| "Unable to create directory structure")?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
|
|
|
|
self.create_stub_files()
|
2020-05-20 14:32:00 -07:00
|
|
|
.with_context(|| "Unable to create stub files")?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
|
|
|
|
if self.create_gitignore {
|
|
|
|
|
self.build_gitignore()
|
2020-05-20 14:32:00 -07:00
|
|
|
.with_context(|| "Unable to create .gitignore")?;
|
2017-11-18 21:22:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.copy_theme {
|
|
|
|
|
self.copy_across_theme()
|
2020-05-20 14:32:00 -07:00
|
|
|
.with_context(|| "Unable to copy across the theme")?;
|
2017-11-18 21:22:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.write_book_toml()?;
|
|
|
|
|
|
2017-12-11 11:24:43 +11: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 11:24:43 +11: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");
|
2025-07-25 13:24:19 -07:00
|
|
|
let cfg =
|
|
|
|
|
toml::to_string(&self.config).with_context(|| "Unable to serialize the config")?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(&book_toml, cfg)?;
|
2017-11-18 21:22:30 +08:00
|
|
|
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
|
|
|
|
2020-03-30 10:38:37 +02:00
|
|
|
let html_config = self.config.html_config().unwrap_or_default();
|
2021-01-06 16:29:38 -08:00
|
|
|
let themedir = html_config.theme_dir(&self.root);
|
2017-11-18 21:22:30 +08:00
|
|
|
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(themedir.join("book.js"), theme::JS)?;
|
|
|
|
|
fs::write(themedir.join("favicon.png"), theme::FAVICON_PNG)?;
|
|
|
|
|
fs::write(themedir.join("favicon.svg"), theme::FAVICON_SVG)?;
|
|
|
|
|
fs::write(themedir.join("highlight.css"), theme::HIGHLIGHT_CSS)?;
|
|
|
|
|
fs::write(themedir.join("highlight.js"), theme::HIGHLIGHT_JS)?;
|
|
|
|
|
fs::write(themedir.join("index.hbs"), theme::INDEX)?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
2018-07-25 15:51:09 -05:00
|
|
|
let cssdir = themedir.join("css");
|
|
|
|
|
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(cssdir.join("general.css"), theme::GENERAL_CSS)?;
|
|
|
|
|
fs::write(cssdir.join("chrome.css"), theme::CHROME_CSS)?;
|
|
|
|
|
fs::write(cssdir.join("variables.css"), theme::VARIABLES_CSS)?;
|
2020-03-30 10:38:37 +02:00
|
|
|
if html_config.print.enable {
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(cssdir.join("print.css"), theme::PRINT_CSS)?;
|
2020-03-30 10:38:37 +02:00
|
|
|
}
|
2018-07-25 15:51:09 -05:00
|
|
|
|
2025-09-20 17:05:33 -07:00
|
|
|
let fonts_dir = themedir.join("fonts");
|
|
|
|
|
fs::write(fonts_dir.join("fonts.css"), theme::fonts::CSS)?;
|
2023-01-15 11:42:46 -08:00
|
|
|
for (file_name, contents) in theme::fonts::LICENSES {
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(themedir.join(file_name), contents)?;
|
2023-01-15 11:42:46 -08:00
|
|
|
}
|
|
|
|
|
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(themedir.join(file_name), contents)?;
|
2023-01-15 11:42:46 -08:00
|
|
|
}
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(
|
|
|
|
|
themedir.join(theme::fonts::SOURCE_CODE_PRO.0),
|
2023-01-15 11:42:46 -08:00
|
|
|
theme::fonts::SOURCE_CODE_PRO.1,
|
|
|
|
|
)?;
|
|
|
|
|
|
2017-11-18 21:22:30 +08:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn build_gitignore(&self) -> Result<()> {
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(
|
|
|
|
|
self.root.join(".gitignore"),
|
|
|
|
|
format!("{}", 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");
|
2019-05-08 21:13:20 +02:00
|
|
|
if !summary.exists() {
|
|
|
|
|
trace!("No summary found creating stub summary and chapter_1.md.");
|
2025-09-20 17:05:33 -07:00
|
|
|
fs::write(
|
|
|
|
|
summary,
|
|
|
|
|
"# Summary\n\
|
|
|
|
|
\n\
|
|
|
|
|
- [Chapter 1](./chapter_1.md)\n",
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
fs::write(src_dir.join("chapter_1.md"), "# Chapter 1\n")?;
|
2019-05-08 21:13:20 +02:00
|
|
|
} else {
|
|
|
|
|
trace!("Existing summary found, no need to create stub files.");
|
|
|
|
|
}
|
2017-11-18 21:22:30 +08:00
|
|
|
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);
|
2023-05-13 09:44:11 -07:00
|
|
|
fs::create_dir_all(src)?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
|
|
|
|
let build = self.root.join(&self.config.build.build_dir);
|
2023-05-13 09:44:11 -07:00
|
|
|
fs::create_dir_all(build)?;
|
2017-11-18 21:22:30 +08:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|