From 6b550cb4bb193e882b1891b3158006dfa3eeb9a7 Mon Sep 17 00:00:00 2001 From: Kim Hermansson Date: Tue, 31 Dec 2019 11:16:59 +0100 Subject: [PATCH] Make missing backends optional. --- book-example/src/for_developers/backends.md | 30 +++++++++++++ src/renderer/mod.rs | 48 +++++++++++++++------ tests/alternative_backends.rs | 29 ++++++++++--- 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/book-example/src/for_developers/backends.md b/book-example/src/for_developers/backends.md index d58583a9..1b289c5f 100644 --- a/book-example/src/for_developers/backends.md +++ b/book-example/src/for_developers/backends.md @@ -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 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 diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 410dd3af..02244ae2 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -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. /// @@ -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 { fn name(&self) -> &str { &self.name @@ -168,17 +202,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), }; { diff --git a/tests/alternative_backends.rs b/tests/alternative_backends.rs index ff2ab687..875b2cfc 100644 --- a/tests/alternative_backends.rs +++ b/tests/alternative_backends.rs @@ -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(); } @@ -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(); @@ -66,7 +71,11 @@ 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(); @@ -74,6 +83,12 @@ fn dummy_book_with_backend(name: &str, command: &str) -> (MDBook, TempDir) { .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()