-
Notifications
You must be signed in to change notification settings - Fork 1.8k
implementing preprocessors #532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
01df904
cad76a9
9668110
f282a55
12815fe
4cc708e
144358b
9c922cf
b98ed3f
08027b8
b599956
4177288
f2d7b70
90fa1b4
47cc571
0d62578
80a20eb
1136f67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ use toml::Value; | |
|
|
||
| use utils; | ||
| use renderer::{CmdRenderer, HtmlHandlebars, RenderContext, Renderer}; | ||
| use preprocess; | ||
| use preprocess::{Preprocessor, LinkPreprocessor, PreprocessorContext}; | ||
| use errors::*; | ||
|
|
||
| use config::Config; | ||
|
|
@@ -40,6 +40,9 @@ pub struct MDBook { | |
|
|
||
| /// The URL used for live reloading when serving up the book. | ||
| pub livereload: Option<String>, | ||
|
|
||
| /// List of pre-processors to be run on the book | ||
| preprocessors: Vec<Box<Preprocessor>> | ||
| } | ||
|
|
||
| impl MDBook { | ||
|
|
@@ -85,13 +88,15 @@ impl MDBook { | |
| let livereload = None; | ||
|
|
||
| let renderers = determine_renderers(&config); | ||
| let preprocessors = determine_preprocessors(&config)?; | ||
|
|
||
| Ok(MDBook { | ||
| root, | ||
| config, | ||
| book, | ||
| renderers, | ||
| livereload, | ||
| preprocessors, | ||
| }) | ||
| } | ||
|
|
||
|
|
@@ -151,14 +156,23 @@ impl MDBook { | |
| pub fn build(&self) -> Result<()> { | ||
| debug!("[fn]: build"); | ||
|
|
||
| let mut preprocessed_book = self.book.clone(); | ||
| let preprocess_ctx = PreprocessorContext { | ||
| src_dir: self.source_dir(), | ||
| }; | ||
|
|
||
| for preprocessor in &self.preprocessors { | ||
| preprocessor.run(&preprocess_ctx, &mut preprocessed_book)?; | ||
| } | ||
|
|
||
| for renderer in &self.renderers { | ||
| self.run_renderer(renderer.as_ref())?; | ||
| self.run_renderer(&preprocessed_book, renderer.as_ref())?; | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| fn run_renderer(&self, renderer: &Renderer) -> Result<()> { | ||
| fn run_renderer(&self, preprocessed_book: &Book, renderer: &Renderer) -> Result<()> { | ||
| let name = renderer.name(); | ||
| let build_dir = self.build_dir_for(name); | ||
| if build_dir.exists() { | ||
|
|
@@ -174,7 +188,7 @@ impl MDBook { | |
|
|
||
| let render_context = RenderContext::new( | ||
| self.root.clone(), | ||
| self.book.clone(), | ||
| preprocessed_book.clone(), | ||
| self.config.clone(), | ||
| build_dir, | ||
| ); | ||
|
|
@@ -192,6 +206,13 @@ impl MDBook { | |
| self | ||
| } | ||
|
|
||
| /// You can add a new preprocessor by using this method. | ||
| /// The only requirement is for your renderer to implement the Preprocessor trait. | ||
|
||
| 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. | ||
| pub fn test(&mut self, library_paths: Vec<&str>) -> Result<()> { | ||
| let library_args: Vec<&str> = (0..library_paths.len()) | ||
|
|
@@ -202,15 +223,18 @@ impl MDBook { | |
|
|
||
| let temp_dir = TempDir::new("mdbook")?; | ||
|
|
||
| let src_dir = self.source_dir(); | ||
| let preprocess_context = PreprocessorContext { | ||
| src_dir | ||
| }; | ||
|
|
||
| LinkPreprocessor::new().run(&preprocess_context, &mut self.book)?; | ||
|
|
||
| for item in self.iter() { | ||
| if let BookItem::Chapter(ref ch) = *item { | ||
| if !ch.path.as_os_str().is_empty() { | ||
| 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)?; | ||
| // Parse and expand links | ||
| let content = preprocess::links::replace_all(&content, base)?; | ||
| println!("[*]: Testing file: {:?}", path); | ||
|
|
||
| // write preprocessed file to tempdir | ||
|
|
@@ -309,6 +333,29 @@ fn determine_renderers(config: &Config) -> Vec<Box<Renderer>> { | |
| renderers | ||
| } | ||
|
|
||
| /// 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(vec![Box::new(LinkPreprocessor::new())]) | ||
|
||
| }; | ||
|
|
||
| let mut preprocessors: Vec<Box<Preprocessor>> = Vec::new(); | ||
|
|
||
| for key in preprocess_list { | ||
| if key == "links" { | ||
| preprocessors.push(Box::new(LinkPreprocessor::new())) | ||
| } else { | ||
| bail!("{:?} is not a recognised preprocessor", key); | ||
| } | ||
| } | ||
|
|
||
| Ok(preprocessors) | ||
| } | ||
|
|
||
| fn interpret_custom_renderer(key: &str, table: &Value) -> Box<Renderer> { | ||
| // look for the `command` field, falling back to using the key | ||
| // prepended by "mdbook-" | ||
|
|
@@ -364,4 +411,65 @@ mod tests { | |
| assert_eq!(got.len(), 1); | ||
| 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()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,17 @@ | ||
| pub mod links; | ||
| pub use self::links::LinkPreprocessor; | ||
|
|
||
| mod links; | ||
|
|
||
| use book::Book; | ||
| use errors::*; | ||
|
|
||
| use std::path::PathBuf; | ||
|
|
||
| pub struct PreprocessorContext { | ||
| pub src_dir: PathBuf | ||
|
||
| } | ||
|
|
||
| pub trait Preprocessor { | ||
| fn name(&self) -> &str; | ||
| fn run(&self, ctx: &PreprocessorContext, book: &mut Book) -> Result<()>; | ||
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that we're not mutating the original book here and instead preprocessing its clone 👍