mirror of
https://github.com/rust-lang/mdBook
synced 2024-12-12 05:42:35 +00:00
Update dependencies. (#1211)
* Removed the itertools dependency * Removed an unused feature flag * Stubbed out a toml_query replacement * Update dependencies. * Bump env_logger. * Use warp instead of iron for http server. Iron does not appear to be maintained anymore. warp/hyper seems to be reasonably maintained. Unfortunately this takes a few seconds more to compile, but shouldn't be too bad. One benefit is that there is no longer a need for a separate websocket port, which makes it easier to run multiple servers at once. * Update pulldown-cmark to 0.7 * Switch from error-chain to anyhow. * Bump MSRV to 1.39. * Update elasticlunr-rs. Co-authored-by: Michael Bryan <michaelfbryan@gmail.com>
This commit is contained in:
parent
5d5c55e619
commit
6c4c3448e3
26 changed files with 1328 additions and 1137 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -31,7 +31,7 @@ jobs:
|
|||
rust: stable
|
||||
- build: msrv
|
||||
os: ubuntu-latest
|
||||
rust: 1.35.0
|
||||
rust: 1.39.0
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Install Rust
|
||||
|
|
1936
Cargo.lock
generated
1936
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
20
Cargo.toml
20
Cargo.toml
|
@ -16,17 +16,16 @@ repository = "https://github.com/rust-lang/mdBook"
|
|||
description = "Creates a book from markdown files"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.28"
|
||||
chrono = "0.4"
|
||||
clap = "2.24"
|
||||
env_logger = "0.6"
|
||||
error-chain = "0.12"
|
||||
env_logger = "0.7.1"
|
||||
handlebars = "3.0"
|
||||
itertools = "0.8"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
memchr = "2.0"
|
||||
open = "1.1"
|
||||
pulldown-cmark = "0.6.1"
|
||||
pulldown-cmark = "0.7.0"
|
||||
regex = "1.0.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
|
@ -34,16 +33,15 @@ serde_json = "1.0"
|
|||
shlex = "0.1"
|
||||
tempfile = "3.0"
|
||||
toml = "0.5.1"
|
||||
toml-query = "0.9"
|
||||
|
||||
# Watch feature
|
||||
notify = { version = "4.0", optional = true }
|
||||
gitignore = { version = "1.0", optional = true }
|
||||
|
||||
# Serve feature
|
||||
iron = { version = "0.6", optional = true }
|
||||
staticfile = { version = "0.5", optional = true }
|
||||
ws = { version = "0.9", optional = true}
|
||||
futures-util = { version = "0.3.4", optional = true }
|
||||
tokio = { version = "0.2.18", features = ["macros"], optional = true }
|
||||
warp = { version = "0.2.2", default-features = false, features = ["websocket"], optional = true }
|
||||
|
||||
# Search feature
|
||||
elasticlunr-rs = { version = "2.3", optional = true, default-features = false }
|
||||
|
@ -55,11 +53,9 @@ pretty_assertions = "0.6"
|
|||
walkdir = "2.0"
|
||||
|
||||
[features]
|
||||
default = ["output", "watch", "serve", "search"]
|
||||
debug = []
|
||||
output = []
|
||||
default = ["watch", "serve", "search"]
|
||||
watch = ["notify", "gitignore"]
|
||||
serve = ["iron", "staticfile", "ws"]
|
||||
serve = ["futures-util", "tokio", "warp"]
|
||||
search = ["elasticlunr-rs", "ammonia"]
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -24,7 +24,7 @@ There are multiple ways to install mdBook.
|
|||
|
||||
2. **From Crates.io**
|
||||
|
||||
This requires at least [Rust] 1.35 and Cargo to be installed. Once you have installed
|
||||
This requires at least [Rust] 1.39 and Cargo to be installed. Once you have installed
|
||||
Rust, type the following in the terminal:
|
||||
|
||||
```
|
||||
|
@ -43,9 +43,9 @@ There are multiple ways to install mdBook.
|
|||
This will constrain the server to install the latest **non-breaking**
|
||||
version of mdBook and will prevent your books from failing to build because
|
||||
we released a new version.
|
||||
|
||||
|
||||
You can also disable default features to speed up compile time.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
|
|
|
@ -87,7 +87,7 @@ mod nop_lib {
|
|||
// particular config value
|
||||
if let Some(nop_cfg) = ctx.config.get_preprocessor(self.name()) {
|
||||
if nop_cfg.contains_key("blow-up") {
|
||||
return Err("Boom!!1!".into());
|
||||
anyhow::bail!("Boom!!1!");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,13 +15,13 @@ pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book>
|
|||
|
||||
let mut summary_content = String::new();
|
||||
File::open(summary_md)
|
||||
.chain_err(|| "Couldn't open SUMMARY.md")?
|
||||
.with_context(|| "Couldn't open SUMMARY.md")?
|
||||
.read_to_string(&mut summary_content)?;
|
||||
|
||||
let summary = parse_summary(&summary_content).chain_err(|| "Summary parsing failed")?;
|
||||
let summary = parse_summary(&summary_content).with_context(|| "Summary parsing failed")?;
|
||||
|
||||
if cfg.create_missing {
|
||||
create_missing(&src_dir, &summary).chain_err(|| "Unable to create missing chapters")?;
|
||||
create_missing(&src_dir, &summary).with_context(|| "Unable to create missing chapters")?;
|
||||
}
|
||||
|
||||
load_book_from_disk(&summary, src_dir)
|
||||
|
@ -257,11 +257,12 @@ fn load_chapter<P: AsRef<Path>>(
|
|||
};
|
||||
|
||||
let mut f = File::open(&location)
|
||||
.chain_err(|| format!("Chapter file not found, {}", link_location.display()))?;
|
||||
.with_context(|| format!("Chapter file not found, {}", link_location.display()))?;
|
||||
|
||||
let mut content = String::new();
|
||||
f.read_to_string(&mut content)
|
||||
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
|
||||
f.read_to_string(&mut content).with_context(|| {
|
||||
format!("Unable to read \"{}\" ({})", link.name, location.display())
|
||||
})?;
|
||||
|
||||
let stripped = location
|
||||
.strip_prefix(&src_dir)
|
||||
|
|
|
@ -64,19 +64,19 @@ impl BookBuilder {
|
|||
info!("Creating a new book with stub content");
|
||||
|
||||
self.create_directory_structure()
|
||||
.chain_err(|| "Unable to create directory structure")?;
|
||||
.with_context(|| "Unable to create directory structure")?;
|
||||
|
||||
self.create_stub_files()
|
||||
.chain_err(|| "Unable to create stub files")?;
|
||||
.with_context(|| "Unable to create stub files")?;
|
||||
|
||||
if self.create_gitignore {
|
||||
self.build_gitignore()
|
||||
.chain_err(|| "Unable to create .gitignore")?;
|
||||
.with_context(|| "Unable to create .gitignore")?;
|
||||
}
|
||||
|
||||
if self.copy_theme {
|
||||
self.copy_across_theme()
|
||||
.chain_err(|| "Unable to copy across the theme")?;
|
||||
.with_context(|| "Unable to copy across the theme")?;
|
||||
}
|
||||
|
||||
self.write_book_toml()?;
|
||||
|
@ -97,12 +97,12 @@ impl BookBuilder {
|
|||
fn write_book_toml(&self) -> Result<()> {
|
||||
debug!("Writing book.toml");
|
||||
let book_toml = self.root.join("book.toml");
|
||||
let cfg = toml::to_vec(&self.config).chain_err(|| "Unable to serialize the config")?;
|
||||
let cfg = toml::to_vec(&self.config).with_context(|| "Unable to serialize the config")?;
|
||||
|
||||
File::create(book_toml)
|
||||
.chain_err(|| "Couldn't create book.toml")?
|
||||
.with_context(|| "Couldn't create book.toml")?
|
||||
.write_all(&cfg)
|
||||
.chain_err(|| "Unable to write config to book.toml")?;
|
||||
.with_context(|| "Unable to write config to book.toml")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -174,13 +174,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).chain_err(|| "Unable to create SUMMARY.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).chain_err(|| "Unable to create chapter_1.md")?;
|
||||
let mut f =
|
||||
File::create(&chapter_1).with_context(|| "Unable to create chapter_1.md")?;
|
||||
writeln!(f, "# Chapter 1")?;
|
||||
} else {
|
||||
trace!("Existing summary found, no need to create stub files.");
|
||||
|
|
|
@ -215,7 +215,7 @@ impl MDBook {
|
|||
|
||||
renderer
|
||||
.render(&render_context)
|
||||
.chain_err(|| "Rendering failed")
|
||||
.with_context(|| "Rendering failed")
|
||||
}
|
||||
|
||||
/// You can change the default renderer to another one by using this method.
|
||||
|
@ -282,10 +282,12 @@ impl MDBook {
|
|||
let output = cmd.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!(ErrorKind::Subprocess(
|
||||
"Rustdoc returned an error".to_string(),
|
||||
output
|
||||
));
|
||||
bail!(
|
||||
"rustdoc returned an error:\n\
|
||||
\n--- stdout\n{}\n--- stderr\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -236,13 +236,13 @@ impl<'a> SummaryParser<'a> {
|
|||
|
||||
let prefix_chapters = self
|
||||
.parse_affix(true)
|
||||
.chain_err(|| "There was an error parsing the prefix chapters")?;
|
||||
.with_context(|| "There was an error parsing the prefix chapters")?;
|
||||
let numbered_chapters = self
|
||||
.parse_parts()
|
||||
.chain_err(|| "There was an error parsing the numbered chapters")?;
|
||||
.with_context(|| "There was an error parsing the numbered chapters")?;
|
||||
let suffix_chapters = self
|
||||
.parse_affix(false)
|
||||
.chain_err(|| "There was an error parsing the suffix chapters")?;
|
||||
.with_context(|| "There was an error parsing the suffix chapters")?;
|
||||
|
||||
Ok(Summary {
|
||||
title,
|
||||
|
@ -320,7 +320,7 @@ impl<'a> SummaryParser<'a> {
|
|||
// Parse the rest of the part.
|
||||
let numbered_chapters = self
|
||||
.parse_numbered(&mut root_items, &mut root_number)
|
||||
.chain_err(|| "There was an error parsing the numbered chapters")?;
|
||||
.with_context(|| "There was an error parsing the numbered chapters")?;
|
||||
|
||||
if let Some(title) = title {
|
||||
parts.push(SummaryItem::PartTitle(title));
|
||||
|
@ -514,8 +514,12 @@ impl<'a> SummaryParser<'a> {
|
|||
|
||||
fn parse_error<D: Display>(&self, msg: D) -> Error {
|
||||
let (line, col) = self.current_location();
|
||||
|
||||
ErrorKind::ParseError(line, col, msg.to_string()).into()
|
||||
anyhow::anyhow!(
|
||||
"failed to parse SUMMARY.md line {}, column {}: {}",
|
||||
line,
|
||||
col,
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
/// Try to parse the title line.
|
||||
|
@ -553,10 +557,9 @@ fn get_last_link(links: &mut [SummaryItem]) -> Result<(usize, &mut Link)> {
|
|||
.filter_map(|(i, item)| item.maybe_link_mut().map(|l| (i, l)))
|
||||
.rev()
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
"Unable to get last link because the list of SummaryItems doesn't contain any Links"
|
||||
.into()
|
||||
})
|
||||
.ok_or_else(||
|
||||
anyhow::anyhow!("Unable to get last link because the list of SummaryItems doesn't contain any Links")
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes the styling from a list of Markdown events and returns just the
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::get_book_dir;
|
||||
use anyhow::Context;
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use mdbook::errors::*;
|
||||
use mdbook::MDBook;
|
||||
use std::fs;
|
||||
|
||||
|
@ -31,7 +31,8 @@ pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
|
|||
};
|
||||
|
||||
if dir_to_remove.exists() {
|
||||
fs::remove_dir_all(&dir_to_remove).chain_err(|| "Unable to remove the build directory")?;
|
||||
fs::remove_dir_all(&dir_to_remove)
|
||||
.with_context(|| "Unable to remove the build directory")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
109
src/cmd/serve.rs
109
src/cmd/serve.rs
|
@ -2,15 +2,19 @@
|
|||
use super::watch;
|
||||
use crate::{get_book_dir, open};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use iron::headers;
|
||||
use iron::{status, AfterMiddleware, Chain, Iron, IronError, IronResult, Request, Response, Set};
|
||||
use futures_util::sink::SinkExt;
|
||||
use futures_util::StreamExt;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::utils;
|
||||
use mdbook::MDBook;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::broadcast;
|
||||
use warp::ws::Message;
|
||||
use warp::Filter;
|
||||
|
||||
struct ErrorRecover;
|
||||
|
||||
struct NoCache;
|
||||
/// The HTTP endpoint for the websocket used to trigger reloads when a file changes.
|
||||
const LIVE_RELOAD_ENDPOINT: &str = "__livereload";
|
||||
|
||||
// Create clap subcommand arguments
|
||||
pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
|
@ -43,42 +47,21 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> {
|
|||
.empty_values(false)
|
||||
.help("Port to use for HTTP connections"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("websocket-hostname")
|
||||
.long("websocket-hostname")
|
||||
.takes_value(true)
|
||||
.empty_values(false)
|
||||
.help(
|
||||
"Hostname to connect to for WebSockets connections (Defaults to the HTTP hostname)",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("websocket-port")
|
||||
.short("w")
|
||||
.long("websocket-port")
|
||||
.takes_value(true)
|
||||
.default_value("3001")
|
||||
.empty_values(false)
|
||||
.help("Port to use for WebSockets livereload connections"),
|
||||
)
|
||||
.arg_from_usage("-o, --open 'Opens the book server in a web browser'")
|
||||
}
|
||||
|
||||
// Watch command implementation
|
||||
// Serve command implementation
|
||||
pub fn execute(args: &ArgMatches) -> Result<()> {
|
||||
let book_dir = get_book_dir(args);
|
||||
let mut book = MDBook::load(&book_dir)?;
|
||||
|
||||
let port = args.value_of("port").unwrap();
|
||||
let ws_port = args.value_of("websocket-port").unwrap();
|
||||
let hostname = args.value_of("hostname").unwrap();
|
||||
let public_address = args.value_of("websocket-hostname").unwrap_or(hostname);
|
||||
let open_browser = args.is_present("open");
|
||||
|
||||
let address = format!("{}:{}", hostname, port);
|
||||
let ws_address = format!("{}:{}", hostname, ws_port);
|
||||
|
||||
let livereload_url = format!("ws://{}:{}", public_address, ws_port);
|
||||
let livereload_url = format!("ws://{}/{}", address, LIVE_RELOAD_ENDPOINT);
|
||||
book.config
|
||||
.set("output.html.livereload-url", &livereload_url)?;
|
||||
|
||||
|
@ -88,20 +71,18 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
|
||||
book.build()?;
|
||||
|
||||
let mut chain = Chain::new(staticfile::Static::new(book.build_dir_for("html")));
|
||||
chain.link_after(NoCache);
|
||||
chain.link_after(ErrorRecover);
|
||||
let _iron = Iron::new(chain)
|
||||
.http(&*address)
|
||||
.chain_err(|| "Unable to launch the server")?;
|
||||
let sockaddr: SocketAddr = address
|
||||
.to_socket_addrs()?
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("no address found for {}", address))?;
|
||||
let build_dir = book.build_dir_for("html");
|
||||
|
||||
let ws_server =
|
||||
ws::WebSocket::new(|_| |_| Ok(())).chain_err(|| "Unable to start the websocket")?;
|
||||
// A channel used to broadcast to any websockets to reload when a file changes.
|
||||
let (tx, _rx) = tokio::sync::broadcast::channel::<Message>(100);
|
||||
|
||||
let broadcaster = ws_server.broadcaster();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
ws_server.listen(&*ws_address).unwrap();
|
||||
let reload_tx = tx.clone();
|
||||
let thread_handle = std::thread::spawn(move || {
|
||||
serve(build_dir, sockaddr, reload_tx);
|
||||
});
|
||||
|
||||
let serving_url = format!("http://{}", address);
|
||||
|
@ -117,7 +98,6 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
info!("Building book...");
|
||||
|
||||
// FIXME: This area is really ugly because we need to re-set livereload :(
|
||||
|
||||
let result = MDBook::load(&book_dir)
|
||||
.and_then(|mut b| {
|
||||
b.config
|
||||
|
@ -130,30 +110,39 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||
error!("Unable to load the book");
|
||||
utils::log_backtrace(&e);
|
||||
} else {
|
||||
let _ = broadcaster.send("reload");
|
||||
let _ = tx.send(Message::text("reload"));
|
||||
}
|
||||
});
|
||||
|
||||
let _ = thread_handle.join();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl AfterMiddleware for NoCache {
|
||||
fn after(&self, _: &mut Request, mut res: Response) -> IronResult<Response> {
|
||||
res.headers.set(headers::CacheControl(vec![
|
||||
headers::CacheDirective::NoStore,
|
||||
headers::CacheDirective::MaxAge(0u32),
|
||||
]));
|
||||
#[tokio::main]
|
||||
async fn serve(build_dir: PathBuf, address: SocketAddr, reload_tx: broadcast::Sender<Message>) {
|
||||
// A warp Filter which captures `reload_tx` and provides an `rx` copy to
|
||||
// receive reload messages.
|
||||
let sender = warp::any().map(move || reload_tx.subscribe());
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl AfterMiddleware for ErrorRecover {
|
||||
fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
|
||||
match err.response.status {
|
||||
// each error will result in 404 response
|
||||
Some(_) => Ok(err.response.set(status::NotFound)),
|
||||
_ => Err(err),
|
||||
}
|
||||
}
|
||||
// A warp Filter to handle the livereload endpoint. This upgrades to a
|
||||
// websocket, and then waits for any filesystem change notifications, and
|
||||
// relays them over the websocket.
|
||||
let livereload = warp::path(LIVE_RELOAD_ENDPOINT)
|
||||
.and(warp::ws())
|
||||
.and(sender)
|
||||
.map(|ws: warp::ws::Ws, mut rx: broadcast::Receiver<Message>| {
|
||||
ws.on_upgrade(move |ws| async move {
|
||||
let (mut user_ws_tx, _user_ws_rx) = ws.split();
|
||||
trace!("websocket got connection");
|
||||
if let Ok(m) = rx.recv().await {
|
||||
trace!("notify of reload");
|
||||
let _ = user_ws_tx.send(m).await;
|
||||
}
|
||||
})
|
||||
});
|
||||
// A warp Filter that serves from the filesystem.
|
||||
let book_route = warp::fs::dir(build_dir);
|
||||
let routes = livereload.or(book_route);
|
||||
warp::serve(routes).run(address).await;
|
||||
}
|
||||
|
|
|
@ -57,12 +57,9 @@ use std::path::{Path, PathBuf};
|
|||
use std::str::FromStr;
|
||||
use toml::value::Table;
|
||||
use toml::{self, Value};
|
||||
use toml_query::delete::TomlValueDeleteExt;
|
||||
use toml_query::insert::TomlValueInsertExt;
|
||||
use toml_query::read::TomlValueReadExt;
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::utils;
|
||||
use crate::utils::{self, toml_ext::TomlExt};
|
||||
|
||||
/// The overall configuration object for MDBook, essentially an in-memory
|
||||
/// representation of `book.toml`.
|
||||
|
@ -82,7 +79,7 @@ impl FromStr for Config {
|
|||
|
||||
/// Load a `Config` from some string.
|
||||
fn from_str(src: &str) -> Result<Self> {
|
||||
toml::from_str(src).chain_err(|| Error::from("Invalid configuration file"))
|
||||
toml::from_str(src).with_context(|| "Invalid configuration file")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,9 +88,9 @@ impl Config {
|
|||
pub fn from_disk<P: AsRef<Path>>(config_file: P) -> Result<Config> {
|
||||
let mut buffer = String::new();
|
||||
File::open(config_file)
|
||||
.chain_err(|| "Unable to open the configuration file")?
|
||||
.with_context(|| "Unable to open the configuration file")?
|
||||
.read_to_string(&mut buffer)
|
||||
.chain_err(|| "Couldn't read the file")?;
|
||||
.with_context(|| "Couldn't read the file")?;
|
||||
|
||||
Config::from_str(&buffer)
|
||||
}
|
||||
|
@ -163,15 +160,12 @@ impl Config {
|
|||
/// `output.html.playpen` will fetch the "playpen" out of the html output
|
||||
/// table).
|
||||
pub fn get(&self, key: &str) -> Option<&Value> {
|
||||
self.rest.read(key).unwrap_or(None)
|
||||
self.rest.read(key)
|
||||
}
|
||||
|
||||
/// Fetch a value from the `Config` so you can mutate it.
|
||||
pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
|
||||
match self.rest.read_mut(key) {
|
||||
Ok(inner) => inner,
|
||||
Err(_) => None,
|
||||
}
|
||||
self.rest.read_mut(key)
|
||||
}
|
||||
|
||||
/// Convenience method for getting the html renderer's configuration.
|
||||
|
@ -182,11 +176,14 @@ impl Config {
|
|||
/// HTML renderer is refactored to be less coupled to `mdbook` internals.
|
||||
#[doc(hidden)]
|
||||
pub fn html_config(&self) -> Option<HtmlConfig> {
|
||||
match self.get_deserialized_opt("output.html") {
|
||||
match self
|
||||
.get_deserialized_opt("output.html")
|
||||
.with_context(|| "Parsing configuration [output.html]")
|
||||
{
|
||||
Ok(Some(config)) => Some(config),
|
||||
Ok(None) => None,
|
||||
Err(e) => {
|
||||
utils::log_backtrace(&e.chain_err(|| "Parsing configuration [output.html]"));
|
||||
utils::log_backtrace(&e);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +211,7 @@ impl Config {
|
|||
value
|
||||
.clone()
|
||||
.try_into()
|
||||
.chain_err(|| "Couldn't deserialize the value")
|
||||
.with_context(|| "Couldn't deserialize the value")
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
@ -226,17 +223,15 @@ impl Config {
|
|||
pub fn set<S: Serialize, I: AsRef<str>>(&mut self, index: I, value: S) -> Result<()> {
|
||||
let index = index.as_ref();
|
||||
|
||||
let value =
|
||||
Value::try_from(value).chain_err(|| "Unable to represent the item as a JSON Value")?;
|
||||
let value = Value::try_from(value)
|
||||
.with_context(|| "Unable to represent the item as a JSON Value")?;
|
||||
|
||||
if index.starts_with("book.") {
|
||||
self.book.update_value(&index[5..], value);
|
||||
} else if index.starts_with("build.") {
|
||||
self.build.update_value(&index[6..], value);
|
||||
} else {
|
||||
self.rest
|
||||
.insert(index, value)
|
||||
.map_err(ErrorKind::TomlQueryError)?;
|
||||
self.rest.insert(index, value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -277,7 +272,7 @@ impl Config {
|
|||
get_and_insert!(table, "source" => cfg.book.src);
|
||||
get_and_insert!(table, "description" => cfg.book.description);
|
||||
|
||||
if let Ok(Some(dest)) = table.delete("output.html.destination") {
|
||||
if let Some(dest) = table.delete("output.html.destination") {
|
||||
if let Ok(destination) = dest.try_into() {
|
||||
cfg.build.build_dir = destination;
|
||||
}
|
||||
|
@ -363,8 +358,8 @@ impl Serialize for Config {
|
|||
};
|
||||
let rust_config = Value::try_from(&self.rust).expect("should always be serializable");
|
||||
|
||||
table.insert("book", book_config).expect("unreachable");
|
||||
table.insert("rust", rust_config).expect("unreachable");
|
||||
table.insert("book", book_config);
|
||||
table.insert("rust", rust_config);
|
||||
table.serialize(s)
|
||||
}
|
||||
}
|
||||
|
@ -391,7 +386,7 @@ fn is_legacy_format(table: &Value) -> bool {
|
|||
];
|
||||
|
||||
for item in &legacy_items {
|
||||
if let Ok(Some(_)) = table.read(item) {
|
||||
if table.read(item).is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
48
src/lib.rs
48
src/lib.rs
|
@ -84,8 +84,6 @@
|
|||
#![deny(rust_2018_idioms)]
|
||||
#![allow(clippy::comparison_chain)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
|
@ -119,48 +117,6 @@ pub use crate::renderer::Renderer;
|
|||
|
||||
/// The error types used through out this crate.
|
||||
pub mod errors {
|
||||
use std::path::PathBuf;
|
||||
|
||||
error_chain! {
|
||||
foreign_links {
|
||||
Io(std::io::Error) #[doc = "A wrapper around `std::io::Error`"];
|
||||
HandlebarsRender(handlebars::RenderError) #[doc = "Handlebars rendering failed"];
|
||||
HandlebarsTemplate(Box<handlebars::TemplateError>) #[doc = "Unable to parse the template"];
|
||||
Utf8(std::string::FromUtf8Error) #[doc = "Invalid UTF-8"];
|
||||
SerdeJson(serde_json::Error) #[doc = "JSON conversion failed"];
|
||||
}
|
||||
|
||||
errors {
|
||||
/// A subprocess exited with an unsuccessful return code.
|
||||
Subprocess(message: String, output: std::process::Output) {
|
||||
description("A subprocess failed")
|
||||
display("{}: {}", message, String::from_utf8_lossy(&output.stdout))
|
||||
}
|
||||
|
||||
/// An error was encountered while parsing the `SUMMARY.md` file.
|
||||
ParseError(line: usize, col: usize, message: String) {
|
||||
description("A SUMMARY.md parsing error")
|
||||
display("Error at line {}, column {}: {}", line, col, message)
|
||||
}
|
||||
|
||||
/// The user tried to use a reserved filename.
|
||||
ReservedFilenameError(filename: PathBuf) {
|
||||
description("Reserved Filename")
|
||||
display("{} is reserved for internal use", filename.display())
|
||||
}
|
||||
|
||||
/// Error with a TOML file.
|
||||
TomlQueryError(inner: toml_query::error::Error) {
|
||||
description("toml_query error")
|
||||
display("{}", inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Box to halve the size of Error
|
||||
impl From<handlebars::TemplateError> for Error {
|
||||
fn from(e: handlebars::TemplateError) -> Error {
|
||||
From::from(Box::new(e))
|
||||
}
|
||||
}
|
||||
pub(crate) use anyhow::{bail, ensure, Context};
|
||||
pub use anyhow::{Error, Result};
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ impl CmdPreprocessor {
|
|||
/// A convenience function custom preprocessors can use to parse the input
|
||||
/// written to `stdin` by a `CmdRenderer`.
|
||||
pub fn parse_input<R: Read>(reader: R) -> Result<(PreprocessorContext, Book)> {
|
||||
serde_json::from_reader(reader).chain_err(|| "Unable to parse the input")
|
||||
serde_json::from_reader(reader).with_context(|| "Unable to parse the input")
|
||||
}
|
||||
|
||||
fn write_input_to_child(&self, child: &mut Child, book: &Book, ctx: &PreprocessorContext) {
|
||||
|
@ -100,7 +100,7 @@ impl Preprocessor for CmdPreprocessor {
|
|||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.chain_err(|| {
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Unable to start the \"{}\" preprocessor. Is it installed?",
|
||||
self.name()
|
||||
|
@ -111,7 +111,7 @@ impl Preprocessor for CmdPreprocessor {
|
|||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.chain_err(|| "Error waiting for the preprocessor to complete")?;
|
||||
.with_context(|| "Error waiting for the preprocessor to complete")?;
|
||||
|
||||
trace!("{} exited with output: {:?}", self.cmd, output);
|
||||
ensure!(
|
||||
|
@ -119,7 +119,8 @@ impl Preprocessor for CmdPreprocessor {
|
|||
"The preprocessor exited unsuccessfully"
|
||||
);
|
||||
|
||||
serde_json::from_slice(&output.stdout).chain_err(|| "Unable to parse the preprocessed book")
|
||||
serde_json::from_slice(&output.stdout)
|
||||
.with_context(|| "Unable to parse the preprocessed book")
|
||||
}
|
||||
|
||||
fn supports_renderer(&self, renderer: &str) -> bool {
|
||||
|
|
|
@ -95,7 +95,7 @@ where
|
|||
}
|
||||
Err(e) => {
|
||||
error!("Error updating \"{}\", {}", link.link_text, e);
|
||||
for cause in e.iter().skip(1) {
|
||||
for cause in e.chain().skip(1) {
|
||||
warn!("Caused By: {}", cause);
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,7 @@ impl<'a> Link<'a> {
|
|||
RangeOrAnchor::Range(range) => take_lines(&s, range.clone()),
|
||||
RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor),
|
||||
})
|
||||
.chain_err(|| {
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Could not read file for link {} ({})",
|
||||
self.link_text,
|
||||
|
@ -316,7 +316,7 @@ impl<'a> Link<'a> {
|
|||
take_rustdoc_include_anchored_lines(&s, anchor)
|
||||
}
|
||||
})
|
||||
.chain_err(|| {
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Could not read file for link {} ({})",
|
||||
self.link_text,
|
||||
|
@ -327,7 +327,7 @@ impl<'a> Link<'a> {
|
|||
LinkType::Playpen(ref pat, ref attrs) => {
|
||||
let target = base.join(pat);
|
||||
|
||||
let contents = fs::read_to_string(&target).chain_err(|| {
|
||||
let contents = fs::read_to_string(&target).with_context(|| {
|
||||
format!(
|
||||
"Could not read file for link {} ({})",
|
||||
self.link_text,
|
||||
|
|
|
@ -49,12 +49,12 @@ impl HtmlHandlebars {
|
|||
// Update the context with data for this file
|
||||
let ctx_path = path
|
||||
.to_str()
|
||||
.chain_err(|| "Could not convert path to str")?;
|
||||
.with_context(|| "Could not convert path to str")?;
|
||||
let filepath = Path::new(&ctx_path).with_extension("html");
|
||||
|
||||
// "print.html" is used for the print page.
|
||||
if path == Path::new("print.md") {
|
||||
bail!(ErrorKind::ReservedFilenameError(path.clone()));
|
||||
bail!("{} is reserved for internal use", path.display());
|
||||
};
|
||||
|
||||
let book_title = ctx
|
||||
|
@ -260,7 +260,7 @@ impl HtmlHandlebars {
|
|||
let output_location = destination.join(custom_file);
|
||||
if let Some(parent) = output_location.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.chain_err(|| format!("Unable to create {}", parent.display()))?;
|
||||
.with_context(|| format!("Unable to create {}", parent.display()))?;
|
||||
}
|
||||
debug!(
|
||||
"Copying {} -> {}",
|
||||
|
@ -268,7 +268,7 @@ impl HtmlHandlebars {
|
|||
output_location.display()
|
||||
);
|
||||
|
||||
fs::copy(&input_location, &output_location).chain_err(|| {
|
||||
fs::copy(&input_location, &output_location).with_context(|| {
|
||||
format!(
|
||||
"Unable to copy {} to {}",
|
||||
input_location.display(),
|
||||
|
@ -314,7 +314,7 @@ impl Renderer for HtmlHandlebars {
|
|||
|
||||
if destination.exists() {
|
||||
utils::fs::remove_dir_content(destination)
|
||||
.chain_err(|| "Unable to remove stale HTML output")?;
|
||||
.with_context(|| "Unable to remove stale HTML output")?;
|
||||
}
|
||||
|
||||
trace!("render");
|
||||
|
@ -355,7 +355,7 @@ impl Renderer for HtmlHandlebars {
|
|||
let mut print_content = String::new();
|
||||
|
||||
fs::create_dir_all(&destination)
|
||||
.chain_err(|| "Unexpected error when constructing destination path")?;
|
||||
.with_context(|| "Unexpected error when constructing destination path")?;
|
||||
|
||||
let mut is_index = true;
|
||||
for item in book.iter() {
|
||||
|
@ -388,9 +388,9 @@ impl Renderer for HtmlHandlebars {
|
|||
|
||||
debug!("Copy static files");
|
||||
self.copy_static_files(&destination, &theme, &html_config)
|
||||
.chain_err(|| "Unable to copy across static files")?;
|
||||
.with_context(|| "Unable to copy across static files")?;
|
||||
self.copy_additional_css_and_js(&html_config, &ctx.root, &destination)
|
||||
.chain_err(|| "Unable to copy across additional CSS and JS")?;
|
||||
.with_context(|| "Unable to copy across additional CSS and JS")?;
|
||||
|
||||
// Render search index
|
||||
#[cfg(feature = "search")]
|
||||
|
@ -549,7 +549,7 @@ fn make_data(
|
|||
if let Some(ref path) = ch.path {
|
||||
let p = path
|
||||
.to_str()
|
||||
.chain_err(|| "Could not convert path to str")?;
|
||||
.with_context(|| "Could not convert path to str")?;
|
||||
chapter.insert("path".to_owned(), json!(p));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ fn render_item(
|
|||
let filepath = Path::new(&chapter_path).with_extension("html");
|
||||
let filepath = filepath
|
||||
.to_str()
|
||||
.chain_err(|| "Could not convert HTML path to str")?;
|
||||
.with_context(|| "Could not convert HTML path to str")?;
|
||||
let anchor_base = utils::fs::normalize_path(filepath);
|
||||
|
||||
let mut p = utils::new_cmark_parser(&chapter.content).peekable();
|
||||
|
|
|
@ -28,7 +28,7 @@ impl Renderer for MarkdownRenderer {
|
|||
|
||||
if destination.exists() {
|
||||
utils::fs::remove_dir_content(destination)
|
||||
.chain_err(|| "Unable to remove stale Markdown output")?;
|
||||
.with_context(|| "Unable to remove stale Markdown output")?;
|
||||
}
|
||||
|
||||
trace!("markdown render");
|
||||
|
@ -45,7 +45,7 @@ impl Renderer for MarkdownRenderer {
|
|||
}
|
||||
|
||||
fs::create_dir_all(&destination)
|
||||
.chain_err(|| "Unexpected error when constructing destination path")?;
|
||||
.with_context(|| "Unexpected error when constructing destination path")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ impl RenderContext {
|
|||
|
||||
/// Load a `RenderContext` from its JSON representation.
|
||||
pub fn from_json<R: Read>(reader: R) -> Result<RenderContext> {
|
||||
serde_json::from_reader(reader).chain_err(|| "Unable to deserialize the `RenderContext`")
|
||||
serde_json::from_reader(reader).with_context(|| "Unable to deserialize the `RenderContext`")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,7 +178,7 @@ impl CmdRenderer {
|
|||
);
|
||||
}
|
||||
}
|
||||
Err(error).chain_err(|| "Unable to start the backend")?
|
||||
Err(error).with_context(|| "Unable to start the backend")?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,7 +216,7 @@ impl Renderer for CmdRenderer {
|
|||
|
||||
let status = child
|
||||
.wait()
|
||||
.chain_err(|| "Error waiting for the backend to complete")?;
|
||||
.with_context(|| "Error waiting for the backend to complete")?;
|
||||
|
||||
trace!("{} exited with output: {:?}", self.cmd, status);
|
||||
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
pub mod fs;
|
||||
mod string;
|
||||
pub(crate) mod toml_ext;
|
||||
use crate::errors::Error;
|
||||
use regex::Regex;
|
||||
|
||||
use pulldown_cmark::{html, CowStr, Event, Options, Parser, Tag};
|
||||
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
|
@ -226,10 +227,10 @@ impl EventQuoteConverter {
|
|||
|
||||
fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> {
|
||||
match event {
|
||||
Event::Start(Tag::CodeBlock(ref info)) => {
|
||||
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => {
|
||||
let info: String = info.chars().filter(|ch| !ch.is_whitespace()).collect();
|
||||
|
||||
Event::Start(Tag::CodeBlock(CowStr::from(info)))
|
||||
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::from(info))))
|
||||
}
|
||||
_ => event,
|
||||
}
|
||||
|
@ -271,7 +272,7 @@ fn convert_quotes_to_curly(original_text: &str) -> String {
|
|||
pub fn log_backtrace(e: &Error) {
|
||||
error!("Error: {}", e);
|
||||
|
||||
for cause in e.iter().skip(1) {
|
||||
for cause in e.chain().skip(1) {
|
||||
error!("\tCaused By: {}", cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use std::ops::Bound::{Excluded, Included, Unbounded};
|
||||
use std::ops::RangeBounds;
|
||||
|
@ -10,11 +9,17 @@ pub fn take_lines<R: RangeBounds<usize>>(s: &str, range: R) -> String {
|
|||
Included(&n) => n,
|
||||
Unbounded => 0,
|
||||
};
|
||||
let mut lines = s.lines().skip(start);
|
||||
let lines = s.lines().skip(start);
|
||||
match range.end_bound() {
|
||||
Excluded(end) => lines.take(end.saturating_sub(start)).join("\n"),
|
||||
Included(end) => lines.take((end + 1).saturating_sub(start)).join("\n"),
|
||||
Unbounded => lines.join("\n"),
|
||||
Excluded(end) => lines
|
||||
.take(end.saturating_sub(start))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
Included(end) => lines
|
||||
.take((end + 1).saturating_sub(start))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
Unbounded => lines.collect::<Vec<_>>().join("\n"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
130
src/utils/toml_ext.rs
Normal file
130
src/utils/toml_ext.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use toml::value::{Table, Value};
|
||||
|
||||
pub(crate) trait TomlExt {
|
||||
fn read(&self, key: &str) -> Option<&Value>;
|
||||
fn read_mut(&mut self, key: &str) -> Option<&mut Value>;
|
||||
fn insert(&mut self, key: &str, value: Value);
|
||||
fn delete(&mut self, key: &str) -> Option<Value>;
|
||||
}
|
||||
|
||||
impl TomlExt for Value {
|
||||
fn read(&self, key: &str) -> Option<&Value> {
|
||||
if let Some((head, tail)) = split(key) {
|
||||
self.get(head)?.read(tail)
|
||||
} else {
|
||||
self.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_mut(&mut self, key: &str) -> Option<&mut Value> {
|
||||
if let Some((head, tail)) = split(key) {
|
||||
self.get_mut(head)?.read_mut(tail)
|
||||
} else {
|
||||
self.get_mut(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(&mut self, key: &str, value: Value) {
|
||||
if !self.is_table() {
|
||||
*self = Value::Table(Table::new());
|
||||
}
|
||||
|
||||
let table = self.as_table_mut().expect("unreachable");
|
||||
|
||||
if let Some((head, tail)) = split(key) {
|
||||
table
|
||||
.entry(head)
|
||||
.or_insert_with(|| Value::Table(Table::new()))
|
||||
.insert(tail, value);
|
||||
} else {
|
||||
table.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self, key: &str) -> Option<Value> {
|
||||
if let Some((head, tail)) = split(key) {
|
||||
self.get_mut(head)?.delete(tail)
|
||||
} else if let Some(table) = self.as_table_mut() {
|
||||
table.remove(key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split(key: &str) -> Option<(&str, &str)> {
|
||||
let ix = key.find(".")?;
|
||||
|
||||
let (head, tail) = key.split_at(ix);
|
||||
// splitting will leave the "."
|
||||
let tail = &tail[1..];
|
||||
|
||||
Some((head, tail))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn read_simple_table() {
|
||||
let src = "[table]";
|
||||
let value = Value::from_str(src).unwrap();
|
||||
|
||||
let got = value.read("table").unwrap();
|
||||
|
||||
assert!(got.is_table());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_nested_item() {
|
||||
let src = "[table]\nnested=true";
|
||||
let value = Value::from_str(src).unwrap();
|
||||
|
||||
let got = value.read("table.nested").unwrap();
|
||||
|
||||
assert_eq!(got, &Value::Boolean(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_item_at_top_level() {
|
||||
let mut value = Value::Table(Table::default());
|
||||
let item = Value::Boolean(true);
|
||||
|
||||
value.insert("first", item.clone());
|
||||
|
||||
assert_eq!(value.get("first").unwrap(), &item);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_nested_item() {
|
||||
let mut value = Value::Table(Table::default());
|
||||
let item = Value::Boolean(true);
|
||||
|
||||
value.insert("first.second", item.clone());
|
||||
|
||||
let inserted = value.read("first.second").unwrap();
|
||||
assert_eq!(inserted, &item);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_a_top_level_item() {
|
||||
let src = "top = true";
|
||||
let mut value = Value::from_str(src).unwrap();
|
||||
|
||||
let got = value.delete("top").unwrap();
|
||||
|
||||
assert_eq!(got, Value::Boolean(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_a_nested_item() {
|
||||
let src = "[table]\n nested = true";
|
||||
let mut value = Value::from_str(src).unwrap();
|
||||
|
||||
let got = value.delete("table.nested").unwrap();
|
||||
|
||||
assert_eq!(got, Value::Boolean(true));
|
||||
}
|
||||
}
|
|
@ -4,12 +4,12 @@
|
|||
// Not all features are used in all test crates, so...
|
||||
#![allow(dead_code, unused_variables, unused_imports, unused_extern_crates)]
|
||||
|
||||
use anyhow::Context;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::MDBook;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use mdbook::MDBook;
|
||||
use tempfile::{Builder as TempFileBuilder, TempDir};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
|
@ -43,10 +43,10 @@ impl DummyBook {
|
|||
let temp = TempFileBuilder::new()
|
||||
.prefix("dummy_book-")
|
||||
.tempdir()
|
||||
.chain_err(|| "Unable to create temp directory")?;
|
||||
.with_context(|| "Unable to create temp directory")?;
|
||||
|
||||
let dummy_book_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy_book");
|
||||
recursive_copy(&dummy_book_root, temp.path()).chain_err(|| {
|
||||
recursive_copy(&dummy_book_root, temp.path()).with_context(|| {
|
||||
"Couldn't copy files into a \
|
||||
temporary directory"
|
||||
})?;
|
||||
|
@ -113,7 +113,7 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
|
|||
let to = to.as_ref();
|
||||
|
||||
for entry in WalkDir::new(&from) {
|
||||
let entry = entry.chain_err(|| "Unable to inspect directory entry")?;
|
||||
let entry = entry.with_context(|| "Unable to inspect directory entry")?;
|
||||
|
||||
let original_location = entry.path();
|
||||
let relative = original_location
|
||||
|
@ -123,11 +123,11 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
|
|||
|
||||
if original_location.is_file() {
|
||||
if let Some(parent) = new_location.parent() {
|
||||
fs::create_dir_all(parent).chain_err(|| "Couldn't create directory")?;
|
||||
fs::create_dir_all(parent).with_context(|| "Couldn't create directory")?;
|
||||
}
|
||||
|
||||
fs::copy(&original_location, &new_location)
|
||||
.chain_err(|| "Unable to copy file contents")?;
|
||||
.with_context(|| "Unable to copy file contents")?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,9 @@ macro_rules! summary_md_test {
|
|||
.unwrap();
|
||||
|
||||
if let Err(e) = book::parse_summary(&content) {
|
||||
use error_chain::ChainedError;
|
||||
|
||||
eprintln!("Error parsing {}", filename.display());
|
||||
eprintln!();
|
||||
eprintln!("{}", e.display_chain());
|
||||
eprintln!("{:?}", e);
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ mod dummy_book;
|
|||
|
||||
use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
|
||||
|
||||
use anyhow::Context;
|
||||
use mdbook::config::Config;
|
||||
use mdbook::errors::*;
|
||||
use mdbook::utils::fs::write_file;
|
||||
|
@ -247,13 +248,13 @@ fn entry_ends_with(entry: &DirEntry, ending: &str) -> bool {
|
|||
fn root_index_html() -> Result<Document> {
|
||||
let temp = DummyBook::new()
|
||||
.build()
|
||||
.chain_err(|| "Couldn't create the dummy book")?;
|
||||
.with_context(|| "Couldn't create the dummy book")?;
|
||||
MDBook::load(temp.path())?
|
||||
.build()
|
||||
.chain_err(|| "Book building failed")?;
|
||||
.with_context(|| "Book building failed")?;
|
||||
|
||||
let index_page = temp.path().join("book").join("index.html");
|
||||
let html = fs::read_to_string(&index_page).chain_err(|| "Unable to read index.html")?;
|
||||
let html = fs::read_to_string(&index_page).with_context(|| "Unable to read index.html")?;
|
||||
|
||||
Ok(Document::from(html.as_str()))
|
||||
}
|
||||
|
|
|
@ -6350,6 +6350,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"lang": "English",
|
||||
"pipeline": [
|
||||
"trimmer",
|
||||
"stopWordFilter",
|
||||
|
|
Loading…
Reference in a new issue