1 use nom::{
2 bytes::complete::take_while,
3 character::complete::{
4 alphanumeric1 as alphanumeric, char, multispace0 as multispace, space0 as space,
5 },
6 combinator::{map, map_res, opt},
7 multi::many0,
8 sequence::{delimited, pair, separated_pair, terminated, tuple},
9 IResult,
10 };
11
12 use std::collections::HashMap;
13 use std::str;
14
category(i: &[u8]) -> IResult<&[u8], &str>15 fn category(i: &[u8]) -> IResult<&[u8], &str> {
16 map_res(
17 delimited(char('['), take_while(|c| c != b']'), char(']')),
18 str::from_utf8,
19 )(i)
20 }
21
key_value(i: &[u8]) -> IResult<&[u8], (&str, &str)>22 fn key_value(i: &[u8]) -> IResult<&[u8], (&str, &str)> {
23 let (i, key) = map_res(alphanumeric, str::from_utf8)(i)?;
24 let (i, _) = tuple((opt(space), char('='), opt(space)))(i)?;
25 let (i, val) = map_res(take_while(|c| c != b'\n' && c != b';'), str::from_utf8)(i)?;
26 let (i, _) = opt(pair(char(';'), take_while(|c| c != b'\n')))(i)?;
27 Ok((i, (key, val)))
28 }
29
keys_and_values(i: &[u8]) -> IResult<&[u8], HashMap<&str, &str>>30 fn keys_and_values(i: &[u8]) -> IResult<&[u8], HashMap<&str, &str>> {
31 map(many0(terminated(key_value, opt(multispace))), |vec| {
32 vec.into_iter().collect()
33 })(i)
34 }
35
category_and_keys(i: &[u8]) -> IResult<&[u8], (&str, HashMap<&str, &str>)>36 fn category_and_keys(i: &[u8]) -> IResult<&[u8], (&str, HashMap<&str, &str>)> {
37 let (i, category) = terminated(category, opt(multispace))(i)?;
38 let (i, keys) = keys_and_values(i)?;
39 Ok((i, (category, keys)))
40 }
41
categories(i: &[u8]) -> IResult<&[u8], HashMap<&str, HashMap<&str, &str>>>42 fn categories(i: &[u8]) -> IResult<&[u8], HashMap<&str, HashMap<&str, &str>>> {
43 map(
44 many0(separated_pair(
45 category,
46 opt(multispace),
47 map(
48 many0(terminated(key_value, opt(multispace))),
49 |vec: Vec<_>| vec.into_iter().collect(),
50 ),
51 )),
52 |vec: Vec<_>| vec.into_iter().collect(),
53 )(i)
54 }
55
56 #[test]
parse_category_test()57 fn parse_category_test() {
58 let ini_file = &b"[category]
59
60 parameter=value
61 key = value2"[..];
62
63 let ini_without_category = &b"\n\nparameter=value
64 key = value2"[..];
65
66 let res = category(ini_file);
67 println!("{:?}", res);
68 match res {
69 Ok((i, o)) => println!("i: {:?} | o: {:?}", str::from_utf8(i), o),
70 _ => println!("error"),
71 }
72
73 assert_eq!(res, Ok((ini_without_category, "category")));
74 }
75
76 #[test]
parse_key_value_test()77 fn parse_key_value_test() {
78 let ini_file = &b"parameter=value
79 key = value2"[..];
80
81 let ini_without_key_value = &b"\nkey = value2"[..];
82
83 let res = key_value(ini_file);
84 println!("{:?}", res);
85 match res {
86 Ok((i, (o1, o2))) => println!("i: {:?} | o: ({:?},{:?})", str::from_utf8(i), o1, o2),
87 _ => println!("error"),
88 }
89
90 assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
91 }
92
93 #[test]
parse_key_value_with_space_test()94 fn parse_key_value_with_space_test() {
95 let ini_file = &b"parameter = value
96 key = value2"[..];
97
98 let ini_without_key_value = &b"\nkey = value2"[..];
99
100 let res = key_value(ini_file);
101 println!("{:?}", res);
102 match res {
103 Ok((i, (o1, o2))) => println!("i: {:?} | o: ({:?},{:?})", str::from_utf8(i), o1, o2),
104 _ => println!("error"),
105 }
106
107 assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
108 }
109
110 #[test]
parse_key_value_with_comment_test()111 fn parse_key_value_with_comment_test() {
112 let ini_file = &b"parameter=value;abc
113 key = value2"[..];
114
115 let ini_without_key_value = &b"\nkey = value2"[..];
116
117 let res = key_value(ini_file);
118 println!("{:?}", res);
119 match res {
120 Ok((i, (o1, o2))) => println!("i: {:?} | o: ({:?},{:?})", str::from_utf8(i), o1, o2),
121 _ => println!("error"),
122 }
123
124 assert_eq!(res, Ok((ini_without_key_value, ("parameter", "value"))));
125 }
126
127 #[test]
parse_multiple_keys_and_values_test()128 fn parse_multiple_keys_and_values_test() {
129 let ini_file = &b"parameter=value;abc
130
131 key = value2
132
133 [category]"[..];
134
135 let ini_without_key_value = &b"[category]"[..];
136
137 let res = keys_and_values(ini_file);
138 println!("{:?}", res);
139 match res {
140 Ok((i, ref o)) => println!("i: {:?} | o: {:?}", str::from_utf8(i), o),
141 _ => println!("error"),
142 }
143
144 let mut expected: HashMap<&str, &str> = HashMap::new();
145 expected.insert("parameter", "value");
146 expected.insert("key", "value2");
147 assert_eq!(res, Ok((ini_without_key_value, expected)));
148 }
149
150 #[test]
parse_category_then_multiple_keys_and_values_test()151 fn parse_category_then_multiple_keys_and_values_test() {
152 //FIXME: there can be an empty line or a comment line after a category
153 let ini_file = &b"[abcd]
154 parameter=value;abc
155
156 key = value2
157
158 [category]"[..];
159
160 let ini_after_parser = &b"[category]"[..];
161
162 let res = category_and_keys(ini_file);
163 println!("{:?}", res);
164 match res {
165 Ok((i, ref o)) => println!("i: {:?} | o: {:?}", str::from_utf8(i), o),
166 _ => println!("error"),
167 }
168
169 let mut expected_h: HashMap<&str, &str> = HashMap::new();
170 expected_h.insert("parameter", "value");
171 expected_h.insert("key", "value2");
172 assert_eq!(res, Ok((ini_after_parser, ("abcd", expected_h))));
173 }
174
175 #[test]
parse_multiple_categories_test()176 fn parse_multiple_categories_test() {
177 let ini_file = &b"[abcd]
178
179 parameter=value;abc
180
181 key = value2
182
183 [category]
184 parameter3=value3
185 key4 = value4
186 "[..];
187
188 let ini_after_parser = &b""[..];
189
190 let res = categories(ini_file);
191 //println!("{:?}", res);
192 match res {
193 Ok((i, ref o)) => println!("i: {:?} | o: {:?}", str::from_utf8(i), o),
194 _ => println!("error"),
195 }
196
197 let mut expected_1: HashMap<&str, &str> = HashMap::new();
198 expected_1.insert("parameter", "value");
199 expected_1.insert("key", "value2");
200 let mut expected_2: HashMap<&str, &str> = HashMap::new();
201 expected_2.insert("parameter3", "value3");
202 expected_2.insert("key4", "value4");
203 let mut expected_h: HashMap<&str, HashMap<&str, &str>> = HashMap::new();
204 expected_h.insert("abcd", expected_1);
205 expected_h.insert("category", expected_2);
206 assert_eq!(res, Ok((ini_after_parser, expected_h)));
207 }
208