#![allow(dead_code)]
use crate::take_until_unbalanced;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::*;
use nom::error::ErrorKind;
pub fn md_link(i: &str) -> nom::IResult<&str, (&str, String, &str)> {
let (i, link_text) = md_link_text(i)?;
let (i, (link_destination, link_title)) = md_link_destination_enclosed(i)?;
Ok((i, (link_text, link_destination, link_title)))
}
pub fn md_link_ref(i: &str) -> nom::IResult<&str, (&str, String, &str)> {
let (i, _) = nom::bytes::complete::take_while_m_n(0, 3, |c| c == ' ')(i)?;
let (i, link_text) = md_link_ref_text(i)?;
let (i, _) = verify(nom::character::complete::multispace1, |s: &str| {
s.find("\n\n").is_none()
})(i)?;
let (i, link_destination) = md_link_destination(i)?;
if let Ok((i, _)) = verify(
nom::character::complete::multispace1::<_, (_, ErrorKind)>,
|s: &str| s.find("\n\n").is_none(),
)(i)
{
let (i, link_title) = verify(md_link_title, |s: &str| s.find("\n\n").is_none())(i)?;
Ok((i, (link_text, link_destination, link_title)))
} else {
Ok((i, (link_text, link_destination, "")))
}
}
fn md_link_text(i: &str) -> nom::IResult<&str, &str> {
nom::sequence::delimited(tag("["), take_until_unbalanced('[', ']'), tag("]"))(i)
}
fn md_link_ref_text(i: &str) -> nom::IResult<&str, &str> {
nom::sequence::delimited(tag("["), take_until_unbalanced('[', ']'), tag("]:"))(i)
}
fn md_link_destination(i: &str) -> nom::IResult<&str, String> {
match md_parse_link_destination(i) {
Ok((i, ld)) => {
let ld = if let Ok((_, ld_trans)) = md_escaped_link_destination_transform(ld) {
ld_trans
} else {
ld.to_string()
};
Ok((i, ld))
}
std::result::Result::Err(nom::Err::Error(nom::error::Error { input: _, code })) => {
Err(nom::Err::Error(nom::error::Error::new(i, code)))
}
Err(_) => Err(nom::Err::Error(nom::error::Error::new(
i,
ErrorKind::EscapedTransform,
))),
}
}
fn md_parse_link_destination(i: &str) -> nom::IResult<&str, &str> {
alt((
nom::sequence::delimited(
tag("<"),
nom::bytes::complete::escaped(
nom::character::complete::none_of(r#"\<>"#),
'\\',
nom::character::complete::one_of(r#"<>"#),
),
tag(">"),
),
map_parser(
nom::bytes::complete::is_not(" \t\r\n"),
all_consuming(take_until_unbalanced('(', ')')),
),
))(i)
}
fn md_escaped_link_destination_transform(i: &str) -> nom::IResult<&str, String> {
nom::bytes::complete::escaped_transform(
nom::bytes::complete::is_not("\\"),
'\\',
alt((
value("\\", tag("\\")),
value("<", tag("<")),
value(">", tag(">")),
)),
)(i)
}
fn md_link_destination_enclosed(i: &str) -> nom::IResult<&str, (String, &str)> {
let (rest, inner) =
nom::sequence::delimited(tag("("), take_until_unbalanced('(', ')'), tag(")"))(i)?;
let (i, link_destination) = md_link_destination(inner)?;
if let Ok((i, _)) = nom::character::complete::multispace1::<_, (_, ErrorKind)>(i) {
let (_, link_title) = md_link_title(i)?;
Ok((rest, (link_destination, link_title)))
} else {
Ok((rest, (link_destination, "")))
}
}
fn md_link_title(i: &str) -> nom::IResult<&str, &str> {
verify(
alt((
nom::sequence::delimited(tag("("), take_until_unbalanced('(', ')'), tag(")")),
nom::sequence::delimited(
tag("'"),
nom::bytes::complete::escaped(
nom::character::complete::none_of(r#"\'"#),
'\\',
nom::character::complete::one_of(r#"n"'()"#),
),
tag("'"),
),
nom::sequence::delimited(
tag("\""),
nom::bytes::complete::escaped(
nom::character::complete::none_of(r#"\""#),
'\\',
nom::character::complete::one_of(r#"n"'()"#),
),
tag("\""),
),
)),
|s: &str| s.find("\n\n").is_none(),
)(i)
}
#[cfg(test)]
mod tests {
use super::*;
use nom::error::ErrorKind;
#[test]
fn test_md_link() {
assert_eq!(
md_link("[text](url)abc"),
Ok(("abc", ("text", "url".to_string(), "")))
);
assert_eq!(
md_link("[text[i]](url)abc"),
Ok(("abc", ("text[i]", "url".to_string(), "")))
);
assert_eq!(
md_link("[text[i]](ur(l))abc"),
Ok(("abc", ("text[i]", "ur(l)".to_string(), "")))
);
assert_eq!(
md_link("[text(url)"),
Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Tag)))
);
assert_eq!(
md_link("[text](<url>)abc"),
Ok(("abc", ("text", "url".to_string(), "")))
);
assert_eq!(
md_link("[text](<url> \"link title\")abc"),
Ok(("abc", ("text", "url".to_string(), "link title")))
);
assert_eq!(
md_link("[text](url \"link title\")abc"),
Ok(("abc", ("text", "url".to_string(), "link title")))
);
}
#[test]
fn test_md_link_ref() {
assert_eq!(
md_link_ref("[text]: url\n\"abc\""),
Ok(("", ("text", "url".to_string(), "abc")))
);
assert_eq!(
md_link_ref(" [text]: url\n\"abc\""),
Ok(("", ("text", "url".to_string(), "abc")))
);
assert_eq!(
md_link_ref("abc[text]: url\n\"abc\""),
Err(nom::Err::Error(nom::error::Error::new(
"abc[text]: url\n\"abc\"",
ErrorKind::Tag
)))
);
assert_eq!(
md_link_ref(" [text]: url\n\"abc\""),
Err(nom::Err::Error(nom::error::Error::new(
" [text]: url\n\"abc\"",
ErrorKind::Tag
)))
);
assert_eq!(
md_link_ref("[text[i]]: ur(l)url"),
Ok(("", ("text[i]", "ur(l)url".to_string(), "")))
);
assert_eq!(
md_link_ref("[text[i]]: ur(l)(url"),
Err(nom::Err::Error(nom::error::Error::new(
"ur(l)(url",
ErrorKind::TakeUntil
)))
);
assert_eq!(
md_link_ref("[text]: \nurl"),
Ok(("", ("text", "url".to_string(), "")))
);
assert_eq!(
md_link_ref("[text]: \n\nurl"),
Err(nom::Err::Error(nom::error::Error::new(
" \n\nurl",
ErrorKind::Verify
)))
);
assert_eq!(
md_link_ref("[text: url"),
Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Tag)))
);
assert_eq!(
md_link_ref("[text] url"),
Err(nom::Err::Error(nom::error::Error::new(
"] url",
ErrorKind::Tag
)))
);
assert_eq!(
md_link_ref("[text]: url \"link title\"\nabc"),
Ok(("\nabc", ("text", "url".to_string(), "link title")))
);
assert_eq!(
md_link_ref("[text]: url \"link\ntitle\"\nabc"),
Ok(("\nabc", ("text", "url".to_string(), "link\ntitle")))
);
assert_eq!(
md_link_ref("[text]: url \"link\n\ntitle\"\nabc"),
Err(nom::Err::Error(nom::error::Error::new(
"\"link\n\ntitle\"\nabc",
ErrorKind::Verify
)))
);
assert_eq!(
md_link_ref("[text]:\nurl \"link\ntitle\"\nabc"),
Ok(("\nabc", ("text", "url".to_string(), "link\ntitle")))
);
assert_eq!(
md_link_ref("[text]:\n\nurl \"link title\"\nabc"),
Err(nom::Err::Error(nom::error::Error::new(
"\n\nurl \"link title\"\nabc",
ErrorKind::Verify
)))
);
}
#[test]
fn test_md_link_text() {
assert_eq!(md_link_text("[text](url)"), Ok(("(url)", "text")));
assert_eq!(md_link_text("[text[i]](url)"), Ok(("(url)", "text[i]")));
assert_eq!(
md_link_text("[text(url)"),
Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Tag)))
);
}
#[test]
fn test_md_link_ref_text() {
assert_eq!(md_link_ref_text("[text]: url"), Ok((" url", "text")));
assert_eq!(md_link_ref_text("[text[i]]: url"), Ok((" url", "text[i]")));
assert_eq!(
md_link_ref_text("[text: url"),
Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Tag)))
);
assert_eq!(
md_link_ref_text("[t[ext: url"),
Err(nom::Err::Error(nom::error::Error::new(
"t[ext: url",
ErrorKind::TakeUntil
)))
);
}
#[test]
fn test_md_link_destination() {
assert_eq!(
md_link_destination("<url>abc"),
Ok(("abc", "url".to_string()))
);
assert_eq!(
md_link_destination(r#"<u\<r\>l>abc"#),
Ok(("abc", r#"u<r>l"#.to_string()))
);
}
#[test]
fn test_md_parse_link_destination() {
assert_eq!(md_parse_link_destination("<url>abc"), Ok(("abc", "url")));
assert_eq!(
md_parse_link_destination(r#"<u\<r\>l>abc"#),
Ok(("abc", r#"u\<r\>l"#))
);
assert_eq!(
md_parse_link_destination("<url 2>abc"),
Ok(("abc", "url 2"))
);
assert_eq!(md_parse_link_destination("url abc"), Ok((" abc", "url")));
assert_eq!(
md_parse_link_destination("<url(1)> abc"),
Ok((" abc", "url(1)"))
);
assert_eq!(
md_parse_link_destination("ur()l abc"),
Ok((" abc", "ur()l"))
);
assert_eq!(
md_link_title("<url>>abc"),
Err(nom::Err::Error(nom::error::Error::new(
"<url>>abc",
ErrorKind::Tag
)))
);
assert_eq!(
md_link_title("<u<rl>abc"),
Err(nom::Err::Error(nom::error::Error::new(
"<u<rl>abc",
ErrorKind::Tag
)))
);
}
#[test]
fn test_md_escaped_link_destination_transform() {
assert_eq!(
md_escaped_link_destination_transform(""),
Ok(("", "".to_string()))
);
assert_eq!(
md_escaped_link_destination_transform(" "),
Ok(("", " ".to_string()))
);
assert_eq!(
md_escaped_link_destination_transform(r#"abc`:<>abc"#),
Ok(("", r#"abc`:<>abc"#.to_string()))
);
assert_eq!(
md_escaped_link_destination_transform(r#"\<\>\\"#),
Ok(("", r#"<>\"#.to_string()))
);
}
#[test]
fn test_md_link_title() {
assert_eq!(md_link_title("(title)abc"), Ok(("abc", "title")));
assert_eq!(md_link_title("(ti(t)le)abc"), Ok(("abc", "ti(t)le")));
assert_eq!(
md_link_title(r#""123\"456"abc"#),
Ok(("abc", r#"123\"456"#))
);
assert_eq!(
md_link_title(r#""tu\nv\"wxy"abc"#),
Ok(("abc", r#"tu\nv\"wxy"#))
);
assert_eq!(
md_link_title(r#"'tu\nv\'wxy'abc"#),
Ok(("abc", r#"tu\nv\'wxy"#))
);
assert_eq!(
md_link_title("(ti\n\ntle)abc"),
Err(nom::Err::Error(nom::error::Error::new(
"(ti\n\ntle)abc",
ErrorKind::Verify
)))
);
}
}