mdbook/book/
init.rs

1use std::fs::{self, File};
2use std::io::Write;
3use std::path::PathBuf;
4
5use super::MDBook;
6use crate::config::Config;
7use crate::errors::*;
8use crate::theme;
9use log::{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 = toml::to_vec(&self.config).with_context(|| "Unable to serialize the config")?;
102
103        File::create(book_toml)
104            .with_context(|| "Couldn't create book.toml")?
105            .write_all(&cfg)
106            .with_context(|| "Unable to write config to book.toml")?;
107        Ok(())
108    }
109
110    fn copy_across_theme(&self) -> Result<()> {
111        debug!("Copying theme");
112
113        let html_config = self.config.html_config().unwrap_or_default();
114        let themedir = html_config.theme_dir(&self.root);
115
116        if !themedir.exists() {
117            debug!(
118                "{} does not exist, creating the directory",
119                themedir.display()
120            );
121            fs::create_dir(&themedir)?;
122        }
123
124        let mut index = File::create(themedir.join("index.hbs"))?;
125        index.write_all(theme::INDEX)?;
126
127        let cssdir = themedir.join("css");
128        if !cssdir.exists() {
129            fs::create_dir(&cssdir)?;
130        }
131
132        let mut general_css = File::create(cssdir.join("general.css"))?;
133        general_css.write_all(theme::GENERAL_CSS)?;
134
135        let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
136        chrome_css.write_all(theme::CHROME_CSS)?;
137
138        if html_config.print.enable {
139            let mut print_css = File::create(cssdir.join("print.css"))?;
140            print_css.write_all(theme::PRINT_CSS)?;
141        }
142
143        let mut variables_css = File::create(cssdir.join("variables.css"))?;
144        variables_css.write_all(theme::VARIABLES_CSS)?;
145
146        let mut favicon = File::create(themedir.join("favicon.png"))?;
147        favicon.write_all(theme::FAVICON_PNG)?;
148
149        let mut favicon = File::create(themedir.join("favicon.svg"))?;
150        favicon.write_all(theme::FAVICON_SVG)?;
151
152        let mut js = File::create(themedir.join("book.js"))?;
153        js.write_all(theme::JS)?;
154
155        let mut highlight_css = File::create(themedir.join("highlight.css"))?;
156        highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
157
158        let mut highlight_js = File::create(themedir.join("highlight.js"))?;
159        highlight_js.write_all(theme::HIGHLIGHT_JS)?;
160
161        Ok(())
162    }
163
164    fn build_gitignore(&self) -> Result<()> {
165        debug!("Creating .gitignore");
166
167        let mut f = File::create(self.root.join(".gitignore"))?;
168
169        writeln!(f, "{}", self.config.build.build_dir.display())?;
170
171        Ok(())
172    }
173
174    fn create_stub_files(&self) -> Result<()> {
175        debug!("Creating example book contents");
176        let src_dir = self.root.join(&self.config.book.src);
177
178        let summary = src_dir.join("SUMMARY.md");
179        if !summary.exists() {
180            trace!("No summary found creating stub summary and chapter_1.md.");
181            let mut f = File::create(&summary).with_context(|| "Unable to create SUMMARY.md")?;
182            writeln!(f, "# Summary")?;
183            writeln!(f)?;
184            writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
185
186            let chapter_1 = src_dir.join("chapter_1.md");
187            let mut f =
188                File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
189            writeln!(f, "# Chapter 1")?;
190        } else {
191            trace!("Existing summary found, no need to create stub files.");
192        }
193        Ok(())
194    }
195
196    fn create_directory_structure(&self) -> Result<()> {
197        debug!("Creating directory tree");
198        fs::create_dir_all(&self.root)?;
199
200        let src = self.root.join(&self.config.book.src);
201        fs::create_dir_all(&src)?;
202
203        let build = self.root.join(&self.config.build.build_dir);
204        fs::create_dir_all(&build)?;
205
206        Ok(())
207    }
208}