Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions crates/mdbook-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,11 @@
//! # run().unwrap()
//! ```

use crate::utils::TomlExt;
use crate::utils::log_backtrace;
use crate::utils::{TomlExt, fs, log_backtrace};
use anyhow::{Context, Error, Result, bail};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::env;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use toml::Value;
Expand Down Expand Up @@ -113,13 +110,8 @@ impl Default for Config {
impl Config {
/// Load the configuration file from disk.
pub fn from_disk<P: AsRef<Path>>(config_file: P) -> Result<Config> {
let mut buffer = String::new();
File::open(config_file)
.with_context(|| "Unable to open the configuration file")?
.read_to_string(&mut buffer)
.with_context(|| "Couldn't read the file")?;

Config::from_str(&buffer)
let cfg = fs::read_to_string(config_file)?;
Config::from_str(&cfg)
}

/// Updates the `Config` from the available environment variables.
Expand Down
106 changes: 49 additions & 57 deletions crates/mdbook-core/src/utils/fs.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
//! Filesystem utilities and helpers.

use anyhow::{Context, Result};
use std::fs::{self, File};
use std::io::Write;
use std::fs;
use std::path::{Component, Path, PathBuf};
use tracing::{debug, trace};
use tracing::debug;

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

create_file(&path)?.write_all(content).map_err(Into::into)
/// Writes a file to disk.
///
/// Equivalent to [`std::fs::write`] with better error messages. This will
/// also create the parent directory if it doesn't exist.
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
let path = path.as_ref();
debug!("Writing `{}`", path.display());
if let Some(parent) = path.parent() {
create_dir_all(parent)?;
}
fs::write(path, contents.as_ref())
.with_context(|| format!("failed to write `{}`", path.display()))
}

/// Equivalent to [`std::fs::create_dir_all`] with better error messages.
pub fn create_dir_all(p: impl AsRef<Path>) -> Result<()> {
let p = p.as_ref();
fs::create_dir_all(p)
.with_context(|| format!("failed to create directory `{}`", p.display()))?;
Ok(())
}

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

/// This function creates a file and returns it. But before creating the file
/// it checks every directory in the path to see if it exists,
/// and if it does not it will be created.
pub fn create_file(path: &Path) -> Result<File> {
debug!("Creating {}", path.display());

// Construct path
if let Some(p) = path.parent() {
trace!("Parent directory is: {:?}", p);

fs::create_dir_all(p)?;
}

File::create(path).map_err(Into::into)
}

/// Removes all the content of a directory but not the directory itself
/// Removes all the content of a directory but not the directory itself.
pub fn remove_dir_content(dir: &Path) -> Result<()> {
for item in fs::read_dir(dir)?.flatten() {
for item in fs::read_dir(dir)
.with_context(|| format!("failed to read directory `{}`", dir.display()))?
.flatten()
{
let item = item.path();
if item.is_dir() {
fs::remove_dir_all(item)?;
fs::remove_dir_all(&item)
.with_context(|| format!("failed to remove `{}`", item.display()))?;
} else {
fs::remove_file(item)?;
fs::remove_file(&item)
.with_context(|| format!("failed to remove `{}`", item.display()))?;
}
}
Ok(())
Expand Down Expand Up @@ -162,7 +173,7 @@ fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
use std::fs::OpenOptions;
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};

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

#[cfg(test)]
mod tests {
use super::copy_files_except_ext;
use std::{fs, io::Result, path::Path};
use super::*;
use std::io::Result;
use std::path::Path;

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

// Create a couple of files
if let Err(err) = fs::File::create(tmp.path().join("file.txt")) {
panic!("Could not create file.txt: {err}");
}
if let Err(err) = fs::File::create(tmp.path().join("file.md")) {
panic!("Could not create file.md: {err}");
}
if let Err(err) = fs::File::create(tmp.path().join("file.png")) {
panic!("Could not create file.png: {err}");
}
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir")) {
panic!("Could not create sub_dir: {err}");
}
if let Err(err) = fs::File::create(tmp.path().join("sub_dir/file.png")) {
panic!("Could not create sub_dir/file.png: {err}");
}
if let Err(err) = fs::create_dir(tmp.path().join("sub_dir_exists")) {
panic!("Could not create sub_dir_exists: {err}");
}
if let Err(err) = fs::File::create(tmp.path().join("sub_dir_exists/file.txt")) {
panic!("Could not create sub_dir_exists/file.txt: {err}");
}
write(tmp.path().join("file.txt"), "").unwrap();
write(tmp.path().join("file.md"), "").unwrap();
write(tmp.path().join("file.png"), "").unwrap();
write(tmp.path().join("sub_dir/file.png"), "").unwrap();
write(tmp.path().join("sub_dir_exists/file.txt"), "").unwrap();
if let Err(err) = symlink(tmp.path().join("file.png"), tmp.path().join("symlink.png")) {
panic!("Could not symlink file.png: {err}");
}

// Create output dir
if let Err(err) = fs::create_dir(tmp.path().join("output")) {
panic!("Could not create output: {err}");
}
if let Err(err) = fs::create_dir(tmp.path().join("output/sub_dir_exists")) {
panic!("Could not create output/sub_dir_exists: {err}");
}
create_dir_all(tmp.path().join("output")).unwrap();
create_dir_all(tmp.path().join("output/sub_dir_exists")).unwrap();

if let Err(e) =
copy_files_except_ext(tmp.path(), &tmp.path().join("output"), true, None, &["md"])
Expand Down
2 changes: 1 addition & 1 deletion crates/mdbook-driver/src/builtin_preprocessors/links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use self::take_lines::{
use anyhow::{Context, Result};
use mdbook_core::book::{Book, BookItem};
use mdbook_core::static_regex;
use mdbook_core::utils::fs;
use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
use regex::{CaptureMatches, Captures};
use std::fs;
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo};
use std::path::{Path, PathBuf};
use tracing::{error, warn};
Expand Down
14 changes: 6 additions & 8 deletions crates/mdbook-driver/src/builtin_renderers/markdown_renderer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use anyhow::{Context, Result};
use mdbook_core::utils;
use mdbook_core::utils::fs;
use mdbook_renderer::{RenderContext, Renderer};
use std::fs;
use tracing::trace;

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

if destination.exists() {
utils::fs::remove_dir_content(destination)
fs::remove_dir_content(destination)
.with_context(|| "Unable to remove stale Markdown output")?;
}

trace!("markdown render");
for ch in book.chapters() {
utils::fs::write_file(
&ctx.destination,
ch.path.as_ref().expect("Checked path exists before"),
ch.content.as_bytes(),
)?;
let path = ctx
.destination
.join(ch.path.as_ref().expect("Checked path exists before"));
fs::write(path, &ch.content)?;
}

fs::create_dir_all(destination)
Expand Down
2 changes: 1 addition & 1 deletion crates/mdbook-driver/src/builtin_renderers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
//! The HTML renderer can be found in the [`mdbook_html`] crate.

use anyhow::{Context, Result, bail};
use mdbook_core::utils::fs;
use mdbook_renderer::{RenderContext, Renderer};
use std::fs;
use std::process::Stdio;
use tracing::{error, info, trace, warn};

Expand Down
97 changes: 31 additions & 66 deletions crates/mdbook-driver/src/init.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
//! Support for initializing a new book.

use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;

use super::MDBook;
use anyhow::{Context, Result};
use mdbook_core::config::Config;
use mdbook_core::utils::fs::write_file;
use mdbook_core::utils::fs;
use mdbook_html::theme;
use std::path::PathBuf;
use tracing::{debug, error, info, trace};

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

std::fs::write(&book_toml, cfg)
.with_context(|| format!("Unable to write config to {book_toml:?}"))?;
fs::write(&book_toml, cfg)?;
Ok(())
}

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

if !themedir.exists() {
debug!(
"{} does not exist, creating the directory",
themedir.display()
);
fs::create_dir(&themedir)?;
}

let mut index = File::create(themedir.join("index.hbs"))?;
index.write_all(theme::INDEX)?;
fs::write(themedir.join("book.js"), theme::JS)?;
fs::write(themedir.join("favicon.png"), theme::FAVICON_PNG)?;
fs::write(themedir.join("favicon.svg"), theme::FAVICON_SVG)?;
fs::write(themedir.join("highlight.css"), theme::HIGHLIGHT_CSS)?;
fs::write(themedir.join("highlight.js"), theme::HIGHLIGHT_JS)?;
fs::write(themedir.join("index.hbs"), theme::INDEX)?;

let cssdir = themedir.join("css");
if !cssdir.exists() {
fs::create_dir(&cssdir)?;
}

let mut general_css = File::create(cssdir.join("general.css"))?;
general_css.write_all(theme::GENERAL_CSS)?;

let mut chrome_css = File::create(cssdir.join("chrome.css"))?;
chrome_css.write_all(theme::CHROME_CSS)?;

fs::write(cssdir.join("general.css"), theme::GENERAL_CSS)?;
fs::write(cssdir.join("chrome.css"), theme::CHROME_CSS)?;
fs::write(cssdir.join("variables.css"), theme::VARIABLES_CSS)?;
if html_config.print.enable {
let mut print_css = File::create(cssdir.join("print.css"))?;
print_css.write_all(theme::PRINT_CSS)?;
fs::write(cssdir.join("print.css"), theme::PRINT_CSS)?;
}

let mut variables_css = File::create(cssdir.join("variables.css"))?;
variables_css.write_all(theme::VARIABLES_CSS)?;

let mut favicon = File::create(themedir.join("favicon.png"))?;
favicon.write_all(theme::FAVICON_PNG)?;

let mut favicon = File::create(themedir.join("favicon.svg"))?;
favicon.write_all(theme::FAVICON_SVG)?;

let mut js = File::create(themedir.join("book.js"))?;
js.write_all(theme::JS)?;

let mut highlight_css = File::create(themedir.join("highlight.css"))?;
highlight_css.write_all(theme::HIGHLIGHT_CSS)?;

let mut highlight_js = File::create(themedir.join("highlight.js"))?;
highlight_js.write_all(theme::HIGHLIGHT_JS)?;

write_file(&themedir.join("fonts"), "fonts.css", theme::fonts::CSS)?;
let fonts_dir = themedir.join("fonts");
fs::write(fonts_dir.join("fonts.css"), theme::fonts::CSS)?;
for (file_name, contents) in theme::fonts::LICENSES {
write_file(&themedir, file_name, contents)?;
fs::write(themedir.join(file_name), contents)?;
}
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
write_file(&themedir, file_name, contents)?;
fs::write(themedir.join(file_name), contents)?;
}
write_file(
&themedir,
theme::fonts::SOURCE_CODE_PRO.0,
fs::write(
themedir.join(theme::fonts::SOURCE_CODE_PRO.0),
theme::fonts::SOURCE_CODE_PRO.1,
)?;

Ok(())
}

fn build_gitignore(&self) -> Result<()> {
debug!("Creating .gitignore");

let mut f = File::create(self.root.join(".gitignore"))?;

writeln!(f, "{}", self.config.build.build_dir.display())?;

fs::write(
self.root.join(".gitignore"),
format!("{}", self.config.build.build_dir.display()),
)?;
Ok(())
}

Expand All @@ -193,14 +158,14 @@ impl BookBuilder {
let summary = src_dir.join("SUMMARY.md");
if !summary.exists() {
trace!("No summary found creating stub summary and chapter_1.md.");
let mut f = File::create(&summary).with_context(|| "Unable to create SUMMARY.md")?;
writeln!(f, "# Summary")?;
writeln!(f)?;
writeln!(f, "- [Chapter 1](./chapter_1.md)")?;

let chapter_1 = src_dir.join("chapter_1.md");
let mut f = File::create(chapter_1).with_context(|| "Unable to create chapter_1.md")?;
writeln!(f, "# Chapter 1")?;
fs::write(
summary,
"# Summary\n\
\n\
- [Chapter 1](./chapter_1.md)\n",
)?;

fs::write(src_dir.join("chapter_1.md"), "# Chapter 1\n")?;
} else {
trace!("Existing summary found, no need to create stub files.");
}
Expand Down
Loading
Loading