Make missing backends optional.
This commit is contained in:
parent
712362f9e7
commit
6b550cb4bb
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")?;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue