mdxbook 0.4.25

Fork of mdBook, with more customizations and flexibility for programmers
Documentation
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;

use super::MDBook;
use crate::config::Config;
use crate::errors::*;
use crate::theme::{
    load_file_contents_from_know_dir_to_vec, CHROME_CSS_NAME, FAVICON_PNG_NAME, FAVICON_SVG_NAME,
    GENERAL_CSS_NAME, HIGHLIGHT_CSS_NAME, HIGHLIGHT_JS_NAME, INDEX_NAME, JS_NAME, PRINT_CSS_NAME,
    VARIABLES_CSS_NAME,
};
use log::{debug, error, info, trace};

/// 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 {
    /// Create a new `BookBuilder` which will generate a book in the provided
    /// root directory.
    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
    }

    /// 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)?
    pub fn copy_theme(&mut self, copy: bool) -> &mut BookBuilder {
        self.copy_theme = copy;
        self
    }

    /// Should we create a `.gitignore` file?
    pub fn create_gitignore(&mut self, create: bool) -> &mut BookBuilder {
        self.create_gitignore = create;
        self
    }

    /// 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,
    /// - Then load the book so we can build it or run tests.
    pub fn build(&self) -> Result<MDBook> {
        info!("Creating a new book with stub content");

        self.create_directory_structure()
            .with_context(|| "Unable to create directory structure")?;

        self.create_stub_files()
            .with_context(|| "Unable to create stub files")?;

        if self.create_gitignore {
            self.build_gitignore()
                .with_context(|| "Unable to create .gitignore")?;
        }

        if self.copy_theme {
            self.copy_across_theme()
                .with_context(|| "Unable to copy across the theme")?;
        }

        self.write_book_toml()?;

        match MDBook::load(&self.root) {
            Ok(book) => Ok(book),
            Err(e) => {
                error!("{}", e);

                panic!(
                    "The BookBuilder should always create a valid book. If you are seeing this it \
                     is a bug and should be reported."
                );
            }
        }
    }

    fn write_book_toml(&self) -> Result<()> {
        debug!("Writing book.toml");
        let book_toml = self.root.join("book.toml");
        let cfg = toml::to_vec(&self.config).with_context(|| "Unable to serialize the config")?;

        File::create(book_toml)
            .with_context(|| "Couldn't create book.toml")?
            .write_all(&cfg)
            .with_context(|| "Unable to write config to book.toml")?;
        Ok(())
    }

    fn copy_across_theme(&self) -> Result<()> {
        debug!("Copying theme");

        let html_config = self.config.html_config().unwrap_or_default();
        let themedir = html_config.theme_dir(&self.root);

        if !themedir.exists() {
            debug!(
                "{} does not exist, creating the directory",
                themedir.display()
            );
            fs::create_dir(&themedir)?;
        }

        let mut index = File::create(themedir.join(INDEX_NAME))?;
        index.write_all(load_file_contents_from_know_dir_to_vec(INDEX_NAME).as_slice())?;

        let cssdir = themedir.join("css");
        if !cssdir.exists() {
            fs::create_dir(&cssdir)?;
        }

        let mut general_css = File::create(themedir.join(GENERAL_CSS_NAME))?;
        general_css
            .write_all(load_file_contents_from_know_dir_to_vec(GENERAL_CSS_NAME).as_slice())?;

        let mut chrome_css = File::create(themedir.join(CHROME_CSS_NAME))?;
        chrome_css
            .write_all(load_file_contents_from_know_dir_to_vec(CHROME_CSS_NAME).as_slice())?;

        if html_config.print.enable {
            let mut print_css = File::create(themedir.join(PRINT_CSS_NAME))?;
            print_css
                .write_all(load_file_contents_from_know_dir_to_vec(PRINT_CSS_NAME).as_slice())?;
        }

        let mut variables_css = File::create(themedir.join(VARIABLES_CSS_NAME))?;
        variables_css
            .write_all(load_file_contents_from_know_dir_to_vec(VARIABLES_CSS_NAME).as_slice())?;

        let mut favicon = File::create(themedir.join(FAVICON_PNG_NAME))?;
        favicon.write_all(load_file_contents_from_know_dir_to_vec(FAVICON_PNG_NAME).as_slice())?;

        let mut favicon = File::create(themedir.join(FAVICON_SVG_NAME))?;
        favicon.write_all(load_file_contents_from_know_dir_to_vec(FAVICON_SVG_NAME).as_slice())?;

        let mut js = File::create(themedir.join(JS_NAME))?;
        js.write_all(load_file_contents_from_know_dir_to_vec(JS_NAME).as_slice())?;

        let mut highlight_css = File::create(themedir.join(HIGHLIGHT_CSS_NAME))?;
        highlight_css
            .write_all(load_file_contents_from_know_dir_to_vec(HIGHLIGHT_CSS_NAME).as_slice())?;

        let mut highlight_js = File::create(themedir.join(HIGHLIGHT_JS_NAME))?;
        highlight_js
            .write_all(load_file_contents_from_know_dir_to_vec(HIGHLIGHT_JS_NAME).as_slice())?;

        Ok(())
    }

    fn build_gitignore(&self) -> Result<()> {
        debug!("Creating .gitignore");

        let mut f = File::create(self.root.join(".gitignore"))?;

        writeln!(f, "{}", self.config.build.build_dir.display())?;

        Ok(())
    }

    fn create_stub_files(&self) -> Result<()> {
        debug!("Creating example book contents");
        let src_dir = self.root.join(&self.config.book.src);

        let summary = src_dir.join("SUMMARY.md");
        if !summary.exists() {
            trace!("No summary found creating stub summary and chapter_1.md.");
            let mut f = File::create(&summary).with_context(|| "Unable to create SUMMARY.md")?;
            writeln!(f, "# Summary")?;
            writeln!(f)?;
            writeln!(f, "- [Chapter 1](./chapter_1.md)")?;

            let chapter_1 = src_dir.join("chapter_1.md");
            let mut f =
                File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
            writeln!(f, "# Chapter 1")?;
        } else {
            trace!("Existing summary found, no need to create stub files.");
        }
        Ok(())
    }

    fn create_directory_structure(&self) -> Result<()> {
        debug!("Creating directory tree");
        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(())
    }
}