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#[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 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 pub fn with_config(&mut self, cfg: Config) -> &mut BookBuilder {
34 self.config = cfg;
35 self
36 }
37
38 pub fn config(&self) -> &Config {
40 &self.config
41 }
42
43 pub fn copy_theme(&mut self, copy: bool) -> &mut BookBuilder {
46 self.copy_theme = copy;
47 self
48 }
49
50 pub fn create_gitignore(&mut self, create: bool) -> &mut BookBuilder {
52 self.create_gitignore = create;
53 self
54 }
55
56 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}