use crate::grammar::shared::Span;
use nom::{
branch::alt,
bytes::complete::take_while,
character::complete::line_ending,
combinator::{eof, recognize},
IResult, Input, Parser,
};
pub fn thematic_break(input: Span) -> IResult<Span, Span> {
log::debug!("Parsing thematic break: {:?}", input.fragment());
let start = input;
let (input, leading_spaces) = take_while(|c| c == ' ').parse(input)?;
if leading_spaces.fragment().len() > 3 {
return Err(nom::Err::Error(nom::error::Error::new(
start,
nom::error::ErrorKind::Tag,
)));
}
let (input, first_char) = nom::character::complete::one_of("-*_")(input)?;
let mut remaining = input;
let mut char_count = 1;
loop {
let (input_after_space, _) = take_while(|c| c == ' ' || c == '\t').parse(remaining)?;
if let Ok((input_after_char, _matched_char)) = nom::character::complete::char::<
_,
nom::error::Error<Span>,
>(first_char)(input_after_space)
{
char_count += 1;
remaining = input_after_char;
} else {
remaining = input_after_space;
break;
}
}
if char_count < 3 {
return Err(nom::Err::Error(nom::error::Error::new(
start,
nom::error::ErrorKind::Tag,
)));
}
let (remaining, _) = take_while(|c| c == ' ' || c == '\t').parse(remaining)?;
let (remaining, _) = alt((recognize(line_ending), recognize(eof))).parse(remaining)?;
log::debug!(
"Thematic break parsed: {} matching '{}' chars",
char_count,
first_char
);
let break_len = remaining
.location_offset()
.saturating_sub(start.location_offset());
let break_span = start.take(break_len);
Ok((remaining, break_span))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke_test_thematic_break_hyphens() {
let input = Span::new("---\n");
let result = thematic_break(input);
assert!(result.is_ok());
let (remaining, _) = result.unwrap();
assert_eq!(*remaining.fragment(), "");
}
#[test]
fn smoke_test_thematic_break_asterisks() {
let input = Span::new("***\n");
let result = thematic_break(input);
assert!(result.is_ok());
let (remaining, _) = result.unwrap();
assert_eq!(*remaining.fragment(), "");
}
#[test]
fn smoke_test_thematic_break_underscores() {
let input = Span::new("___\n");
let result = thematic_break(input);
assert!(result.is_ok());
let (remaining, _) = result.unwrap();
assert_eq!(*remaining.fragment(), "");
}
#[test]
fn smoke_test_thematic_break_with_spaces() {
let input = Span::new("- - -\n");
let result = thematic_break(input);
assert!(result.is_ok());
let (remaining, _) = result.unwrap();
assert_eq!(*remaining.fragment(), "");
}
#[test]
fn smoke_test_thematic_break_many_chars() {
let input = Span::new("----------\n");
let result = thematic_break(input);
assert!(result.is_ok());
}
#[test]
fn smoke_test_thematic_break_leading_spaces() {
let input = Span::new(" ***\n");
let result = thematic_break(input);
assert!(result.is_ok());
}
#[test]
fn smoke_test_thematic_break_two_chars_fails() {
let input = Span::new("--\n");
let result = thematic_break(input);
assert!(result.is_err());
}
#[test]
fn smoke_test_thematic_break_mixed_chars_fails() {
let input = Span::new("-*-\n");
let result = thematic_break(input);
assert!(result.is_err());
}
}