Merge pull request #1122 from segfaultsourcery/zero-exit-code-when-plugin-not-found-893

Fixed zero exit code when plugin not found
This commit is contained in:
Eric Huss 2020-04-21 15:42:52 -07:00 committed by GitHub
commit 11f95f76e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 23 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 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

@ -306,11 +306,17 @@ specify which preprocessors should run before the Markdown renderer.
A custom renderer can be enabled by adding a `[output.foo]` table to your A custom renderer can be enabled by adding a `[output.foo]` table to your
`book.toml`. Similar to [preprocessors](#configuring-preprocessors) this will `book.toml`. Similar to [preprocessors](#configuring-preprocessors) this will
instruct `mdbook` to pass a representation of the book to `mdbook-foo` for instruct `mdbook` to pass a representation of the book to `mdbook-foo` for
rendering. rendering. See the [alternative backends] chapter for more detail.
Custom renderers will have access to all configuration within their table The custom renderer has access to all the fields within its table (i.e.
(i.e. anything under `[output.foo]`), and the command to be invoked can be anything under `[output.foo]`). mdBook checks for two common fields:
manually specified with the `command` field.
- **command:** The command to execute for this custom renderer. Defaults to
the name of the renderer with the `mdbook-` prefix (such as `mdbook-foo`).
- **optional:** If `true`, then the command will be ignored if it is not
installed, otherwise mdBook will fail with an error. Defaults to `false`.
[alternative backends]: ../for_developers/backends.md
## Environment Variables ## Environment Variables

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,41 @@ 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 `{}` for backend `{}` was not found, \
but was marked as optional.",
self.cmd, self.name
);
return Ok(());
} else {
error!(
"The command `{}` wasn't found, is the `{}` backend installed?",
self.cmd, self.name
);
}
}
_ => {}
}
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 +204,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()