use crate::parse::{attributes::attributes, phrase::phrase};
use crate::structs::InlineTag;
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag},
character::complete::{char, none_of},
combinator::{complete, fail, opt, value},
multi::{fold_many1, many_till},
sequence::{delimited, preceded, tuple},
IResult,
};
pub fn link(input: &str) -> IResult<&str, InlineTag> {
let (rest, (attributes, (content_vec, (title, url)))) = preceded(
char('"'),
tuple((
opt(attributes),
many_till(
alt((
value('\\', tag("\\\\")),
value('"', tag("\\\"")),
value('(', tag("\\(")),
value(')', tag("\\)")),
none_of("\\\"()"),
)),
tuple((
opt(delimited(
tag(" ("),
escaped_transform(
none_of("\\()"),
'\\',
alt((
value("\\", tag("\\")),
value("(", tag("(")),
value(")", tag(")")),
)),
),
char(')'),
)),
preceded(char('"'), link_suffix),
)),
),
)),
)(input)?;
let content_string = content_vec.into_iter().collect::<String>();
let content = match complete(phrase)(content_string.as_str()) {
Ok((_, content)) => content,
Err(_) => return fail(input),
};
Ok((
rest,
InlineTag::Link {
attributes,
title,
url,
content: Box::new(content),
},
))
}
pub fn link_suffix(input: &str) -> IResult<&str, String> {
preceded(
char(':'),
alt((
delimited(char('('), link_url_parenthesized, char(')')),
link_url_standard,
)),
)(input)
}
fn link_url_parenthesized(input: &str) -> IResult<&str, String> {
let (rest, url) = escaped_transform(
none_of("\\() \r\n\t"),
'\\',
alt((
value("\\", tag("\\")),
value("(", tag("(")),
value(")", tag(")")),
)),
)(input)?;
Ok((rest, String::from(url)))
}
fn link_url_standard(input: &str) -> IResult<&str, String> {
fold_many1(none_of(" \r\n\t"), String::new, |mut acc, c| {
acc.push(c);
acc
})(input)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::structs::Attributes;
#[test]
fn link_basic() {
let input = "\"a link\":https://git.sr.ht/~autumnull/flatiron";
let result = link(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Link {
attributes: None,
title: None,
url: String::from("https://git.sr.ht/~autumnull/flatiron"),
content: Box::new(InlineTag::Plaintext(String::from(
"a link"
)))
}
))
);
}
#[test]
fn link_attributes() {
let input = "\"(class)a link\":https://git.sr.ht/~autumnull/flatiron";
let result = link(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Link {
attributes: Some(Attributes {
class: Some(String::from("class")),
id: None,
style: None,
language: None,
}),
title: None,
url: String::from("https://git.sr.ht/~autumnull/flatiron"),
content: Box::new(InlineTag::Plaintext(String::from(
"a link"
)))
}
))
);
}
#[test]
fn link_attributes_title() {
let input =
"\"(class)a link (title)\":https://git.sr.ht/~autumnull/flatiron";
let result = link(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Link {
attributes: Some(Attributes {
class: Some(String::from("class")),
id: None,
style: None,
language: None,
}),
title: Some(String::from("title")),
url: String::from("https://git.sr.ht/~autumnull/flatiron"),
content: Box::new(InlineTag::Plaintext(String::from(
"a link"
)))
}
))
);
}
#[test]
fn link_escaped() {
let input =
"\"Earvin \\\"Magic\\\" Johnson \\(Basketball Player\\)\":https://en.wikipedia.org/wiki/Magic_Johnson";
let result = link(input);
assert_eq!(
result,
Ok((
"",
InlineTag::Link {
attributes: None,
title: None,
url: String::from(
"https://en.wikipedia.org/wiki/Magic_Johnson"
),
content: Box::new(InlineTag::Plaintext(String::from(
"Earvin “Magic” Johnson (Basketball Player)"
)))
}
))
);
}
#[test]
fn link_parenthesized() {
let input = "\"this\":(https://git.sr.ht/~autumnull/flatiron)?";
let result = link(input);
assert_eq!(
result,
Ok((
"?",
InlineTag::Link {
attributes: None,
title: None,
url: String::from("https://git.sr.ht/~autumnull/flatiron"),
content: Box::new(InlineTag::Plaintext(String::from(
"this"
)))
}
))
);
}
}