#![allow(dead_code)]
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::char;
use nom::character::complete::space0;
use nom::combinator::peek;
use nom::error::ErrorKind;
use percent_encoding::percent_decode_str;
use std::borrow::Cow;
pub fn adoc_link(i: &str) -> nom::IResult<&str, (Cow<str>, Cow<str>, Cow<str>)> {
let (i, (link_destination, link_text)) = nom::sequence::preceded(
space0,
nom::sequence::pair(adoc_link_destination, adoc_link_text),
)(i)?;
Ok((i, (link_text, link_destination, Cow::Borrowed(""))))
}
fn adoc_link_text(i: &str) -> nom::IResult<&str, Cow<str>> {
let (i, r) = nom::sequence::preceded(tag("["), remove_newline_take_till(']'))(i)?;
let (i, _) = char(']')(i)?;
Ok((i, r))
}
fn remove_newline_take_till<'a>(
pat: char,
) -> impl Fn(&'a str) -> nom::IResult<&'a str, Cow<'a, str>> {
move |i: &str| {
let mut res = Cow::Borrowed("");
let mut j = i;
while j != "" {
let (k, s) =
nom::bytes::complete::take_till(|c| c == pat || c == '\n' || c == '\\')(j)?;
res = match res {
Cow::Borrowed("") => Cow::Borrowed(s),
Cow::Borrowed(res_str) => {
let mut strg = res_str.to_string();
strg.push_str(s);
Cow::Owned(strg)
}
Cow::Owned(mut strg) => {
strg.push_str(s);
Cow::Owned(strg)
}
};
if let Ok((_, c)) = nom::combinator::peek::<_, _, nom::error::Error<_>, _>(
nom::character::complete::anychar,
)(k)
{
let m = match c {
c if c == pat => return Ok((k, res)),
c if c == '\\' => {
let (l, _) = char('\\')(k)?;
let (l, c) = alt((char(pat), nom::combinator::success('\\')))(l)?;
let mut strg = res.to_string();
strg.push(c);
res = Cow::Owned(strg);
l
}
c if c == '\n' => {
let (l, _) = char('\n')(k)?;
let (l, _) = space0(l)?;
let _ = nom::combinator::not(char('\n'))(l)?;
let mut strg = res.to_string();
strg.push(' ');
res = Cow::Owned(strg);
l
}
_ => unreachable!(),
};
j = m;
} else {
j = k;
}
}
Ok(("", res))
}
}
fn adoc_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
alt((
adoc_parse_http_link_destination,
adoc_parse_literal_link_destination,
adoc_parse_escaped_link_destination,
))(i)
}
fn adoc_parse_http_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
let (j, s) = nom::sequence::delimited(
peek(alt((tag("http://"), (tag("https://"))))),
nom::bytes::complete::take_till1(|c| {
c == '[' || c == ' ' || c == '\t' || c == '\r' || c == '\n'
}),
peek(char('[')),
)(i)?;
Ok((j, Cow::Borrowed(s)))
}
fn percent_decode(i: &str) -> nom::IResult<&str, Cow<str>> {
let decoded = percent_decode_str(i)
.decode_utf8()
.map_err(|_| nom::Err::Error(nom::error::Error::new(i, ErrorKind::EscapedTransform)))?;
Ok(("", decoded))
}
fn adoc_parse_escaped_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
nom::combinator::map_parser(
nom::sequence::delimited(
nom::sequence::pair(tag("link:"), peek(alt((tag("http://"), (tag("https://")))))),
nom::bytes::complete::take_till1(|c| {
c == '[' || c == ' ' || c == '\t' || c == '\r' || c == '\n'
}),
peek(char('[')),
),
percent_decode,
)(i)
}
fn adoc_parse_literal_link_destination(i: &str) -> nom::IResult<&str, Cow<str>> {
let (j, s) = nom::sequence::preceded(
tag("link:"),
nom::sequence::delimited(tag("++"), nom::bytes::complete::take_until("++"), tag("++")),
)(i)?;
Ok((j, Cow::Borrowed(s)))
}
#[cfg(test)]
mod tests {
use super::*;
use nom::error::ErrorKind;
use std::matches;
#[test]
fn test_adoc_link() {
assert_eq!(
adoc_link("http://getreu.net[My blog]abc"),
Ok((
"abc",
(
Cow::from("My blog"),
Cow::from("http://getreu.net"),
Cow::from("")
)
))
);
assert_eq!(
adoc_link(" \t http://getreu.net[My blog]abc"),
Ok((
"abc",
(
Cow::from("My blog"),
Cow::from("http://getreu.net"),
Cow::from("")
)
))
);
assert_eq!(
adoc_link(r#"http://getreu.net[My blog[1\]]abc"#),
Ok((
"abc",
(
Cow::from("My blog[1]"),
Cow::from("http://getreu.net"),
Cow::from("")
)
))
);
assert_eq!(
adoc_link("http://getreu.net[My\n blog]abc"),
Ok((
"abc",
(
Cow::from("My blog"),
Cow::from("http://getreu.net"),
Cow::from("")
)
))
);
assert_eq!(
adoc_link("link:http://getreu.net[My blog]abc"),
Ok((
"abc",
(
Cow::from("My blog"),
Cow::from("http://getreu.net"),
Cow::from("")
)
))
);
assert_eq!(
adoc_link("link:https://getreu.net/?q=%5Ba%20b%5D[My blog]abc"),
Ok((
"abc",
(
Cow::from("My blog"),
Cow::from("https://getreu.net/?q=[a b]"),
Cow::from("")
)
))
);
assert_eq!(
adoc_link("link:++https://getreu.net/?q=[a b]++[My blog]abc"),
Ok((
"abc",
(
Cow::from("My blog"),
Cow::from("https://getreu.net/?q=[a b]"),
Cow::from("")
)
))
);
}
#[test]
fn test_adoc_link_text() {
assert_eq!(adoc_link_text("[text]abc"), Ok(("abc", Cow::from("text"))));
assert_eq!(
adoc_link_text("[te\nxt]abc"),
Ok(("abc", Cow::from("te xt")))
);
assert_eq!(
adoc_link_text("[te\n\nxt]abc"),
Err(nom::Err::Error(nom::error::Error::new(
"\nxt]abc",
ErrorKind::Not
)))
);
assert_eq!(
adoc_link_text(r#"[text[i\]]abc"#),
Ok(("abc", Cow::from(r#"text[i]"#.to_string())))
);
assert_eq!(
adoc_link_text("[textabc"),
Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Char)))
);
}
#[test]
fn test_remove_newline_take_till() {
let res = remove_newline_take_till(']')("").unwrap();
assert_eq!(res, ("", Cow::from("")));
assert!(matches!(res.1,
Cow::Borrowed{..}
));
let res = remove_newline_take_till(']')("text text]abc").unwrap();
assert_eq!(res, ("]abc", Cow::from("text text")));
assert!(matches!(res.1,
Cow::Borrowed{..}
));
let res = remove_newline_take_till(']')("text text").unwrap();
assert_eq!(res, ("", Cow::from("text text")));
assert!(matches!(res.1,
Cow::Borrowed{..}
));
let res = remove_newline_take_till(']')(r#"te\]xt]abc"#).unwrap();
assert_eq!(res, ("]abc", Cow::from("te]xt")));
assert!(matches!(res.1,
Cow::Owned{..}
));
let res = remove_newline_take_till(']')(r#"text\]]abc"#).unwrap();
assert_eq!(res, ("]abc", Cow::from("text]")));
assert!(matches!(res.1,
Cow::Owned{..}
));
let res = remove_newline_take_till(']')(r#"te\xt]abc"#).unwrap();
assert_eq!(res, ("]abc", Cow::from(r#"te\xt"#)));
assert!(matches!(res.1,
Cow::Owned{..}
));
let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
assert_eq!(res, ("]abc", Cow::from("text text")));
assert!(matches!(res.1,
Cow::Owned{..}
));
let res = remove_newline_take_till(']')("text\n text]abc").unwrap();
assert_eq!(res, ("]abc", Cow::from("text text")));
assert!(matches!(res.1,
Cow::Owned{..}
));
assert_eq!(
remove_newline_take_till(']')("text\n\ntext]abc").unwrap_err(),
nom::Err::Error(nom::error::Error::new("\ntext]abc", ErrorKind::Not))
);
assert_eq!(
remove_newline_take_till(']')("text\n \n text]abc").unwrap_err(),
nom::Err::Error(nom::error::Error::new("\n text]abc", ErrorKind::Not))
);
}
#[test]
fn test_adoc_parse_html_link_destination() {
let res = adoc_parse_http_link_destination("http://destination/[abc").unwrap();
assert_eq!(res, ("[abc", Cow::from("http://destination/")));
assert!(matches!(res.1,
Cow::Borrowed{..}
));
let res = adoc_parse_http_link_destination("https://destination/[abc").unwrap();
assert_eq!(res, ("[abc", Cow::from("https://destination/")));
assert!(matches!(res.1,
Cow::Borrowed{..}
));
assert_eq!(
adoc_parse_http_link_destination("http:/destination/[abc").unwrap_err(),
nom::Err::Error(nom::error::Error::new(
"http:/destination/[abc",
ErrorKind::Tag
))
);
assert_eq!(
adoc_parse_http_link_destination("http://destination/(abc").unwrap_err(),
nom::Err::Error(nom::error::Error::new("", ErrorKind::Char))
);
}
#[test]
fn test_adoc_parse_escaped_link_destination() {
let res = adoc_parse_escaped_link_destination("link:http://destination/[abc").unwrap();
assert_eq!(res, ("[abc", Cow::from("http://destination/")));
assert!(matches!(res.1,
Cow::Borrowed{..}
));
assert_eq!(
adoc_parse_escaped_link_destination("link:httpX:/destination/[abc").unwrap_err(),
nom::Err::Error(nom::error::Error::new(
"httpX:/destination/[abc",
ErrorKind::Tag
))
);
assert_eq!(
adoc_link_destination("http://destination/(abc").unwrap_err(),
nom::Err::Error(nom::error::Error::new(
"http://destination/(abc",
ErrorKind::Tag
))
);
let res = adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%5Ba%20b%5D[abc")
.unwrap();
assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
assert!(matches!(res.1,
Cow::Owned{..}
));
assert_eq!(
adoc_parse_escaped_link_destination("link:https://getreu.net/?q=%FF%FF[abc")
.unwrap_err(),
nom::Err::Error(nom::error::Error::new(
"https://getreu.net/?q=%FF%FF",
ErrorKind::EscapedTransform
))
);
}
#[test]
fn test_adoc_parse_literal_link_destination() {
let res = adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]++[abc")
.unwrap();
assert_eq!(res, ("[abc", Cow::from("https://getreu.net/?q=[a b]")));
assert_eq!(
adoc_parse_literal_link_destination("link:++https://getreu.net/?q=[a b]+[abc")
.unwrap_err(),
nom::Err::Error(nom::error::Error::new(
"https://getreu.net/?q=[a b]+[abc",
ErrorKind::TakeUntil
))
);
}
}