1mod error;
4mod item;
5
6#[cfg(test)]
7mod test;
8
9use nom::{
10 branch::alt,
11 bytes::complete::{escaped, is_not, tag, take_while1, take_while_m_n},
12 character::complete::{char, one_of},
13 combinator::{eof, map, map_res, verify},
14 multi::{many0, many_till, separated_list1},
15 sequence::tuple,
16 Err as NomErr, IResult,
17};
18use nom_locate::LocatedSpan;
19
20pub use error::{Error, ErrorKind};
21pub use item::{Item, ItemC, ItemG};
22
23pub type LSpan<'a> = LocatedSpan<&'a str, usize>;
25
26type ParseResult<'a, O = LSpan<'a>> = IResult<LSpan<'a>, O, Error<'a>>;
27
28fn force_failure<E>(err: NomErr<E>) -> NomErr<E> {
29 match err {
30 e @ (NomErr::Incomplete(_) | NomErr::Failure(_)) => e,
31 NomErr::Error(e) => NomErr::Failure(e),
32 }
33}
34
35fn one_tag(s: LSpan) -> ParseResult {
36 take_while1(|c: char| c.is_alphanumeric() || c == ':' || c == '+' || c == '-')(s)
37}
38
39fn tag_list(s: LSpan) -> ParseResult<Vec<LSpan>> {
40 separated_list1(char(','), one_tag)(s)
41}
42
43fn element_inner_space(s: LSpan) -> ParseResult<char> {
44 char(' ')(s)
45}
46
47fn element_end(s: LSpan) -> ParseResult {
48 tag(">")(s).map_err(|e: NomErr<Error>| e.map(|e| e.attach(ErrorKind::ElementNotClose)))
49}
50
51fn element(s: LSpan) -> ParseResult<Item> {
52 let input = s;
53
54 let (s, (_, tags, _)) = tuple((char('<'), tag_list, element_inner_space))(s)?;
55 let (s, (parts, _)) = tuple((items, element_end))(s)
56 .map_err(|e: NomErr<Error>| {
57 e.map(|mut e| {
58 if e.kind() == Some(ErrorKind::ElementNotClose) {
59 e.span = input;
60 }
61 e
62 })
63 })
64 .map_err(force_failure)?;
65
66 Ok((s, Item::Element(tags, parts)))
67}
68
69fn plain_text_normal(s: LSpan) -> ParseResult {
70 is_not("<>\\")(s)
71}
72
73fn escapable_char(s: LSpan) -> ParseResult<char> {
74 one_of("<>\\")(s).map_err(|e: NomErr<Error>| e.map(|e| e.attach(ErrorKind::UnescapableChar)))
75}
76
77fn plain_text(s: LSpan) -> ParseResult {
78 verify(escaped(plain_text_normal, '\\', escapable_char), |ls| !ls.is_empty())(s).map_err(|e| {
79 e.map(|mut e| {
80 use nom::Slice;
81
82 if !s.is_empty()
83 && e.kind().map_or(true, |x| x == ErrorKind::UnescapedChar)
84 && !s.starts_with(['\\', '<', '>'])
85 {
86 e.span = e.span.slice(e.span.len() - 1..);
87 }
88
89 e.attach(ErrorKind::UnescapedChar)
90 })
91 })
92}
93
94fn item(s: LSpan) -> ParseResult<Item> {
95 alt((element, map(plain_text, Item::PlainText)))(s)
96}
97
98fn items(s: LSpan) -> ParseResult<Vec<Item>> {
99 many0(item)(s)
100}
101
102fn output(s: LSpan) -> ParseResult<Vec<Item>> {
103 let (s, result) = many_till(item, eof)(s)?;
104 Ok((s, result.0))
105}
106
107fn parse_line((line, s): (usize, &str)) -> Result<Vec<Item>, Error> {
108 let located = LSpan::new_extra(s, line + 1);
109
110 match output(located) {
111 Ok((_, result)) => Ok(result),
112 Err(NomErr::Error(e) | NomErr::Failure(e)) => Err(e),
113 Err(NomErr::Incomplete(_)) => unreachable!("Parser is not streaming, so incomplete state shouldn't appear"),
114 }
115}
116
117pub fn parse(s: &str) -> Result<Vec<Vec<Item>>, Error> {
123 s.lines()
124 .enumerate()
125 .map(parse_line)
126 .collect::<Result<Vec<Vec<Item>>, Error>>()
127}
128
129fn hex_color_part(s: &str) -> IResult<&str, u8> {
130 map_res(take_while_m_n(2, 2, |c: char| c.is_ascii_hexdigit()), |x| {
131 u8::from_str_radix(x, 16)
132 })(s)
133}
134
135#[must_use]
137pub fn hex_rgb(s: &str) -> Option<(u8, u8, u8)> {
138 let (s, (r, g, b)) = tuple((hex_color_part, hex_color_part, hex_color_part))(s).ok()?;
139
140 if !s.is_empty() {
141 return None;
142 }
143
144 Some((r, g, b))
145}