1 #![allow(clippy::unneeded_field_pattern)]
2 #![allow(clippy::toplevel_ref_arg)]
3 
4 #[macro_use]
5 mod macros;
6 mod array;
7 mod datetime;
8 mod document;
9 mod errors;
10 mod inline_table;
11 mod key;
12 mod numbers;
13 pub(crate) mod strings;
14 mod table;
15 mod trivia;
16 mod value;
17 
18 pub use self::errors::TomlError;
19 pub(crate) use self::key::key as key_parser;
20 pub(crate) use self::value::value as value_parser;
21 
22 use crate::document::Document;
23 use crate::key::Key;
24 
25 pub struct TomlParser {
26     document: Box<Document>,
27     current_table_path: Vec<Key>,
28     current_table_position: usize,
29 }
30 
31 impl Default for TomlParser {
default() -> Self32     fn default() -> Self {
33         Self {
34             document: Box::new(Document::new()),
35             current_table_path: Vec::new(),
36             current_table_position: 0,
37         }
38     }
39 }
40 
41 #[cfg(test)]
42 mod tests {
43     use crate::parser::*;
44     use combine::stream::state::State;
45     use combine::*;
46     use pretty_assertions::assert_eq;
47     use std;
48     use std::fmt;
49     // Copied from https://github.com/colin-kiegel/rust-pretty-assertions/issues/24
50     /// Wrapper around string slice that makes debug output `{:?}` to print string same way as `{}`.
51     /// Used in different `assert*!` macros in combination with `pretty_assertions` crate to make
52     /// test failures to show nice diffs.
53     #[derive(PartialEq, Eq)]
54     struct PrettyString<'a>(pub &'a str);
55     /// Make diff to display string as multi-line string
56     impl<'a> fmt::Debug for PrettyString<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result57         fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58             f.write_str(self.0)
59         }
60     }
61 
62     macro_rules! parsed_eq {
63         ($parsed:ident, $expected:expr) => {{
64             assert!($parsed.is_ok());
65             let (v, rest) = $parsed.unwrap();
66             assert_eq!(v, $expected);
67             assert!(rest.input.is_empty());
68         }};
69     }
70 
71     macro_rules! parsed_float_eq {
72         ($input:ident, $expected:expr) => {{
73             let parsed = numbers::float().easy_parse(State::new($input));
74             assert!(parsed.is_ok());
75             let (v, rest) = parsed.unwrap();
76             assert!(($expected - v).abs() < std::f64::EPSILON);
77             assert!(rest.input.is_empty());
78         }};
79     }
80 
81     macro_rules! parsed_value_eq {
82         ($input:expr) => {
83             let parsed = value::value().easy_parse(State::new(*$input));
84             assert!(parsed.is_ok());
85             let (v, rest) = parsed.unwrap();
86             assert_eq!(v.to_string(), *$input);
87             assert!(rest.input.is_empty());
88         };
89     }
90 
91     macro_rules! parsed_date_time_eq {
92         ($input:expr, $is:ident) => {{
93             let parsed = value::value().easy_parse(State::new(*$input));
94             assert!(parsed.is_ok());
95             let (v, rest) = parsed.unwrap();
96             assert_eq!(v.to_string(), *$input);
97             assert!(rest.input.is_empty());
98             assert!(v.is_date_time());
99             assert!(v.as_date_time().unwrap().$is());
100         }};
101     }
102 
103     #[test]
integers()104     fn integers() {
105         let cases = [
106             ("+99", 99),
107             ("42", 42),
108             ("0", 0),
109             ("-17", -17),
110             ("1_000", 1_000),
111             ("5_349_221", 5_349_221),
112             ("1_2_3_4_5", 1_2_3_4_5),
113             (&std::i64::MIN.to_string()[..], std::i64::MIN),
114             (&std::i64::MAX.to_string()[..], std::i64::MAX),
115         ];
116         for &(input, expected) in &cases {
117             let parsed = numbers::integer().easy_parse(State::new(input));
118             parsed_eq!(parsed, expected);
119         }
120 
121         let overflow = "1000000000000000000000000000000000";
122         let parsed = numbers::integer().easy_parse(State::new(overflow));
123         assert!(parsed.is_err());
124     }
125 
126     #[test]
floats()127     fn floats() {
128         let cases = [
129             ("+1.0", 1.0),
130             ("3.1419", 3.1419),
131             ("-0.01", -0.01),
132             ("5e+22", 5e+22),
133             ("1e6", 1e6),
134             ("-2E-2", -2E-2),
135             ("6.626e-34", 6.626e-34),
136             ("9_224_617.445_991_228_313", 9_224_617.445_991_228_313),
137             ("-1.7976931348623157e+308", std::f64::MIN),
138             ("1.7976931348623157e+308", std::f64::MAX),
139             // ("1e+400", std::f64::INFINITY),
140         ];
141         for &(input, expected) in &cases {
142             parsed_float_eq!(input, expected);
143         }
144     }
145 
146     #[test]
basic_string()147     fn basic_string() {
148         let input =
149             r#""I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF. \U0002070E""#;
150         let parsed = strings::string().easy_parse(State::new(input));
151         parsed_eq!(
152             parsed,
153             "I\'m a string. \"You can quote me\". Name\tJosé\nLocation\tSF. \u{2070E}"
154         )
155     }
156 
157     #[test]
ml_basic_string()158     fn ml_basic_string() {
159         let cases = [
160             (
161                 r#""""
162 Roses are red
163 Violets are blue""""#,
164                 r#"Roses are red
165 Violets are blue"#,
166             ),
167             (r#"""" \""" """"#, " \"\"\" "),
168             (r#"""" \\""""#, " \\"),
169         ];
170 
171         for &(input, expected) in &cases {
172             let parsed = strings::string().easy_parse(State::new(input));
173             parsed_eq!(parsed, expected);
174         }
175 
176         let invalid_cases = [r#""""  """#, r#""""  \""""#];
177 
178         for input in &invalid_cases {
179             let parsed = strings::ml_basic_string().easy_parse(State::new(*input));
180             assert!(parsed.is_err());
181         }
182     }
183 
184     #[test]
ml_basic_string_escape_ws()185     fn ml_basic_string_escape_ws() {
186         let inputs = [
187             r#""""
188 The quick brown \
189 
190 
191   fox jumps over \
192     the lazy dog.""""#,
193             r#""""\
194        The quick brown \
195        fox jumps over \
196        the lazy dog.\
197        """"#,
198         ];
199         for input in &inputs {
200             let parsed = strings::string().easy_parse(State::new(*input));
201             parsed_eq!(parsed, "The quick brown fox jumps over the lazy dog.");
202         }
203         let empties = [
204             r#""""\
205        """"#,
206             r#""""
207 \
208   \
209 """"#,
210         ];
211         for empty in &empties {
212             let parsed = strings::string().easy_parse(State::new(*empty));
213             parsed_eq!(parsed, "");
214         }
215     }
216 
217     #[test]
literal_string()218     fn literal_string() {
219         let inputs = [
220             r#"'C:\Users\nodejs\templates'"#,
221             r#"'\\ServerX\admin$\system32\'"#,
222             r#"'Tom "Dubs" Preston-Werner'"#,
223             r#"'<\i\c*\s*>'"#,
224         ];
225 
226         for input in &inputs {
227             let parsed = strings::string().easy_parse(State::new(*input));
228             parsed_eq!(parsed, &input[1..input.len() - 1]);
229         }
230     }
231 
232     #[test]
ml_literal_string()233     fn ml_literal_string() {
234         let input = r#"'''I [dw]on't need \d{2} apples'''"#;
235         let parsed = strings::string().easy_parse(State::new(input));
236         parsed_eq!(parsed, &input[3..input.len() - 3]);
237         let input = r#"'''
238 The first newline is
239 trimmed in raw strings.
240    All other whitespace
241    is preserved.
242 '''"#;
243         let parsed = strings::string().easy_parse(State::new(input));
244         parsed_eq!(parsed, &input[4..input.len() - 3]);
245     }
246 
247     #[test]
offset_date_time()248     fn offset_date_time() {
249         let inputs = [
250             "1979-05-27T07:32:00Z",
251             "1979-05-27T00:32:00-07:00",
252             "1979-05-27T00:32:00.999999-07:00",
253         ];
254         for input in &inputs {
255             parsed_date_time_eq!(input, is_offset_date_time);
256         }
257     }
258 
259     #[test]
local_date_time()260     fn local_date_time() {
261         let inputs = ["1979-05-27T07:32:00", "1979-05-27T00:32:00.999999"];
262         for input in &inputs {
263             parsed_date_time_eq!(input, is_local_date_time);
264         }
265     }
266 
267     #[test]
local_date()268     fn local_date() {
269         let inputs = ["1979-05-27", "2017-07-20"];
270         for input in &inputs {
271             parsed_date_time_eq!(input, is_local_date);
272         }
273     }
274 
275     #[test]
local_time()276     fn local_time() {
277         let inputs = ["07:32:00", "00:32:00.999999"];
278         for input in &inputs {
279             parsed_date_time_eq!(input, is_local_time);
280         }
281     }
282 
283     #[test]
trivia()284     fn trivia() {
285         let inputs = [
286             "",
287             r#" "#,
288             r#"
289 "#,
290             r#"
291 # comment
292 
293 # comment2
294 
295 
296 "#,
297             r#"
298         "#,
299             r#"# comment
300 # comment2
301 
302 
303    "#,
304         ];
305         for input in &inputs {
306             let parsed = trivia::ws_comment_newline().easy_parse(State::new(*input));
307             assert!(parsed.is_ok());
308             let (t, rest) = parsed.unwrap();
309             assert!(rest.input.is_empty());
310             assert_eq!(&t, input);
311         }
312     }
313 
314     #[test]
arrays()315     fn arrays() {
316         let inputs = [
317             r#"[]"#,
318             r#"[   ]"#,
319             r#"[
320   1, 2, 3
321 ]"#,
322             r#"[
323   1,
324   2, # this is ok
325 ]"#,
326             r#"[# comment
327 # comment2
328 
329 
330    ]"#,
331             r#"[# comment
332 # comment2
333       1
334 
335 #sd
336 ,
337 # comment3
338 
339    ]"#,
340             r#"[1]"#,
341             r#"[1,]"#,
342             r#"[ "all", 'strings', """are the same""", '''type''']"#,
343             r#"[ 100, -2,]"#,
344             r#"[1, 2, 3]"#,
345             r#"[1.1, 2.1, 3.1]"#,
346             r#"["a", "b", "c"]"#,
347             r#"[ [ 1, 2 ], [3, 4, 5] ]"#,
348             r#"[ [ 1, 2 ], ["a", "b", "c"] ]"#,
349             r#"[ { x = 1, a = "2" }, {a = "a",b = "b",     c =    "c"} ]"#,
350         ];
351         for input in &inputs {
352             parsed_value_eq!(input);
353         }
354 
355         let invalid_inputs = [r#"["#, r#"[,]"#, r#"[,2]"#, r#"[1e165,,]"#, r#"[ 1, 2.0 ]"#];
356         for input in &invalid_inputs {
357             let parsed = array::array().easy_parse(State::new(*input));
358             assert!(parsed.is_err());
359         }
360     }
361 
362     #[test]
inline_tables()363     fn inline_tables() {
364         let inputs = [
365             r#"{}"#,
366             r#"{   }"#,
367             r#"{a = 1e165}"#,
368             r#"{ hello = "world", a = 1}"#,
369         ];
370         for input in &inputs {
371             parsed_value_eq!(input);
372         }
373         let invalid_inputs = [r#"{a = 1e165"#, r#"{ hello = "world", a = 2, hello = 1}"#];
374         for input in &invalid_inputs {
375             let parsed = inline_table::inline_table().easy_parse(State::new(*input));
376             assert!(parsed.is_err());
377         }
378     }
379 
380     #[test]
keys()381     fn keys() {
382         let cases = [
383             ("a", "a"),
384             (r#""hello\n ""#, "hello\n "),
385             (r#"'hello\n '"#, "hello\\n "),
386         ];
387 
388         for &(input, expected) in &cases {
389             let parsed = key::key().easy_parse(State::new(input));
390             assert!(parsed.is_ok());
391             let ((.., k), rest) = parsed.unwrap();
392             assert_eq!(k, expected);
393             assert_eq!(rest.input.len(), 0);
394         }
395     }
396 
397     #[test]
values()398     fn values() {
399         let inputs = [
400             "1979-05-27T00:32:00.999999",
401             "-239",
402             "1e200",
403             "9_224_617.445_991_228_313",
404             r#"'''I [dw]on't need \d{2} apples'''"#,
405             r#"'''
406 The first newline is
407 trimmed in raw strings.
408    All other whitespace
409    is preserved.
410 '''"#,
411             r#""Jos\u00E9\n""#,
412             r#""\\\"\b\/\f\n\r\t\u00E9\U000A0000""#,
413             r#"{ hello = "world", a = 1}"#,
414             r#"[ { x = 1, a = "2" }, {a = "a",b = "b",     c =    "c"} ]"#,
415         ];
416         for input in &inputs {
417             parsed_value_eq!(input);
418         }
419     }
420 
421     #[test]
documents()422     fn documents() {
423         let documents = [
424             r#"
425 # This is a TOML document.
426 
427 title = "TOML Example"
428 
429     [owner]
430     name = "Tom Preston-Werner"
431     dob = 1979-05-27T07:32:00-08:00 # First class dates
432 
433     [database]
434     server = "192.168.1.1"
435     ports = [ 8001, 8001, 8002 ]
436     connection_max = 5000
437     enabled = true
438 
439     [servers]
440 
441     # Indentation (tabs and/or spaces) is allowed but not required
442 [servers.alpha]
443     ip = "10.0.0.1"
444     dc = "eqdc10"
445 
446     [servers.beta]
447     ip = "10.0.0.2"
448     dc = "eqdc10"
449 
450     [clients]
451     data = [ ["gamma", "delta"], [1, 2] ]
452 
453     # Line breaks are OK when inside arrays
454 hosts = [
455     "alpha",
456     "omega"
457 ]
458 
459    'some.wierd .stuff'   =  """
460                          like
461                          that
462                       #   """ # this broke my sintax highlighting
463    " also. like " = '''
464 that
465 '''
466    double = 2e39 # this number looks familiar
467 # trailing comment"#,
468             r#""#,
469             r#"  "#,
470             r#" hello = 'darkness' # my old friend
471 "#,
472         ];
473         for document in &documents {
474             let doc = TomlParser::parse(document);
475 
476             assert!(doc.is_ok());
477             let doc = doc.unwrap();
478 
479             assert_eq!(PrettyString(document), PrettyString(&doc.to_string()));
480         }
481 
482         let invalid_inputs = [r#" hello = 'darkness' # my old friend
483 $"#];
484         for document in &invalid_inputs {
485             let doc = TomlParser::parse(document);
486 
487             assert!(doc.is_err());
488         }
489     }
490 }
491