1use std::collections::BTreeMap;
2
3use nom::{
4 branch::alt,
5 bytes::complete::tag,
6 character::complete::{line_ending, multispace0, not_line_ending},
7 combinator::opt,
8 multi::{many0, many1, separated_list0},
9 sequence::{delimited, pair, preceded, separated_pair, terminated},
10 IResult,
11};
12
13use crate::parser::{without_chars, without_chars_and_line_ending};
14
15use super::{Attribute, AttributeOrNote, Conf, Section};
16
17pub fn custom_conf<'a: 'b, 'b: 'c, 'c>(
18 note_starting: &'a str,
19) -> impl FnMut(&'b str) -> IResult<&'b str, Conf> + 'c {
20 move |input: &'b str| {
21 let (input, mut sections) = many0(custom_section(note_starting))(input)?;
22 Ok((input, {
23 let mut map = BTreeMap::new();
24 sections.drain(..).for_each(|(name, section)| {
25 map.insert(name, section);
26 });
27 map
28 }))
29 }
30}
31
32pub fn conf(input: &str) -> IResult<&str, Conf> {
33 custom_conf("#")(input)
34}
35
36pub fn custom_section<'a: 'b, 'b: 'c, 'c>(
37 note_starting: &'a str,
38) -> impl FnMut(&'b str) -> IResult<&'b str, Section> + 'c {
39 move |input: &'b str| {
40 let (input, name) = preceded(
41 multispace0,
42 terminated(
43 delimited(
44 tag("["),
45 without_chars_and_line_ending(("]".to_owned() + note_starting).as_str()),
46 tag("]"),
47 ),
48 opt(pair(tag(note_starting), not_line_ending)),
49 ),
50 )(input)?;
51 let (input, mut attributes) = preceded(
52 multispace0,
53 separated_list0(many1(line_ending), custom_attribute_or_note(note_starting)),
54 )(input)?;
55 Ok((input, {
56 let mut map = BTreeMap::new();
57 attributes
58 .drain(..)
59 .filter_map(|x| if let Some(x) = x { Some(x) } else { None })
60 .for_each(|(k, v)| {
61 map.insert(k, v);
62 });
63 (name.to_owned(), map)
64 }))
65 }
66}
67
68pub fn section(input: &str) -> IResult<&str, Section> {
69 custom_section(";")(input)
70}
71
72pub fn attribute(input: &str) -> IResult<&str, AttributeOrNote> {
73 let (input, (s1, s2)) = separated_pair(
74 without_chars("[]:"),
75 tag(":"),
76 without_chars_and_line_ending("[]"),
77 )(input)?;
78 Ok((
79 input,
80 AttributeOrNote::Attribute((s1.to_owned(), s2.to_owned())),
81 ))
82}
83
84pub fn custom_attribute_with_note<'a: 'b, 'b: 'c, 'c>(
85 note_starting: &'a str,
86) -> impl FnMut(&'b str) -> IResult<&'b str, AttributeOrNote> + 'c {
87 move |input: &'b str| {
88 let (input, ((k, v), note)) = separated_pair(
89 separated_pair(
90 without_chars_and_line_ending(("[]:".to_owned() + note_starting).as_str()),
91 tag("="),
92 without_chars_and_line_ending(("[]".to_owned() + note_starting).as_str()),
93 ),
94 tag(note_starting),
95 not_line_ending,
96 )(input)?;
97 Ok((
98 input,
99 AttributeOrNote::AttributeWithNote {
100 attribute: (k.to_owned(), v.to_owned()),
101 note: note.to_owned(),
102 },
103 ))
104 }
105}
106
107pub fn attribute_with_note(input: &str) -> IResult<&str, AttributeOrNote> {
108 custom_attribute_with_note("#")(input)
109}
110
111pub fn custom_note<'a: 'b, 'b: 'c, 'c>(
112 note_starting: &'a str,
113) -> impl FnMut(&'b str) -> IResult<&'b str, AttributeOrNote> + 'c {
114 move |input: &'b str| {
115 let (input, s) =
116 preceded(multispace0, preceded(tag(note_starting), not_line_ending))(input)?;
117 Ok((input, AttributeOrNote::Note(s.to_owned())))
118 }
119}
120
121pub fn note(input: &str) -> IResult<&str, AttributeOrNote> {
122 custom_note("#")(input)
123}
124
125pub fn custom_attribute_or_note<'a: 'b, 'b: 'c, 'c>(
126 note_starting: &'a str,
127) -> impl FnMut(&'b str) -> IResult<&str, Option<Attribute>> + 'c {
128 move |input: &'b str| {
129 let (input, may_be_attribute) = alt((
130 custom_note(note_starting),
131 custom_attribute_with_note(note_starting),
132 attribute,
133 ))(input)?;
134 Ok((
135 input,
136 match may_be_attribute {
137 AttributeOrNote::AttributeWithNote { attribute, .. } => Some(attribute),
138 AttributeOrNote::Attribute(attribute) => Some(attribute),
139 AttributeOrNote::Note(_) => None,
140 },
141 ))
142 }
143}
144
145pub fn attribute_or_note(input: &str) -> IResult<&str, Option<Attribute>> {
146 custom_attribute_or_note("#")(input)
147}