use nom::{branch::alt,
bytes::complete::{tag, take_while1},
character::complete::anychar,
combinator::{map, not, opt, recognize},
multi::many1,
sequence::{preceded, terminated},
IResult,
Parser};
use crate::{md_parser::constants::{self, NEW_LINE},
HeadingData,
HeadingLevel};
#[rustfmt::skip]
pub fn parse_block_heading_opt_eol(input: &str) -> IResult<&str, HeadingData<'_>> {
let (remainder, output) = parse(input)?;
Ok((remainder, output))
}
#[rustfmt::skip]
fn parse(input: &str) -> IResult<&str, HeadingData<'_>> {
let (input, (level, text, _)) = (
parse_heading_tag,
parse_anychar_in_heading_no_new_line,
opt(tag(NEW_LINE)),
)
.parse(input)?;
Ok((input, HeadingData { heading_level: level, text }))
}
#[rustfmt::skip]
pub fn parse_anychar_in_heading_no_new_line(input: &str) -> IResult<&str, &str> {
recognize(
many1(
preceded(
not(
alt((
tag(NEW_LINE),
))
),
anychar,
)
)
).parse(input)
}
#[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())
).parse(input)
}
#[cfg(test)]
mod tests {
use nom::{error::{Error, ErrorKind},
Err as NomErr};
use super::*;
use crate::assert_eq2;
#[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
}))
);
assert_eq2!(parse_heading_tag("####### "), Ok(("", 7.into())));
}
#[test]
fn test_parse_header() {
assert_eq2!(
parse_block_heading_opt_eol("# h1\n"),
Ok((
"",
HeadingData {
heading_level: 1.into(),
text: "h1",
}
))
);
assert_eq2!(
parse_block_heading_opt_eol("## h2\n"),
Ok((
"",
HeadingData {
heading_level: 2.into(),
text: "h2",
}
))
);
assert_eq2!(
parse_block_heading_opt_eol("### h3\n"),
Ok((
"",
HeadingData {
heading_level: 3.into(),
text: " h3",
}
))
);
assert_eq2!(
parse_block_heading_opt_eol("### h3 *foo* **bar**\n"),
Ok((
"",
HeadingData {
heading_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_new_line() {
assert_eq2!(
parse_block_heading_opt_eol("# \n"),
Err(NomErr::Error(Error {
input: "\n",
code: ErrorKind::Not
}))
);
}
#[test]
fn test_parse_header_with_no_new_line() {
assert_eq2!(
parse_block_heading_opt_eol("# test"),
Ok((
"",
HeadingData {
heading_level: 1.into(),
text: "test",
}
))
);
}
}