preprocess/links: fail for invalid links
Before, `mdbook` would continue processing even when errors, such as invalid links, are encountered. Moreover, it would exit with a `0` return code. Such behavior is unexpected and leads to confusion when run in CI. Now, when links that don't point to existing files are encounter and error is returned which yields to `mdbook` exiting with an error code. The change in behavior has revealed that some tests were run with invalid links.
This commit is contained in:
parent
090eba0db5
commit
d755bf9e09
|
@ -102,11 +102,11 @@ impl Book {
|
||||||
/// Unlike the `iter()` method, this requires a closure instead of returning
|
/// Unlike the `iter()` method, this requires a closure instead of returning
|
||||||
/// an iterator. This is because using iterators can possibly allow you
|
/// an iterator. This is because using iterators can possibly allow you
|
||||||
/// to have iterator invalidation errors.
|
/// to have iterator invalidation errors.
|
||||||
pub fn for_each_mut<F>(&mut self, mut func: F)
|
pub fn for_each_mut<F>(&mut self, mut func: F) -> Result<()>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut BookItem),
|
F: FnMut(&mut BookItem) -> Result<()>,
|
||||||
{
|
{
|
||||||
for_each_mut(&mut func, &mut self.sections);
|
for_each_mut(&mut func, &mut self.sections)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a `BookItem` to the `Book`.
|
/// Append a `BookItem` to the `Book`.
|
||||||
|
@ -116,18 +116,19 @@ impl Book {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_each_mut<'a, F, I>(func: &mut F, items: I)
|
pub fn for_each_mut<'a, F, I>(func: &mut F, items: I) -> Result<()>
|
||||||
where
|
where
|
||||||
F: FnMut(&mut BookItem),
|
F: FnMut(&mut BookItem) -> Result<()>,
|
||||||
I: IntoIterator<Item = &'a mut BookItem>,
|
I: IntoIterator<Item = &'a mut BookItem>,
|
||||||
{
|
{
|
||||||
for item in items {
|
for item in items {
|
||||||
if let BookItem::Chapter(ch) = item {
|
if let BookItem::Chapter(ch) = item {
|
||||||
for_each_mut(func, &mut ch.sub_items);
|
for_each_mut(func, &mut ch.sub_items)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
func(item);
|
func(item)?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum representing any type of item which can be added to a book.
|
/// Enum representing any type of item which can be added to a book.
|
||||||
|
@ -595,7 +596,7 @@ And here is some \
|
||||||
let num_items = book.iter().count();
|
let num_items = book.iter().count();
|
||||||
let mut visited = 0;
|
let mut visited = 0;
|
||||||
|
|
||||||
book.for_each_mut(|_| visited += 1);
|
book.for_each_mut(|_| Ok(visited += 1)).unwrap();
|
||||||
|
|
||||||
assert_eq!(visited, num_items);
|
assert_eq!(visited, num_items);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,8 @@ impl Preprocessor for IndexPreprocessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(book)
|
Ok(book)
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl Preprocessor for LinkPreprocessor {
|
||||||
|
|
||||||
let mut chapter_title = ch.name.clone();
|
let mut chapter_title = ch.name.clone();
|
||||||
let content =
|
let content =
|
||||||
replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title);
|
replace_all(&ch.content, base, chapter_path, 0, &mut chapter_title)?;
|
||||||
ch.content = content;
|
ch.content = content;
|
||||||
if chapter_title != ch.name {
|
if chapter_title != ch.name {
|
||||||
ctx.chapter_titles
|
ctx.chapter_titles
|
||||||
|
@ -65,7 +65,8 @@ impl Preprocessor for LinkPreprocessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(book)
|
Ok(book)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +78,7 @@ fn replace_all<P1, P2>(
|
||||||
source: P2,
|
source: P2,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
chapter_title: &mut String,
|
chapter_title: &mut String,
|
||||||
) -> String
|
) -> Result<String>
|
||||||
where
|
where
|
||||||
P1: AsRef<Path>,
|
P1: AsRef<Path>,
|
||||||
P2: AsRef<Path>,
|
P2: AsRef<Path>,
|
||||||
|
@ -93,43 +94,32 @@ where
|
||||||
for link in find_links(s) {
|
for link in find_links(s) {
|
||||||
replaced.push_str(&s[previous_end_index..link.start_index]);
|
replaced.push_str(&s[previous_end_index..link.start_index]);
|
||||||
|
|
||||||
match link.render_with_path(path, chapter_title) {
|
let new_content = link
|
||||||
Ok(new_content) => {
|
.render_with_path(path, chapter_title)
|
||||||
if depth < MAX_LINK_NESTED_DEPTH {
|
.context(format!("Error updating \"{}\"", link.link_text))?;
|
||||||
if let Some(rel_path) = link.link_type.relative_path(path) {
|
if depth < MAX_LINK_NESTED_DEPTH {
|
||||||
replaced.push_str(&replace_all(
|
if let Some(rel_path) = link.link_type.relative_path(path) {
|
||||||
&new_content,
|
replaced.push_str(&replace_all(
|
||||||
rel_path,
|
&new_content,
|
||||||
source,
|
rel_path,
|
||||||
depth + 1,
|
source,
|
||||||
chapter_title,
|
depth + 1,
|
||||||
));
|
chapter_title,
|
||||||
} else {
|
)?);
|
||||||
replaced.push_str(&new_content);
|
} else {
|
||||||
}
|
replaced.push_str(&new_content);
|
||||||
} else {
|
|
||||||
error!(
|
|
||||||
"Stack depth exceeded in {}. Check for cyclic includes",
|
|
||||||
source.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
previous_end_index = link.end_index;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Error updating \"{}\", {}", link.link_text, e);
|
|
||||||
for cause in e.chain().skip(1) {
|
|
||||||
warn!("Caused By: {}", cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should make sure we include the raw `{{# ... }}` snippet
|
|
||||||
// in the page content if there are any errors.
|
|
||||||
previous_end_index = link.start_index;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
"Stack depth exceeded in {}. Check for cyclic includes",
|
||||||
|
source.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
previous_end_index = link.end_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
replaced.push_str(&s[previous_end_index..]);
|
replaced.push_str(&s[previous_end_index..]);
|
||||||
replaced
|
Ok(replaced)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
@ -444,7 +434,10 @@ mod tests {
|
||||||
{{#include file.rs}} << an escaped link!
|
{{#include file.rs}} << an escaped link!
|
||||||
```";
|
```";
|
||||||
let mut chapter_title = "test_replace_all_escaped".to_owned();
|
let mut chapter_title = "test_replace_all_escaped".to_owned();
|
||||||
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
|
assert_eq!(
|
||||||
|
replace_all(start, "", "", 0, &mut chapter_title).unwrap(),
|
||||||
|
end
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -456,7 +449,10 @@ mod tests {
|
||||||
# My Chapter
|
# My Chapter
|
||||||
";
|
";
|
||||||
let mut chapter_title = "test_set_chapter_title".to_owned();
|
let mut chapter_title = "test_set_chapter_title".to_owned();
|
||||||
assert_eq!(replace_all(start, "", "", 0, &mut chapter_title), end);
|
assert_eq!(
|
||||||
|
replace_all(start, "", "", 0, &mut chapter_title).unwrap(),
|
||||||
|
end
|
||||||
|
);
|
||||||
assert_eq!(chapter_title, "My Title");
|
assert_eq!(chapter_title, "My Title");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,10 +136,10 @@ fn recursive_copy<A: AsRef<Path>, B: AsRef<Path>>(from: A, to: B) -> Result<()>
|
||||||
|
|
||||||
pub fn new_copy_of_example_book() -> Result<TempDir> {
|
pub fn new_copy_of_example_book() -> Result<TempDir> {
|
||||||
let temp = TempFileBuilder::new().prefix("guide").tempdir()?;
|
let temp = TempFileBuilder::new().prefix("guide").tempdir()?;
|
||||||
|
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|
||||||
let guide = Path::new(env!("CARGO_MANIFEST_DIR")).join("guide");
|
recursive_copy(manifest_dir.join("guide"), temp.path().join("guide"))?;
|
||||||
|
recursive_copy(manifest_dir.join("examples"), temp.path().join("examples"))?;
|
||||||
recursive_copy(guide, temp.path())?;
|
|
||||||
|
|
||||||
Ok(temp)
|
Ok(temp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,7 +420,7 @@ Around the world, around the world"];
|
||||||
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();
|
||||||
|
|
||||||
let md = MDBook::load(example_book_dir.path()).unwrap();
|
let md = MDBook::load(example_book_dir.path().join("guide")).unwrap();
|
||||||
|
|
||||||
md.build().unwrap();
|
md.build().unwrap();
|
||||||
}
|
}
|
||||||
|
@ -484,7 +484,7 @@ fn first_chapter_is_copied_as_index_even_if_not_first_elem() {
|
||||||
#[test]
|
#[test]
|
||||||
fn theme_dir_overrides_work_correctly() {
|
fn theme_dir_overrides_work_correctly() {
|
||||||
let book_dir = dummy_book::new_copy_of_example_book().unwrap();
|
let book_dir = dummy_book::new_copy_of_example_book().unwrap();
|
||||||
let book_dir = book_dir.path();
|
let book_dir = &book_dir.path().join("guide");
|
||||||
let theme_dir = book_dir.join("theme");
|
let theme_dir = book_dir.join("theme");
|
||||||
|
|
||||||
let mut index = mdbook::theme::INDEX.to_vec();
|
let mut index = mdbook::theme::INDEX.to_vec();
|
||||||
|
|
Loading…
Reference in New Issue