use nom::{branch::alt, combinator::map, multi::many0, IResult, Parser};
use crate::{md_parser::constants::{AUTHORS, DATE, TAGS, TITLE},
parse_block_code,
parse_block_heading_opt_eol,
parse_block_markdown_text_with_or_without_new_line,
parse_block_smart_list,
parse_csv_opt_eol,
parse_unique_kv_opt_eol,
List,
MdBlock,
MdDocument};
#[rustfmt::skip]
pub fn parse_markdown(input: &str) -> IResult<&str, MdDocument<'_>> {
let (input, output) = many0(
alt((
map(parse_title_value, MdBlock::Title),
map(parse_tags_list, MdBlock::Tags),
map(parse_authors_list, MdBlock::Authors),
map(parse_date_value, MdBlock::Date),
map(parse_block_heading_opt_eol, MdBlock::Heading),
map(parse_block_smart_list, MdBlock::SmartList),
map(parse_block_code, MdBlock::CodeBlock),
map(parse_block_markdown_text_with_or_without_new_line, MdBlock::Text),
)),
).parse(input)?;
let it = List::from(output);
Ok((input, it))
}
fn parse_tags_list(input: &str) -> IResult<&str, List<&str>> {
parse_csv_opt_eol(TAGS, input)
}
fn parse_authors_list(input: &str) -> IResult<&str, List<&str>> {
parse_csv_opt_eol(AUTHORS, input)
}
fn parse_title_value(input: &str) -> IResult<&str, &str> {
parse_unique_kv_opt_eol(TITLE, input)
}
fn parse_date_value(input: &str) -> IResult<&str, &str> {
parse_unique_kv_opt_eol(DATE, input)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_eq2,
convert_into_code_block_lines,
list,
BulletKind,
HeadingData,
HeadingLevel,
HyperlinkData,
MdLineFragment};
#[test]
fn test_no_line() {
let input = "Something";
let (remainder, blocks) = parse_markdown(input).unwrap();
println!("remainder: {remainder:?}");
println!("blocks: {blocks:?}");
assert_eq2!(remainder, "");
assert_eq2!(
blocks[0],
MdBlock::Text(list![MdLineFragment::Plain("Something")])
);
}
#[test]
fn test_one_line() {
let input = "Something\n";
let (remainder, blocks) = parse_markdown(input).unwrap();
println!("remainder: {remainder:?}");
println!("blocks: {blocks:?}");
assert_eq2!(remainder, "");
assert_eq2!(
blocks[0],
MdBlock::Text(list![MdLineFragment::Plain("Something")])
);
}
#[test]
fn test_parse_markdown_with_invalid_text_in_heading() {
let input = ["# LINE 1", "", "##% LINE 2 FOO_BAR:", ""].join("\n");
let (remainder, blocks) = parse_markdown(&input).unwrap();
println!("\nremainder:\n{remainder:?}");
println!("\nblocks:\n{blocks:#?}");
assert_eq2!(remainder, "");
assert_eq2!(blocks.len(), 3);
assert_eq2!(
blocks[0],
MdBlock::Heading(HeadingData {
heading_level: HeadingLevel { level: 1 },
text: "LINE 1",
})
);
assert_eq2!(
blocks[1],
MdBlock::Text(list![]), );
assert_eq2!(
blocks[2],
MdBlock::Text(list![
MdLineFragment::Plain("##% LINE 2 FOO"),
MdLineFragment::Plain("_"),
MdLineFragment::Plain("BAR:"),
])
);
}
#[test]
fn test_parse_markdown_single_line_plain_text() {
let input = ["_this should not be italic", ""].join("\n");
let (remainder, blocks) = parse_markdown(&input).unwrap();
println!("\nremainder:\n{remainder:?}");
println!("\nblocks:\n{blocks:?}");
assert_eq2!(remainder, "");
assert_eq2!(blocks.len(), 1);
assert_eq2!(
blocks[0],
MdBlock::Text(list![
MdLineFragment::Plain("_"),
MdLineFragment::Plain("this should not be italic"),
])
);
}
#[test]
fn test_parse_markdown_valid() {
let input = vec to install foobar.",
"```python",
"import foobar",
"",
"foobar.pluralize('word') # returns 'words'",
"foobar.pluralize('goose') # returns 'geese'",
"foobar.singularize('phenomena') # returns 'phenomenon'",
"```",
"- ul1",
"- ul2",
"1. ol1",
"2. ol2",
"- [ ] todo",
"- [x] done",
"end",
"",
]
.join("\n");
let (remainder, list_block) = parse_markdown(&input).unwrap();
let vec_block = &[
MdBlock::Title("Something"),
MdBlock::Tags(list!["tag1", "tag2", "tag3"]),
MdBlock::Heading(HeadingData {
heading_level: HeadingLevel { level: 1 },
text: "Foobar",
}),
MdBlock::Text(list![]),
MdBlock::Text(list![MdLineFragment::Plain(
"Foobar is a Python library for dealing with word pluralization.",
)]),
MdBlock::Text(list![]),
MdBlock::CodeBlock(convert_into_code_block_lines(
Some("bash"),
vec!["pip install foobar"],
)),
MdBlock::CodeBlock(convert_into_code_block_lines(Some("fish"), vec![])),
MdBlock::CodeBlock(convert_into_code_block_lines(Some("python"), vec![""])),
MdBlock::Heading(HeadingData {
heading_level: HeadingLevel { level: 2 },
text: "Installation",
}),
MdBlock::Text(list![]),
MdBlock::Text(list![
MdLineFragment::Plain("Use the package manager "),
MdLineFragment::Link(HyperlinkData::from((
"pip",
"https://pip.pypa.io/en/stable/",
))),
MdLineFragment::Plain(" to install foobar."),
]),
MdBlock::CodeBlock(convert_into_code_block_lines(
Some("python"),
vec![
"import foobar",
"",
"foobar.pluralize('word') # returns 'words'",
"foobar.pluralize('goose') # returns 'geese'",
"foobar.singularize('phenomena') # returns 'phenomenon'",
],
)),
MdBlock::SmartList((
list![list![
MdLineFragment::UnorderedListBullet {
indent: 0,
is_first_line: true
},
MdLineFragment::Plain("ul1"),
],],
BulletKind::Unordered,
0,
)),
MdBlock::SmartList((
list![list![
MdLineFragment::UnorderedListBullet {
indent: 0,
is_first_line: true
},
MdLineFragment::Plain("ul2"),
],],
BulletKind::Unordered,
0,
)),
MdBlock::SmartList((
list![list![
MdLineFragment::OrderedListBullet {
indent: 0,
number: 1,
is_first_line: true
},
MdLineFragment::Plain("ol1"),
],],
BulletKind::Ordered(1),
0,
)),
MdBlock::SmartList((
list![list![
MdLineFragment::OrderedListBullet {
indent: 0,
number: 2,
is_first_line: true
},
MdLineFragment::Plain("ol2"),
],],
BulletKind::Ordered(2),
0,
)),
MdBlock::SmartList((
list![list![
MdLineFragment::UnorderedListBullet {
indent: 0,
is_first_line: true
},
MdLineFragment::Checkbox(false),
MdLineFragment::Plain(" todo"),
],],
BulletKind::Unordered,
0,
)),
MdBlock::SmartList((
list![list![
MdLineFragment::UnorderedListBullet {
indent: 0,
is_first_line: true
},
MdLineFragment::Checkbox(true),
MdLineFragment::Plain(" done"),
],],
BulletKind::Unordered,
0,
)),
MdBlock::Text(list![MdLineFragment::Plain("end")]),
];
assert_eq2!(remainder, "");
let size_left = list_block.len();
let size_right = vec_block.len();
assert_eq2!(size_left, size_right);
list_block
.iter()
.zip(vec_block.iter())
.for_each(|(lhs, rhs)| assert_eq2!(lhs, rhs));
}
#[test]
fn test_markdown_invalid() {
let input = [
"@tags: [foo, bar",
"",
"```rs",
"let a=1;",
"```",
"",
"*italic* **bold** [link](https://example.com)",
"",
"`inline code`",
]
.join("\n");
let (remainder, blocks) = parse_markdown(&input).unwrap();
assert_eq2!(remainder, "");
assert_eq2!(blocks.len(), 7);
}
}