Skip to content

Commit

Permalink
Merge pull request rust-lang#1122 from segfaultsourcery/zero-exit-cod…
Browse files Browse the repository at this point in the history
…e-when-plugin-not-found-893

Fixed zero exit code when plugin not found
  • Loading branch information
ehuss authored Apr 21, 2020
2 parents 99cf501 + 5a83689 commit 2e267ec
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 23 deletions.
30 changes: 30 additions & 0 deletions book-example/src/for_developers/backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,36 @@ generation or a warning).
All environment variables are passed through to the backend, allowing you to use
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

Expand Down
14 changes: 10 additions & 4 deletions book-example/src/format/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
`book.toml`. Similar to [preprocessors](#configuring-preprocessors) this will
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
(i.e. anything under `[output.foo]`), and the command to be invoked can be
manually specified with the `command` field.
The custom renderer has access to all the fields within its table (i.e.
anything under `[output.foo]`). mdBook checks for two common fields:

- **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

Expand Down
50 changes: 38 additions & 12 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ mod markdown_renderer;

use shlex::Shlex;
use std::fs;
use std::io::{self, Read};
use std::io::{self, ErrorKind, Read};
use std::path::PathBuf;
use std::process::{Command, Stdio};

use crate::book::Book;
use crate::config::Config;
use crate::errors::*;
use toml::Value;

/// An arbitrary `mdbook` backend.
///
Expand Down Expand Up @@ -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 {
fn name(&self) -> &str {
&self.name
Expand All @@ -168,17 +204,7 @@ impl Renderer for CmdRenderer {
.spawn()
{
Ok(c) => c,
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
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")?;
}
Err(e) => return self.handle_render_command_error(ctx, e),
};

{
Expand Down
29 changes: 22 additions & 7 deletions tests/alternative_backends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,33 @@ use tempfile::{Builder as TempFileBuilder, TempDir};

#[test]
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();
}

#[test]
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();
}

#[test]
fn missing_backends_arent_fatal() {
let (md, _temp) = dummy_book_with_backend("missing", "trduyvbhijnorgevfuhn");
fn missing_backends_are_fatal() {
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());
}

#[test]
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();
}
Expand All @@ -56,7 +61,7 @@ fn backends_receive_render_context_via_stdin() {
let out_file = temp.path().join("out.txt");
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());
md.build().unwrap();
Expand All @@ -66,14 +71,24 @@ fn backends_receive_render_context_via_stdin() {
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 mut config = Config::default();
config
.set(format!("output.{}.command", name), command)
.unwrap();

if backend_is_optional {
config
.set(format!("output.{}.optional", name), true)
.unwrap();
}

let md = MDBook::init(temp.path())
.with_config(config)
.build()
Expand Down

0 comments on commit 2e267ec

Please sign in to comment.