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};
|
use book::{Book, BookItem};
|
||||||
|
|
||||||
const ESCAPE_CHAR: char = '\\';
|
const ESCAPE_CHAR: char = '\\';
|
||||||
|
const MAX_LINK_NESTED_DEPTH: usize = 10;
|
||||||
|
|
||||||
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}`
|
/// A preprocessor for expanding the `{{# playpen}}` and `{{# include}}`
|
||||||
/// helpers in a chapter.
|
/// helpers in a chapter.
|
||||||
|
@ -36,7 +37,7 @@ impl Preprocessor for LinkPreprocessor {
|
||||||
.map(|dir| src_dir.join(dir))
|
.map(|dir| src_dir.join(dir))
|
||||||
.expect("All book items have a parent");
|
.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;
|
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,
|
// 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
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
let source = source.as_ref();
|
||||||
let mut previous_end_index = 0;
|
let mut previous_end_index = 0;
|
||||||
let mut replaced = String::new();
|
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) {
|
match playpen.render_with_path(&path) {
|
||||||
Ok(new_content) => {
|
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;
|
previous_end_index = playpen.end_index;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -84,6 +94,27 @@ enum LinkType<'a> {
|
||||||
Playpen(PathBuf, Vec<&'a str>),
|
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> {
|
fn parse_include_path(path: &str) -> LinkType<'static> {
|
||||||
let mut parts = path.split(':');
|
let mut parts = path.split(':');
|
||||||
let path = parts.next().unwrap().into();
|
let path = parts.next().unwrap().into();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
- [First Chapter](first/index.md)
|
- [First Chapter](first/index.md)
|
||||||
- [Nested Chapter](first/nested.md)
|
- [Nested Chapter](first/nested.md)
|
||||||
- [Includes](first/includes.md)
|
- [Includes](first/includes.md)
|
||||||
|
- [Recursive](first/recursive.md)
|
||||||
- [Second Chapter](second.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",
|
"Conclusion",
|
||||||
"Introduction",
|
"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.
|
/// Make sure you can load the dummy book and build it without panicking.
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -313,6 +313,20 @@ fn able_to_include_files_in_chapters() {
|
||||||
assert_doesnt_contain_strings(&includes, &["{{#include ../SUMMARY.md::}}"]);
|
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]
|
#[test]
|
||||||
fn example_book_can_build() {
|
fn example_book_can_build() {
|
||||||
let example_book_dir = dummy_book::new_copy_of_example_book().unwrap();
|
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/index.html#some-section"]["body"], "");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
docs["first/includes.html#summary"]["body"],
|
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!(
|
assert_eq!(
|
||||||
docs["first/includes.html#summary"]["breadcrumbs"],
|
docs["first/includes.html#summary"]["breadcrumbs"],
|
||||||
|
@ -439,7 +453,7 @@ mod search {
|
||||||
// Setting this to `true` may cause issues with `cargo watch`,
|
// Setting this to `true` may cause issues with `cargo watch`,
|
||||||
// since it may not finish writing the fixture before the tests
|
// since it may not finish writing the fixture before the tests
|
||||||
// are run again.
|
// are run again.
|
||||||
const GENERATE_FIXTURE: bool = false;
|
const GENERATE_FIXTURE: bool = true;
|
||||||
|
|
||||||
fn get_fixture() -> serde_json::Value {
|
fn get_fixture() -> serde_json::Value {
|
||||||
if GENERATE_FIXTURE {
|
if GENERATE_FIXTURE {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"title": 1
|
"title": 1
|
||||||
},
|
},
|
||||||
"first/includes.html#summary": {
|
"first/includes.html#summary": {
|
||||||
"body": 9,
|
"body": 10,
|
||||||
"breadcrumbs": 3,
|
"breadcrumbs": 3,
|
||||||
"title": 1
|
"title": 1
|
||||||
},
|
},
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
"title": "Includes"
|
"title": "Includes"
|
||||||
},
|
},
|
||||||
"first/includes.html#summary": {
|
"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",
|
"breadcrumbs": "First Chapter » Summary",
|
||||||
"id": "first/includes.html#summary",
|
"id": "first/includes.html#summary",
|
||||||
"title": "Summary"
|
"title": "Summary"
|
||||||
|
@ -742,6 +742,30 @@
|
||||||
"r": {
|
"r": {
|
||||||
"df": 0,
|
"df": 0,
|
||||||
"docs": {},
|
"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": {
|
"u": {
|
||||||
"df": 0,
|
"df": 0,
|
||||||
"docs": {},
|
"docs": {},
|
||||||
|
@ -1630,6 +1654,30 @@
|
||||||
"r": {
|
"r": {
|
||||||
"df": 0,
|
"df": 0,
|
||||||
"docs": {},
|
"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": {
|
"u": {
|
||||||
"df": 0,
|
"df": 0,
|
||||||
"docs": {},
|
"docs": {},
|
||||||
|
|
Loading…
Reference in New Issue