raw
1use nom::{
2 IResult, Parser,
3 branch::alt,
4 bytes::complete::tag,
5 character::complete::{char, none_of},
6 combinator::{opt, recognize, value, verify},
7 multi::{many_m_n, many1},
8 sequence::preceded,
9};
10
11use crate::{
12 ast::Callout,
13 parser::util::{line_terminated, not_eof_or_eol0, not_eof_or_eol1},
14};
15
16pub(super) fn callout<'a>() -> impl FnMut(&'a str) -> IResult<&'a str, Callout> {
17 move |input: &'a str| {
18 let prefix = || preceded(many_m_n(0, 3, char(' ')), char('>'));
19
20 let (input, (level, kind, title)) =
21 preceded(prefix(), line_terminated(callout_header)).parse(input)?;
22
23 let (input, lines) =
24 many1(preceded(prefix(), line_terminated(not_eof_or_eol0))).parse(input)?;
25 let inner = lines.join("\n");
26
27 let (_, inner) = many1(crate::parser::blocks::block())
28 .parse(&inner)
29 .map_err(|err| err.map_input(|_| input))?;
30
31 let callout = Callout {
32 level,
33 title,
34 foldable: kind.is_some(),
35 open: kind.unwrap_or(true),
36 blocks: inner.into_iter().flatten().collect(),
37 };
38
39 Ok((input, callout))
40 }
41}
42
43fn callout_header(input: &str) -> IResult<&str, (String, Option<bool>, Option<String>)> {
44 let (input, _) = many_m_n(0, 3, char(' ')).parse(input)?;
45 let (input, _) = tag("[!").parse(input)?;
46 let (input, level) = recognize(many1(verify(none_of("]"), |c| *c != ']'))).parse(input)?;
47 let (input, _) = tag("]").parse(input)?;
48 let (input, kind) = opt(alt((value(true, tag("+")), value(false, tag("-"))))).parse(input)?;
49 let (input, title) = opt(callout_title).parse(input)?;
50
51 Ok((input, (level.to_owned(), kind, title)))
52}
53
54fn callout_title(input: &str) -> IResult<&str, String> {
55 let (input, _) = many_m_n(0, 3, char(' ')).parse(input)?;
56 let (input, label) = not_eof_or_eol1.parse(input)?;
57
58 Ok((input, label.to_owned()))
59}
60