use nom::{branch::alt,
bytes::complete::*,
character::complete::anychar,
combinator::*,
multi::many1,
sequence::*,
IResult};
use crate::{constants::NEW_LINE, *};
#[rustfmt::skip]
pub fn parse_block_heading_opt_eol(input: &str) -> IResult<&str, HeadingData> {
let (remainder, output) = parse(input)?;
if output.text.starts_with(NEW_LINE) {
if let Some(stripped) = output.text.strip_prefix(NEW_LINE) {
return Ok((stripped, HeadingData {
level: output.level,
text: "",
}));
}
}
let (remainder, _) = opt(tag(NEW_LINE))(remainder)?;
Ok((remainder, output))
}
#[rustfmt::skip]
fn parse(input: &str) -> IResult<&str, HeadingData> {
let (input, (level, text)) = tuple((
parse_heading_tag,
alt((
is_not(NEW_LINE),
recognize(many1(anychar)),
))
))(input)?;
Ok((input, HeadingData { level, text }))
}
#[rustfmt::skip]
fn parse_heading_tag(input: &str) -> IResult<&str, HeadingLevel> {
map(
terminated(
take_while1(|it| it == constants::HEADING_CHAR),
tag(constants::SPACE),
),
|it: &str| HeadingLevel::from(it.len()),
)(input)
}
#[cfg(test)]
mod tests {
use nom::{error::{Error, ErrorKind},
Err as NomErr};
use r3bl_rs_utils_core::assert_eq2;
use super::*;
#[test]
fn test_parse_header_tag() {
assert_eq2!(parse_heading_tag("# "), Ok(("", 1.into())));
assert_eq2!(parse_heading_tag("### "), Ok(("", 3.into())));
assert_eq2!(parse_heading_tag("# h1"), Ok(("h1", 1.into())));
assert_eq2!(parse_heading_tag("# h1"), Ok(("h1", 1.into())));
assert_eq2!(
parse_heading_tag(" "),
Err(NomErr::Error(Error {
input: " ",
code: ErrorKind::TakeWhile1
}))
);
assert_eq2!(
parse_heading_tag("#"),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_header() {
assert_eq2!(
parse_block_heading_opt_eol("# h1\n"),
Ok((
"",
HeadingData {
level: 1.into(),
text: "h1",
}
))
);
assert_eq2!(
parse_block_heading_opt_eol("## h2\n"),
Ok((
"",
HeadingData {
level: 2.into(),
text: "h2",
}
))
);
assert_eq2!(
parse_block_heading_opt_eol("### h3\n"),
Ok((
"",
HeadingData {
level: 3.into(),
text: " h3",
}
))
);
assert_eq2!(
parse_block_heading_opt_eol("### h3 *foo* **bar**\n"),
Ok((
"",
HeadingData {
level: 3.into(),
text: "h3 *foo* **bar**",
}
))
);
assert_eq2!(
parse_block_heading_opt_eol("###h3"),
Err(NomErr::Error(Error {
input: "h3",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_block_heading_opt_eol("###"),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
assert_eq2!(
parse_block_heading_opt_eol(""),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::TakeWhile1
}))
);
assert_eq2!(
parse_block_heading_opt_eol("#"),
Err(NomErr::Error(Error {
input: "",
code: ErrorKind::Tag
}))
);
}
#[test]
fn test_parse_header_with_newline() {
assert_eq2!(
parse_block_heading_opt_eol("# \n"),
Ok((
"",
HeadingData {
level: 1.into(),
text: "",
}
))
);
}
#[test]
fn test_parse_header_with_no_newline() {
assert_eq2!(
parse_block_heading_opt_eol("# test"),
Ok((
"",
HeadingData {
level: 1.into(),
text: "test",
}
))
);
}
}