Recursively apply preprocessor (#682)
This commit is contained in:
parent
6bf86806e4
commit
2a55ff62f3
|
@ -9,6 +9,7 @@ use super::{Preprocessor, PreprocessorContext};
|
|||
use book::{Book, BookItem};
|
||||
|
||||
const ESCAPE_CHAR: char = '\\';
|
||||
const MAX_LINK_NESTED_DEPTH: usize = 10;
|
||||
|
||||
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}`
|
||||
/// helpers in a chapter.
|
||||
|
@ -36,7 +37,7 @@ impl Preprocessor for LinkPreprocessor {
|
|||
.map(|dir| src_dir.join(dir))
|
||||
.expect("All book items have a parent");
|
||||
|
||||
let content = replace_all(&ch.content, base);
|
||||
let content = replace_all(&ch.content, base, &ch.path, 0);
|
||||
ch.content = content;
|
||||
}
|
||||
});
|
||||
|
@ -45,11 +46,12 @@ impl Preprocessor for LinkPreprocessor {
|
|||
}
|
||||
}
|
||||
|
||||
fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> String {
|
||||
fn replace_all<P: AsRef<Path>>(s: &str, path: P, source: &P, depth: usize) -> String {
|
||||
// When replacing one thing in a string by something with a different length,
|
||||
// the indices after that will not correspond,
|
||||
// we therefore have to store the difference to correct this
|
||||
let path = path.as_ref();
|
||||
let source = source.as_ref();
|
||||
let mut previous_end_index = 0;
|
||||
let mut replaced = String::new();
|
||||
|
||||
|
@ -58,7 +60,15 @@ fn replace_all<P: AsRef<Path>>(s: &str, path: P) -> String {
|
|||
|
||||
match playpen.render_with_path(&path) {
|
||||
Ok(new_content) => {
|
||||
replaced.push_str(&new_content);
|
||||
if depth < MAX_LINK_NESTED_DEPTH {
|
||||
if let Some(rel_path) = playpen.link.relative_path(path) {
|
||||
replaced.push_str(&replace_all(&new_content, rel_path, &source.to_path_buf(), depth + 1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
error!("Stack depth exceeded in {}. Check for cyclic includes",
|
||||
source.display());
|
||||
}
|
||||
previous_end_index = playpen.end_index;
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -84,6 +94,27 @@ enum LinkType<'a> {
|
|||
Playpen(PathBuf, Vec<&'a str>),
|
||||
}
|
||||
|
||||
impl<'a> LinkType<'a> {
|
||||
fn relative_path<P: AsRef<Path>>(self, base: P) -> Option<PathBuf> {
|
||||
let base = base.as_ref();
|
||||
match self {
|
||||
LinkType::Escaped => None,
|
||||
LinkType::IncludeRange(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::IncludeRangeFrom(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::IncludeRangeTo(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::IncludeRangeFull(p, _) => Some(return_relative_path(base, &p)),
|
||||
LinkType::Playpen(p,_) => Some(return_relative_path(base, &p))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn return_relative_path<P: AsRef<Path>>(base: P, relative: P) -> PathBuf {
|
||||
base.as_ref()
|
||||
.join(relative)
|
||||
.parent()
|
||||
.expect("Included file should not be /")
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
fn parse_include_path(path: &str) -> LinkType<'static> {
|
||||
let mut parts = path.split(':');
|
||||
let path = parts.next().unwrap().into();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
- [First Chapter](first/index.md)
|
||||
- [Nested Chapter](first/nested.md)
|
||||
- [Includes](first/includes.md)
|
||||
- [Recursive](first/recursive.md)
|
||||
- [Second Chapter](second.md)
|
||||
|
||||
---
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Around the world, around the world
|
||||
{{#include recursive.md}}
|
|
@ -29,7 +29,7 @@ const TOC_TOP_LEVEL: &[&'static str] = &[
|
|||
"Conclusion",
|
||||
"Introduction",
|
||||
];
|
||||
const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter", "1.2. Includes"];
|
||||
const TOC_SECOND_LEVEL: &[&'static str] = &["1.1. Nested Chapter", "1.2. Includes", "1.3. Recursive"];
|
||||
|
||||
/// Make sure you can load the dummy book and build it without panicking.
|
||||
#[test]
|
||||
|
@ -313,6 +313,20 @@ fn able_to_include_files_in_chapters() {
|
|||
assert_doesnt_contain_strings(&includes, &["{{#include ../SUMMARY.md::}}"]);
|
||||
}
|
||||
|
||||
/// Ensure cyclic includes are capped so that no exceptions occur
|
||||
#[test]
|
||||
fn recursive_includes_are_capped() {
|
||||
let temp = DummyBook::new().build().unwrap();
|
||||
let md = MDBook::load(temp.path()).unwrap();
|
||||
md.build().unwrap();
|
||||
|
||||
let recursive = temp.path().join("book/first/recursive.html");
|
||||
let content = &["Around the world, around the world
|
||||
Around the world, around the world
|
||||
Around the world, around the world"];
|
||||
assert_contains_strings(&recursive, content);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_book_can_build() {
|
||||
let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
|
||||
|
@ -424,7 +438,7 @@ mod search {
|
|||
assert_eq!(docs["first/index.html#some-section"]["body"], "");
|
||||
assert_eq!(
|
||||
docs["first/includes.html#summary"]["body"],
|
||||
"Introduction First Chapter Nested Chapter Includes Second Chapter Conclusion"
|
||||
"Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Conclusion"
|
||||
);
|
||||
assert_eq!(
|
||||
docs["first/includes.html#summary"]["breadcrumbs"],
|
||||
|
@ -439,7 +453,7 @@ mod search {
|
|||
// Setting this to `true` may cause issues with `cargo watch`,
|
||||
// since it may not finish writing the fixture before the tests
|
||||
// are run again.
|
||||
const GENERATE_FIXTURE: bool = false;
|
||||
const GENERATE_FIXTURE: bool = true;
|
||||
|
||||
fn get_fixture() -> serde_json::Value {
|
||||
if GENERATE_FIXTURE {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"title": 1
|
||||
},
|
||||
"first/includes.html#summary": {
|
||||
"body": 9,
|
||||
"body": 10,
|
||||
"breadcrumbs": 3,
|
||||
"title": 1
|
||||
},
|
||||
|
@ -62,7 +62,7 @@
|
|||
"title": "Includes"
|
||||
},
|
||||
"first/includes.html#summary": {
|
||||
"body": "Introduction First Chapter Nested Chapter Includes Second Chapter Conclusion",
|
||||
"body": "Introduction First Chapter Nested Chapter Includes Recursive Second Chapter Conclusion",
|
||||
"breadcrumbs": "First Chapter » Summary",
|
||||
"id": "first/includes.html#summary",
|
||||
"title": "Summary"
|
||||
|
@ -742,6 +742,30 @@
|
|||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"e": {
|
||||
"c": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"s": {
|
||||
"df": 1,
|
||||
"docs": {
|
||||
"first/includes.html#summary": {
|
||||
"tf": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"df": 0,
|
||||
"docs": {}
|
||||
},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
|
@ -1630,6 +1654,30 @@
|
|||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"e": {
|
||||
"c": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"r": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
"s": {
|
||||
"df": 1,
|
||||
"docs": {
|
||||
"first/includes.html#summary": {
|
||||
"tf": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"df": 0,
|
||||
"docs": {}
|
||||
},
|
||||
"u": {
|
||||
"df": 0,
|
||||
"docs": {},
|
||||
|
|
Loading…
Reference in New Issue