Make missing backends optional.

This commit is contained in:
Kim Hermansson 2019-12-31 11:16:59 +01:00 committed by Eric Huss
parent 712362f9e7
commit 6b550cb4bb
3 changed files with 88 additions and 19 deletions

View File

@ -329,6 +329,36 @@ generation or a warning).
All environment variables are passed through to the backend, allowing you to use All environment variables are passed through to the backend, allowing you to use
the usual `RUST_LOG` to control logging verbosity. the usual `RUST_LOG` to control logging verbosity.
## Handling missing backends
If you should enable a backend that isn't installed, the default behavior is to throw an error:
```text
The command wasn't found, is the "wordcount" backend installed?
```
This behavior can be changed by marking the backend as optional.
```diff
[book]
title = "mdBook Documentation"
description = "Create book from markdown files. Like Gitbook but implemented in Rust"
authors = ["Mathieu David", "Michael-F-Bryan"]
[output.html]
[output.wordcount]
command = "python /path/to/wordcount.py"
+ optional = true
```
This demotes the error to a warning, and it will instead look like this:
```text
The command was not found, but was marked as optional.
Command: wordcount
```
## Wrapping Up ## Wrapping Up

View File

@ -19,13 +19,14 @@ mod markdown_renderer;
use shlex::Shlex; use shlex::Shlex;
use std::fs; use std::fs;
use std::io::{self, Read}; use std::io::{self, ErrorKind, Read};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use crate::book::Book; use crate::book::Book;
use crate::config::Config; use crate::config::Config;
use crate::errors::*; use crate::errors::*;
use toml::Value;
/// An arbitrary `mdbook` backend. /// An arbitrary `mdbook` backend.
/// ///
@ -149,6 +150,39 @@ impl CmdRenderer {
} }
} }
impl CmdRenderer {
fn handle_render_command_error(&self, ctx: &RenderContext, error: io::Error) -> Result<()> {
match error.kind() {
ErrorKind::NotFound => {
// Look for "output.{self.name}.optional".
// If it exists and is true, treat this as a warning.
// Otherwise, fail the build.
let optional_key = format!("output.{}.optional", self.name);
let is_optional = match ctx.config.get(&optional_key) {
Some(Value::Boolean(value)) => *value,
_ => false,
};
if is_optional {
warn!("The command was not found, but was marked as optional.");
warn!("\tCommand: {}", self.cmd);
return Ok(());
} else {
error!(
"The command wasn't found, is the \"{}\" backend installed?",
self.name
);
error!("\tCommand: {}", self.cmd);
}
}
_ => {}
}
Err(error).chain_err(|| "Unable to start the backend")?
}
}
impl Renderer for CmdRenderer { impl Renderer for CmdRenderer {
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name &self.name
@ -168,17 +202,7 @@ impl Renderer for CmdRenderer {
.spawn() .spawn()
{ {
Ok(c) => c, Ok(c) => c,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => { Err(e) => return self.handle_render_command_error(ctx, e),
warn!(
"The command wasn't found, is the \"{}\" backend installed?",
self.name
);
warn!("\tCommand: {}", self.cmd);
return Ok(());
}
Err(e) => {
return Err(e).chain_err(|| "Unable to start the backend")?;
}
}; };
{ {

View File

@ -8,28 +8,33 @@ use tempfile::{Builder as TempFileBuilder, TempDir};
#[test] #[test]
fn passing_alternate_backend() { fn passing_alternate_backend() {
let (md, _temp) = dummy_book_with_backend("passing", success_cmd()); let (md, _temp) = dummy_book_with_backend("passing", success_cmd(), false);
md.build().unwrap(); md.build().unwrap();
} }
#[test] #[test]
fn failing_alternate_backend() { fn failing_alternate_backend() {
let (md, _temp) = dummy_book_with_backend("failing", fail_cmd()); let (md, _temp) = dummy_book_with_backend("failing", fail_cmd(), false);
md.build().unwrap_err(); md.build().unwrap_err();
} }
#[test] #[test]
fn missing_backends_arent_fatal() { fn missing_backends_are_fatal() {
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn"); let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", false);
assert!(md.build().is_err());
}
#[test]
fn missing_optional_backends_are_not_fatal() {
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn", true);
assert!(md.build().is_ok()); assert!(md.build().is_ok());
} }
#[test] #[test]
fn alternate_backend_with_arguments() { fn alternate_backend_with_arguments() {
let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!"); let (md, _temp) = dummy_book_with_backend("arguments", "echo Hello World!", false);
md.build().unwrap(); md.build().unwrap();
} }
@ -56,7 +61,7 @@ fn backends_receive_render_context_via_stdin() {
let out_file = temp.path().join("out.txt"); let out_file = temp.path().join("out.txt");
let cmd = tee_command(&out_file); let cmd = tee_command(&out_file);
let (md, _temp) = dummy_book_with_backend("cat-to-file", &cmd); let (md, _temp) = dummy_book_with_backend("cat-to-file", &cmd, false);
assert!(!out_file.exists()); assert!(!out_file.exists());
md.build().unwrap(); md.build().unwrap();
@ -66,7 +71,11 @@ fn backends_receive_render_context_via_stdin() {
assert!(got.is_ok()); assert!(got.is_ok());
} }
fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) { fn dummy_book_with_backend(
name: &str,
command: &str,
backend_is_optional: bool,
) -> (MDBook, TempDir) {
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap(); let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
let mut config = Config::default(); let mut config = Config::default();
@ -74,6 +83,12 @@ fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) {
.set(format!("output.{}.command", name), command) .set(format!("output.{}.command", name), command)
.unwrap(); .unwrap();
if backend_is_optional {
config
.set(format!("output.{}.optional", name), true)
.unwrap();
}
let md = MDBook::init(temp.path()) let md = MDBook::init(temp.path())
.with_config(config) .with_config(config)
.build() .build()