Merge pull request #532 from JaimeValdemoros/implementing-preprocessors
implementing preprocessors
This commit is contained in:
commit
7b4b70a49d
128
src/book/mod.rs
128
src/book/mod.rs
|
@ -23,7 +23,7 @@ use toml::Value;
|
||||||
|
|
||||||
use utils;
|
use utils;
|
||||||
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
|
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
|
||||||
use preprocess;
|
use preprocess::{Preprocessor, LinkPreprocessor, PreprocessorContext};
|
||||||
use errors::*;
|
use errors::*;
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
@ -40,6 +40,9 @@ pub struct MDBook {
|
||||||
|
|
||||||
/// The URL used for live reloading when serving up the book.
|
/// The URL used for live reloading when serving up the book.
|
||||||
pub livereload: Option<String>,
|
pub livereload: Option<String>,
|
||||||
|
|
||||||
|
/// List of pre-processors to be run on the book
|
||||||
|
preprocessors: Vec<Box<Preprocessor>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MDBook {
|
impl MDBook {
|
||||||
|
@ -85,6 +88,7 @@ impl MDBook {
|
||||||
let livereload = None;
|
let livereload = None;
|
||||||
|
|
||||||
let renderers = determine_renderers(&config);
|
let renderers = determine_renderers(&config);
|
||||||
|
let preprocessors = determine_preprocessors(&config)?;
|
||||||
|
|
||||||
Ok(MDBook {
|
Ok(MDBook {
|
||||||
root,
|
root,
|
||||||
|
@ -92,6 +96,7 @@ impl MDBook {
|
||||||
book,
|
book,
|
||||||
renderers,
|
renderers,
|
||||||
livereload,
|
livereload,
|
||||||
|
preprocessors,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,14 +156,22 @@ impl MDBook {
|
||||||
pub fn build(&self) -> Result<()> {
|
pub fn build(&self) -> Result<()> {
|
||||||
debug!("[fn]: build");
|
debug!("[fn]: build");
|
||||||
|
|
||||||
|
let mut preprocessed_book = self.book.clone();
|
||||||
|
let preprocess_ctx = PreprocessorContext::new(self.root.clone(), self.config.clone());
|
||||||
|
|
||||||
|
for preprocessor in &self.preprocessors {
|
||||||
|
debug!("Running the {} preprocessor.", preprocessor.name());
|
||||||
|
preprocessor.run(&preprocess_ctx, &mut preprocessed_book)?;
|
||||||
|
}
|
||||||
|
|
||||||
for renderer in &self.renderers {
|
for renderer in &self.renderers {
|
||||||
self.run_renderer(renderer.as_ref())?;
|
self.run_renderer(&preprocessed_book, renderer.as_ref())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_renderer(&self, renderer: &Renderer) -> Result<()> {
|
fn run_renderer(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> {
|
||||||
let name = renderer.name();
|
let name = renderer.name();
|
||||||
let build_dir = self.build_dir_for(name);
|
let build_dir = self.build_dir_for(name);
|
||||||
if build_dir.exists() {
|
if build_dir.exists() {
|
||||||
|
@ -174,7 +187,7 @@ impl MDBook {
|
||||||
|
|
||||||
let render_context = RenderContext::new(
|
let render_context = RenderContext::new(
|
||||||
self.root.clone(),
|
self.root.clone(),
|
||||||
self.book.clone(),
|
preprocessed_book.clone(),
|
||||||
self.config.clone(),
|
self.config.clone(),
|
||||||
build_dir,
|
build_dir,
|
||||||
);
|
);
|
||||||
|
@ -185,13 +198,19 @@ impl MDBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// You can change the default renderer to another one by using this method.
|
/// You can change the default renderer to another one by using this method.
|
||||||
/// The only requirement is for your renderer to implement the [Renderer
|
/// The only requirement is for your renderer to implement the [`Renderer`
|
||||||
/// trait](../../renderer/renderer/trait.Renderer.html)
|
/// trait](../renderer/trait.Renderer.html)
|
||||||
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
|
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
|
||||||
self.renderers.push(Box::new(renderer));
|
self.renderers.push(Box::new(renderer));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a [`Preprocessor`](../preprocess/trait.Preprocessor.html) to be used when rendering the book.
|
||||||
|
pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
|
||||||
|
self.preprocessors.push(Box::new(preprocessor));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Run `rustdoc` tests on the book, linking against the provided libraries.
|
/// Run `rustdoc` tests on the book, linking against the provided libraries.
|
||||||
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
|
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
|
||||||
let library_args: Vec<&str> = (0..library_paths.len())
|
let library_args: Vec<&str> = (0..library_paths.len())
|
||||||
|
@ -202,15 +221,15 @@ impl MDBook {
|
||||||
|
|
||||||
let temp_dir = TempDir::new("mdbook")?;
|
let temp_dir = TempDir::new("mdbook")?;
|
||||||
|
|
||||||
|
let preprocess_context = PreprocessorContext::new(self.root.clone(), self.config.clone());
|
||||||
|
|
||||||
|
LinkPreprocessor::new().run(&preprocess_context, &mut self.book)?;
|
||||||
|
|
||||||
for item in self.iter() {
|
for item in self.iter() {
|
||||||
if let BookItem::Chapter(ref ch) = *item {
|
if let BookItem::Chapter(ref ch) = *item {
|
||||||
if !ch.path.as_os_str().is_empty() {
|
if !ch.path.as_os_str().is_empty() {
|
||||||
let path = self.source_dir().join(&ch.path);
|
let path = self.source_dir().join(&ch.path);
|
||||||
let base = path.parent()
|
|
||||||
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
|
|
||||||
let content = utils::fs::file_to_string(&path)?;
|
let content = utils::fs::file_to_string(&path)?;
|
||||||
// Parse and expand links
|
|
||||||
let content = preprocess::links::replace_all(&content, base)?;
|
|
||||||
println!("[*]: Testing file: {:?}", path);
|
println!("[*]: Testing file: {:?}", path);
|
||||||
|
|
||||||
// write preprocessed file to tempdir
|
// write preprocessed file to tempdir
|
||||||
|
@ -309,6 +328,34 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
|
||||||
renderers
|
renderers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_preprocessors() -> Vec<Box<Preprocessor>> {
|
||||||
|
vec![Box::new(LinkPreprocessor::new())]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look at the `MDBook` and try to figure out what preprocessors to run.
|
||||||
|
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
|
||||||
|
|
||||||
|
let preprocess_list = match config.build.preprocess {
|
||||||
|
Some(ref p) => p,
|
||||||
|
// If no preprocessor field is set, default to the LinkPreprocessor. This allows you
|
||||||
|
// to disable the LinkPreprocessor by setting "preprocess" to an empty list.
|
||||||
|
None => return Ok(default_preprocessors())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();
|
||||||
|
|
||||||
|
for key in preprocess_list {
|
||||||
|
match key.as_ref() {
|
||||||
|
"links" => {
|
||||||
|
preprocessors.push(Box::new(LinkPreprocessor::new()))
|
||||||
|
}
|
||||||
|
_ => bail!("{:?} is not a recognised preprocessor", key),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(preprocessors)
|
||||||
|
}
|
||||||
|
|
||||||
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
|
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
|
||||||
// look for the `command` field, falling back to using the key
|
// look for the `command` field, falling back to using the key
|
||||||
// prepended by "mdbook-"
|
// prepended by "mdbook-"
|
||||||
|
@ -364,4 +411,65 @@ mod tests {
|
||||||
assert_eq!(got.len(), 1);
|
assert_eq!(got.len(), 1);
|
||||||
assert_eq!(got[0].name(), "random");
|
assert_eq!(got[0].name(), "random");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_defaults_to_link_preprocessor_if_not_set() {
|
||||||
|
let cfg = Config::default();
|
||||||
|
|
||||||
|
// make sure we haven't got anything in the `output` table
|
||||||
|
assert!(cfg.build.preprocess.is_none());
|
||||||
|
|
||||||
|
let got = determine_preprocessors(&cfg);
|
||||||
|
|
||||||
|
assert!(got.is_ok());
|
||||||
|
assert_eq!(got.as_ref().unwrap().len(), 1);
|
||||||
|
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_doesnt_default_if_empty() {
|
||||||
|
let cfg_str: &'static str = r#"
|
||||||
|
[book]
|
||||||
|
title = "Some Book"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
build-dir = "outputs"
|
||||||
|
create-missing = false
|
||||||
|
preprocess = []
|
||||||
|
"#;
|
||||||
|
|
||||||
|
|
||||||
|
let cfg = Config::from_str(cfg_str).unwrap();
|
||||||
|
|
||||||
|
// make sure we have something in the `output` table
|
||||||
|
assert!(cfg.build.preprocess.is_some());
|
||||||
|
|
||||||
|
let got = determine_preprocessors(&cfg);
|
||||||
|
|
||||||
|
assert!(got.is_ok());
|
||||||
|
assert!(got.unwrap().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_complains_if_unimplemented_preprocessor() {
|
||||||
|
let cfg_str: &'static str = r#"
|
||||||
|
[book]
|
||||||
|
title = "Some Book"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
build-dir = "outputs"
|
||||||
|
create-missing = false
|
||||||
|
preprocess = ["random"]
|
||||||
|
"#;
|
||||||
|
|
||||||
|
|
||||||
|
let cfg = Config::from_str(cfg_str).unwrap();
|
||||||
|
|
||||||
|
// make sure we have something in the `output` table
|
||||||
|
assert!(cfg.build.preprocess.is_some());
|
||||||
|
|
||||||
|
let got = determine_preprocessors(&cfg);
|
||||||
|
|
||||||
|
assert!(got.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,6 +329,9 @@ pub struct BuildConfig {
|
||||||
/// Should non-existent markdown files specified in `SETTINGS.md` be created
|
/// Should non-existent markdown files specified in `SETTINGS.md` be created
|
||||||
/// if they don't exist?
|
/// if they don't exist?
|
||||||
pub create_missing: bool,
|
pub create_missing: bool,
|
||||||
|
/// Which preprocessors should be applied
|
||||||
|
pub preprocess: Option<Vec<String>>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BuildConfig {
|
impl Default for BuildConfig {
|
||||||
|
@ -336,6 +339,7 @@ impl Default for BuildConfig {
|
||||||
BuildConfig {
|
BuildConfig {
|
||||||
build_dir: PathBuf::from("book"),
|
build_dir: PathBuf::from("book"),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
|
preprocess: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -422,6 +426,7 @@ mod tests {
|
||||||
[build]
|
[build]
|
||||||
build-dir = "outputs"
|
build-dir = "outputs"
|
||||||
create-missing = false
|
create-missing = false
|
||||||
|
preprocess = ["first_preprocessor", "second_preprocessor"]
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
theme = "./themedir"
|
theme = "./themedir"
|
||||||
|
@ -449,6 +454,8 @@ mod tests {
|
||||||
let build_should_be = BuildConfig {
|
let build_should_be = BuildConfig {
|
||||||
build_dir: PathBuf::from("outputs"),
|
build_dir: PathBuf::from("outputs"),
|
||||||
create_missing: false,
|
create_missing: false,
|
||||||
|
preprocess: Some(vec!["first_preprocessor".to_string(),
|
||||||
|
"second_preprocessor".to_string()]),
|
||||||
};
|
};
|
||||||
let playpen_should_be = Playpen {
|
let playpen_should_be = Playpen {
|
||||||
editable: true,
|
editable: true,
|
||||||
|
@ -550,6 +557,7 @@ mod tests {
|
||||||
let build_should_be = BuildConfig {
|
let build_should_be = BuildConfig {
|
||||||
build_dir: PathBuf::from("my-book"),
|
build_dir: PathBuf::from("my-book"),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
|
preprocess: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let html_should_be = HtmlConfig {
|
let html_should_be = HtmlConfig {
|
||||||
|
|
|
@ -118,7 +118,7 @@ extern crate toml_query;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pretty_assertions;
|
extern crate pretty_assertions;
|
||||||
|
|
||||||
mod preprocess;
|
pub mod preprocess;
|
||||||
pub mod book;
|
pub mod book;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
|
|
@ -5,9 +5,45 @@ use utils::fs::file_to_string;
|
||||||
use utils::take_lines;
|
use utils::take_lines;
|
||||||
use errors::*;
|
use errors::*;
|
||||||
|
|
||||||
|
use super::{Preprocessor, PreprocessorContext};
|
||||||
|
use book::{Book, BookItem};
|
||||||
|
|
||||||
const ESCAPE_CHAR: char = '\\';
|
const ESCAPE_CHAR: char = '\\';
|
||||||
|
|
||||||
pub fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> Result<String> {
|
pub struct LinkPreprocessor;
|
||||||
|
|
||||||
|
impl LinkPreprocessor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
LinkPreprocessor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Preprocessor for LinkPreprocessor {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"links"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()> {
|
||||||
|
let src_dir = ctx.root.join(&ctx.config.book.src);
|
||||||
|
|
||||||
|
for section in &mut book.sections {
|
||||||
|
match *section {
|
||||||
|
BookItem::Chapter(ref mut ch) => {
|
||||||
|
let base = ch.path.parent()
|
||||||
|
.map(|dir| src_dir.join(dir))
|
||||||
|
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
|
||||||
|
let content = replace_all(&ch.content, base)?;
|
||||||
|
ch.content = content
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> Result<String> {
|
||||||
// When replacing one thing in a string by something with a different length,
|
// When replacing one thing in a string by something with a different length,
|
||||||
// the indices after that will not correspond,
|
// the indices after that will not correspond,
|
||||||
// we therefore have to store the difference to correct this
|
// we therefore have to store the difference to correct this
|
||||||
|
|
|
@ -1 +1,25 @@
|
||||||
pub mod links;
|
pub use self::links::LinkPreprocessor;
|
||||||
|
|
||||||
|
mod links;
|
||||||
|
|
||||||
|
use book::Book;
|
||||||
|
use config::Config;
|
||||||
|
use errors::*;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub struct PreprocessorContext {
|
||||||
|
pub root: PathBuf,
|
||||||
|
pub config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreprocessorContext {
|
||||||
|
pub fn new(root: PathBuf, config: Config) -> Self {
|
||||||
|
PreprocessorContext { root, config }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Preprocessor {
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
use renderer::html_handlebars::helpers;
|
use renderer::html_handlebars::helpers;
|
||||||
use preprocess;
|
|
||||||
use renderer::{RenderContext, Renderer};
|
use renderer::{RenderContext, Renderer};
|
||||||
use book::{Book, BookItem, Chapter};
|
use book::{Book, BookItem, Chapter};
|
||||||
use config::{Config, HtmlConfig, Playpen};
|
use config::{Config, HtmlConfig, Playpen};
|
||||||
|
@ -50,12 +49,6 @@ impl HtmlHandlebars {
|
||||||
match *item {
|
match *item {
|
||||||
BookItem::Chapter(ref ch) => {
|
BookItem::Chapter(ref ch) => {
|
||||||
let content = ch.content.clone();
|
let content = ch.content.clone();
|
||||||
let base = ch.path.parent()
|
|
||||||
.map(|dir| ctx.src_dir.join(dir))
|
|
||||||
.expect("All chapters must have a parent directory");
|
|
||||||
|
|
||||||
// Parse and expand links
|
|
||||||
let content = preprocess::links::replace_all(&content, base)?;
|
|
||||||
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
|
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
|
||||||
print_content.push_str(&content);
|
print_content.push_str(&content);
|
||||||
|
|
||||||
|
@ -322,7 +315,6 @@ impl Renderer for HtmlHandlebars {
|
||||||
let ctx = RenderItemContext {
|
let ctx = RenderItemContext {
|
||||||
handlebars: &handlebars,
|
handlebars: &handlebars,
|
||||||
destination: destination.to_path_buf(),
|
destination: destination.to_path_buf(),
|
||||||
src_dir: src_dir.clone(),
|
|
||||||
data: data.clone(),
|
data: data.clone(),
|
||||||
is_index: i == 0,
|
is_index: i == 0,
|
||||||
html_config: html_config.clone(),
|
html_config: html_config.clone(),
|
||||||
|
@ -634,7 +626,6 @@ fn partition_source(s: &str) -> (String, String) {
|
||||||
struct RenderItemContext<'a> {
|
struct RenderItemContext<'a> {
|
||||||
handlebars: &'a Handlebars,
|
handlebars: &'a Handlebars,
|
||||||
destination: PathBuf,
|
destination: PathBuf,
|
||||||
src_dir: PathBuf,
|
|
||||||
data: serde_json::Map<String, serde_json::Value>,
|
data: serde_json::Map<String, serde_json::Value>,
|
||||||
is_index: bool,
|
is_index: bool,
|
||||||
html_config: HtmlConfig,
|
html_config: HtmlConfig,
|
||||||
|
|
|
@ -3,8 +3,14 @@ extern crate mdbook;
|
||||||
mod dummy_book;
|
mod dummy_book;
|
||||||
|
|
||||||
use dummy_book::DummyBook;
|
use dummy_book::DummyBook;
|
||||||
use mdbook::MDBook;
|
|
||||||
|
|
||||||
|
use mdbook::MDBook;
|
||||||
|
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
|
||||||
|
use mdbook::book::Book;
|
||||||
|
use mdbook::config::Config;
|
||||||
|
use mdbook::errors::*;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mdbook_can_correctly_test_a_passing_book() {
|
fn mdbook_can_correctly_test_a_passing_book() {
|
||||||
|
@ -21,3 +27,31 @@ fn mdbook_detects_book_with_failing_tests() {
|
||||||
|
|
||||||
assert!(md.test(vec![]).is_err());
|
assert!(md.test(vec![]).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mdbook_runs_preprocessors() {
|
||||||
|
|
||||||
|
let has_run: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
|
||||||
|
|
||||||
|
struct DummyPreprocessor(Arc<Mutex<bool>>);
|
||||||
|
|
||||||
|
impl Preprocessor for DummyPreprocessor {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"dummy"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _ctx: &PreprocessorContext, _book: &mut Book) -> Result<()> {
|
||||||
|
*self.0.lock().unwrap() = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let temp = DummyBook::new().build().unwrap();
|
||||||
|
let cfg = Config::default();
|
||||||
|
|
||||||
|
let mut book = MDBook::load_with_config(temp.path(), cfg).unwrap();
|
||||||
|
book.with_preprecessor(DummyPreprocessor(Arc::clone(&has_run)));
|
||||||
|
book.build().unwrap();
|
||||||
|
|
||||||
|
assert!(*has_run.lock().unwrap())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue