1use nom::{character::complete::*, multi::many1_count, IResult};
2use nom_supreme::{
3 error::ErrorTree,
4 final_parser::{final_parser, Location},
5};
6use std::fmt;
7
8pub mod headings;
9pub mod paragraphs;
10pub mod thematic_breaks;
11
12pub use headings::{atx_heading, ATXHeading};
13pub use paragraphs::{paragraph, Paragraph};
14pub use thematic_breaks::{thematic_break, ThematicBreak};
15
16#[derive(Debug, PartialEq, Eq, Copy, Clone)]
19pub enum MdxAst<'a> {
20 ATXHeading(ATXHeading<'a>),
21 ThematicBreak(ThematicBreak),
22 Paragraph(Paragraph<'a>),
23}
24impl<'a> fmt::Display for MdxAst<'a> {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 match self {
27 MdxAst::ATXHeading(atx) => write!(f, "{}", atx),
28 MdxAst::ThematicBreak(brk) => write!(f, "{}", brk),
29 MdxAst::Paragraph(para) => write!(f, "{}", para),
30 }
31 }
33}
34
35pub fn mdx_elements(input: &str) -> Result<Vec<MdxAst>, ErrorTree<Location>> {
36 final_parser(mdx_elements_internal)(input)
37}
38fn mdx_elements_internal(input: &str) -> IResult<&str, Vec<MdxAst>, ErrorTree<&str>> {
39 let (input, _) = multispace0(input)?;
40 let (input, result) = nom::multi::separated_list1(many1_count(newline), mdx_ast)(input)?;
41 let (input, _) = multispace0(input)?;
42 let (input, _) = nom::combinator::eof(input)?;
43 Ok((input, result))
44}
45
46fn mdx_ast(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
47 nom::branch::alt((ast_atx_heading, ast_thematic_break, ast_paragraph))(input)
48}
49
50fn ast_atx_heading(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
52 let (input, atx) = atx_heading(input)?;
53 Ok((input, MdxAst::ATXHeading(atx)))
54}
55
56fn ast_thematic_break(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
57 let (input, thematic_break) = thematic_break(input)?;
58 Ok((input, MdxAst::ThematicBreak(thematic_break)))
59}
60
61fn ast_paragraph(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
62 let (input, paragraph) = paragraph(input)?;
63 Ok((input, MdxAst::Paragraph(paragraph)))
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn parse_heading() {
72 assert_eq!(
73 mdx_ast("# boop").unwrap(),
74 (
75 "",
76 MdxAst::ATXHeading(ATXHeading {
77 level: 1,
78 value: "boop"
79 }),
80 )
81 );
82 }
83 #[test]
84 fn parse_thematic_break() {
85 assert_eq!(
86 mdx_ast("---").unwrap(),
87 (
88 "",
89 MdxAst::ThematicBreak(ThematicBreak {
90 char_count: 3,
91 break_char: '-'
92 }),
93 )
94 );
95 }
96}
97
98#[cfg(test)]
99mod tests_2 {
100 use super::*;
101
102 #[test]
103 fn parse_headings() {
107 assert_eq!(
108 mdx_elements(
109 "
110# boop
111
112
113## boop
114
115"
116 )
117 .unwrap(),
118 vec![
119 MdxAst::ATXHeading(ATXHeading {
120 level: 1,
121 value: "boop"
122 }),
123 MdxAst::ATXHeading(ATXHeading {
124 level: 2,
125 value: "boop"
126 }),
127 ]
128 );
129 }
130}