use nom::{
bytes::complete::{is_not, tag, take, take_while},
character::complete::alphanumeric1,
combinator::{opt, peek, recognize},
sequence::{delimited, tuple},
IResult,
};
use super::{base_parsers::*, parse_all};
use crate::parser::{
link_url::LinkDestination,
parse_from_text::{
base_parsers::direct_delimited,
text_elements::{email_address, parse_text_element},
Element,
},
utils::{is_white_space, is_white_space_but_not_linebreak},
};
mod label_elements;
use label_elements::parse_label_elements;
pub(crate) fn inline_code(input: &str) -> IResult<&str, &str, CustomError<&str>> {
delimited(tag("`"), is_not("`"), tag("`"))(input)
}
pub(crate) fn code_block(input: &str) -> IResult<&str, Element, CustomError<&str>> {
let (input, content): (&str, &str) = delimited(tag("```"), is_not("```"), tag("```"))(input)?;
let (content, lang) = if is_white_space(
content
.chars()
.next()
.ok_or(nom::Err::Error(CustomError::NoContent))?,
) {
(content, None)
} else {
let (content, lang): (&str, &str) = alphanumeric1(content)?;
(content, Some(lang))
};
let char_in_question = content
.chars()
.next()
.ok_or(nom::Err::Error(CustomError::NoContent))?;
let content = if is_white_space_but_not_linebreak(char_in_question) {
let (content, _) = take_while(is_white_space_but_not_linebreak)(content)?;
let (content, _) = opt(tag("\n"))(content)?;
content
} else {
let (content, _) = tag("\n")(content)?;
content
};
let mut offset: usize = 0;
let mut c_iter = content.chars().rev();
while is_white_space(
c_iter
.next()
.ok_or(nom::Err::Error(CustomError::NoContent))?,
) {
offset = offset.saturating_add(1);
}
Ok((
input,
Element::CodeBlock {
language: lang,
content: content
.get(0..content.len().saturating_sub(offset))
.into_result()?,
},
))
}
pub(crate) fn delimited_email_address(input: &str) -> IResult<&str, Element, CustomError<&str>> {
let (input, content): (&str, &str) = delimited(tag("<"), is_not(">"), tag(">"))(input)?;
if content.is_empty() {
return Err(nom::Err::Error(CustomError::NoContent));
}
let (rest, email) = email_address(content)?;
if !rest.is_empty() {
return Err(nom::Err::Error(CustomError::UnexpectedContent));
}
Ok((input, email))
}
pub(crate) fn delimited_link(input: &str) -> IResult<&str, Element, CustomError<&str>> {
let (input, (_, destination, _)): (&str, (&str, LinkDestination, &str)) =
tuple((tag("<"), LinkDestination::parse_labelled, tag(">")))(input)?;
Ok((input, Element::Link { destination }))
}
pub(crate) fn labeled_link(input: &str) -> IResult<&str, Element, CustomError<&str>> {
let (input, raw_label): (&str, &str) = delimited(tag("["), is_not("]"), tag("]"))(input)?;
if raw_label.is_empty() {
return Err(nom::Err::Error(CustomError::NoContent));
}
let label = parse_label_elements(raw_label);
let (input, (_, destination, _)) =
tuple((tag("("), LinkDestination::parse_labelled, tag(")")))(input)?;
Ok((input, Element::LabeledLink { label, destination }))
}
pub(crate) fn parse_element(
input: &str,
prev_char: Option<char>,
) -> IResult<&str, Element, CustomError<&str>> {
if let Ok((i, b)) = direct_delimited(input, "**") {
Ok((i, Element::Bold(parse_all(b))))
} else if let Ok((i, b)) = direct_delimited(input, "__") {
Ok((i, Element::Bold(parse_all(b))))
} else if let Ok((i, b)) = direct_delimited(input, "_") {
Ok((i, Element::Italics(parse_all(b))))
} else if let Ok((i, b)) = direct_delimited(input, "*") {
Ok((i, Element::Italics(parse_all(b))))
} else if let Ok((i, b)) = direct_delimited(input, "~~") {
Ok((i, Element::StrikeThrough(parse_all(b))))
} else if let Ok((i, elm)) = code_block(input) {
Ok((i, elm))
} else if let Ok((i, b)) = inline_code(input) {
Ok((i, Element::InlineCode { content: b }))
} else if let Ok((i, elm)) = labeled_link(input) {
Ok((i, elm))
} else if let Ok((i, elm)) = delimited_email_address(input) {
Ok((i, elm))
} else if let Ok((i, elm)) = delimited_link(input) {
Ok((i, elm))
} else {
parse_text_element(input, prev_char)
}
}
fn eat_markdown_text(input: &str) -> IResult<&str, (), CustomError<&str>> {
let mut remaining = input;
while !remaining.is_empty() {
let (remainder, taken) = take(1usize)(remaining)?;
remaining = remainder;
if peek(|input| parse_element(input, taken.chars().next()))(remaining).is_ok() {
break;
}
}
Ok((remaining, ()))
}
pub(crate) fn markdown_text(input: &str) -> IResult<&str, Element, CustomError<&str>> {
let (rest, content) = recognize(eat_markdown_text)(input)?;
Ok((rest, Element::Text(content)))
}