mdbook_driver/
init.rs

1//! Support for initializing a new book.
2
3use super::MDBook;
4use anyhow::{Context, Result};
5use mdbook_core::config::Config;
6use mdbook_core::utils::fs;
7use mdbook_html::theme::Theme;
8use std::path::PathBuf;
9use tracing::{debug, error, info, trace};
10
11/// A helper for setting up a new book and its directory structure.
12#[derive(Debug, Clone, PartialEq)]
13pub struct BookBuilder {
14    root: PathBuf,
15    create_gitignore: bool,
16    config: Config,
17    copy_theme: bool,
18}
19
20impl BookBuilder {
21    /// Create a new `BookBuilder` which will generate a book in the provided
22    /// root directory.
23    pub fn new<P: Into<PathBuf>>(root: P) -> BookBuilder {
24        BookBuilder {
25            root: root.into(),
26            create_gitignore: false,
27            config: Config::default(),
28            copy_theme: false,
29        }
30    }
31
32    /// Set the [`Config`] to be used.
33    pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
34        self.config = cfg;
35        self
36    }
37
38    /// Get the config used by the `BookBuilder`.
39    pub fn config(&self) -> &Config {
40        &self.config
41    }
42
43    /// Should the theme be copied into the generated book (so users can tweak
44    /// it)?
45    pub fn copy_theme(&mut self, copy: bool) -> &mut BookBuilder {
46        self.copy_theme = copy;
47        self
48    }
49
50    /// Should we create a `.gitignore` file?
51    pub fn create_gitignore(&mut self, create: bool) -> &mut BookBuilder {
52        self.create_gitignore = create;
53        self
54    }
55
56    /// Generate the actual book. This will:
57    ///
58    /// - Create the directory structure.
59    /// - Stub out some dummy chapters and the `SUMMARY.md`.
60    /// - Create a `.gitignore` (if applicable)
61    /// - Create a themes directory and populate it (if applicable)
62    /// - Generate a `book.toml` file,
63    /// - Then load the book so we can build it or run tests.
64    pub fn build(&self) -> Result<MDBook> {
65        info!("Creating a new book with stub content");
66
67        self.create_directory_structure()
68            .with_context(|| "Unable to create directory structure")?;
69
70        self.create_stub_files()
71            .with_context(|| "Unable to create stub files")?;
72
73        if self.create_gitignore {
74            self.build_gitignore()
75                .with_context(|| "Unable to create .gitignore")?;
76        }
77
78        if self.copy_theme {
79            self.copy_across_theme()
80                .with_context(|| "Unable to copy across the theme")?;
81        }
82
83        self.write_book_toml()?;
84
85        match MDBook::load(&self.root) {
86            Ok(book) => Ok(book),
87            Err(e) => {
88                error!("{}", e);
89
90                panic!(
91                    "The BookBuilder should always create a valid book. If you are seeing this it \
92                     is a bug and should be reported."
93                );
94            }
95        }
96    }
97
98    fn write_book_toml(&self) -> Result<()> {
99        debug!("Writing book.toml");
100        let book_toml = self.root.join("book.toml");
101        let cfg =
102            toml::to_string(&self.config).with_context(|| "Unable to serialize the config")?;
103
104        fs::write(&book_toml, cfg)?;
105        Ok(())
106    }
107
108    fn copy_across_theme(&self) -> Result<()> {
109        debug!("Copying theme");
110
111        let html_config = self.config.html_config().unwrap_or_default();
112        Theme::copy_theme(&html_config, &self.root)?;
113        Ok(())
114    }
115
116    fn build_gitignore(&self) -> Result<()> {
117        fs::write(
118            self.root.join(".gitignore"),
119            format!("{}", self.config.build.build_dir.display()),
120        )?;
121        Ok(())
122    }
123
124    fn create_stub_files(&self) -> Result<()> {
125        debug!("Creating example book contents");
126        let src_dir = self.root.join(&self.config.book.src);
127
128        let summary = src_dir.join("SUMMARY.md");
129        if !summary.exists() {
130            trace!("No summary found creating stub summary and chapter_1.md.");
131            fs::write(
132                summary,
133                "# Summary\n\
134                 \n\
135                 - [Chapter 1](./chapter_1.md)\n",
136            )?;
137
138            fs::write(src_dir.join("chapter_1.md"), "# Chapter 1\n")?;
139        } else {
140            trace!("Existing summary found, no need to create stub files.");
141        }
142        Ok(())
143    }
144
145    fn create_directory_structure(&self) -> Result<()> {
146        debug!("Creating directory tree");
147        fs::create_dir_all(&self.root)?;
148
149        let src = self.root.join(&self.config.book.src);
150        fs::create_dir_all(src)?;
151
152        let build = self.root.join(&self.config.build.build_dir);
153        fs::create_dir_all(build)?;
154
155        Ok(())
156    }
157}