Skip to content

Commit 2aa2b95

Browse files
authored
Merge pull request #2856 from ehuss/fs-update
Clean up some fs-related utilities
2 parents f24221a + 797112e commit 2aa2b95

File tree

13 files changed

+159
-254
lines changed

13 files changed

+159
-254
lines changed

crates/mdbook-core/src/config.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,11 @@
4343
//! # run().unwrap()
4444
//! ```
4545
46-
use crate::utils::TomlExt;
47-
use crate::utils::log_backtrace;
46+
use crate::utils::{TomlExt, fs, log_backtrace};
4847
use anyhow::{Context, Error, Result, bail};
4948
use serde::{Deserialize, Serialize};
5049
use std::collections::{BTreeMap, HashMap};
5150
use std::env;
52-
use std::fs::File;
53-
use std::io::Read;
5451
use std::path::{Path, PathBuf};
5552
use std::str::FromStr;
5653
use toml::Value;
@@ -113,13 +110,8 @@ impl Default for Config {
113110
impl Config {
114111
/// Load the configuration file from disk.
115112
pub fn from_disk<P: AsRef<Path>>(config_file: P) -> Result<Config> {
116-
let mut buffer = String::new();
117-
File::open(config_file)
118-
.with_context(|| "Unable to open the configuration file")?
119-
.read_to_string(&mut buffer)
120-
.with_context(|| "Couldn't read the file")?;
121-
122-
Config::from_str(&buffer)
113+
let cfg = fs::read_to_string(config_file)?;
114+
Config::from_str(&cfg)
123115
}
124116

125117
/// Updates the `Config` from the available environment variables.

crates/mdbook-core/src/utils/fs.rs

Lines changed: 49 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
//! Filesystem utilities and helpers.
22
33
use anyhow::{Context, Result};
4-
use std::fs::{self, File};
5-
use std::io::Write;
4+
use std::fs;
65
use std::path::{Component, Path, PathBuf};
7-
use tracing::{debug, trace};
6+
use tracing::debug;
87

9-
/// Write the given data to a file, creating it first if necessary
10-
pub fn write_file<P: AsRef<Path>>(build_dir: &Path, filename: P, content: &[u8]) -> Result<()> {
11-
let path = build_dir.join(filename);
8+
/// Reads a file into a string.
9+
///
10+
/// Equivalent to [`std::fs::read_to_string`] with better error messages.
11+
pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
12+
let path = path.as_ref();
13+
fs::read_to_string(path).with_context(|| format!("failed to read `{}`", path.display()))
14+
}
1215

13-
create_file(&path)?.write_all(content).map_err(Into::into)
16+
/// Writes a file to disk.
17+
///
18+
/// Equivalent to [`std::fs::write`] with better error messages. This will
19+
/// also create the parent directory if it doesn't exist.
20+
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
21+
let path = path.as_ref();
22+
debug!("Writing `{}`", path.display());
23+
if let Some(parent) = path.parent() {
24+
create_dir_all(parent)?;
25+
}
26+
fs::write(path, contents.as_ref())
27+
.with_context(|| format!("failed to write `{}`", path.display()))
28+
}
29+
30+
/// Equivalent to [`std::fs::create_dir_all`] with better error messages.
31+
pub fn create_dir_all(p: impl AsRef<Path>) -> Result<()> {
32+
let p = p.as_ref();
33+
fs::create_dir_all(p)
34+
.with_context(|| format!("failed to create directory `{}`", p.display()))?;
35+
Ok(())
1436
}
1537

1638
/// Takes a path and returns a path containing just enough `../` to point to
@@ -48,30 +70,19 @@ pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
4870
})
4971
}
5072

51-
/// This function creates a file and returns it. But before creating the file
52-
/// it checks every directory in the path to see if it exists,
53-
/// and if it does not it will be created.
54-
pub fn create_file(path: &Path) -> Result<File> {
55-
debug!("Creating {}", path.display());
56-
57-
// Construct path
58-
if let Some(p) = path.parent() {
59-
trace!("Parent directory is: {:?}", p);
60-
61-
fs::create_dir_all(p)?;
62-
}
63-
64-
File::create(path).map_err(Into::into)
65-
}
66-
67-
/// Removes all the content of a directory but not the directory itself
73+
/// Removes all the content of a directory but not the directory itself.
6874
pub fn remove_dir_content(dir: &Path) -> Result<()> {
69-
for item in fs::read_dir(dir)?.flatten() {
75+
for item in fs::read_dir(dir)
76+
.with_context(|| format!("failed to read directory `{}`", dir.display()))?
77+
.flatten()
78+
{
7079
let item = item.path();
7180
if item.is_dir() {
72-
fs::remove_dir_all(item)?;
81+
fs::remove_dir_all(&item)
82+
.with_context(|| format!("failed to remove `{}`", item.display()))?;
7383
} else {
74-
fs::remove_file(item)?;
84+
fs::remove_file(&item)
85+
.with_context(|| format!("failed to remove `{}`", item.display()))?;
7586
}
7687
}
7788
Ok(())
@@ -162,7 +173,7 @@ fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
162173
use std::fs::OpenOptions;
163174
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
164175

165-
let mut reader = File::open(from)?;
176+
let mut reader = std::fs::File::open(from)?;
166177
let metadata = reader.metadata()?;
167178
if !metadata.is_file() {
168179
anyhow::bail!(
@@ -198,8 +209,9 @@ fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
198209

199210
#[cfg(test)]
200211
mod tests {
201-
use super::copy_files_except_ext;
202-
use std::{fs, io::Result, path::Path};
212+
use super::*;
213+
use std::io::Result;
214+
use std::path::Path;
203215

204216
#[cfg(target_os = "windows")]
205217
fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
@@ -219,38 +231,18 @@ mod tests {
219231
};
220232

221233
// Create a couple of files
222-
if let Err(err) = fs::File::create(tmp.path().join("file.txt")) {
223-
panic!("Could not create file.txt: {err}");
224-
}
225-
if let Err(err) = fs::File::create(tmp.path().join("file.md")) {
226-
panic!("Could not create file.md: {err}");
227-
}
228-
if let Err(err) = fs::File::create(tmp.path().join("file.png")) {
229-
panic!("Could not create file.png: {err}");
230-
}
231-
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir")) {
232-
panic!("Could not create sub_dir: {err}");
233-
}
234-
if let Err(err) = fs::File::create(tmp.path().join("sub_dir/file.png")) {
235-
panic!("Could not create sub_dir/file.png: {err}");
236-
}
237-
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir_exists")) {
238-
panic!("Could not create sub_dir_exists: {err}");
239-
}
240-
if let Err(err) = fs::File::create(tmp.path().join("sub_dir_exists/file.txt")) {
241-
panic!("Could not create sub_dir_exists/file.txt: {err}");
242-
}
234+
write(tmp.path().join("file.txt"), "").unwrap();
235+
write(tmp.path().join("file.md"), "").unwrap();
236+
write(tmp.path().join("file.png"), "").unwrap();
237+
write(tmp.path().join("sub_dir/file.png"), "").unwrap();
238+
write(tmp.path().join("sub_dir_exists/file.txt"), "").unwrap();
243239
if let Err(err) = symlink(tmp.path().join("file.png"), tmp.path().join("symlink.png")) {
244240
panic!("Could not symlink file.png: {err}");
245241
}
246242

247243
// Create output dir
248-
if let Err(err) = fs::create_dir(tmp.path().join("output")) {
249-
panic!("Could not create output: {err}");
250-
}
251-
if let Err(err) = fs::create_dir(tmp.path().join("output/sub_dir_exists")) {
252-
panic!("Could not create output/sub_dir_exists: {err}");
253-
}
244+
create_dir_all(tmp.path().join("output")).unwrap();
245+
create_dir_all(tmp.path().join("output/sub_dir_exists")).unwrap();
254246

255247
if let Err(e) =
256248
copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])

crates/mdbook-driver/src/builtin_preprocessors/links.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use self::take_lines::{
55
use anyhow::{Context, Result};
66
use mdbook_core::book::{Book, BookItem};
77
use mdbook_core::static_regex;
8+
use mdbook_core::utils::fs;
89
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
910
use regex::{CaptureMatches, Captures};
10-
use std::fs;
1111
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
1212
use std::path::{Path, PathBuf};
1313
use tracing::{error, warn};

crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use anyhow::{Context, Result};
2-
use mdbook_core::utils;
2+
use mdbook_core::utils::fs;
33
use mdbook_renderer::{RenderContext, Renderer};
4-
use std::fs;
54
use tracing::trace;
65

76
/// A renderer to output the Markdown after the preprocessors have run. Mostly useful
@@ -27,17 +26,16 @@ impl Renderer for MarkdownRenderer {
2726
let book = &ctx.book;
2827

2928
if destination.exists() {
30-
utils::fs::remove_dir_content(destination)
29+
fs::remove_dir_content(destination)
3130
.with_context(|| "Unable to remove stale Markdown output")?;
3231
}
3332

3433
trace!("markdown render");
3534
for ch in book.chapters() {
36-
utils::fs::write_file(
37-
&ctx.destination,
38-
ch.path.as_ref().expect("Checked path exists before"),
39-
ch.content.as_bytes(),
40-
)?;
35+
let path = ctx
36+
.destination
37+
.join(ch.path.as_ref().expect("Checked path exists before"));
38+
fs::write(path, &ch.content)?;
4139
}
4240

4341
fs::create_dir_all(destination)

crates/mdbook-driver/src/builtin_renderers/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
//! The HTML renderer can be found in the [`mdbook_html`] crate.
44
55
use anyhow::{Context, Result, bail};
6+
use mdbook_core::utils::fs;
67
use mdbook_renderer::{RenderContext, Renderer};
7-
use std::fs;
88
use std::process::Stdio;
99
use tracing::{error, info, trace, warn};
1010

crates/mdbook-driver/src/init.rs

Lines changed: 31 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
//! Support for initializing a new book.
22
3-
use std::fs::{self, File};
4-
use std::io::Write;
5-
use std::path::PathBuf;
6-
73
use super::MDBook;
84
use anyhow::{Context, Result};
95
use mdbook_core::config::Config;
10-
use mdbook_core::utils::fs::write_file;
6+
use mdbook_core::utils::fs;
117
use mdbook_html::theme;
8+
use std::path::PathBuf;
129
use tracing::{debug, error, info, trace};
1310

1411
/// A helper for setting up a new book and its directory structure.
@@ -104,8 +101,7 @@ impl BookBuilder {
104101
let cfg =
105102
toml::to_string(&self.config).with_context(|| "Unable to serialize the config")?;
106103

107-
std::fs::write(&book_toml, cfg)
108-
.with_context(|| format!("Unable to write config to {book_toml:?}"))?;
104+
fs::write(&book_toml, cfg)?;
109105
Ok(())
110106
}
111107

@@ -115,74 +111,43 @@ impl BookBuilder {
115111
let html_config = self.config.html_config().unwrap_or_default();
116112
let themedir = html_config.theme_dir(&self.root);
117113

118-
if !themedir.exists() {
119-
debug!(
120-
"{} does not exist, creating the directory",
121-
themedir.display()
122-
);
123-
fs::create_dir(&themedir)?;
124-
}
125-
126-
let mut index = File::create(themedir.join("index.hbs"))?;
127-
index.write_all(theme::INDEX)?;
114+
fs::write(themedir.join("book.js"), theme::JS)?;
115+
fs::write(themedir.join("favicon.png"), theme::FAVICON_PNG)?;
116+
fs::write(themedir.join("favicon.svg"), theme::FAVICON_SVG)?;
117+
fs::write(themedir.join("highlight.css"), theme::HIGHLIGHT_CSS)?;
118+
fs::write(themedir.join("highlight.js"), theme::HIGHLIGHT_JS)?;
119+
fs::write(themedir.join("index.hbs"), theme::INDEX)?;
128120

129121
let cssdir = themedir.join("css");
130-
if !cssdir.exists() {
131-
fs::create_dir(&cssdir)?;
132-
}
133-
134-
let mut general_css = File::create(cssdir.join("general.css"))?;
135-
general_css.write_all(theme::GENERAL_CSS)?;
136-
137-
let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
138-
chrome_css.write_all(theme::CHROME_CSS)?;
139122

123+
fs::write(cssdir.join("general.css"), theme::GENERAL_CSS)?;
124+
fs::write(cssdir.join("chrome.css"), theme::CHROME_CSS)?;
125+
fs::write(cssdir.join("variables.css"), theme::VARIABLES_CSS)?;
140126
if html_config.print.enable {
141-
let mut print_css = File::create(cssdir.join("print.css"))?;
142-
print_css.write_all(theme::PRINT_CSS)?;
127+
fs::write(cssdir.join("print.css"), theme::PRINT_CSS)?;
143128
}
144129

145-
let mut variables_css = File::create(cssdir.join("variables.css"))?;
146-
variables_css.write_all(theme::VARIABLES_CSS)?;
147-
148-
let mut favicon = File::create(themedir.join("favicon.png"))?;
149-
favicon.write_all(theme::FAVICON_PNG)?;
150-
151-
let mut favicon = File::create(themedir.join("favicon.svg"))?;
152-
favicon.write_all(theme::FAVICON_SVG)?;
153-
154-
let mut js = File::create(themedir.join("book.js"))?;
155-
js.write_all(theme::JS)?;
156-
157-
let mut highlight_css = File::create(themedir.join("highlight.css"))?;
158-
highlight_css.write_all(theme::HIGHLIGHT_CSS)?;
159-
160-
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
161-
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
162-
163-
write_file(&themedir.join("fonts"), "fonts.css", theme::fonts::CSS)?;
130+
let fonts_dir = themedir.join("fonts");
131+
fs::write(fonts_dir.join("fonts.css"), theme::fonts::CSS)?;
164132
for (file_name, contents) in theme::fonts::LICENSES {
165-
write_file(&themedir, file_name, contents)?;
133+
fs::write(themedir.join(file_name), contents)?;
166134
}
167135
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
168-
write_file(&themedir, file_name, contents)?;
136+
fs::write(themedir.join(file_name), contents)?;
169137
}
170-
write_file(
171-
&themedir,
172-
theme::fonts::SOURCE_CODE_PRO.0,
138+
fs::write(
139+
themedir.join(theme::fonts::SOURCE_CODE_PRO.0),
173140
theme::fonts::SOURCE_CODE_PRO.1,
174141
)?;
175142

176143
Ok(())
177144
}
178145

179146
fn build_gitignore(&self) -> Result<()> {
180-
debug!("Creating .gitignore");
181-
182-
let mut f = File::create(self.root.join(".gitignore"))?;
183-
184-
writeln!(f, "{}", self.config.build.build_dir.display())?;
185-
147+
fs::write(
148+
self.root.join(".gitignore"),
149+
format!("{}", self.config.build.build_dir.display()),
150+
)?;
186151
Ok(())
187152
}
188153

@@ -193,14 +158,14 @@ impl BookBuilder {
193158
let summary = src_dir.join("SUMMARY.md");
194159
if !summary.exists() {
195160
trace!("No summary found creating stub summary and chapter_1.md.");
196-
let mut f = File::create(&summary).with_context(|| "Unable to create SUMMARY.md")?;
197-
writeln!(f, "# Summary")?;
198-
writeln!(f)?;
199-
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;
200-
201-
let chapter_1 = src_dir.join("chapter_1.md");
202-
let mut f = File::create(chapter_1).with_context(|| "Unable to create chapter_1.md")?;
203-
writeln!(f, "# Chapter 1")?;
161+
fs::write(
162+
summary,
163+
"# Summary\n\
164+
\n\
165+
- [Chapter 1](./chapter_1.md)\n",
166+
)?;
167+
168+
fs::write(src_dir.join("chapter_1.md"), "# Chapter 1\n")?;
204169
} else {
205170
trace!("Existing summary found, no need to create stub files.");
206171
}

0 commit comments

Comments
 (0)