use constants::*;
use crossterm::style::Stylize;
use nom::{branch::*,
bytes::complete::*,
character::complete::*,
combinator::*,
error::ErrorKind,
multi::*,
sequence::*,
IResult};
use r3bl_rs_utils_core::{call_if_true, log_debug};
use crate::*;
pub fn parse_element_starts_with_star(input: &str) -> IResult<&str, &str> {
let it = parse_fenced_no_newline(input, BOLD);
it
}
pub fn parse_element_starts_with_underscore(input: &str) -> IResult<&str, &str> {
let it = parse_fenced_no_newline(input, ITALIC);
it
}
#[rustfmt::skip]
pub fn parse_element_starts_with_backtick(input: &str) -> IResult<&str, &str> {
delimited(
tag(BACK_TICK),
is_not(BACK_TICK),
tag(BACK_TICK)
)(input)
}
#[rustfmt::skip]
pub fn parse_element_starts_with_left_bracket(input: &str) -> IResult<&str, HyperlinkData<'_>> {
let (input, output) = pair(
delimited(
tag(LEFT_BRACKET),
is_not(RIGHT_BRACKET),
tag(RIGHT_BRACKET)
),
delimited(
tag(LEFT_PARENTHESIS),
is_not(RIGHT_PARENTHESIS),
tag(RIGHT_PARENTHESIS)
),
)(input)?;
Ok((input, HyperlinkData::from(output)))
}
#[rustfmt::skip]
pub fn parse_element_checkbox_into_str(input: &str) -> IResult<&str, &str> {
alt((
recognize(tag(CHECKED)),
recognize(tag(UNCHECKED))
))(input)
}
#[rustfmt::skip]
pub fn parse_element_checkbox_into_bool(input: &str) -> IResult<&str, bool> {
alt((
map(tag(CHECKED), |_| true),
map(tag(UNCHECKED), |_| false),
))(input)
}
#[rustfmt::skip]
pub fn parse_element_starts_with_left_image(input: &str) -> IResult<&str, HyperlinkData<'_>> {
let (input, output) =pair(
delimited( tag(LEFT_IMAGE), is_not(RIGHT_IMAGE), tag(RIGHT_IMAGE)),
delimited( tag(LEFT_PARENTHESIS), is_not(RIGHT_PARENTHESIS), tag(RIGHT_PARENTHESIS)),
)(input)?;
Ok((input, HyperlinkData::from(output)))
}
#[rustfmt::skip]
pub fn parse_plain_text_no_new_line1(input: &str) -> IResult<&str, &str> {
if input.starts_with(ITALIC)
|| input.starts_with(BOLD)
|| input.starts_with(BACK_TICK)
|| input.starts_with(LEFT_BRACKET)
|| input.starts_with(LEFT_IMAGE)
{
return take_till1(|c: char|
c == '\n'
)(input);
}
recognize(
many1(
preceded(
not(
alt((
tag(BOLD),
tag(ITALIC),
tag(BACK_TICK),
tag(LEFT_BRACKET),
tag(LEFT_IMAGE),
tag(NEW_LINE),
))
),
anychar,
)
)
)(input)
}
#[rustfmt::skip]
pub fn parse_anychar_in_heading_no_new_line1(input: &str) -> IResult<&str, &str> {
recognize(
many1(
preceded(
not(
alt((
tag(NEW_LINE),
))
),
anychar,
)
)
)(input)
}
#[rustfmt::skip]
pub fn parse_element_markdown_inline(
input: &str,
checkbox_policy: CheckboxParsePolicy,
) -> IResult<&str, MdLineFragment<'_>> {
let it = match checkbox_policy {
CheckboxParsePolicy::IgnoreCheckbox => alt((
map(parse_element_starts_with_underscore, MdLineFragment::Italic),
map(parse_element_starts_with_star, MdLineFragment::Bold),
map(parse_element_starts_with_backtick, MdLineFragment::InlineCode),
map(parse_element_starts_with_left_image, MdLineFragment::Image),
map(parse_element_starts_with_left_bracket, MdLineFragment::Link),
map(parse_element_checkbox_into_str, MdLineFragment::Plain),
map(parse_plain_text_no_new_line1, MdLineFragment::Plain),
))(input),
CheckboxParsePolicy::ParseCheckbox => alt((
map(parse_element_starts_with_underscore, MdLineFragment::Italic),
map(parse_element_starts_with_star, MdLineFragment::Bold),
map(parse_element_starts_with_backtick, MdLineFragment::InlineCode),
map(parse_element_starts_with_left_image, MdLineFragment::Image),
map(parse_element_starts_with_left_bracket, MdLineFragment::Link),
map(parse_element_checkbox_into_bool, MdLineFragment::Checkbox),
map(parse_plain_text_no_new_line1, MdLineFragment::Plain),
))(input)
};
call_if_true!(DEBUG_MD_PARSER, {
log_debug(format!("\n📣📣📣\n input: {:?}", input).green().to_string());
match it {
Ok(ref element) => log_debug(format!("✅✅✅ OK {:#?}", element).magenta().to_string()),
Result::Err(ref error) => log_debug(format!("🟥🟥🟥 NO {:#?}", error)),
}
});
it
}
fn fenced<'a>(
start: &'a str,
end: &'a str,
) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str> {
map(tuple((tag(start), take_until(end), tag(end))), |x| x.1)
}
pub fn parse_fenced_no_newline<'a>(
input: &'a str,
delim: &'a str,
) -> IResult<&'a str, &'a str> {
let it = fenced(delim, delim)(input);
if let Ok((_, output)) = &it {
if output.contains(NEW_LINE) {
return Err(nom::Err::Error(nom::error::Error {
input: "",
code: ErrorKind::TakeUntil,
}));
};
}
it
}
#[cfg(test)]
mod tests_parse_element {
use nom::{error::Error, Err as NomErr};
use r3bl_rs_utils_core::assert_eq2;
use super::*;
#[test]
fn test_parse_plain_text_no_new_line1() {
assert_eq2!(
parse_plain_text_no_new_line1("this _bar"),
Ok(("_bar", "this "))
);
assert_eq2!(parse_plain_text_no_new_line1("_bar"), Ok(("", "_bar")));
assert_eq2!(parse_plain_text_no_new_line1("bar_"), Ok(("_", "bar")));
}
#[test]
fn test_parse_fenced_no_newline() {
let input = "_foo\nbar_";
let it = parse_fenced_no_newline(input, "_");
println!("it: {:?}", it);
assert_eq2!(
it,
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::TakeUntil
}))
);
let input = "_foo bar_";
let it = parse_fenced_no_newline(input, "_");
println!("it: {:?}", it);
assert_eq2!(it, Ok(("", "foo bar")));
}
#[test]
fn test_fenced() {
let input = "_foo bar baz_";
let it = fenced("_", "_")(input);
println!("it: {:?}", it);
assert_eq2!(it, Ok(("", "foo bar baz")));
let input = "_foo bar baz";
let it = fenced("_", "_")(input);
println!("it: {:?}", it);
assert_eq2!(
it,
Err(NomErr::Error(Error {
input: "foo bar baz",
code: ErrorKind::TakeUntil
}))
);
let input = "foo _bar_ baz";
let it = fenced("_", "_")(input);
println!("it: {:?}", it);
assert_eq2!(
it,
Err(NomErr::Error(Error {
input: "foo _bar_ baz",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_element_checkbox_into_str() {
assert_eq2!(
parse_element_checkbox_into_str("[x] here is a checkbox"),
Ok((" here is a checkbox", "[x]"))
);
assert_eq2!(
parse_element_checkbox_into_str("[ ] here is a checkbox"),
Ok((" here is a checkbox", "[ ]"))
);
}
#[test]
fn test_parse_element_checkbox_into_bool() {
assert_eq2!(
parse_element_checkbox_into_bool("[x] here is a checkbox"),
Ok((" here is a checkbox", true))
);
assert_eq2!(
parse_element_checkbox_into_bool("[ ] here is a checkbox"),
Ok((" here is a checkbox", false))
);
}
#[test]
fn test_parse_element_italic() {
assert_eq2!(
parse_element_starts_with_underscore("_here is italic_"),
Ok(("", "here is italic"))
);
assert_eq2!(
parse_element_starts_with_underscore("_here is italic_"),
Ok(("", "here is italic"))
);
assert_eq2!(
parse_element_starts_with_underscore("*here is italic"),
Err(NomErr::Error(Error {
input: "*here is italic",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_underscore("here is italic*"),
Err(NomErr::Error(Error {
input: "here is italic*",
code: ErrorKind::Tag,
}))
);
assert_eq2!(
parse_element_starts_with_underscore("here is italic"),
Err(NomErr::Error(Error {
input: "here is italic",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_underscore("*"),
Err(NomErr::Error(Error {
input: "*",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_underscore("**"),
Err(NomErr::Error(Error {
input: "**",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_underscore(""),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_underscore("**we are doing bold**"),
Err(NomErr::Error(Error {
input: "**we are doing bold**",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_element_bold() {
assert_eq2!(
parse_element_starts_with_star("*here is bold*"),
Ok(("", "here is bold"))
);
assert_eq2!(
parse_element_starts_with_star("*here is bold"),
Err(NomErr::Error(Error {
input: "here is bold",
code: ErrorKind::TakeUntil
}))
);
assert_eq2!(
parse_element_starts_with_star("here is bold*"),
Err(NomErr::Error(Error {
input: "here is bold*",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_star("here is bold"),
Err(NomErr::Error(Error {
input: "here is bold",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_star("*"),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::TakeUntil
}))
);
assert_eq2!(
parse_element_starts_with_star(""),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_element_code() {
assert_eq2!(
parse_element_starts_with_backtick("`here is code"),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_backtick("here is code`"),
Err(NomErr::Error(Error {
input: "here is code`",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_element_starts_with_backtick("``"),
Err(NomErr::Error(Error {
input: "`",
code: ErrorKind::IsNot
}))
);
assert_eq2!(
parse_element_starts_with_backtick("`"),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::IsNot
}))
);
assert_eq2!(
parse_element_starts_with_backtick(""),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_element_link() {
assert_eq2!(
parse_element_starts_with_left_bracket("[title](https://www.example.com)"),
Ok(("", HyperlinkData::new("title", "https://www.example.com")))
);
assert_eq2!(
parse_element_starts_with_backtick(""),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_element_image() {
assert_eq2!(
parse_element_starts_with_left_image(""),
Ok(("", HyperlinkData::new("alt text", "image.jpg")))
);
assert_eq2!(
parse_element_starts_with_backtick(""),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_element_plaintext_unicode() {
let result = parse_plain_text_no_new_line1("- straight😃\n");
let remainder = result.as_ref().unwrap().0;
let output = result.as_ref().unwrap().1;
assert_eq2!(remainder, "\n");
assert_eq2!(output, "- straight😃");
}
#[test]
fn test_parse_element_plaintext() {
assert_eq2!(
parse_plain_text_no_new_line1("1234567890"),
Ok(("", "1234567890"))
);
assert_eq2!(
parse_plain_text_no_new_line1("oh my gosh!"),
Ok(("", "oh my gosh!"))
);
assert_eq2!(
parse_plain_text_no_new_line1("oh my gosh"),
Ok(("", "[link baby](and then somewhat)"))
);
assert_eq2!(
parse_plain_text_no_new_line1("`codeblock for bums`"),
Ok(("", "`codeblock for bums`"))
);
assert_eq2!(
parse_plain_text_no_new_line1(""),
Ok(("", ""))
);
assert_eq2!(
parse_plain_text_no_new_line1("here is plaintext"),
Ok(("", "here is plaintext"))
);
assert_eq2!(
parse_plain_text_no_new_line1("here is plaintext!"),
Ok(("", "here is plaintext!"))
);
assert_eq2!(
parse_plain_text_no_new_line1("here is plaintext"),
Ok(("", "[title](https://www.example.com)"))
);
assert_eq2!(
parse_plain_text_no_new_line1(""),
Ok(("", ""))
);
assert_eq2!(
parse_plain_text_no_new_line1(""),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Eof
}))
);
}
#[test]
fn test_parse_element_markdown_inline() {
assert_eq2!(
parse_element_markdown_inline(
"*here is bold*",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok(("", MdLineFragment::Bold("here is bold")))
);
assert_eq2!(
parse_element_markdown_inline(
"_here is italic_",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok(("", MdLineFragment::Italic("here is italic")))
);
assert_eq2!(
parse_element_markdown_inline(
"`here is code`",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok(("", MdLineFragment::InlineCode("here is code")))
);
assert_eq2!(
parse_element_markdown_inline(
"[title](https://www.example.com)",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok((
"",
(MdLineFragment::Link(HyperlinkData::new(
"title",
"https://www.example.com"
)))
))
);
assert_eq2!(
parse_element_markdown_inline(
"",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok((
"",
(MdLineFragment::Image(HyperlinkData::new("alt text", "image.jpg")))
))
);
assert_eq2!(
parse_element_markdown_inline(
"here is plaintext!",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok(("", MdLineFragment::Plain("here is plaintext!")))
);
assert_eq2!(
parse_element_markdown_inline(
"here is some plaintext *but what if we italicize?",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok((
"*but what if we italicize?",
MdLineFragment::Plain("here is some plaintext ")
))
);
assert_eq2!(
parse_element_markdown_inline(
"here is some plaintext \n*but what if we italicize?",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok((
"\n*but what if we italicize?",
MdLineFragment::Plain("here is some plaintext ")
))
);
assert_eq2!(
parse_element_markdown_inline("\n", CheckboxParsePolicy::IgnoreCheckbox),
Err(NomErr::Error(Error {
input: "\n",
code: ErrorKind::Not
}))
);
assert_eq2!(
parse_element_markdown_inline("", CheckboxParsePolicy::IgnoreCheckbox),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Eof
}))
);
assert_eq2!(
parse_element_markdown_inline(
"[ ] this is a checkbox",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok((" this is a checkbox", MdLineFragment::Plain("[ ]")))
);
assert_eq2!(
parse_element_markdown_inline(
"[x] this is a checkbox",
CheckboxParsePolicy::IgnoreCheckbox
),
Ok((" this is a checkbox", MdLineFragment::Plain("[x]")))
);
assert_eq2!(
parse_element_markdown_inline(
"[ ] this is a checkbox",
CheckboxParsePolicy::ParseCheckbox
),
Ok((" this is a checkbox", MdLineFragment::Checkbox(false)))
);
assert_eq2!(
parse_element_markdown_inline(
"[x] this is a checkbox",
CheckboxParsePolicy::ParseCheckbox
),
Ok((" this is a checkbox", MdLineFragment::Checkbox(true)))
);
}
}