Skip to content

Commit

Permalink
fix: replace unresolvable remote images with their descriptions (#105)
Browse files Browse the repository at this point in the history
Mirrors Pandoc's behavior of replacing remote images that can't be
loaded with their description. We only resolve images that Pandoc can't
handle, so if we fail to resolve it, we want to make sure the URL
doesn't get to Pandoc, which would try and fail to process it.

Closes #96
  • Loading branch information
max-heller authored Jun 25, 2024
1 parent 3362752 commit d130982
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 8 deletions.
21 changes: 21 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,27 @@ to = "markdown"
"###);
}

#[test]
fn unresolvable_remote_images() {
let book = MDBook::init()
.config(Config::markdown())
.chapter(Chapter::new(
"Some Chapter",
"prefix ![test image](https://doesnotexist.fake/main.yml?style=flat-square) suffix",
"chapter.md",
))
.build();
insta::assert_snapshot!(book, @r###"
├─ log output
│ INFO mdbook::book: Running the pandoc backend
│ WARN mdbook_pandoc::preprocess: Failed to resolve image link 'https://doesnotexist.fake/main.yml?style=flat-square' in chapter 'Some Chapter': could not fetch remote image: Dns Failed
│ WARN mdbook_pandoc::preprocess: Replacing image with description
│ INFO mdbook_pandoc::pandoc::renderer: Wrote output to book/markdown/book.md
├─ markdown/book.md
│ prefix test image suffix
"###);
}

#[test]
fn pandoc_working_dir_is_root() {
let cfg = r#"
Expand Down
61 changes: 53 additions & 8 deletions src/preprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ enum LinkContext {
Image,
}

#[derive(Debug)]
struct UnresolvableRemoteImage {
err: ureq::Error,
}

impl fmt::Display for UnresolvableRemoteImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if cfg!(test) {
// in tests, print less verbose error message to be consistent across operating systems
write!(f, "could not fetch remote image: {}", self.err.kind())
} else {
write!(f, "could not fetch remote image: {}", self.err)
}
}
}

impl std::error::Error for UnresolvableRemoteImage {}

impl<'book> Preprocessor<'book> {
pub fn new(ctx: RenderContext<'book>) -> anyhow::Result<Self> {
let preprocessed = ctx.destination.join("src");
Expand Down Expand Up @@ -451,7 +469,7 @@ impl<'book> Preprocessor<'book> {

fn download_remote_image(&self, link: &str) -> anyhow::Result<PathBuf> {
match ureq::get(link).call() {
Err(err) => anyhow::bail!("Unable to load remote image '{link}': {err:#}"),
Err(err) => Err(UnresolvableRemoteImage { err }.into()),
Ok(response) => {
const IMAGE_CONTENT_TYPES: &[(&str, &str)] = &[("image/svg+xml", "svg")];
let extension = IMAGE_CONTENT_TYPES.iter().find_map(|&(ty, extension)| {
Expand Down Expand Up @@ -768,7 +786,7 @@ impl<'book, 'preprocessor> PreprocessChapter<'book, 'preprocessor> {
) {
use pulldown_cmark::{Event, Tag, TagEnd};

while let Some((event, range)) = self.parser.next() {
'events: while let Some((event, range)) = self.parser.next() {
let event = match event {
Event::Start(tag) => 'current_event: {
let tag = match tag {
Expand Down Expand Up @@ -852,12 +870,39 @@ impl<'book, 'preprocessor> PreprocessChapter<'book, 'preprocessor> {
title,
id,
} => {
let dest_url = self.preprocessor.normalize_link_or_leave_as_is(
self.chapter,
link_type,
dest_url,
LinkContext::Image,
);
let resolved = match self.chapter.path.as_ref() {
None => Err((anyhow!("chapter has no path"), dest_url)),
Some(chapter_path) => {
let chapter_dir = chapter_path.parent().unwrap();
self.preprocessor.normalize_link(
chapter_path,
chapter_dir,
link_type,
dest_url,
LinkContext::Image,
)
}
};
let dest_url = match resolved {
Ok(link) => link,
Err((err, link)) => {
log::warn!(
"Failed to resolve image link '{link}' in chapter '{}': {err:#}",
self.chapter.name,
);
if err.downcast_ref::<UnresolvableRemoteImage>().is_some() {
log::warn!("Replacing image with description");
for (event, range) in &mut self.parser {
match event {
Event::End(TagEnd::Image) => break,
event => co.yield_((event, Some(range))).await,
}
}
continue 'events;
}
link
}
};
Tag::Image {
link_type,
dest_url,
Expand Down

0 comments on commit d130982

Please sign in to comment.