CorrosionMark 0.1.1

This is a markdown parser libary
Documentation
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;
                            }
                            // anychar errors if there is no data left, so we are finished
                            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> {
        // let mut output_vec: Vec<(usize, FormatetText)> = vec![];
        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)
}