@@ -23,7 +23,7 @@ use toml::Value;
23
23
24
24
use utils;
25
25
use renderer:: { CmdRenderer , HtmlHandlebars , RenderContext , Renderer } ;
26
- use preprocess;
26
+ use preprocess:: { Preprocessor , LinkPreprocessor , PreprocessorContext } ;
27
27
use errors:: * ;
28
28
29
29
use config:: Config ;
@@ -40,6 +40,9 @@ pub struct MDBook {
40
40
41
41
/// The URL used for live reloading when serving up the book.
42
42
pub livereload : Option < String > ,
43
+
44
+ /// List of pre-processors to be run on the book
45
+ preprocessors : Vec < Box < Preprocessor > >
43
46
}
44
47
45
48
impl MDBook {
@@ -85,13 +88,15 @@ impl MDBook {
85
88
let livereload = None ;
86
89
87
90
let renderers = determine_renderers ( & config) ;
91
+ let preprocessors = determine_preprocessors ( & config) ?;
88
92
89
93
Ok ( MDBook {
90
94
root,
91
95
config,
92
96
book,
93
97
renderers,
94
98
livereload,
99
+ preprocessors,
95
100
} )
96
101
}
97
102
@@ -151,14 +156,22 @@ impl MDBook {
151
156
pub fn build ( & self ) -> Result < ( ) > {
152
157
debug ! ( "[fn]: build" ) ;
153
158
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
+
154
167
for renderer in & self . renderers {
155
- self . run_renderer ( renderer. as_ref ( ) ) ?;
168
+ self . run_renderer ( & preprocessed_book , renderer. as_ref ( ) ) ?;
156
169
}
157
170
158
171
Ok ( ( ) )
159
172
}
160
173
161
- fn run_renderer ( & self , renderer : & Renderer ) -> Result < ( ) > {
174
+ fn run_renderer ( & self , preprocessed_book : & Book , renderer : & Renderer ) -> Result < ( ) > {
162
175
let name = renderer. name ( ) ;
163
176
let build_dir = self . build_dir_for ( name) ;
164
177
if build_dir. exists ( ) {
@@ -174,7 +187,7 @@ impl MDBook {
174
187
175
188
let render_context = RenderContext :: new (
176
189
self . root . clone ( ) ,
177
- self . book . clone ( ) ,
190
+ preprocessed_book . clone ( ) ,
178
191
self . config . clone ( ) ,
179
192
build_dir,
180
193
) ;
@@ -185,13 +198,19 @@ impl MDBook {
185
198
}
186
199
187
200
/// 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)
190
203
pub fn with_renderer < R : Renderer + ' static > ( & mut self , renderer : R ) -> & mut Self {
191
204
self . renderers . push ( Box :: new ( renderer) ) ;
192
205
self
193
206
}
194
207
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
+
195
214
/// Run `rustdoc` tests on the book, linking against the provided libraries.
196
215
pub fn test ( & mut self , library_paths : Vec < & str > ) -> Result < ( ) > {
197
216
let library_args: Vec < & str > = ( 0 ..library_paths. len ( ) )
@@ -202,15 +221,15 @@ impl MDBook {
202
221
203
222
let temp_dir = TempDir :: new ( "mdbook" ) ?;
204
223
224
+ let preprocess_context = PreprocessorContext :: new ( self . root . clone ( ) , self . config . clone ( ) ) ;
225
+
226
+ LinkPreprocessor :: new ( ) . run ( & preprocess_context, & mut self . book ) ?;
227
+
205
228
for item in self . iter ( ) {
206
229
if let BookItem :: Chapter ( ref ch) = * item {
207
230
if !ch. path . as_os_str ( ) . is_empty ( ) {
208
231
let path = self . source_dir ( ) . join ( & ch. path ) ;
209
- let base = path. parent ( )
210
- . ok_or_else ( || String :: from ( "Invalid bookitem path!" ) ) ?;
211
232
let content = utils:: fs:: file_to_string ( & path) ?;
212
- // Parse and expand links
213
- let content = preprocess:: links:: replace_all ( & content, base) ?;
214
233
println ! ( "[*]: Testing file: {:?}" , path) ;
215
234
216
235
// write preprocessed file to tempdir
@@ -309,6 +328,34 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> {
309
328
renderers
310
329
}
311
330
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
+
312
359
fn interpret_custom_renderer ( key : & str , table : & Value ) -> Box < Renderer > {
313
360
// look for the `command` field, falling back to using the key
314
361
// prepended by "mdbook-"
@@ -364,4 +411,65 @@ mod tests {
364
411
assert_eq ! ( got. len( ) , 1 ) ;
365
412
assert_eq ! ( got[ 0 ] . name( ) , "random" ) ;
366
413
}
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
+ }
367
475
}
0 commit comments