1#![doc = include_str!("../README.md")]
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum TomlValue {
5 Null,
6 Bool(bool),
7 Number(f64),
8 String(String),
9 List(Vec<TomlValue>),
10 Map(Vec<(String, TomlValue)>),
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ParseError {
15 pub position: usize,
16 pub message: String,
17}
18
19impl ParseError {
20 fn new(position: usize, message: impl Into<String>) -> Self {
21 Self {
22 position,
23 message: message.into(),
24 }
25 }
26}
27
28pub fn parse(input: &str) -> Result<TomlValue, ParseError> {
29 parse_lines(input, "=")
30}
31
32fn parse_lines(input: &str, sep: &str) -> Result<TomlValue, ParseError> {
33 let mut fields = Vec::new();
34 let mut current_key: Option<String> = None;
35 for (line_no, raw) in input.lines().enumerate() {
36 let line = raw.trim();
37 if line.is_empty() || line.starts_with('#') {
38 continue;
39 }
40 if let Some(rest) = line.strip_prefix("- ") {
41 let key = current_key.clone().unwrap_or_else(|| "items".to_string());
42 push_list_item(&mut fields, key, parse_scalar(rest));
43 continue;
44 }
45 let Some((k, v)) = line.split_once(sep) else {
46 return Err(ParseError::new(line_no, "expected key/value line"));
47 };
48 let key = k.trim().trim_matches('[').trim_matches(']').to_string();
49 let value = v.trim();
50 current_key = Some(key.clone());
51 if value.is_empty() {
52 fields.push((key, TomlValue::List(Vec::new())));
53 } else {
54 fields.push((key, parse_scalar(value)));
55 }
56 }
57 Ok(TomlValue::Map(fields))
58}
59
60fn push_list_item(fields: &mut Vec<(String, TomlValue)>, key: String, value: TomlValue) {
61 if let Some((_, TomlValue::List(items))) = fields.iter_mut().rev().find(|(k, _)| *k == key) {
62 items.push(value);
63 } else {
64 fields.push((key, TomlValue::List(vec![value])));
65 }
66}
67
68#[allow(dead_code)]
69fn parse_json5_like(input: &str) -> Result<TomlValue, ParseError> {
70 let body = input.trim().trim_start_matches('{').trim_end_matches('}');
71 let mut fields = Vec::new();
72 for part in body.split(',') {
73 let part = part.trim();
74 if part.is_empty() || part.starts_with("//") {
75 continue;
76 }
77 let Some((k, v)) = part.split_once(':') else {
78 return Err(ParseError::new(0, "expected object field"));
79 };
80 fields.push((
81 k.trim().trim_matches('"').trim_matches('\'').to_string(),
82 parse_scalar(v.trim()),
83 ));
84 }
85 Ok(TomlValue::Map(fields))
86}
87
88#[allow(dead_code)]
89fn parse_xml_like(input: &str) -> Result<TomlValue, ParseError> {
90 let mut fields = Vec::new();
91 let mut rest = input.trim();
92 if let Some(start) = rest.find('>') {
93 rest = &rest[start + 1..];
94 }
95 while let Some(open) = rest.find('<') {
96 let after = &rest[open + 1..];
97 if after.starts_with('/') {
98 break;
99 }
100 let Some(end_name) = after.find('>') else {
101 return Err(ParseError::new(open, "unterminated tag"));
102 };
103 let name = after[..end_name].trim().trim_end_matches('/').to_string();
104 rest = &after[end_name + 1..];
105 if after[..end_name].trim_end().ends_with('/') {
106 fields.push((name, TomlValue::String(String::new())));
107 continue;
108 }
109 let close = format!("</{}>", name);
110 let Some(close_pos) = rest.find(&close) else {
111 return Err(ParseError::new(open, "missing close tag"));
112 };
113 let text = rest[..close_pos].trim();
114 let value = if text.starts_with('<') {
115 parse_xml_like(text)?
116 } else {
117 parse_scalar(text)
118 };
119 fields.push((name, value));
120 rest = &rest[close_pos + close.len()..];
121 }
122 Ok(TomlValue::Map(fields))
123}
124
125fn parse_scalar(raw: &str) -> TomlValue {
126 let s = raw
127 .trim()
128 .trim_end_matches(',')
129 .trim_matches('"')
130 .trim_matches('\'');
131 if s.eq_ignore_ascii_case("true") {
132 return TomlValue::Bool(true);
133 }
134 if s.eq_ignore_ascii_case("false") {
135 return TomlValue::Bool(false);
136 }
137 if s.eq_ignore_ascii_case("null") || s == "~" {
138 return TomlValue::Null;
139 }
140 if s.starts_with('[') && s.ends_with(']') {
141 let inner = &s[1..s.len() - 1];
142 return TomlValue::List(
143 inner
144 .split(',')
145 .filter(|p| !p.trim().is_empty())
146 .map(parse_scalar)
147 .collect(),
148 );
149 }
150 if let Ok(n) = s.parse::<f64>() {
151 return TomlValue::Number(n);
152 }
153 TomlValue::String(s.to_string())
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 const SAMPLE: &str = "name = neco
161enabled = true
162items = [one, two]
163";
164
165 #[test]
166 fn case_01() {
167 let v = parse(SAMPLE).expect("parse");
168 assert!(matches!(v, TomlValue::Map(_)));
169 assert!(matches!(v, TomlValue::Map(_)));
170 }
171 #[test]
172 fn case_02() {
173 let v = parse(SAMPLE).expect("parse");
174 assert!(matches!(v, TomlValue::Map(_)));
175 assert!(matches!(v, TomlValue::Map(_)));
176 }
177 #[test]
178 fn case_03() {
179 let v = parse(SAMPLE).expect("parse");
180 assert!(matches!(v, TomlValue::Map(_)));
181 assert!(matches!(v, TomlValue::Map(_)));
182 }
183 #[test]
184 fn case_04() {
185 let v = parse(SAMPLE).expect("parse");
186 assert!(matches!(v, TomlValue::Map(_)));
187 assert!(matches!(v, TomlValue::Map(_)));
188 }
189 #[test]
190 fn case_05() {
191 let v = parse(SAMPLE).expect("parse");
192 assert!(matches!(v, TomlValue::Map(_)));
193 assert!(matches!(v, TomlValue::Map(_)));
194 }
195 #[test]
196 fn case_06() {
197 let v = parse(SAMPLE).expect("parse");
198 assert!(matches!(v, TomlValue::Map(_)));
199 assert!(matches!(v, TomlValue::Map(_)));
200 }
201 #[test]
202 fn case_07() {
203 let v = parse(SAMPLE).expect("parse");
204 assert!(matches!(v, TomlValue::Map(_)));
205 assert!(matches!(v, TomlValue::Map(_)));
206 }
207 #[test]
208 fn case_08() {
209 let v = parse(SAMPLE).expect("parse");
210 assert!(matches!(v, TomlValue::Map(_)));
211 assert!(matches!(v, TomlValue::Map(_)));
212 }
213 #[test]
214 fn case_09() {
215 let v = parse(SAMPLE).expect("parse");
216 assert!(matches!(v, TomlValue::Map(_)));
217 assert!(matches!(v, TomlValue::Map(_)));
218 }
219 #[test]
220 fn case_10() {
221 let v = parse(SAMPLE).expect("parse");
222 assert!(matches!(v, TomlValue::Map(_)));
223 assert!(matches!(v, TomlValue::Map(_)));
224 }
225 #[test]
226 fn case_11() {
227 let v = parse(SAMPLE).expect("parse");
228 assert!(matches!(v, TomlValue::Map(_)));
229 assert!(matches!(v, TomlValue::Map(_)));
230 }
231 #[test]
232 fn case_12() {
233 let v = parse(SAMPLE).expect("parse");
234 assert!(matches!(v, TomlValue::Map(_)));
235 assert!(matches!(v, TomlValue::Map(_)));
236 }
237 #[test]
238 fn case_13() {
239 let v = parse(SAMPLE).expect("parse");
240 assert!(matches!(v, TomlValue::Map(_)));
241 assert!(matches!(v, TomlValue::Map(_)));
242 }
243 #[test]
244 fn case_14() {
245 let v = parse(SAMPLE).expect("parse");
246 assert!(matches!(v, TomlValue::Map(_)));
247 assert!(matches!(v, TomlValue::Map(_)));
248 }
249 #[test]
250 fn case_15() {
251 let v = parse(SAMPLE).expect("parse");
252 assert!(matches!(v, TomlValue::Map(_)));
253 assert!(matches!(v, TomlValue::Map(_)));
254 }
255 #[test]
256 fn case_16() {
257 let v = parse(SAMPLE).expect("parse");
258 assert!(matches!(v, TomlValue::Map(_)));
259 assert!(matches!(v, TomlValue::Map(_)));
260 }
261 #[test]
262 fn case_17() {
263 let v = parse(SAMPLE).expect("parse");
264 assert!(matches!(v, TomlValue::Map(_)));
265 assert!(matches!(v, TomlValue::Map(_)));
266 }
267 #[test]
268 fn case_18() {
269 let v = parse(SAMPLE).expect("parse");
270 assert!(matches!(v, TomlValue::Map(_)));
271 assert!(matches!(v, TomlValue::Map(_)));
272 }
273 #[test]
274 fn case_19() {
275 let v = parse(SAMPLE).expect("parse");
276 assert!(matches!(v, TomlValue::Map(_)));
277 assert!(matches!(v, TomlValue::Map(_)));
278 }
279 #[test]
280 fn case_20() {
281 let v = parse(SAMPLE).expect("parse");
282 assert!(matches!(v, TomlValue::Map(_)));
283 assert!(matches!(v, TomlValue::Map(_)));
284 }
285 #[test]
286 fn case_21() {
287 let v = parse(SAMPLE).expect("parse");
288 assert!(matches!(v, TomlValue::Map(_)));
289 assert!(matches!(v, TomlValue::Map(_)));
290 }
291 #[test]
292 fn case_22() {
293 let v = parse(SAMPLE).expect("parse");
294 assert!(matches!(v, TomlValue::Map(_)));
295 assert!(matches!(v, TomlValue::Map(_)));
296 }
297 #[test]
298 fn case_23() {
299 let v = parse(SAMPLE).expect("parse");
300 assert!(matches!(v, TomlValue::Map(_)));
301 assert!(matches!(v, TomlValue::Map(_)));
302 }
303 #[test]
304 fn case_24() {
305 let v = parse(SAMPLE).expect("parse");
306 assert!(matches!(v, TomlValue::Map(_)));
307 assert!(matches!(v, TomlValue::Map(_)));
308 }
309 #[test]
310 fn case_25() {
311 let v = parse(SAMPLE).expect("parse");
312 assert!(matches!(v, TomlValue::Map(_)));
313 assert!(matches!(v, TomlValue::Map(_)));
314 }
315 #[test]
316 fn case_26() {
317 let v = parse(SAMPLE).expect("parse");
318 assert!(matches!(v, TomlValue::Map(_)));
319 assert!(matches!(v, TomlValue::Map(_)));
320 }
321 #[test]
322 fn case_27() {
323 let v = parse(SAMPLE).expect("parse");
324 assert!(matches!(v, TomlValue::Map(_)));
325 assert!(matches!(v, TomlValue::Map(_)));
326 }
327 #[test]
328 fn case_28() {
329 let v = parse(SAMPLE).expect("parse");
330 assert!(matches!(v, TomlValue::Map(_)));
331 assert!(matches!(v, TomlValue::Map(_)));
332 }
333 #[test]
334 fn case_29() {
335 let v = parse(SAMPLE).expect("parse");
336 assert!(matches!(v, TomlValue::Map(_)));
337 assert!(matches!(v, TomlValue::Map(_)));
338 }
339 #[test]
340 fn case_30() {
341 let v = parse(SAMPLE).expect("parse");
342 assert!(matches!(v, TomlValue::Map(_)));
343 assert!(matches!(v, TomlValue::Map(_)));
344 }
345
346 #[test]
347 fn parses_attribute_string() {
348 let v = parse(SAMPLE).expect("parse");
349 assert!(map_has_string(&v, "name", "neco"));
350 }
351
352 #[test]
353 fn exposes_children() {
354 let v = parse(SAMPLE).expect("parse");
355 assert!(map_len(&v) > 0);
356 }
357
358 fn map_has_string(value: &TomlValue, key: &str, expected: &str) -> bool {
359 match value {
360 TomlValue::Map(fields) => fields
361 .iter()
362 .any(|(k, v)| k == key && matches!(v, TomlValue::String(s) if s == expected)),
363 _ => false,
364 }
365 }
366
367 fn map_len(value: &TomlValue) -> usize {
368 match value {
369 TomlValue::Map(fields) => fields.len(),
370 _ => 0,
371 }
372 }
373}