use nom::branch::alt;
use nom::bytes::complete::{is_not, tag, take_until};
use nom::character::complete::{anychar, char, digit1, line_ending, not_line_ending, space1};
use nom::combinator::{map, map_parser, map_res, peek};
use nom::multi::{many0_count, many1, many_m_n, separated_list1};
use nom::sequence::{delimited, preceded, separated_pair, tuple};
use nom::IResult;
use crate::{ASTElement, FormattedTextBlock, ListItem, ListItemType};
impl ASTElement {
pub(crate) fn parser(mut input: &str) -> (&str, Vec<ASTElement>) {
let mut output_vec: Vec<ASTElement> = vec![];
while !input.is_empty() {
match Self::testing_all_parser_without_text_box(input) {
Ok((rest, element)) => {
input = rest;
output_vec.push(element);
}
Err(_) => {
let mut textblock = String::new();
while peek(Self::testing_all_parser_without_text_box)(input).is_err() {
match anychar::<&str, ()>(input) {
Ok((rest, c)) => {
textblock.push(c);
input = rest;
}
Err(_) => break,
}
}
let (_, text) = ASTElement::parse_text_block(&textblock.trim()).unwrap();
output_vec.push(text);
}
}
}
(input, output_vec)
}
pub(crate) fn testing_all_parser_without_text_box(input: &str) -> IResult<&str, ASTElement> {
alt((
Self::parse_heading_for_combined,
Self::parse_image,
Self::parse_list_for_combined,
Self::parse_table_for_combined,
))(input)
}
pub(crate) fn parse_image(input: &str) -> IResult<&str, Self> {
alt((Self::parse_image_with_link, Self::parse_image_without_link))(input)
}
pub(crate) fn parse_image_without_link(input: &str) -> IResult<&str, Self> {
map(
preceded(tag("!"), parser_square_bracket_and_bracket),
|(image_alt, image_url)| {
let image_alt: Option<String> =
(!image_alt.is_empty()).then_some(image_alt.trim().to_owned());
Self::Image {
link_text: None,
link_url: None,
image_alt,
image_url: image_url.to_owned(),
}
},
)(input)
}
pub(crate) fn parse_image_with_link(input: &str) -> IResult<&str, Self> {
map(
Self::parse_iamge_with_link_plain_parser,
|(link_text, image_alt, image_url, link_url): (&str, &str, &str, &str)| {
let link_text: Option<String> =
(!link_text.is_empty()).then_some(link_text.trim().to_owned());
let image_alt: Option<String> =
(!image_alt.is_empty()).then_some(image_alt.trim().to_owned());
Self::Image {
link_text,
link_url: Some(link_url.to_owned()),
image_alt,
image_url: image_url.to_owned(),
}
},
)(input)
}
pub(crate) fn parse_iamge_with_link_plain_parser(
input: &str,
) -> IResult<&str, (&str, &str, &str, &str)> {
tuple((
preceded(tag("["), take_until("!")),
delimited(tag("!["), take_until("]"), tag("]")),
delimited(tag("("), is_not(")"), tag(")]")),
delimited(tag("("), is_not(")"), tag(")")),
))(input)
}
pub(crate) fn parse_list_for_combined(input: &str) -> IResult<&str, Self> {
Self::parse_list(input)
}
pub(crate) fn parse_list(input: &str) -> IResult<&str, ASTElement> {
map(
separated_list1(
line_ending,
map_parser(
not_line_ending,
alt((
Self::parse_unsorted_list_element,
Self::parse_sorted_list_element,
)),
),
),
|elements| ASTElement::Liste { elements },
)(input)
}
pub(crate) fn parse_unsorted_list_element(input: &str) -> IResult<&str, ListItem> {
map(
tuple((
many0_count(char(' ')),
tag("- "),
FormattedTextBlock::parser,
)),
|(lvl, _, inhalt)| ListItem {
item_type: ListItemType::Unordered,
level: lvl,
content: inhalt,
},
)(input)
}
pub(crate) fn parse_sorted_list_element(input: &str) -> IResult<&str, ListItem> {
map(
tuple((
many0_count(char(' ')),
map_res(digit1, |s: &str| s.parse::<usize>()),
char('.'),
FormattedTextBlock::parser,
)),
|(lvl, number, _, inhalt)| ListItem {
item_type: ListItemType::Ordered(number),
level: lvl,
content: inhalt,
},
)(input)
}
pub(crate) fn parse_table_for_combined(input: &str) -> IResult<&str, Self> {
Self::parse_table(input)
}
pub(crate) fn parse_table(input: &str) -> IResult<&str, Self> {
let (rest, ((headers_input, _), (second_line, _))) = tuple((
tuple((not_line_ending, line_ending)),
tuple((not_line_ending, line_ending)),
))(input)?;
let (_, headers) = Self::parser_heading_table(headers_input)?;
let (_, _) = Self::parser_second_line_table(second_line)?;
let (rest, inhalt) = Self::parser_inhalt_table(rest)?;
Ok((rest, Self::Table { headers, inhalt }))
}
pub(crate) fn parser_heading_table(input: &str) -> IResult<&str, Vec<FormattedTextBlock>> {
map(
tuple((
tag("|"),
separated_list1(
tag("|"),
map_parser(take_until("|"), FormattedTextBlock::parser),
),
tag("|"),
)),
|parsed| parsed.1,
)(input)
}
pub(crate) fn parser_second_line_table(input: &str) -> IResult<&str, ()> {
let (rest, _) = tuple((
tag("|"),
separated_list1(tag("|"), many1(tag("-"))),
tag("|"),
))(input)?;
Ok((rest, ()))
}
pub(crate) fn parser_inhalt_table(input: &str) -> IResult<&str, Vec<Vec<FormattedTextBlock>>> {
separated_list1(
line_ending,
map_parser(not_line_ending, Self::parser_inhalt_one_line_table),
)(input)
}
pub(crate) fn parser_inhalt_one_line_table(
input: &str,
) -> IResult<&str, Vec<FormattedTextBlock>> {
map(
tuple((
tag("|"),
separated_list1(
tag("|"),
map_parser(take_until("|"), FormattedTextBlock::parser),
),
tag("|"),
)),
|(_, parsed, _)| parsed,
)(input)
}
pub(crate) fn parse_heading_for_combined(input: &str) -> IResult<&str, Self> {
Self::parse_heading(input)
}
pub(crate) fn parse_heading(input: &str) -> IResult<&str, Self> {
map(
separated_pair(
map(many_m_n(1, 6, tag("#")), |parsed| parsed.len() as u8),
space1,
map_parser(not_line_ending, FormattedTextBlock::parser),
),
|(tier, text)| ASTElement::Heading { text, tier },
)(input)
}
pub(crate) fn parse_text_block(input: &str) -> IResult<&str, Self> {
map(FormattedTextBlock::parser, |parsed| Self::TextBlock {
text: parsed,
})(input)
}
}
pub(crate) fn parser_square_bracket_and_bracket(input: &str) -> IResult<&str, (&str, &str)> {
tuple((parser_square_bracket, parser_bracket))(input)
}
pub(crate) fn parser_bracket(input: &str) -> IResult<&str, &str> {
delimited(tag("("), is_not(")"), tag(")"))(input)
}
pub(crate) fn parser_square_bracket(input: &str) -> IResult<&str, &str> {
delimited(tag("["), take_until("]"), tag("]"))(input)
}