Skip to content

Commit 7b4b70a

Browse files
Merge pull request #532 from JaimeValdemoros/implementing-preprocessors
implementing preprocessors
2 parents bf093e2 + 1136f67 commit 7b4b70a

File tree

7 files changed

+223
-22
lines changed

7 files changed

+223
-22
lines changed

src/book/mod.rs

Lines changed: 118 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use toml::Value;
2323

2424
use utils;
2525
use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer};
26-
use preprocess;
26+
use preprocess::{Preprocessor, LinkPreprocessor, PreprocessorContext};
2727
use errors::*;
2828

2929
use config::Config;
@@ -40,6 +40,9 @@ pub struct MDBook {
4040

4141
/// The URL used for live reloading when serving up the book.
4242
pub livereload: Option<String>,
43+
44+
/// List of pre-processors to be run on the book
45+
preprocessors: Vec<Box<Preprocessor>>
4346
}
4447

4548
impl MDBook {
@@ -85,13 +88,15 @@ impl MDBook {
8588
let livereload = None;
8689

8790
let renderers = determine_renderers(&config);
91+
let preprocessors = determine_preprocessors(&config)?;
8892

8993
Ok(MDBook {
9094
root,
9195
config,
9296
book,
9397
renderers,
9498
livereload,
99+
preprocessors,
95100
})
96101
}
97102

@@ -151,14 +156,22 @@ impl MDBook {
151156
pub fn build(&self) -> Result<()> {
152157
debug!("[fn]: build");
153158

159+
let mut preprocessed_book = self.book.clone();
160+
let preprocess_ctx = PreprocessorContext::new(self.root.clone(), self.config.clone());
161+
162+
for preprocessor in &self.preprocessors {
163+
debug!("Running the {} preprocessor.", preprocessor.name());
164+
preprocessor.run(&preprocess_ctx, &mut preprocessed_book)?;
165+
}
166+
154167
for renderer in &self.renderers {
155-
self.run_renderer(renderer.as_ref())?;
168+
self.run_renderer(&preprocessed_book, renderer.as_ref())?;
156169
}
157170

158171
Ok(())
159172
}
160173

161-
fn run_renderer(&self, renderer: &Renderer) -> Result<()> {
174+
fn run_renderer(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> {
162175
let name = renderer.name();
163176
let build_dir = self.build_dir_for(name);
164177
if build_dir.exists() {
@@ -174,7 +187,7 @@ impl MDBook {
174187

175188
let render_context = RenderContext::new(
176189
self.root.clone(),
177-
self.book.clone(),
190+
preprocessed_book.clone(),
178191
self.config.clone(),
179192
build_dir,
180193
);
@@ -185,13 +198,19 @@ impl MDBook {
185198
}
186199

187200
/// You can change the default renderer to another one by using this method.
188-
/// The only requirement is for your renderer to implement the [Renderer
189-
/// trait](../../renderer/renderer/trait.Renderer.html)
201+
/// The only requirement is for your renderer to implement the [`Renderer`
202+
/// trait](../renderer/trait.Renderer.html)
190203
pub fn with_renderer<R: Renderer + 'static>(&mut self, renderer: R) -> &mut Self {
191204
self.renderers.push(Box::new(renderer));
192205
self
193206
}
194207

208+
/// Register a [`Preprocessor`](../preprocess/trait.Preprocessor.html) to be used when rendering the book.
209+
pub fn with_preprecessor<P: Preprocessor + 'static>(&mut self, preprocessor: P) -> &mut Self {
210+
self.preprocessors.push(Box::new(preprocessor));
211+
self
212+
}
213+
195214
/// Run `rustdoc` tests on the book, linking against the provided libraries.
196215
pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> {
197216
let library_args: Vec<&str> = (0..library_paths.len())
@@ -202,15 +221,15 @@ impl MDBook {
202221

203222
let temp_dir = TempDir::new("mdbook")?;
204223

224+
let preprocess_context = PreprocessorContext::new(self.root.clone(), self.config.clone());
225+
226+
LinkPreprocessor::new().run(&preprocess_context, &mut self.book)?;
227+
205228
for item in self.iter() {
206229
if let BookItem::Chapter(ref ch) = *item {
207230
if !ch.path.as_os_str().is_empty() {
208231
let path = self.source_dir().join(&ch.path);
209-
let base = path.parent()
210-
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
211232
let content = utils::fs::file_to_string(&path)?;
212-
// Parse and expand links
213-
let content = preprocess::links::replace_all(&content, base)?;
214233
println!("[*]: Testing file: {:?}", path);
215234

216235
// write preprocessed file to tempdir
@@ -309,6 +328,34 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
309328
renderers
310329
}
311330

331+
fn default_preprocessors() -> Vec<Box<Preprocessor>> {
332+
vec![Box::new(LinkPreprocessor::new())]
333+
}
334+
335+
/// Look at the `MDBook` and try to figure out what preprocessors to run.
336+
fn determine_preprocessors(config: &Config) -> Result<Vec<Box<Preprocessor>>> {
337+
338+
let preprocess_list = match config.build.preprocess {
339+
Some(ref p) => p,
340+
// If no preprocessor field is set, default to the LinkPreprocessor. This allows you
341+
// to disable the LinkPreprocessor by setting "preprocess" to an empty list.
342+
None => return Ok(default_preprocessors())
343+
};
344+
345+
let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new();
346+
347+
for key in preprocess_list {
348+
match key.as_ref() {
349+
"links" => {
350+
preprocessors.push(Box::new(LinkPreprocessor::new()))
351+
}
352+
_ => bail!("{:?} is not a recognised preprocessor", key),
353+
}
354+
}
355+
356+
Ok(preprocessors)
357+
}
358+
312359
fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> {
313360
// look for the `command` field, falling back to using the key
314361
// prepended by "mdbook-"
@@ -364,4 +411,65 @@ mod tests {
364411
assert_eq!(got.len(), 1);
365412
assert_eq!(got[0].name(), "random");
366413
}
414+
415+
#[test]
416+
fn config_defaults_to_link_preprocessor_if_not_set() {
417+
let cfg = Config::default();
418+
419+
// make sure we haven't got anything in the `output` table
420+
assert!(cfg.build.preprocess.is_none());
421+
422+
let got = determine_preprocessors(&cfg);
423+
424+
assert!(got.is_ok());
425+
assert_eq!(got.as_ref().unwrap().len(), 1);
426+
assert_eq!(got.as_ref().unwrap()[0].name(), "links");
427+
}
428+
429+
#[test]
430+
fn config_doesnt_default_if_empty() {
431+
let cfg_str: &'static str = r#"
432+
[book]
433+
title = "Some Book"
434+
435+
[build]
436+
build-dir = "outputs"
437+
create-missing = false
438+
preprocess = []
439+
"#;
440+
441+
442+
let cfg = Config::from_str(cfg_str).unwrap();
443+
444+
// make sure we have something in the `output` table
445+
assert!(cfg.build.preprocess.is_some());
446+
447+
let got = determine_preprocessors(&cfg);
448+
449+
assert!(got.is_ok());
450+
assert!(got.unwrap().is_empty());
451+
}
452+
453+
#[test]
454+
fn config_complains_if_unimplemented_preprocessor() {
455+
let cfg_str: &'static str = r#"
456+
[book]
457+
title = "Some Book"
458+
459+
[build]
460+
build-dir = "outputs"
461+
create-missing = false
462+
preprocess = ["random"]
463+
"#;
464+
465+
466+
let cfg = Config::from_str(cfg_str).unwrap();
467+
468+
// make sure we have something in the `output` table
469+
assert!(cfg.build.preprocess.is_some());
470+
471+
let got = determine_preprocessors(&cfg);
472+
473+
assert!(got.is_err());
474+
}
367475
}

src/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,13 +329,17 @@ pub struct BuildConfig {
329329
/// Should non-existent markdown files specified in `SETTINGS.md` be created
330330
/// if they don't exist?
331331
pub create_missing: bool,
332+
/// Which preprocessors should be applied
333+
pub preprocess: Option<Vec<String>>,
334+
332335
}
333336

334337
impl Default for BuildConfig {
335338
fn default() -> BuildConfig {
336339
BuildConfig {
337340
build_dir: PathBuf::from("book"),
338341
create_missing: true,
342+
preprocess: None,
339343
}
340344
}
341345
}
@@ -422,6 +426,7 @@ mod tests {
422426
[build]
423427
build-dir = "outputs"
424428
create-missing = false
429+
preprocess = ["first_preprocessor", "second_preprocessor"]
425430
426431
[output.html]
427432
theme = "./themedir"
@@ -449,6 +454,8 @@ mod tests {
449454
let build_should_be = BuildConfig {
450455
build_dir: PathBuf::from("outputs"),
451456
create_missing: false,
457+
preprocess: Some(vec!["first_preprocessor".to_string(),
458+
"second_preprocessor".to_string()]),
452459
};
453460
let playpen_should_be = Playpen {
454461
editable: true,
@@ -550,6 +557,7 @@ mod tests {
550557
let build_should_be = BuildConfig {
551558
build_dir: PathBuf::from("my-book"),
552559
create_missing: true,
560+
preprocess: None,
553561
};
554562

555563
let html_should_be = HtmlConfig {

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ extern crate toml_query;
118118
#[macro_use]
119119
extern crate pretty_assertions;
120120

121-
mod preprocess;
121+
pub mod preprocess;
122122
pub mod book;
123123
pub mod config;
124124
pub mod renderer;

src/preprocess/links.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,45 @@ use utils::fs::file_to_string;
55
use utils::take_lines;
66
use errors::*;
77

8+
use super::{Preprocessor, PreprocessorContext};
9+
use book::{Book, BookItem};
10+
811
const ESCAPE_CHAR: char = '\\';
912

10-
pub fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> Result<String> {
13+
pub struct LinkPreprocessor;
14+
15+
impl LinkPreprocessor {
16+
pub fn new() -> Self {
17+
LinkPreprocessor
18+
}
19+
}
20+
21+
impl Preprocessor for LinkPreprocessor {
22+
fn name(&self) -> &str {
23+
"links"
24+
}
25+
26+
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()> {
27+
let src_dir = ctx.root.join(&ctx.config.book.src);
28+
29+
for section in &mut book.sections {
30+
match *section {
31+
BookItem::Chapter(ref mut ch) => {
32+
let base = ch.path.parent()
33+
.map(|dir| src_dir.join(dir))
34+
.ok_or_else(|| String::from("Invalid bookitem path!"))?;
35+
let content = replace_all(&ch.content, base)?;
36+
ch.content = content
37+
}
38+
_ => {}
39+
}
40+
}
41+
42+
Ok(())
43+
}
44+
}
45+
46+
fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> Result<String> {
1147
// When replacing one thing in a string by something with a different length,
1248
// the indices after that will not correspond,
1349
// we therefore have to store the difference to correct this

src/preprocess/mod.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
1-
pub mod links;
1+
pub use self::links::LinkPreprocessor;
2+
3+
mod links;
4+
5+
use book::Book;
6+
use config::Config;
7+
use errors::*;
8+
9+
use std::path::PathBuf;
10+
11+
pub struct PreprocessorContext {
12+
pub root: PathBuf,
13+
pub config: Config,
14+
}
15+
16+
impl PreprocessorContext {
17+
pub fn new(root: PathBuf, config: Config) -> Self {
18+
PreprocessorContext { root, config }
19+
}
20+
}
21+
22+
pub trait Preprocessor {
23+
fn name(&self) -> &str;
24+
fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>;
25+
}

src/renderer/html_handlebars/hbs_renderer.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use renderer::html_handlebars::helpers;
2-
use preprocess;
32
use renderer::{RenderContext, Renderer};
43
use book::{Book, BookItem, Chapter};
54
use config::{Config, HtmlConfig, Playpen};
@@ -50,12 +49,6 @@ impl HtmlHandlebars {
5049
match *item {
5150
BookItem::Chapter(ref ch) => {
5251
let content = ch.content.clone();
53-
let base = ch.path.parent()
54-
.map(|dir| ctx.src_dir.join(dir))
55-
.expect("All chapters must have a parent directory");
56-
57-
// Parse and expand links
58-
let content = preprocess::links::replace_all(&content, base)?;
5952
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
6053
print_content.push_str(&content);
6154

@@ -322,7 +315,6 @@ impl Renderer for HtmlHandlebars {
322315
let ctx = RenderItemContext {
323316
handlebars: &handlebars,
324317
destination: destination.to_path_buf(),
325-
src_dir: src_dir.clone(),
326318
data: data.clone(),
327319
is_index: i == 0,
328320
html_config: html_config.clone(),
@@ -634,7 +626,6 @@ fn partition_source(s: &str) -> (String, String) {
634626
struct RenderItemContext<'a> {
635627
handlebars: &'a Handlebars,
636628
destination: PathBuf,
637-
src_dir: PathBuf,
638629
data: serde_json::Map<String, serde_json::Value>,
639630
is_index: bool,
640631
html_config: HtmlConfig,

0 commit comments

Comments
 (0)