Skip to content

Commit

Permalink
handle brackets in link text and img alt
Browse files Browse the repository at this point in the history
Fixes #88

We could output things a bit nicer, but I'm deferring that to #183. The
approach in this change is functionally correct.
  • Loading branch information
yshavit authored Aug 15, 2024
1 parent 5efd894 commit c6dac84
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 4 deletions.
113 changes: 111 additions & 2 deletions src/fmt_md_inlines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,24 @@ impl<'md> MdInlinesWriter<'md> {
out.write_char('!');
}
out.write_char('[');

match &label {
LinkLabel::Text(text) => out.write_str(text),
LinkLabel::Inline(text) => self.write_line(out, *text),
LinkLabel::Text(text) => self.write_link_descriptions(out, text),
LinkLabel::Inline(text) => {
// Write to a string, and then dump that to out. This lets us escaping, and will
// eventually let us handle matched square brackets.
// Note that it's not really worth it to do the transformation "on the fly":
// the SimpleWrite trait only writes strings anyway (not chars), so even if we
// had an intercepting transform, it would still have to work on allocated strings.
// So we may as well just do it once.
// This could be output a bit nicer: see #183.
let mut sub_out = Output::new(String::with_capacity(64));
self.write_line(&mut sub_out, *text);
let as_string = sub_out.take_underlying().unwrap();
self.write_link_descriptions(out, &as_string);
}
}

out.write_char(']');

let link_ref = LinkTransformation::new(self.link_transformer.transform_variant(), self, link_like)
Expand Down Expand Up @@ -230,6 +244,18 @@ impl<'md> MdInlinesWriter<'md> {
}
}

fn write_link_descriptions<W>(&mut self, out: &mut Output<W>, description: &str)
where
W: SimpleWrite,
{
description.chars().for_each(|ch| {
if ch == '[' || ch == ']' {
out.write_char('\\');
}
out.write_char(ch);
});
}

pub fn write_url_title<W>(&mut self, out: &mut Output<W>, title: &Option<String>)
where
W: SimpleWrite,
Expand Down Expand Up @@ -429,6 +455,89 @@ mod tests {
}
}

mod link_description {
use super::*;

#[test]
fn simple() {
check_link_description("hello, world", "hello, world");
}

#[test]
fn matched_brackets() {
check_link_description("link [foo [bar]]", "link \\[foo \\[bar\\]\\]");
}

#[test]
fn unmatched_brackets() {
check_link_description("link [foo bar", "link \\[foo bar");
}

fn check_link_description(input_description: &str, expected: &str) {
let mut output = Output::new(String::new());
let mut writer = MdInlinesWriter::new(MdInlinesWriterOptions {
link_format: LinkTransform::Keep,
});
let link = Inline::Link(Link {
text: vec![Inline::Text(Text {
variant: TextVariant::Plain,
value: input_description.to_string(),
})],
link_definition: LinkDefinition {
url: "https://www.example.com".to_string(),
title: None,
reference: LinkReference::Inline,
},
});
writer.write_inline_element(&mut output, &link);

assert_eq!(
output.take_underlying().unwrap(),
format!("[{expected}](https://www.example.com)")
);
}
}

mod img_alt {
use super::*;

#[test]
fn simple() {
check_img_alt("hello, world", "hello, world");
}

#[test]
fn matched_brackets() {
check_img_alt("link [foo [bar]]", "link \\[foo \\[bar\\]\\]");
}

#[test]
fn unmatched_brackets() {
check_img_alt("link [foo bar", "link \\[foo bar");
}

fn check_img_alt(input_description: &str, expected: &str) {
let mut output = Output::new(String::new());
let mut writer = MdInlinesWriter::new(MdInlinesWriterOptions {
link_format: LinkTransform::Keep,
});
let link = Inline::Image(Image {
alt: input_description.to_string(),
link: LinkDefinition {
url: "https://www.example.com".to_string(),
title: None,
reference: LinkReference::Inline,
},
});
writer.write_inline_element(&mut output, &link);

assert_eq!(
output.take_underlying().unwrap(),
format!("![{expected}](https://www.example.com)")
);
}
}

/// Not a pure unit test; semi-integ. Checks that writing an inline to markdown and then parsing
/// that markdown results in the original inline.
fn round_trip(orig: &Inline, expect: &Inline) {
Expand Down
46 changes: 44 additions & 2 deletions src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,13 +770,12 @@ mod tests {
///
/// For example, footnote are `[^a]` in markdown; does that identifier get parsed as `"^a"` or `"a"`?
mod all_nodes {
use super::*;
use crate::unwrap;
use indoc::indoc;
use markdown::mdast::Node;
use markdown::{mdast, ParseOptions};

use super::*;

macro_rules! check {
(error: $enum_value:expr, $enum_variant:pat, $lookups:expr => $err:expr $(, $body:block)? ) => {{
let node = $enum_value;
Expand Down Expand Up @@ -2311,6 +2310,49 @@ mod tests {
}
}

mod link_descriptions {
use super::*;
use markdown::ParseOptions;

#[test]
fn simple() {
check("the text", "the text");
}

#[test]
fn matched_text_brackets() {
check("link [foo [bar]]", "link [foo [bar]]");
}

#[test]
fn escaped_text_brackets() {
check("link \\[foo bar", "link [foo bar")
}

fn check(in_description: &str, expected: &str) {
let md_str = format!("[{in_description}](https://example.com)");
let nodes = markdown::to_mdast(&md_str, &ParseOptions::default()).unwrap();
let root_elems = MdElem::read(nodes, &ReadOptions::default()).unwrap();

assert_eq!(
root_elems,
vec![MdElem::Paragraph(Paragraph {
body: vec![Inline::Link(Link {
text: vec![Inline::Text(Text {
variant: TextVariant::Plain,
value: expected.to_string(),
})],
link_definition: LinkDefinition {
url: "https://example.com".to_string(),
title: None,
reference: LinkReference::Inline,
},
})],
})]
);
}
}

/// A simple representation of some nodes. Very non-exhaustive, just for testing.
fn simple_to_string(nodes: &Vec<mdast::Node>) -> String {
fn build(out: &mut String, node: &mdast::Node) {
Expand Down

0 comments on commit c6dac84

Please sign in to comment.