1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
//! Handles CLI configuration, environment variables, and defaults
use std::ffi::OsStr;
use std::path::PathBuf;
use anyhow::Context;
use anyhow::Result;
use serde::Deserialize;
use tracing::debug;
use super::args::CargoTomlDirArgs;
use super::args::DestDirArgs;
use super::args::DestFileArgs;
use super::args::MarkdownDirArgs;
use super::args::UrlArgs;
use super::GlobalOpts;
/// Stores environment variables into a Configuration struct.
/// Defaults apply if not present.
pub(crate) fn init(global_opts: GlobalOpts) -> Result<Configuration> {
// Serialize environment variables into the Configuration struct
let mut c = envy::from_env::<Configuration>()?;
c.global_opts = global_opts;
Ok(c)
}
/// Application configuration and environment variables
#[derive(Deserialize, Debug)]
#[serde(default)]
pub(crate) struct Configuration {
/// BOOK_ROOT_DIR_PATH environment variable:
/// the book's root directory (which contains `book.toml`),
/// typically '.'
book_root_dir_path: PathBuf,
/// MARKDOWN_DIR_PATH environment variable:
/// Markdown source directory,
/// typically ./src/
markdown_dir_path: Option<PathBuf>,
/// BOOK_HTML_BUILD_DIR_PATH environment variable:
/// Directory where `mdbook` outputs the book's HTML and JS,
/// typically ./book/ or ./book/html/
book_html_build_dir_path: Option<PathBuf>,
/// BOOK_MARKDOWN_BUILD_DIR_PATH environment variable:
/// Directory where `mdbook` outputs the book's fully expanded Markdown,
/// i.e. with all includes resolved. It is typically ./book/markdown/
/// The directory is created only if `[output.markdown]` is added to
/// `book.toml`.
book_markdown_build_dir_path: Option<PathBuf>,
/// CARGO_TOML_DIR_PATH environment variable:
/// Directory where `Cargo.toml` may be found,
/// typically '.'
cargo_toml_dir_path: Option<PathBuf>,
/// DEFAULT_DEST_DIR_PATH environment variable:
/// Default destination directory for `mdbook-utils` outputs.
default_dest_dir_path: Option<PathBuf>,
/// BASE_URL environment variable:
/// Base url of the website where the book will be deployed
/// e.g. https://example.com/mybook/
/// It is used to build sitemaps.
base_url: String,
/// Global options that apply to all (sub)commands.
#[serde(skip)]
global_opts: GlobalOpts,
}
/// Defaults if the environment variables are not set
impl Default for Configuration {
fn default() -> Self {
Self {
book_root_dir_path: PathBuf::from("."),
markdown_dir_path: None,
book_html_build_dir_path: None,
book_markdown_build_dir_path: None,
cargo_toml_dir_path: None,
default_dest_dir_path: None,
base_url: String::from("http://example.com/mybook/"),
global_opts: GlobalOpts::default(),
}
}
}
impl Configuration {
/// Returns the Markdown source directory provided by the
/// command-line argument (if set);
/// the MARKDOWN_DIR_PATH environment variable (if set);
/// the "book.src" field (which defaults to {book_root_dir_path}/src) in
/// `book.toml` (if `book.toml` is found); otherwise the default value
/// passed as function argument (./src/ or ./drafts/ typically).
///
/// `book.toml` is looked up in BOOK_ROOT_DIR_PATH, if set,
/// or the current working directory.
pub(crate) fn markdown_src_dir_path<S: AsRef<OsStr>>(
&self,
args: MarkdownDirArgs,
default_dir_path: S,
) -> Result<PathBuf> {
let p = args
.markdown_dir_path
.unwrap_or(if let Some(ref mdp) = self.markdown_dir_path {
debug!("MARKDOWN_DIR_PATH set: {}", mdp.display());
mdp.clone()
} else if let Some(p) = self.get_markdown_dir_path_from_book_toml() {
debug!("markdown_dir_path set from `book.toml`: {}", p.display());
p
} else {
debug!(
"markdown_dir_path set to default: {:?}",
default_dir_path.as_ref()
);
PathBuf::from(default_dir_path.as_ref())
});
let p = p.canonicalize()
.with_context(|| format!("[markdown_dir_path] The Markdown source directory {} does not exist or cannot be resolved.", p.display()))?;
Ok(p)
}
/// Return markdown_dir_path if retrievable from `book.toml`,
/// None otherwise.
///
/// Does not propagate errors, since having a `book.toml` is optional.
fn get_markdown_dir_path_from_book_toml(&self) -> Option<PathBuf> {
match super::book_toml::try_parse_book_toml(self.book_root_dir_path.clone()) {
// `book.toml` exists and is parseable
Ok((src, _, _)) => Some(src),
Err(e) => {
debug!(
"`book.toml` does not exist in {} or is not parseable. Error: {}",
self.book_root_dir_path.display(),
e
);
None
}
}
}
/// Returns the directory where `mdbook` outputs the book's fully expanded
/// Markdown, i.e. with all includes resolved, if `[output.markdown]` is
/// added to `book.toml`
///
/// The return value is provided by the command-line argument (if set);
/// the BOOK_MARKDOWN_BUILD_DIR_PATH environment variable (if set);
/// the "build.build-dir" field in `book.toml` (which defaults to
/// `{book_root_dir_path}/book`) followed by `markdown` (if `book.toml`
/// is found); otherwise the default value passed as function argument
/// (`./book/markdown` typically).
///
/// `book.toml` is looked up in BOOK_ROOT_DIR_PATH, if set,
/// or the current working directory.
pub(crate) fn book_markdown_build_dir_path<S: AsRef<OsStr>>(
&self,
args: MarkdownDirArgs,
default_dir_path: S,
) -> Result<PathBuf> {
let p = args.markdown_dir_path.unwrap_or(
if let Some(ref mdp) = self.book_markdown_build_dir_path {
debug!("BOOK_MARKDOWN_BUILD_DIR_PATH set: {}", mdp.display());
mdp.clone()
} else if let Ok((_, _, Some(p))) =
super::book_toml::try_parse_book_toml(self.book_root_dir_path.clone())
{
debug!(
"book_markdown_build_dir_path set from `book.toml`: {}",
p.display()
);
p
} else {
debug!(
"book_markdown_build_dir_path set to default: {:?}",
default_dir_path.as_ref()
);
PathBuf::from(default_dir_path.as_ref())
},
);
let p = p.canonicalize()
.with_context(|| format!("[book_markdown_build_dir_path] The Markdown output (build) directory {} does not exist or cannot be resolved. Try `mdbook build`.", p.display()))?;
Ok(p)
}
/// Returns the default destination directory where to store mdbook-utils
/// outputs, as provided by the DEFAULT_DEST_DIR_PATH environment variable
/// (if set), otherwise the book root directory (which defaults to '.').
fn default_dest_dir_path(&self) -> PathBuf {
if let Some(ref pb) = self.default_dest_dir_path {
pb.into()
} else {
self.book_root_dir_path.clone()
}
}
/// Returns the destination directory where to store mdbook-utils
/// outputs, as provided by the command-line argument (if set),
/// the DEFAULT_DEST_DIR_PATH environment variable (if set),
/// or the current working directory otherwise.
pub(crate) fn dest_dir_path(&self, args: DestDirArgs) -> PathBuf {
args.dir_path.unwrap_or(self.default_dest_dir_path())
}
/// Returns the destination file path, as provided by
/// the command-line argument (if set) or the default destination path and
/// default filename otherwise (see `default_dest_dir_path`).
pub(crate) fn dest_file_path(&self, args: DestFileArgs, filename: &str) -> PathBuf {
args.file_path
.unwrap_or_else(|| self.default_dest_dir_path().join(filename))
}
/// Returns the directory where `Cargo.toml` may be found,
/// as provided by the command-line argument (if set),
/// the CARGO_TOML_DIR_PATH environment variable (if set),
/// or BOOK_ROOT_DIR_PATH (which defaults to '.') otherwise.
pub(crate) fn cargo_toml_dir_path(&self, args: CargoTomlDirArgs) -> Result<PathBuf> {
let p =
args.cargo_toml_dir_path
.unwrap_or(if let Some(ref ctdp) = self.cargo_toml_dir_path {
ctdp.clone()
} else {
self.book_root_dir_path.clone()
});
let p = p.canonicalize().with_context(|| format!("[cargo_toml_dir_path] The directory {} where `Cargo.toml` may be found does not exist or cannot be resolved.", p.display()))?;
Ok(p)
}
/// Returns the base url of the website where the book will be deployed
/// (used to build sitemaps), as provided by the BASE_URL environment
/// variable (if set), otherwise the default value.
pub(crate) fn base_url(&self, args: UrlArgs) -> Result<url::Url> {
Ok(args.url.unwrap_or(
url::Url::parse(&self.base_url)
.context("[base_url] Could not parse the base url provided.")?,
))
}
/// Returns the sitemap output file path, as provided by
/// the command-line argument (if set); or {path}/sitemap.xml,
/// where the HTML output path is retrieved from `book.toml`, if possible,
/// or the default (`./book`) otherwise.
pub(crate) fn sitemap_file_path(&self, args: DestFileArgs) -> PathBuf {
if let Some(file_path) = args.file_path {
file_path
} else {
let dir: PathBuf = if let Some(ref d) = self.book_html_build_dir_path {
d.clone()
} else if let Ok((_, html_output_dir, _)) =
super::book_toml::try_parse_book_toml(self.book_root_dir_path.clone())
{
// `book.toml`` exists, is parseable and build.build-dir is defined
html_output_dir
} else {
"./book".into()
};
dir.join("sitemap.xml")
}
}
/// if true, skip confirmation prompts
pub(crate) fn skip_confirm(&self) -> bool {
self.global_opts.yes
}
}
#[cfg(test)]
mod test {
// use super::*;
// #[test]
// fn test() {
// }
}