cfg_lib/
lib.rs

1//
2//  Copyright (C) 2018-2021 Vinay Sajip.
3//
4/*!
5A library for working with the CFG configuration format.
6
7The CFG configuration format is a text format for configuration files which is similar to, and a
8superset of, the JSON format. It dates from [2008](https://wiki.python.org/moin/HierConfig) and has
9the following aims:
10
11* Allow a hierarchical configuration scheme with support for key-value mappings and lists.
12* Support cross-references between one part of the configuration and another.
13* Provide the ability to compose configurations (using include and merge facilities).
14* Provide the ability to access real application objects safely.
15
16It overcomes a number of drawbacks of JSON when used as a configuration format:
17
18* JSON is more verbose than necessary.
19* JSON doesn’t allow comments.
20* JSON doesn’t allow trailing commas in lists and mappings.
21
22A simple example
23================
24
25With the following configuration file, `test0.cfg`:
26```text
27a: 'Hello, '
28b: 'world!'
29c: {
30  d: 'e'
31}
32'f.g': 'h'
33christmas_morning: `2019-12-25 08:39:49`
34home: `$HOME`
35foo: `$FOO|bar`
36```
37
38You can load and query the above configuration using, for example,
39[the evcxr REPL](https://github.com/google/evcxr/blob/master/evcxr_repl/README.md):
40
41```text
42$ evcxr
43>> :dep cfg-lib
44>> use cfg_lib::*;
45```
46
47Loading a configuration
48-----------------------
49
50The configuration above can be loaded as shown below. In the REPL shell:
51```text
52>> let cfg = Config::from_file("test0.cfg").unwrap();
53```
54
55The successful [`from_file()`](config/struct.Config.html#method.from_file) call returns a [Config](config/struct.Config.html) instance
56which can be used to query the configuration.
57
58Access elements with keys
59-------------------------
60Accessing elements of the configuration with a simple key is not much harder than using a HashMap:
61```text
62>> cfg.get("a")
63Ok(Base(String("Hello, ")))
64>> cfg.get("b")
65Ok(Base(String("world!")))
66```
67The values returned are of type [Value](config/enum.Value.html).
68
69Access elements with paths
70--------------------------
71As well as simple keys, elements can also be accessed using path strings:
72```text
73>> cfg.get("c.d")
74Ok(Base(String("e")))
75```
76Here, the desired value is obtained in a single step, by (under the hood) walking the path `c.d` –
77first getting the mapping at key c, and then the value at d in the resulting mapping.
78
79Note that you can have simple keys which look like paths:
80```text
81>> cfg.get("f.g")
82Ok(Base(String("h")))
83```
84If a key is given that exists in the configuration, it is used as such, and if it is not present in
85the configuration, an attempt is made to interpret it as a path. Thus, f.g is present and accessed
86via key, whereas c.d is not an existing key, so is interpreted as a path.
87
88Access to date/time objects
89---------------------------
90You can also get native Rust date/time objects from a configuration, by using an ISO date/time
91pattern in a backtick-string:
92```text
93>> cfg.get("christmas_morning")
94Ok(Base(DateTime(2019-12-25T08:39:49+00:00)))
95```
96You get either NaiveDate objects, if you specify the date part only, or else DateTime<FixedOffset>
97objects, if you specify a time component as well. If no offset is specified, it is assumed to be
98zero.
99
100Access to environment variables
101-------------------------------
102To access an environment variable, use a backtick-string of the form `$VARNAME`:
103```text
104>> cfg.get("home")
105Ok(Base(String("/home/vinay")))
106```
107You can specify a default value to be used if an environment variable isn’t present using the
108`$VARNAME|default-value` form. Whatever string follows the pipe character (including the empty
109string) is returned if the VARNAME is not a variable in the environment.
110```text
111>> cfg.get("foo")
112Ok(Base(String("bar")))
113```
114
115For more information, see [the CFG documentation](https://docs.red-dove.com/cfg/index.html).
116
117*/
118
119#![deny(missing_docs)]
120
121pub mod config;
122
123pub use config::*;
124
125#[macro_use]
126extern crate log;
127
128#[cfg(test)]
129mod tests {
130    use std::collections::HashMap;
131    use std::env;
132    use std::fs::{canonicalize, File};
133    use std::io::{BufRead, BufReader, Cursor};
134    use std::path::{self};
135    use std::result::Result;
136
137    use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime};
138    use lazy_static::lazy_static;
139    use num_complex::Complex64;
140    use regex::Regex;
141
142    use crate::config::*;
143
144    lazy_static! {
145        static ref SEPARATOR_PATTERN: Regex =
146            Regex::new(r"^-- ([A-Z]\d+) -+").expect("couldn't compile regex");
147        static ref PATH_SEP_STRING: String = path::MAIN_SEPARATOR.to_string();
148    }
149
150    fn data_file_path(parts: &[&str]) -> String {
151        let mut v = Vec::new();
152
153        v.push("resources");
154        v.extend_from_slice(parts);
155        let s = v.join(&PATH_SEP_STRING);
156
157        String::from(s)
158    }
159
160    fn load_data(file_name: String) -> HashMap<String, String> {
161        let mut result = HashMap::new();
162        let f = File::open(file_name).expect("Couldn't open the file");
163        let mut key = String::new();
164        let mut lines: Vec<String> = vec![];
165
166        for line in BufReader::new(f).lines() {
167            match line {
168                Err(e) => {
169                    panic!("Failed to read: {}", e);
170                }
171                Ok(s) => match SEPARATOR_PATTERN.captures(&s) {
172                    None => {
173                        lines.push(String::from(s.clone()));
174                    }
175                    Some(c) => {
176                        if key.len() > 0 && lines.len() > 0 {
177                            result.insert(key, lines.join("\n"));
178                        }
179                        match c.get(0) {
180                            None => {
181                                panic!("A match was expected");
182                            }
183                            Some(m) => {
184                                key = String::from(m.as_str());
185                            }
186                        }
187                        lines.clear();
188                    }
189                },
190            }
191        }
192        return result;
193    }
194
195    macro_rules! loc {
196        ($line:expr, $column:expr) => {{
197            Location {
198                line: $line,
199                column: $column,
200            }
201        }};
202    }
203
204    macro_rules! assert_loc {
205        ($loc:expr, $line:expr, $column:expr) => {{
206            assert_eq!($line, $loc.line);
207            assert_eq!($column, $loc.column);
208        }};
209    }
210
211    #[test]
212    fn location() {
213        let mut loc = loc!(1, 1);
214        let loc2 = loc!(2, 7);
215
216        assert_loc!(loc, 1, 1);
217
218        loc.next_line();
219        assert_loc!(loc, 2, 1);
220
221        assert_loc!(loc2, 2, 7);
222
223        let loc3 = loc2.clone();
224
225        assert_loc!(loc3, 2, 7);
226
227        loc.update(&loc3);
228        assert_loc!(loc, 2, 7);
229    }
230
231    #[test]
232    fn decoding() {
233        let p = data_file_path(&["all_unicode.bin"]);
234        let f = File::open(p).expect("Couldn't open all_unicode.bin");
235        let mut decoder = Decoder::new(f);
236
237        fn check(i: u32, offset: usize, result: DecodeResult) {
238            match result {
239                DecodeResult::EOF => {
240                    panic!("EOF unexpected at {:06x}", offset);
241                }
242                DecodeResult::Error(e) => panic!("Failed at {:06x}: {:?}", offset, e),
243                DecodeResult::Char(c, the_bytes) => {
244                    assert_eq!(
245                        i,
246                        c as u32,
247                        "Failed at {}, bytes {:?}, file offset {:06x}",
248                        i,
249                        the_bytes,
250                        offset - the_bytes.len()
251                    );
252                }
253            }
254        }
255
256        for i in 0..0xD800 {
257            let result = decoder.decode(/* &mut log */);
258            let offset = decoder.bytes_read;
259
260            check(i, offset, result);
261        }
262
263        for i in 0xE000..0x110000 {
264            let result = decoder.decode(/* &mut log */);
265            let offset = decoder.bytes_read;
266
267            check(i, offset, result);
268        }
269    }
270
271    #[test]
272    fn decoding_edge_cases() {
273        let mut d = Decoder::new(Box::new("".as_bytes()));
274
275        match d.decode() {
276            DecodeResult::EOF => {}
277            _ => panic!("unexpected result"),
278        }
279        d = Decoder::new(Box::new(Cursor::new([195u8])));
280        //d.enable_logging(true);
281        match d.decode() {
282            DecodeResult::Error(e) => match e {
283                DecoderError::InvalidBytes(invalid) => {
284                    assert_eq!(vec![195u8], invalid);
285                }
286                v => panic!("decoder error expected, {:?} seen", v),
287            },
288            v => panic!("error expected, {:?} seen", v),
289        }
290        d = Decoder::new(Box::new(Cursor::new([0u8])));
291        match d.decode() {
292            DecodeResult::Char(c, valid) => {
293                assert_eq!('\0', c);
294                assert_eq!(vec![0u8], valid);
295            }
296            v => panic!("char expected, {:?} seen", v),
297        }
298    }
299
300    #[test]
301    fn from_data() {
302        let p = data_file_path(&["testdata.txt"]);
303        //let file_name = String::from("testdata.txt");
304        let mapping = load_data(p);
305
306        for (_key, _value) in &mapping {
307            let _tokenizer = Tokenizer::new(Box::new(_value.as_bytes()));
308        }
309    }
310
311    type LocationTuple = (u16, u16);
312
313    #[test]
314    fn tokenizing() {
315        fn build_ok_case(
316            source: &str,
317            kind: TokenKind,
318            value: ScalarValue,
319            start: LocationTuple,
320            end: LocationTuple,
321        ) -> (&str, Result<Token, RecognizerError>) {
322            let s = loc!(start.0, start.1);
323            let e = loc!(end.0, end.1);
324
325            (
326                source,
327                Ok(Token {
328                    kind,
329                    text: source.trim_start_matches(' ').to_string(),
330                    value,
331                    start: s,
332                    end: e,
333                }),
334            )
335        }
336
337        let cases = vec![
338            build_ok_case("", TokenKind::EOF, ScalarValue::None, (1, 1), (1, 1)),
339            build_ok_case(
340                "# a comment",
341                TokenKind::Newline,
342                ScalarValue::None,
343                (1, 1),
344                (1, 12),
345            ),
346            build_ok_case("\n", TokenKind::Newline, ScalarValue::None, (1, 1), (2, 0)),
347            build_ok_case(
348                " foo",
349                TokenKind::Word,
350                ScalarValue::Identifier("foo".to_string()),
351                (1, 2),
352                (1, 4),
353            ),
354            build_ok_case(
355                "true",
356                TokenKind::True,
357                ScalarValue::Bool(true),
358                (1, 1),
359                (1, 4),
360            ),
361            build_ok_case(
362                "false",
363                TokenKind::False,
364                ScalarValue::Bool(false),
365                (1, 1),
366                (1, 5),
367            ),
368            build_ok_case("null", TokenKind::None, ScalarValue::Null, (1, 1), (1, 4)),
369            build_ok_case("is", TokenKind::Is, ScalarValue::None, (1, 1), (1, 2)),
370            build_ok_case("in", TokenKind::In, ScalarValue::None, (1, 1), (1, 2)),
371            build_ok_case("not", TokenKind::Not, ScalarValue::None, (1, 1), (1, 3)),
372            build_ok_case("and", TokenKind::And, ScalarValue::None, (1, 1), (1, 3)),
373            build_ok_case("or", TokenKind::Or, ScalarValue::None, (1, 1), (1, 2)),
374            ("'", Err(RecognizerError::UnterminatedString(loc!(1, 1)))),
375            ("'''", Err(RecognizerError::UnterminatedString(loc!(1, 3)))),
376            build_ok_case(
377                "''",
378                TokenKind::String,
379                ScalarValue::String("".to_string()),
380                (1, 1),
381                (1, 2),
382            ),
383            build_ok_case(
384                "''''''",
385                TokenKind::String,
386                ScalarValue::String("".to_string()),
387                (1, 1),
388                (1, 6),
389            ),
390            build_ok_case(
391                "'foo'",
392                TokenKind::String,
393                ScalarValue::String("foo".to_string()),
394                (1, 1),
395                (1, 5),
396            ),
397            build_ok_case(
398                "'''bar'''",
399                TokenKind::String,
400                ScalarValue::String("bar".to_string()),
401                (1, 1),
402                (1, 9),
403            ),
404            build_ok_case(
405                "`bar`",
406                TokenKind::BackTick,
407                ScalarValue::String("bar".to_string()),
408                (1, 1),
409                (1, 5),
410            ),
411            ("'foo", Err(RecognizerError::UnterminatedString(loc!(1, 4)))),
412            (
413                "'''bar''",
414                Err(RecognizerError::UnterminatedString(loc!(1, 8))),
415            ),
416            (
417                "'''bar'",
418                Err(RecognizerError::UnterminatedString(loc!(1, 7))),
419            ),
420            ("`foo", Err(RecognizerError::UnterminatedString(loc!(1, 4)))),
421            ("`foo\n", Err(RecognizerError::InvalidString(loc!(1, 5)))),
422            ("`foo\r\n", Err(RecognizerError::InvalidString(loc!(1, 5)))),
423            (
424                "'abc\\\ndef",
425                Err(RecognizerError::UnterminatedString(loc!(2, 3))),
426            ),
427            (
428                "\\ ",
429                Err(RecognizerError::UnexpectedCharacter(' ', loc!(1, 2))),
430            ),
431            build_ok_case(
432                "\"\"",
433                TokenKind::String,
434                ScalarValue::String("".to_string()),
435                (1, 1),
436                (1, 2),
437            ),
438            build_ok_case(
439                "4",
440                TokenKind::Number,
441                ScalarValue::Integer(4i64),
442                (1, 1),
443                (1, 1),
444            ),
445            build_ok_case(
446                "2.71828",
447                TokenKind::Number,
448                ScalarValue::Float(2.71828),
449                (1, 1),
450                (1, 7),
451            ),
452            build_ok_case(
453                ".5",
454                TokenKind::Number,
455                ScalarValue::Float(0.5),
456                (1, 1),
457                (1, 2),
458            ),
459            build_ok_case(
460                "-.5",
461                TokenKind::Number,
462                ScalarValue::Float(-0.5),
463                (1, 1),
464                (1, 3),
465            ),
466            build_ok_case(
467                "0x123aBc",
468                TokenKind::Number,
469                ScalarValue::Integer(0x123abci64),
470                (1, 1),
471                (1, 8),
472            ),
473            build_ok_case(
474                "0o123",
475                TokenKind::Number,
476                ScalarValue::Integer(83i64),
477                (1, 1),
478                (1, 5),
479            ),
480            build_ok_case(
481                "0123",
482                TokenKind::Number,
483                ScalarValue::Integer(83i64),
484                (1, 1),
485                (1, 4),
486            ),
487            build_ok_case(
488                "0b000101100111",
489                TokenKind::Number,
490                ScalarValue::Integer(0x167i64),
491                (1, 1),
492                (1, 14),
493            ),
494            build_ok_case(
495                "0b00_01_0110_0111",
496                TokenKind::Number,
497                ScalarValue::Integer(0x167i64),
498                (1, 1),
499                (1, 17),
500            ),
501            build_ok_case(
502                "1e8",
503                TokenKind::Number,
504                ScalarValue::Float(1e8),
505                (1, 1),
506                (1, 3),
507            ),
508            build_ok_case(
509                "1e-8",
510                TokenKind::Number,
511                ScalarValue::Float(1e-8),
512                (1, 1),
513                (1, 4),
514            ),
515            ("9a", Err(RecognizerError::InvalidNumber(loc!(1, 2)))),
516            ("079", Err(RecognizerError::InvalidNumber(loc!(1, 1)))),
517            ("0xaBcZ", Err(RecognizerError::InvalidNumber(loc!(1, 6)))),
518            ("0.5.7", Err(RecognizerError::InvalidNumber(loc!(1, 4)))),
519            (".5z", Err(RecognizerError::InvalidNumber(loc!(1, 3)))),
520            ("0o79", Err(RecognizerError::InvalidNumber(loc!(1, 4)))),
521            (" 0.4e-z", Err(RecognizerError::InvalidNumber(loc!(1, 7)))),
522            (" 0.4e-8.3", Err(RecognizerError::InvalidNumber(loc!(1, 8)))),
523            ("4e-8.3", Err(RecognizerError::InvalidNumber(loc!(1, 5)))),
524            (" 089z", Err(RecognizerError::InvalidNumber(loc!(1, 5)))),
525            ("0o89z", Err(RecognizerError::InvalidNumber(loc!(1, 3)))),
526            ("0X89g", Err(RecognizerError::InvalidNumber(loc!(1, 5)))),
527            ("10z", Err(RecognizerError::InvalidNumber(loc!(1, 3)))),
528            ("0.4e-8Z", Err(RecognizerError::InvalidNumber(loc!(1, 7)))),
529            ("123_", Err(RecognizerError::InvalidNumber(loc!(1, 4)))),
530            ("1__23", Err(RecognizerError::InvalidNumber(loc!(1, 3)))),
531            ("1_2__3", Err(RecognizerError::InvalidNumber(loc!(1, 5)))),
532            ("0.4e-8_", Err(RecognizerError::InvalidNumber(loc!(1, 7)))),
533            ("0.4_e-8", Err(RecognizerError::InvalidNumber(loc!(1, 5)))),
534            ("0._4e-8", Err(RecognizerError::InvalidNumber(loc!(1, 3)))),
535            build_ok_case(":", TokenKind::Colon, ScalarValue::None, (1, 1), (1, 1)),
536            build_ok_case("-", TokenKind::Minus, ScalarValue::None, (1, 1), (1, 1)),
537            build_ok_case("+", TokenKind::Plus, ScalarValue::None, (1, 1), (1, 1)),
538            build_ok_case("*", TokenKind::Star, ScalarValue::None, (1, 1), (1, 1)),
539            build_ok_case("**", TokenKind::Power, ScalarValue::None, (1, 1), (1, 2)),
540            build_ok_case("/", TokenKind::Slash, ScalarValue::None, (1, 1), (1, 1)),
541            build_ok_case(
542                "//",
543                TokenKind::SlashSlash,
544                ScalarValue::None,
545                (1, 1),
546                (1, 2),
547            ),
548            build_ok_case("%", TokenKind::Modulo, ScalarValue::None, (1, 1), (1, 1)),
549            build_ok_case(",", TokenKind::Comma, ScalarValue::None, (1, 1), (1, 1)),
550            build_ok_case("{", TokenKind::LeftCurly, ScalarValue::None, (1, 1), (1, 1)),
551            build_ok_case(
552                "}",
553                TokenKind::RightCurly,
554                ScalarValue::None,
555                (1, 1),
556                (1, 1),
557            ),
558            build_ok_case(
559                "[",
560                TokenKind::LeftBracket,
561                ScalarValue::None,
562                (1, 1),
563                (1, 1),
564            ),
565            build_ok_case(
566                "]",
567                TokenKind::RightBracket,
568                ScalarValue::None,
569                (1, 1),
570                (1, 1),
571            ),
572            build_ok_case(
573                "(",
574                TokenKind::LeftParenthesis,
575                ScalarValue::None,
576                (1, 1),
577                (1, 1),
578            ),
579            build_ok_case(
580                ")",
581                TokenKind::RightParenthesis,
582                ScalarValue::None,
583                (1, 1),
584                (1, 1),
585            ),
586            build_ok_case("@", TokenKind::At, ScalarValue::None, (1, 1), (1, 1)),
587            build_ok_case("$", TokenKind::Dollar, ScalarValue::None, (1, 1), (1, 1)),
588            build_ok_case("<", TokenKind::LessThan, ScalarValue::None, (1, 1), (1, 1)),
589            build_ok_case(
590                "<=",
591                TokenKind::LessThanOrEqual,
592                ScalarValue::None,
593                (1, 1),
594                (1, 2),
595            ),
596            build_ok_case(
597                "<>",
598                TokenKind::AltUnequal,
599                ScalarValue::None,
600                (1, 1),
601                (1, 2),
602            ),
603            build_ok_case(
604                "<<",
605                TokenKind::LeftShift,
606                ScalarValue::None,
607                (1, 1),
608                (1, 2),
609            ),
610            build_ok_case(
611                ">",
612                TokenKind::GreaterThan,
613                ScalarValue::None,
614                (1, 1),
615                (1, 1),
616            ),
617            build_ok_case(
618                ">=",
619                TokenKind::GreaterThanOrEqual,
620                ScalarValue::None,
621                (1, 1),
622                (1, 2),
623            ),
624            build_ok_case(
625                ">>",
626                TokenKind::RightShift,
627                ScalarValue::None,
628                (1, 1),
629                (1, 2),
630            ),
631            build_ok_case("!", TokenKind::Not, ScalarValue::None, (1, 1), (1, 1)),
632            build_ok_case("!=", TokenKind::Unequal, ScalarValue::None, (1, 1), (1, 2)),
633            build_ok_case(
634                "~",
635                TokenKind::BitwiseComplement,
636                ScalarValue::None,
637                (1, 1),
638                (1, 1),
639            ),
640            build_ok_case(
641                "&",
642                TokenKind::BitwiseAnd,
643                ScalarValue::None,
644                (1, 1),
645                (1, 1),
646            ),
647            build_ok_case("|", TokenKind::BitwiseOr, ScalarValue::None, (1, 1), (1, 1)),
648            build_ok_case(
649                "^",
650                TokenKind::BitwiseXor,
651                ScalarValue::None,
652                (1, 1),
653                (1, 1),
654            ),
655            build_ok_case(".", TokenKind::Dot, ScalarValue::None, (1, 1), (1, 1)),
656            build_ok_case("==", TokenKind::Equal, ScalarValue::None, (1, 1), (1, 2)),
657            (
658                ";",
659                Err(RecognizerError::UnexpectedCharacter(';', loc!(1, 1))),
660            ),
661        ];
662
663        for case in cases {
664            let c = Cursor::new(case.0);
665            let mut tokenizer = Tokenizer::new(Box::new(c));
666
667            let token = tokenizer.get_token();
668            let expected = case.1;
669
670            //println!("case: {}", case.0);
671            match expected {
672                Ok(e) => {
673                    let msg = format!("failed for {}", case.0);
674
675                    let t = token.expect("a token was expected");
676                    assert_eq!(t.kind, e.kind, "{}", msg);
677                    assert_eq!(t.text, e.text, "{}", msg);
678                    assert_eq!(t.start, e.start, "{}", msg);
679                    assert_eq!(t.end, e.end, "{}", msg);
680                }
681                Err(e) => {
682                    let te = token.err().expect("an error was expected");
683                    assert_eq!(te, e);
684                }
685            }
686        }
687    }
688
689    #[test]
690    fn escapes() {
691        let cases = vec![
692            ("'\\a'", "\u{0007}"),
693            ("'\\b'", "\u{0008}"),
694            ("'\\f'", "\u{000C}"),
695            ("'\\n'", "\n"),
696            ("'\\r'", "\r"),
697            ("'\\t'", "\t"),
698            ("'\\v'", "\u{000B}"),
699            ("'\\\\'", "\\"),
700            ("'\\\\'", "\\"),
701            ("'\\''", "'"),
702            ("'\\\"'", "\""),
703            ("'\\xAB'", "\u{00AB}"),
704            ("'\\u2803'", "\u{2803}"),
705            ("'\\u2803'", "\u{2803}"),
706            ("'\\u28A0abc\\u28a0'", "\u{28A0}abc\u{28A0}"),
707            ("'\\u28A0abc'", "\u{28A0}abc"),
708            ("'\\U0010ffff'", "\u{10FFFF}"),
709        ];
710
711        for (s, e) in cases {
712            let c = Cursor::new(s);
713            let mut tokenizer = Tokenizer::new(Box::new(c));
714            let token = tokenizer.get_token().expect("a token was expected");
715
716            match token.value {
717                ScalarValue::String(s) => {
718                    assert_eq!(e.to_string(), s);
719                }
720                v => panic!("unexpected token value: {:?}", v),
721            }
722        }
723
724        let bad_cases = vec![
725            "'\\z'",
726            "'\\x'",
727            "'\\xa'",
728            "'\\xaz'",
729            "'\\u'",
730            "'\\u0'",
731            "'\\u01'",
732            "'\\u012'",
733            "'\\u012z'",
734            "'\\u012zA'",
735            "'\\ud800'",
736            "'\\udfff'",
737            "'\\U00110000'",
738        ];
739
740        for s in bad_cases {
741            let c = Cursor::new(s);
742            let mut tokenizer = Tokenizer::new(Box::new(c));
743            let e = tokenizer.get_token().err().expect("an error was expected");
744
745            match e {
746                RecognizerError::InvalidEscapeSequence(_, _) => {}
747                v => panic!("unexpected error value: {:?}", v),
748            }
749        }
750    }
751
752    #[test]
753    fn locations() {
754        let mut positions: Vec<Vec<u16>> = vec![];
755        let mut p = data_file_path(&["pos.forms.cfg.txt"]);
756        let mut f = File::open(&p).expect("Couldn't open pos.forms.cfg");
757
758        fn to_int(s: &str) -> u16 {
759            u16::from_str_radix(s, 10).expect("couldn't parse string to integer")
760        }
761
762        for line in BufReader::new(f).lines() {
763            let p = line
764                .expect("a line was expected")
765                .split(' ')
766                .map(to_int)
767                .collect::<Vec<u16>>();
768
769            assert_eq!(4, p.len());
770            positions.push(p);
771        }
772        p = data_file_path(&["forms.cfg"]);
773        f = File::open(&p).expect("Couldn't open forms.cfg");
774
775        let mut tokenizer = Tokenizer::new(Box::new(f));
776
777        for (_i, p) in positions.iter().enumerate() {
778            let t = tokenizer.get_token().expect("a token was expected");
779            let s = loc!(p[0], p[1]);
780            let e = loc!(p[2], p[3]);
781
782            assert_eq!(s, t.start);
783            assert_eq!(e, t.end);
784        }
785    }
786
787    #[test]
788    fn strings() {
789        let c = Cursor::new("'foo' \"bar\" 'baz'");
790        let mut parser = Parser::new(Box::new(c)).expect("unable to create parser");
791        let t = parser.strings().expect("a token was expected");
792
793        assert_eq!(t.kind, TokenKind::String);
794        match t.value {
795            ScalarValue::String(s) => assert_eq!(s, "foobarbaz"),
796            e => panic!("unexpected result: {:?}", e),
797        }
798        assert_loc!(t.start, 1, 1);
799        assert_loc!(t.end, 1, 17);
800    }
801
802    fn build_token(kind: TokenKind, source: &str, value: ScalarValue, scol: u16) -> Token {
803        let src = source.to_string();
804        let ecol: u16 = (src.len() as u16) - 1 + scol;
805        let s = loc!(1, scol);
806        let e = loc!(1, ecol);
807        Token {
808            kind,
809            text: source.to_string(),
810            value,
811            start: s,
812            end: e,
813        }
814    }
815
816    fn build_identifier(source: &str, scol: u16) -> Token {
817        build_token(
818            TokenKind::Word,
819            source,
820            ScalarValue::Identifier(source.to_string()),
821            scol,
822        )
823    }
824
825    fn build_string(source: &str, v: &str, scol: u16) -> Token {
826        build_token(
827            TokenKind::String,
828            source,
829            ScalarValue::String(v.to_string()),
830            scol,
831        )
832    }
833
834    fn build_integer(source: &str, i: i64, scol: u16) -> Token {
835        build_token(TokenKind::Number, source, ScalarValue::Integer(i), scol)
836    }
837
838    fn build_float(source: &str, fv: f64, scol: u16) -> Token {
839        let v = ScalarValue::Float(fv);
840
841        build_token(TokenKind::Number, source, v, scol)
842    }
843
844    fn build_complex(source: &str, re: f64, im: f64, scol: u16) -> Token {
845        let v = ScalarValue::Complex(Complex64::new(re, im));
846
847        build_token(TokenKind::Complex, source, v, scol)
848    }
849
850    fn build_scalar(source: &str, scol: u16) -> Token {
851        let kind;
852        let value;
853
854        if source.eq("true") {
855            kind = TokenKind::True;
856            value = ScalarValue::Bool(true);
857        } else if source.eq("false") {
858            kind = TokenKind::False;
859            value = ScalarValue::Bool(false);
860        } else {
861            kind = TokenKind::None;
862            value = ScalarValue::Null;
863        }
864        build_token(kind, source, value, scol)
865    }
866
867    #[test]
868    fn values() {
869        let cases = vec![
870            ("foo", Ok(build_identifier("foo", 1))),
871            ("'foo'", Ok(build_string("'foo'", "foo", 1))),
872            ("4", Ok(build_integer("4", 4i64, 1))),
873            ("3.14159", Ok(build_float("3.14159", 3.14159f64, 1))),
874            ("2j", Ok(build_complex("2j", 0.0f64, 2.0f64, 1))),
875            ("null", Ok(build_scalar("null", 1))),
876            ("true", Ok(build_scalar("true", 1))),
877            ("false", Ok(build_scalar("false", 1))),
878            ("-4", Ok(build_integer("-4", -4i64, 1))),
879            ("1234", Ok(build_integer("1234", 1234i64, 1))),
880            ("1234_5678", Ok(build_integer("1234_5678", 12345678i64, 1))),
881            (
882                "123_456_789",
883                Ok(build_integer("123_456_789", 123456789i64, 1)),
884            ),
885        ];
886
887        for case in cases {
888            let c = Cursor::new(case.0);
889            let mut parser = Parser::new(Box::new(c)).expect("unable to create parser");
890            let value = parser.value();
891            let expected = case.1;
892
893            match expected {
894                Ok(e) => {
895                    let v = value.expect("a value was expected");
896                    assert_eq!(v, e);
897                }
898                Err(e) => {
899                    let ve = value.err().expect("an error was expected");
900                    assert_eq!(ve, e);
901                }
902            }
903        }
904    }
905
906    #[test]
907    fn atoms() {
908        let cases = vec![
909            ("foo", Ok(ASTValue::TokenValue(build_identifier("foo", 1)))),
910            (
911                "'foo'",
912                Ok(ASTValue::TokenValue(build_string("'foo'", "foo", 1))),
913            ),
914            ("4", Ok(ASTValue::TokenValue(build_integer("4", 4i64, 1)))),
915            (
916                "3.14159",
917                Ok(ASTValue::TokenValue(build_float("3.14159", 3.14159f64, 1))),
918            ),
919            (
920                "2j",
921                Ok(ASTValue::TokenValue(build_complex("2j", 0.0, 2.0f64, 1))),
922            ),
923            ("null", Ok(ASTValue::TokenValue(build_scalar("null", 1)))),
924            ("true", Ok(ASTValue::TokenValue(build_scalar("true", 1)))),
925            ("false", Ok(ASTValue::TokenValue(build_scalar("false", 1)))),
926            (
927                "-4",
928                Ok(ASTValue::TokenValue(build_integer("-4", -4i64, 1))),
929            ),
930        ];
931
932        for case in cases {
933            let c = Cursor::new(case.0);
934            let mut parser = Parser::new(Box::new(c)).expect("unable to create parser");
935            let atom = parser.atom();
936            let expected = case.1;
937
938            match expected {
939                Ok(e) => {
940                    let a = atom.expect("an atom was expected");
941                    assert_eq!(a, e);
942                }
943                Err(e) => {
944                    let ae = atom.err().expect("an error was expected");
945                    assert_eq!(ae, e);
946                }
947            }
948        }
949    }
950
951    #[test]
952    fn primaries() {
953        let cases = vec![
954            (
955                "a.b",
956                Ok(ASTValue::Binary(BinaryNode {
957                    kind: TokenKind::Dot,
958                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
959                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 3))),
960                    start: loc!(1, 2),
961                })),
962            ),
963            (
964                "a[1:2]",
965                Ok(ASTValue::Binary(BinaryNode {
966                    kind: TokenKind::Colon,
967                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
968                    right: Box::new(ASTValue::Slice(
969                        loc!(1, 3),
970                        Box::new(Some(ASTValue::TokenValue(build_integer("1", 1i64, 3)))),
971                        Box::new(Some(ASTValue::TokenValue(build_integer("2", 2i64, 5)))),
972                        Box::new(None),
973                    )),
974                    start: loc!(1, 2),
975                })),
976            ),
977            (
978                "foo[start:stop:step]",
979                Ok(ASTValue::Binary(BinaryNode {
980                    kind: TokenKind::Colon,
981                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
982                    right: Box::new(ASTValue::Slice(
983                        loc!(1, 5),
984                        Box::new(Some(ASTValue::TokenValue(build_identifier("start", 5)))),
985                        Box::new(Some(ASTValue::TokenValue(build_identifier("stop", 11)))),
986                        Box::new(Some(ASTValue::TokenValue(build_identifier("step", 16)))),
987                    )),
988                    start: loc!(1, 4),
989                })),
990            ),
991            (
992                "foo[start:stop]",
993                Ok(ASTValue::Binary(BinaryNode {
994                    kind: TokenKind::Colon,
995                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
996                    right: Box::new(ASTValue::Slice(
997                        loc!(1, 5),
998                        Box::new(Some(ASTValue::TokenValue(build_identifier("start", 5)))),
999                        Box::new(Some(ASTValue::TokenValue(build_identifier("stop", 11)))),
1000                        Box::new(None),
1001                    )),
1002                    start: loc!(1, 4),
1003                })),
1004            ),
1005            (
1006                "foo[start:stop:]",
1007                Ok(ASTValue::Binary(BinaryNode {
1008                    kind: TokenKind::Colon,
1009                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1010                    right: Box::new(ASTValue::Slice(
1011                        loc!(1, 5),
1012                        Box::new(Some(ASTValue::TokenValue(build_identifier("start", 5)))),
1013                        Box::new(Some(ASTValue::TokenValue(build_identifier("stop", 11)))),
1014                        Box::new(None),
1015                    )),
1016                    start: loc!(1, 4),
1017                })),
1018            ),
1019            (
1020                "foo[start:]",
1021                Ok(ASTValue::Binary(BinaryNode {
1022                    kind: TokenKind::Colon,
1023                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1024                    right: Box::new(ASTValue::Slice(
1025                        loc!(1, 5),
1026                        Box::new(Some(ASTValue::TokenValue(build_identifier("start", 5)))),
1027                        Box::new(None),
1028                        Box::new(None),
1029                    )),
1030                    start: loc!(1, 4),
1031                })),
1032            ),
1033            (
1034                "foo[start::]",
1035                Ok(ASTValue::Binary(BinaryNode {
1036                    kind: TokenKind::Colon,
1037                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1038                    right: Box::new(ASTValue::Slice(
1039                        loc!(1, 5),
1040                        Box::new(Some(ASTValue::TokenValue(build_identifier("start", 5)))),
1041                        Box::new(None),
1042                        Box::new(None),
1043                    )),
1044                    start: loc!(1, 4),
1045                })),
1046            ),
1047            (
1048                "foo[:stop]",
1049                Ok(ASTValue::Binary(BinaryNode {
1050                    kind: TokenKind::Colon,
1051                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1052                    right: Box::new(ASTValue::Slice(
1053                        loc!(1, 5),
1054                        Box::new(None),
1055                        Box::new(Some(ASTValue::TokenValue(build_identifier("stop", 6)))),
1056                        Box::new(None),
1057                    )),
1058                    start: loc!(1, 4),
1059                })),
1060            ),
1061            (
1062                "foo[:stop:]",
1063                Ok(ASTValue::Binary(BinaryNode {
1064                    kind: TokenKind::Colon,
1065                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1066                    right: Box::new(ASTValue::Slice(
1067                        loc!(1, 5),
1068                        Box::new(None),
1069                        Box::new(Some(ASTValue::TokenValue(build_identifier("stop", 6)))),
1070                        Box::new(None),
1071                    )),
1072                    start: loc!(1, 4),
1073                })),
1074            ),
1075            (
1076                "foo[::step]",
1077                Ok(ASTValue::Binary(BinaryNode {
1078                    kind: TokenKind::Colon,
1079                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1080                    right: Box::new(ASTValue::Slice(
1081                        loc!(1, 5),
1082                        Box::new(None),
1083                        Box::new(None),
1084                        Box::new(Some(ASTValue::TokenValue(build_identifier("step", 7)))),
1085                    )),
1086                    start: loc!(1, 4),
1087                })),
1088            ),
1089            (
1090                "foo[::]",
1091                Ok(ASTValue::Binary(BinaryNode {
1092                    kind: TokenKind::Colon,
1093                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1094                    right: Box::new(ASTValue::Slice(
1095                        loc!(1, 5),
1096                        Box::new(None),
1097                        Box::new(None),
1098                        Box::new(None),
1099                    )),
1100                    start: loc!(1, 4),
1101                })),
1102            ),
1103            (
1104                "foo[start::step]",
1105                Ok(ASTValue::Binary(BinaryNode {
1106                    kind: TokenKind::Colon,
1107                    left: Box::new(ASTValue::TokenValue(build_identifier("foo", 1))),
1108                    right: Box::new(ASTValue::Slice(
1109                        loc!(1, 5),
1110                        Box::new(Some(ASTValue::TokenValue(build_identifier("start", 5)))),
1111                        Box::new(None),
1112                        Box::new(Some(ASTValue::TokenValue(build_identifier("step", 12)))),
1113                    )),
1114                    start: loc!(1, 4),
1115                })),
1116            ),
1117            (
1118                "foo[start::step:]",
1119                Err(RecognizerError::UnexpectedToken(
1120                    token_text(TokenKind::RightBracket),
1121                    token_text(TokenKind::Colon),
1122                    loc!(1, 16),
1123                )),
1124            ),
1125            (
1126                "[1, 2]",
1127                Ok(ASTValue::List(vec![
1128                    ASTValue::TokenValue(build_integer("1", 1i64, 2)),
1129                    ASTValue::TokenValue(build_integer("2", 2i64, 5)),
1130                ])),
1131            ),
1132            (
1133                "{a: b, c: d}",
1134                Ok(ASTValue::Mapping(vec![
1135                    (
1136                        Token {
1137                            kind: TokenKind::Word,
1138                            text: "a".to_string(),
1139                            value: ScalarValue::Identifier("a".to_string()),
1140                            start: loc!(1, 2),
1141                            end: loc!(1, 2),
1142                        },
1143                        ASTValue::TokenValue(build_identifier("b", 5)),
1144                    ),
1145                    (
1146                        Token {
1147                            kind: TokenKind::Word,
1148                            text: "c".to_string(),
1149                            value: ScalarValue::Identifier("c".to_string()),
1150                            start: loc!(1, 8),
1151                            end: loc!(1, 8),
1152                        },
1153                        ASTValue::TokenValue(build_identifier("d", 11)),
1154                    ),
1155                ])),
1156            ),
1157            (
1158                "-4",
1159                Ok(ASTValue::TokenValue(build_integer("-4", -4i64, 1))),
1160            ),
1161        ];
1162
1163        for case in cases {
1164            let c = Cursor::new(case.0);
1165            let mut parser = Parser::new(Box::new(c)).expect("unable to create parser");
1166            let primary = parser.primary();
1167            let expected = case.1;
1168
1169            match expected {
1170                Ok(e) => {
1171                    let p = primary.expect("a primary was expected");
1172                    assert_eq!(p, e, "failed for {}", case.0);
1173                }
1174                Err(e) => {
1175                    let pe = primary.err().expect("an error was expected");
1176                    assert_eq!(pe, e, "failed for {}", case.0);
1177                }
1178            }
1179        }
1180    }
1181
1182    #[test]
1183    fn exprs() {
1184        let cases = vec![
1185            (
1186                "a or b || c",
1187                Ok(ASTValue::Binary(BinaryNode {
1188                    kind: TokenKind::Or,
1189                    left: Box::new(ASTValue::Binary(BinaryNode {
1190                        kind: TokenKind::Or,
1191                        left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1192                        right: Box::new(ASTValue::TokenValue(build_identifier("b", 6))),
1193                        start: loc!(1, 3),
1194                    })),
1195                    right: Box::new(ASTValue::TokenValue(build_identifier("c", 11))),
1196                    start: loc!(1, 8),
1197                })),
1198            ),
1199            (
1200                "a and b && c",
1201                Ok(ASTValue::Binary(BinaryNode {
1202                    kind: TokenKind::And,
1203                    left: Box::new(ASTValue::Binary(BinaryNode {
1204                        kind: TokenKind::And,
1205                        left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1206                        right: Box::new(ASTValue::TokenValue(build_identifier("b", 7))),
1207                        start: loc!(1, 3),
1208                    })),
1209                    right: Box::new(ASTValue::TokenValue(build_identifier("c", 12))),
1210                    start: loc!(1, 9),
1211                })),
1212            ),
1213            (
1214                "not a",
1215                Ok(ASTValue::Unary(UnaryNode {
1216                    kind: TokenKind::Not,
1217                    operand: Box::new(ASTValue::TokenValue(build_identifier("a", 5))),
1218                    start: loc!(1, 1),
1219                })),
1220            ),
1221            (
1222                "not ! a",
1223                Ok(ASTValue::Unary(UnaryNode {
1224                    kind: TokenKind::Not,
1225                    operand: Box::new(ASTValue::Unary(UnaryNode {
1226                        kind: TokenKind::Not,
1227                        operand: Box::new(ASTValue::TokenValue(build_identifier("a", 7))),
1228                        start: loc!(1, 5),
1229                    })),
1230                    start: loc!(1, 1),
1231                })),
1232            ),
1233            (
1234                "a < b",
1235                Ok(ASTValue::Binary(BinaryNode {
1236                    kind: TokenKind::LessThan,
1237                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1238                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 5))),
1239                    start: loc!(1, 3),
1240                })),
1241            ),
1242            (
1243                "a is not b",
1244                Ok(ASTValue::Binary(BinaryNode {
1245                    kind: TokenKind::IsNot,
1246                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1247                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 10))),
1248                    start: loc!(1, 3),
1249                })),
1250            ),
1251            (
1252                "a not in b",
1253                Ok(ASTValue::Binary(BinaryNode {
1254                    kind: TokenKind::NotIn,
1255                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1256                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 10))),
1257                    start: loc!(1, 3),
1258                })),
1259            ),
1260            (
1261                "a | b",
1262                Ok(ASTValue::Binary(BinaryNode {
1263                    kind: TokenKind::BitwiseOr,
1264                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1265                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 5))),
1266                    start: loc!(1, 3),
1267                })),
1268            ),
1269            (
1270                "a ^ b",
1271                Ok(ASTValue::Binary(BinaryNode {
1272                    kind: TokenKind::BitwiseXor,
1273                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1274                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 5))),
1275                    start: loc!(1, 3),
1276                })),
1277            ),
1278            (
1279                "a & b",
1280                Ok(ASTValue::Binary(BinaryNode {
1281                    kind: TokenKind::BitwiseAnd,
1282                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1283                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 5))),
1284                    start: loc!(1, 3),
1285                })),
1286            ),
1287            (
1288                "a << b",
1289                Ok(ASTValue::Binary(BinaryNode {
1290                    kind: TokenKind::LeftShift,
1291                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1292                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 6))),
1293                    start: loc!(1, 3),
1294                })),
1295            ),
1296            (
1297                "a + b",
1298                Ok(ASTValue::Binary(BinaryNode {
1299                    kind: TokenKind::Plus,
1300                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1301                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 5))),
1302                    start: loc!(1, 3),
1303                })),
1304            ),
1305            (
1306                "a // b",
1307                Ok(ASTValue::Binary(BinaryNode {
1308                    kind: TokenKind::SlashSlash,
1309                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1310                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 6))),
1311                    start: loc!(1, 3),
1312                })),
1313            ),
1314            (
1315                "-a",
1316                Ok(ASTValue::Unary(UnaryNode {
1317                    kind: TokenKind::Minus,
1318                    operand: Box::new(ASTValue::TokenValue(build_identifier("a", 2))),
1319                    start: loc!(1, 1),
1320                })),
1321            ),
1322            (
1323                "a ** b",
1324                Ok(ASTValue::Binary(BinaryNode {
1325                    kind: TokenKind::Power,
1326                    left: Box::new(ASTValue::TokenValue(build_identifier("a", 1))),
1327                    right: Box::new(ASTValue::TokenValue(build_identifier("b", 6))),
1328                    start: loc!(1, 3),
1329                })),
1330            ),
1331        ];
1332
1333        for case in cases {
1334            let c = Cursor::new(case.0);
1335            let mut parser = Parser::new(Box::new(c)).expect("unable to create parser");
1336            let expr = parser.expr();
1337            let expected = case.1;
1338
1339            match expected {
1340                Ok(e) => {
1341                    let ee = expr.expect("an expression was expected");
1342                    assert_eq!(ee, e, "failed for {}", case.0);
1343                    assert!(parser.at_end()); // should have exhausted input looking for more clauses
1344                }
1345                Err(e) => {
1346                    let ee = expr.err().expect("an error was expected");
1347                    assert_eq!(ee, e, "failed for {}", case.0);
1348                }
1349            }
1350        }
1351    }
1352
1353    fn make_map(av: ASTValue) -> HashMap<String, ASTValue> {
1354        let mut result = HashMap::new();
1355
1356        match av {
1357            ASTValue::Mapping(kvps) => {
1358                for (t, v) in kvps {
1359                    let k;
1360
1361                    assert!(t.kind == TokenKind::String || t.kind == TokenKind::Word);
1362                    match t.value {
1363                        ScalarValue::String(s) => {
1364                            k = s;
1365                        }
1366                        ScalarValue::Identifier(s) => {
1367                            k = s;
1368                        }
1369                        e => panic!("unexpected value: {:?}", e),
1370                    }
1371                    result.insert(k, v);
1372                }
1373            }
1374            v => panic!("unexpected value: {:?}", v),
1375        }
1376        result
1377    }
1378
1379    #[test]
1380    fn json() {
1381        let p = data_file_path(&["forms.conf"]);
1382        let f = File::open(&p).expect("Couldn't open forms.conf");
1383        let mut parser = Parser::new(Box::new(f)).expect("unable to create parser");
1384        let r = parser.mapping().expect("parse was not successful");
1385        let k = parser
1386            .consume_new_lines()
1387            .expect("unable to consume newlines at end");
1388
1389        assert!(k == TokenKind::EOF);
1390
1391        let d = make_map(r);
1392
1393        assert!(d.contains_key(&"refs".to_string()));
1394        assert!(d.contains_key(&"fieldsets".to_string()));
1395        assert!(d.contains_key(&"forms".to_string()));
1396        assert!(d.contains_key(&"modals".to_string()));
1397        assert!(d.contains_key(&"pages".to_string()));
1398    }
1399
1400    #[test]
1401    fn config_file() {
1402        let p = data_file_path(&["forms.cfg"]);
1403        let f = File::open(&p).expect("Couldn't open forms.cfg");
1404        let mut parser = Parser::new(Box::new(f)).expect("unable to create parser");
1405        let r = parser.mapping_body().expect("parse was not successful");
1406        let d = make_map(r);
1407
1408        assert!(parser.at_end());
1409        assert!(d.contains_key(&"refs".to_string()));
1410        assert!(d.contains_key(&"fieldsets".to_string()));
1411        assert!(d.contains_key(&"forms".to_string()));
1412        assert!(d.contains_key(&"modals".to_string()));
1413        assert!(d.contains_key(&"pages".to_string()));
1414    }
1415
1416    // Config API tests
1417
1418    #[test]
1419    fn basic() {
1420        let cfg = Config::new();
1421
1422        assert!(cfg.no_duplicates);
1423        //        assert!(cfg.strict_conversions);
1424        assert!(!cfg.contains_key("foo"));
1425        match cfg.get("bar") {
1426            Err(e) => match e {
1427                ConfigError::NotLoaded => {}
1428                _ => panic!("unexpected error type"),
1429            },
1430            _ => panic!("unexpected success"),
1431        }
1432    }
1433
1434    #[test]
1435    fn duplicates() {
1436        let p = data_file_path(&["derived", "dupes.cfg"]);
1437        let mut cfg = Config::new();
1438
1439        // As nothing loaded, get should give a suitable error
1440        match cfg.get("foo") {
1441            Err(e) => match e {
1442                ConfigError::NotLoaded => {}
1443                _ => panic!("expecting NotLoaded, got {:?}", e),
1444            },
1445            Ok(v) => panic!("expecting NotLoaded, got {:?}", v),
1446        }
1447        match cfg.load_from_file(&p) {
1448            Err(ConfigError::DuplicateKey(key, loc, orig_loc)) => {
1449                assert_eq!(key, "foo".to_string());
1450                assert_eq!(loc, loc!(4, 1));
1451                assert_eq!(orig_loc, loc!(1, 1));
1452            }
1453            _ => panic!("the expected error was not returned"),
1454        }
1455        cfg.no_duplicates = false;
1456        match cfg.load_from_file(&p) {
1457            Ok(()) => {
1458                // TODO check that the value of cfg['foo'] == 'not again!'
1459                assert_eq!(
1460                    "not again!".to_string(),
1461                    cfg.get("foo").unwrap().as_string()
1462                );
1463            }
1464            _ => panic!("unexpected failure"),
1465        }
1466    }
1467
1468    #[test]
1469    fn context() {
1470        let p = data_file_path(&["derived", "context.cfg"]);
1471
1472        match Config::from_file(&p) {
1473            Ok(mut cfg) => {
1474                let mut ctx = HashMap::new();
1475
1476                ctx.insert(
1477                    "bozz".to_string(),
1478                    Value::Base(ScalarValue::String("bozz-bozz".to_string())),
1479                );
1480                cfg.set_context(ctx);
1481                assert_eq!("buzz".to_string(), cfg.get("fizz").unwrap().as_string());
1482                assert_eq!("bozz-bozz".to_string(), cfg.get("baz").unwrap().as_string());
1483                match cfg.get("bad") {
1484                    Ok(v) => panic!("Expected error, but got {:?}", v),
1485                    Err(e) => match e {
1486                        ConfigError::UnknownVariable(s, loc) => {
1487                            assert_eq!("not_there".to_string(), s);
1488                            assert_loc!(loc, 3, 7);
1489                        }
1490                        _ => panic!("Expected error wasn't found, got {:?}", e),
1491                    },
1492                }
1493            }
1494            _ => panic!("unexpected failure"),
1495        }
1496    }
1497
1498    #[test]
1499    fn bad_paths() {
1500        let cases: Vec<(&str, RecognizerError)> = vec![
1501            ("foo [1, 2", RecognizerError::UnexpectedListSize(loc!(1, 6))),
1502            ("foo [1] bar", RecognizerError::TrailingText(loc!(1, 9))),
1503            (
1504                "foo.",
1505                RecognizerError::UnexpectedToken(
1506                    token_text(TokenKind::Word),
1507                    token_text(TokenKind::EOF),
1508                    loc!(1, 5),
1509                ),
1510            ),
1511            ("foo.123", RecognizerError::TrailingText(loc!(1, 4))),
1512            ("foo[]", RecognizerError::UnexpectedListSize(loc!(1, 5))),
1513            ("foo[1a]", RecognizerError::InvalidNumber(loc!(1, 6))),
1514            (
1515                "4",
1516                RecognizerError::UnexpectedToken(
1517                    token_text(TokenKind::Word),
1518                    token_text(TokenKind::Number),
1519                    loc!(1, 1),
1520                ),
1521            ),
1522        ];
1523
1524        for case in cases {
1525            let r = parse_path(case.0);
1526            assert_eq!(case.1, r.expect_err("An error was expected, but found: "));
1527        }
1528    }
1529
1530    #[test]
1531    fn identifiers() {
1532        let cases = vec![
1533            ("foo", true),
1534            ("\u{0935}\u{092e}\u{0938}", true),
1535            (
1536                "\u{73b0}\u{4ee3}\u{6c49}\u{8bed}\u{5e38}\u{7528}\u{5b57}\u{8868}",
1537                true,
1538            ),
1539            ("foo ", false),
1540            ("foo[", false),
1541            ("foo [", false),
1542            ("foo.", false),
1543            ("foo .", false),
1544            ("\u{0935}\u{092e}\u{0938}.", false),
1545            (
1546                "\u{73b0}\u{4ee3}\u{6c49}\u{8bed}\u{5e38}\u{7528}\u{5b57}\u{8868}.",
1547                false,
1548            ),
1549            ("9", false),
1550            ("9foo", false),
1551            ("hyphenated-key", false),
1552        ];
1553
1554        for case in cases {
1555            let r = is_identifier(case.0);
1556            assert_eq!(case.1, r)
1557        }
1558    }
1559
1560    #[test]
1561    fn good_paths() {
1562        let unary = ASTValue::Unary(UnaryNode {
1563            kind: TokenKind::Minus,
1564            operand: Box::new(ASTValue::TokenValue(Token {
1565                kind: TokenKind::Word,
1566                text: "bar".to_string(),
1567                value: ScalarValue::Identifier("bar".to_string()),
1568                start: loc!(1, 6),
1569                end: loc!(1, 8),
1570            })),
1571            start: loc!(1, 5),
1572        });
1573
1574        let tokens = vec![
1575            Token {
1576                kind: TokenKind::Word,
1577                text: "foo".to_string(),
1578                value: ScalarValue::Identifier("foo".to_string()),
1579                start: loc!(1, 1),
1580                end: loc!(1, 3),
1581            },
1582            Token {
1583                kind: TokenKind::Word,
1584                text: "baz".to_string(),
1585                value: ScalarValue::Identifier("baz".to_string()),
1586                start: loc!(1, 11),
1587                end: loc!(1, 13),
1588            },
1589            Token {
1590                kind: TokenKind::Word,
1591                text: "bozz".to_string(),
1592                value: ScalarValue::Identifier("bozz".to_string()),
1593                start: loc!(1, 15),
1594                end: loc!(1, 18),
1595            },
1596            Token {
1597                kind: TokenKind::Word,
1598                text: "fizz".to_string(),
1599                value: ScalarValue::Identifier("fizz".to_string()),
1600                start: loc!(1, 23),
1601                end: loc!(1, 26),
1602            },
1603            Token {
1604                kind: TokenKind::Word,
1605                text: "futz".to_string(),
1606                value: ScalarValue::Identifier("futz".to_string()),
1607                start: loc!(1, 32),
1608                end: loc!(1, 35),
1609            },
1610        ];
1611        let slice = ASTValue::Slice(
1612            loc!(1, 28),
1613            Box::new(None),
1614            Box::new(Some(ASTValue::TokenValue(Token {
1615                kind: TokenKind::Number,
1616                text: "3".to_string(),
1617                value: ScalarValue::Integer(3),
1618                start: loc!(1, 29),
1619                end: loc!(1, 29),
1620            }))),
1621            Box::new(None),
1622        );
1623
1624        let indexes = vec![
1625            ASTValue::TokenValue(Token {
1626                kind: TokenKind::Number,
1627                text: "3".to_string(),
1628                value: ScalarValue::Integer(3),
1629                start: loc!(1, 20),
1630                end: loc!(1, 20),
1631            }),
1632            ASTValue::TokenValue(Token {
1633                kind: TokenKind::String,
1634                text: "\'foo\'".to_string(),
1635                value: ScalarValue::String("foo".to_string()),
1636                start: loc!(1, 37),
1637                end: loc!(1, 41),
1638            }),
1639        ];
1640
1641        let cases = vec![(
1642            "foo[-bar].baz.bozz[3].fizz[:3].futz['foo']",
1643            vec![
1644                PathElement::Attribute(&tokens[0]),
1645                PathElement::IndexedAccess(&unary),
1646                PathElement::Attribute(&tokens[1]),
1647                PathElement::Attribute(&tokens[2]),
1648                PathElement::IndexedAccess(&indexes[0]),
1649                PathElement::Attribute(&tokens[3]),
1650                PathElement::SliceAccess(&slice),
1651                PathElement::Attribute(&tokens[4]),
1652                PathElement::IndexedAccess(&indexes[1]),
1653            ],
1654        )];
1655        for case in cases {
1656            let r = parse_path(case.0);
1657
1658            match r {
1659                Err(e) => panic!("Unexpected error {:?}", e),
1660                Ok(node) => {
1661                    let path_elements = unpack_path(&node);
1662
1663                    if case.1.len() > 0 {
1664                        assert_eq!(case.1, path_elements, "failed for {}", case.0);
1665                    } else {
1666                        println!("{:#?}", path_elements);
1667                    }
1668                }
1669            }
1670        }
1671    }
1672
1673    macro_rules! make_seq {
1674        ($($s: expr),+) => {{
1675            let mut result = vec![];
1676
1677            $(result.push(CV!($s));)+
1678            Value::List(result)
1679        }};
1680    }
1681    macro_rules! make_map (
1682    {
1683        $($key:expr => $value:expr),* } => {{
1684            let mut m = HashMap::new();
1685            $(
1686                m.insert($key.to_string(), CV!($value));
1687            )*
1688            Value::Mapping(m)
1689        }};
1690    );
1691
1692    macro_rules! CV {
1693        ($s: expr) => {
1694            Value::from($s)
1695        };
1696    }
1697
1698    macro_rules! make_date {
1699        ($y: expr, $m: expr, $d: expr) => {
1700            CV!(NaiveDate::from_ymd($y, $m, $d))
1701        };
1702    }
1703
1704    macro_rules! make_date_time {
1705        ($y: expr, $mo: expr, $d: expr, $h: expr, $mn: expr, $s: expr, $ns: expr, $os: expr) => {{
1706            let nd = NaiveDate::from_ymd($y, $mo, $d);
1707            let nt = NaiveTime::from_hms_nano($h, $mn, $s, $ns);
1708            let ndt = NaiveDateTime::new(nd, nt);
1709            let offset = FixedOffset::east($os);
1710            let dt = DateTime::<FixedOffset>::from_utc(ndt, offset);
1711            CV!(dt)
1712        }};
1713    }
1714
1715    #[test]
1716    fn slices_and_indices() {
1717        let p = data_file_path(&["derived", "test.cfg"]);
1718        let cfg = Config::from_file(&p).expect("failed to load test.cfg");
1719        let the_list = make_seq!("a", "b", "c", "d", "e", "f", "g");
1720
1721        // slices
1722
1723        let slice_cases = vec![
1724            ("test_list[:]", the_list.clone()),
1725            ("test_list[::]", the_list.clone()),
1726            ("test_list[:20]", the_list.clone()),
1727            ("test_list[-20:4]", make_seq!("a", "b", "c", "d")),
1728            ("test_list[-20:20]", the_list.clone()),
1729            ("test_list[2:]", make_seq!("c", "d", "e", "f", "g")),
1730            ("test_list[-3:]", make_seq!("e", "f", "g")),
1731            ("test_list[-2:2:-1]", make_seq!("f", "e", "d")),
1732            (
1733                "test_list[::-1]",
1734                make_seq!("g", "f", "e", "d", "c", "b", "a"),
1735            ),
1736            ("test_list[2:-2:2]", make_seq!("c", "e")),
1737            ("test_list[::2]", make_seq!("a", "c", "e", "g")),
1738            ("test_list[::3]", make_seq!("a", "d", "g")),
1739            ("test_list[::2][::3]", make_seq!("a", "g")),
1740        ];
1741
1742        for case in slice_cases {
1743            match cfg.get(case.0) {
1744                Err(e) => panic!("unexpected failure {:?}", e),
1745                Ok(v) => assert_eq!(case.1, v),
1746            }
1747        }
1748
1749        // indices
1750
1751        match the_list {
1752            Value::List(items) => {
1753                // non-negative indices
1754
1755                for (i, item) in items.iter().enumerate() {
1756                    let s = format!("test_list[{}]", i);
1757
1758                    match cfg.get(&s) {
1759                        Err(e) => panic!("unexpected failure {:?}", e),
1760                        Ok(v) => assert_eq!(item, &v),
1761                    }
1762                }
1763
1764                // negative indices
1765
1766                let n = items.len();
1767
1768                for i in (1..n + 1).rev() {
1769                    let s = format!("test_list[-{}]", i);
1770
1771                    match cfg.get(&s) {
1772                        Err(e) => panic!("unexpected failure {:?}", e),
1773                        Ok(v) => assert_eq!(items[n - i], v),
1774                    }
1775                }
1776
1777                // invalid indices
1778
1779                let len = n as i64;
1780                let bad_indices = vec![len, len + 1, -(len + 1), -(len + 2)];
1781
1782                for i in bad_indices {
1783                    let s = format!("test_list[{}]", i);
1784
1785                    match cfg.get(&s) {
1786                        Ok(v) => panic!("unexpected success {:?}", v),
1787                        Err(e) => match e {
1788                            ConfigError::IndexOutOfRange(index, pos) => {
1789                                assert_eq!(index, i);
1790                                assert_loc!(pos, 1, 11);
1791                            }
1792                            _ => panic!("unexpected error type {:?}", e),
1793                        },
1794                    }
1795                }
1796            }
1797            _ => panic!("unexpected variant for the_list"),
1798        }
1799    }
1800
1801    macro_rules! assert_delta {
1802        ($a:expr, $b:expr) => {{
1803            assert_delta!($a, $b, 1.0e-6);
1804        }};
1805        ($a:expr, $b:expr, $eps:expr) => {{
1806            let av: f64 = match $a {
1807                Value::Base(ref sv) => match &sv {
1808                    ScalarValue::Float(f) => *f,
1809                    _ => panic!("not the correct type: {:?}", $a),
1810                },
1811                _ => panic!("not the correct type: {:?}", $a),
1812            };
1813            let bv: f64 = match $b {
1814                Value::Base(ref sv) => match &sv {
1815                    ScalarValue::Float(f) => *f,
1816                    _ => panic!("not the correct type: {:?}", $a),
1817                },
1818                _ => panic!("not the correct type: {:?}", $b),
1819            };
1820            let (a, b) = (&av, &bv);
1821            let eps = $eps;
1822            let diff = (*a - *b).abs();
1823            assert!(
1824                diff < eps,
1825                "assertion failed: `(left !== right)` \
1826                 (left: `{:?}`, right: `{:?}`, min diff: `{:?}`, diff: `{:?}`)",
1827                *a,
1828                *b,
1829                eps,
1830                diff
1831            );
1832        }};
1833    }
1834
1835    #[test]
1836    fn main_config() {
1837        let mut cfg = Config::new();
1838        let ip = data_file_path(&["base"]);
1839
1840        cfg.add_include(&ip);
1841        let p = data_file_path(&["derived", "main.cfg"]);
1842        cfg.load_from_file(&p).expect("failed to load main.cfg");
1843
1844        let main_success_cases = vec![(
1845            "combined_list",
1846            make_seq!(
1847                "derived_foo",
1848                "derived_bar",
1849                "derived_baz",
1850                "test_foo",
1851                "test_bar",
1852                "test_baz",
1853                "base_foo",
1854                "base_bar",
1855                "base_baz"
1856            ),
1857            (
1858                "combined_map_1",
1859                make_map!(
1860                    "foo_key" => "base_foo",
1861                    "bar_key" => "base_bar",
1862                    "baz_key" => "base_baz",
1863                    "base_foo_key" => "base_foo",
1864                    "base_bar_key" => "base_bar",
1865                    "base_baz_key" => "base_baz",
1866                    "derived_foo_key" => "derived_foo",
1867                    "derived_bar_key" => "derived_bar",
1868                    "derived_baz_key" => "derived_baz",
1869                    "test_foo_key" => "test_foo",
1870                    "test_bar_key" => "test_bar",
1871                    "test_baz_key" => "test_baz"
1872                ),
1873            ),
1874            (
1875                "combined_map_2",
1876                make_map!(
1877                    "derived_foo_key" => "derived_foo",
1878                    "derived_bar_key" => "derived_bar",
1879                    "derived_baz_key" => "derived_baz"
1880                ),
1881            ),
1882        )];
1883
1884        for case in main_success_cases {
1885            let r = cfg.get(case.0);
1886            match r {
1887                Err(e) => panic!("unexpected failure {:?}", e),
1888                Ok(v) => assert_eq!(case.1, v),
1889            }
1890        }
1891
1892        let n1 = cfg.get("number_1").unwrap().as_i64();
1893        let n2 = cfg.get("number_2").unwrap().as_i64();
1894        let n3 = cfg.get("number_3").unwrap().as_i64();
1895        let n4 = cfg.get("number_4").unwrap().as_i64();
1896
1897        assert_eq!(n1 & n2, n3);
1898        assert_eq!(n1 ^ n2, n4);
1899
1900        let log_conf: Config;
1901
1902        match cfg
1903            .get("logging")
1904            .expect("failed to get 'logging' sub-configuration")
1905        {
1906            Value::Config(cfg) => {
1907                for k in &["root", "loggers", "handlers", "formatters"] {
1908                    assert!(cfg.contains_key(k));
1909                }
1910                log_conf = cfg;
1911            }
1912            _ => panic!("unexpected return type"),
1913        }
1914
1915        let log_success_cases = vec![
1916            ("handlers.file.filename", CV!("run/server.log")),
1917            ("handlers.debug.filename", CV!("run/server-debug.log")),
1918            ("root.handlers", make_seq!("file", "error", "debug")),
1919            ("root.handlers[:2]", make_seq!("file", "error")),
1920            ("root.handlers[::2]", make_seq!("file", "debug")),
1921        ];
1922
1923        let log_failure_cases = vec![
1924            (
1925                "handlers.file/filename",
1926                ConfigError::InvalidPath(RecognizerError::TrailingText(loc!(1, 14))),
1927            ),
1928            (
1929                "\"handlers.file/filename",
1930                ConfigError::InvalidPath(RecognizerError::UnterminatedString(loc!(1, 23))),
1931            ),
1932            (
1933                "handlers.debug.levl",
1934                ConfigError::NotPresent(
1935                    "levl".to_string(),
1936                    Some(Location {
1937                        line: 1,
1938                        column: 16,
1939                    }),
1940                ),
1941            ),
1942        ];
1943
1944        for case in log_failure_cases {
1945            match log_conf.get(case.0) {
1946                Ok(v) => panic!("expected failure not seen, got {:?}", v),
1947                Err(e) => assert_eq!(case.1, e),
1948            }
1949        }
1950
1951        for case in log_success_cases {
1952            match log_conf.get(case.0) {
1953                Err(e) => panic!("unexpected failure {:?}", e),
1954                Ok(v) => assert_eq!(case.1, v),
1955            }
1956        }
1957
1958        let test: Config;
1959
1960        match cfg
1961            .get("test")
1962            .expect("failed to get 'test' sub-configuration")
1963        {
1964            Value::Config(cfg) => {
1965                test = cfg;
1966            }
1967            _ => panic!("unexpected return type"),
1968        }
1969
1970        match cfg
1971            .get("base")
1972            .expect("failed to get 'base' sub-configuration")
1973        {
1974            Value::Config(_) => {}
1975            _ => panic!("unexpected return type"),
1976        }
1977
1978        let test_success_cases = vec![
1979            ("float", CV!(1.0e-7f64), false),
1980            ("float2", CV!(0.3f64), false),
1981            ("float3", CV!(3.0f64), false),
1982            ("list[1]", CV!(2i64), false),
1983            ("dict.a", CV!("b"), false),
1984            ("date", make_date!(2019, 3, 28), false),
1985            (
1986                "date_time",
1987                make_date_time!(2019, 3, 28, 23, 27, 4, 314159000, 19800),
1988                false,
1989            ),
1990            (
1991                "neg_offset_time",
1992                make_date_time!(2019, 3, 28, 23, 27, 4, 314159000, -19800),
1993                false,
1994            ),
1995            (
1996                "alt_date_time",
1997                make_date_time!(2019, 3, 28, 23, 27, 4, 271828000, 0),
1998                false,
1999            ),
2000            (
2001                "no_ms_time",
2002                make_date_time!(2019, 3, 28, 23, 27, 4, 0, 0),
2003                false,
2004            ),
2005            ("computed", CV!(3.3), false),
2006            ("computed2", CV!(2.7), false),
2007            ("computed3", CV!(0.9), true),
2008            ("computed4", CV!(10.0), false),
2009        ];
2010
2011        for case in test_success_cases {
2012            match test.get(case.0) {
2013                Err(e) => panic!("unexpected failure {:?}", e),
2014                Ok(v) => {
2015                    if !case.2 {
2016                        assert_eq!(case.1, v);
2017                    } else {
2018                        assert_delta!(case.1, v);
2019                    }
2020                }
2021            }
2022        }
2023
2024        // test unboxers
2025        assert_eq!(test.get("dict.a").unwrap().as_string(), "b".to_string());
2026        assert_eq!(test.get("list[1]").unwrap().as_i64(), 2);
2027        assert_eq!(test.get("float3").unwrap().as_f64(), 3.0);
2028        assert_eq!(test.get("c1").unwrap().as_c64(), Complex64::new(4.0, 3.0));
2029        assert_eq!(test.get("boolean").unwrap().as_bool(), true);
2030        assert_eq!(
2031            test.get("date").unwrap().as_date(),
2032            NaiveDate::from_ymd(2019, 3, 28)
2033        );
2034        let nd = NaiveDate::from_ymd(2019, 3, 28);
2035        let nt = NaiveTime::from_hms_nano(23, 27, 4, 0);
2036        let ndt = NaiveDateTime::new(nd, nt);
2037        let offset = FixedOffset::east(0);
2038        let dt = DateTime::<FixedOffset>::from_utc(ndt, offset);
2039        assert_eq!(test.get("no_ms_time").unwrap().as_datetime(), dt);
2040    }
2041
2042    #[test]
2043    fn example_config() {
2044        let mut cfg = Config::new();
2045        let ip = data_file_path(&["base"]);
2046
2047        cfg.add_include(&ip);
2048        let p = data_file_path(&["derived", "example.cfg"]);
2049        cfg.load_from_file(&p).expect("failed to load example.cfg");
2050
2051        assert_eq!(cfg.get("snowman_escaped"), cfg.get("snowman_unescaped"));
2052
2053        let success_cases = vec![
2054            ("snowman_escaped", CV!("\u{2603}")),
2055            ("face_with_tears_of_joy", CV!("\u{01F602}")),
2056            ("unescaped_face_with_tears_of_joy", CV!("\u{01F602}")),
2057            ("special_value_2", CV!(env::var("HOME").unwrap())),
2058            (
2059                "special_value_3",
2060                make_date_time!(2019, 3, 28, 23, 27, 4, 314159000, 19800),
2061            ),
2062            ("special_value_4", CV!("bar")),
2063            // integers
2064            ("decimal_integer", CV!(123)),
2065            ("hexadecimal_integer", CV!(0x123)),
2066            ("octal_integer", CV!(83)),
2067            ("binary_integer", CV!(0b000100100011)),
2068            // floats
2069            ("common_or_garden", CV!(123.456)),
2070            ("leading_zero_not_needed", CV!(0.123)),
2071            ("trailing_zero_not_needed", CV!(123.0)),
2072            ("scientific_large", CV!(1.0e6)),
2073            ("scientific_small", CV!(1.0e-7)),
2074            ("expression_1", CV!(3.14159)),
2075            // complex
2076            ("expression_2", CV!(Complex64::new(3.0, 2.0))),
2077            ("list_value[4]", CV!(Complex64::new(1.0, 3.0))),
2078            //bool
2079            ("boolean_value", CV!(true)),
2080            ("opposite_boolean_value", CV!(false)),
2081            ("computed_boolean_2", CV!(false)),
2082            ("computed_boolean_1", CV!(true)),
2083            //sequence
2084            ("incl_list", make_seq!("a", "b", "c")),
2085            //mappings
2086            (
2087                "incl_mapping",
2088                make_map!(
2089                    "bar" => "baz",
2090                    "foo" => "bar"
2091                ),
2092            ),
2093            (
2094                "incl_mapping_body",
2095                make_map!(
2096                    "baz" => "bozz",
2097                    "fizz" => "buzz"
2098                ),
2099            ),
2100        ];
2101
2102        for case in success_cases {
2103            match cfg.get(case.0) {
2104                Err(e) => panic!("unexpected error {:?}", e),
2105                Ok(v) => match v {
2106                    Value::Config(c) => {
2107                        let m = c.as_mapping(true).unwrap();
2108                        assert_eq!(case.1, Value::Mapping(m), "failed for {}", case.0);
2109                    }
2110                    _ => assert_eq!(case.1, v, "failed for {}", case.0),
2111                },
2112            }
2113        }
2114
2115        let v = cfg.get("strings").unwrap();
2116        match &v {
2117            Value::List(strings) => {
2118                assert_eq!(CV!("Oscar Fingal O'Flahertie Wills Wilde"), strings[0]);
2119                assert_eq!(CV!("size: 5\""), strings[1]);
2120                if cfg!(windows) {
2121                    assert_eq!(
2122                        CV!("Triple quoted form\r\ncan span\r\n'multiple' lines"),
2123                        strings[2]
2124                    );
2125                    assert_eq!(
2126                        CV!("with \"either\"\r\nkind of 'quote' embedded within"),
2127                        strings[3]
2128                    );
2129                } else {
2130                    assert_eq!(
2131                        CV!("Triple quoted form\ncan span\n'multiple' lines"),
2132                        strings[2]
2133                    );
2134                    assert_eq!(
2135                        CV!("with \"either\"\nkind of 'quote' embedded within"),
2136                        strings[3]
2137                    );
2138                }
2139            }
2140            _ => panic!("list expected, but got {:?}", v),
2141        }
2142    }
2143
2144    #[test]
2145    fn expressions() {
2146        let p = data_file_path(&["derived", "test.cfg"]);
2147        let cfg = Config::from_file(&p).expect("failed to load test.cfg");
2148
2149        let success_cases = vec![
2150            (
2151                "dicts_added",
2152                make_map!(
2153                    "a" => "b",
2154                    "c" => "d"
2155                ),
2156            ),
2157            (
2158                "nested_dicts_added",
2159                make_map!(
2160                    "a" => make_map!("b" => "c", "w" => "x"),
2161                    "d" => make_map!("e" => "f", "y" => "z")
2162                ),
2163            ),
2164            ("lists_added", make_seq!("a", 1, "b", 2)),
2165            ("list[:2]", make_seq!(1, 2)),
2166            ("dicts_subtracted", make_map!("a" => "b")),
2167            ("nested_dicts_subtracted", Value::Mapping(HashMap::new())),
2168            (
2169                "dict_with_nested_stuff",
2170                make_map!(
2171                    "a_list" => make_seq!(1, 2, make_map!("a" => 3)),
2172                    "a_map" => make_map!("k1" => make_seq!("b", "c", make_map!("d" => "e")))
2173                ),
2174            ),
2175            ("dict_with_nested_stuff.a_list[:2]", make_seq!(1, 2)),
2176            ("unary", CV!(-4)),
2177            ("abcdefghijkl", CV!("mno")),
2178            ("power", CV!(8)),
2179            ("computed5", CV!(2.5)),
2180            ("computed6", CV!(2)),
2181            ("c3", CV!(Complex64::new(3.0, 1.0))),
2182            ("c4", CV!(Complex64::new(5.0, 5.0))),
2183            ("computed8", CV!(2)),
2184            ("computed9", CV!(160)),
2185            ("computed10", CV!(62)),
2186            (
2187                "interp",
2188                CV!("A-4 a test_foo true 10 0.0000001 1 b [a, c, e, g]Z"),
2189            ),
2190            ("interp2", CV!("{a: b}")),
2191        ];
2192
2193        for case in success_cases {
2194            match cfg.get(case.0) {
2195                Err(e) => panic!("unexpected failure {:?}", e),
2196                Ok(v) => assert_eq!(case.1, v),
2197            }
2198        }
2199
2200        let failure_cases = vec![
2201            ("bad_include", ConfigError::StringExpected(loc!(71, 16))),
2202            (
2203                "computed7",
2204                ConfigError::NotPresent(
2205                    "float4".to_string(),
2206                    Some(Location {
2207                        line: 72,
2208                        column: 16,
2209                    }),
2210                ),
2211            ),
2212            (
2213                "bad_interp",
2214                ConfigError::ConversionError("${computed7}".to_string()),
2215            ),
2216        ];
2217
2218        for case in failure_cases {
2219            match cfg.get(case.0) {
2220                Ok(v) => panic!("unexpected success {:?}", v),
2221                Err(e) => assert_eq!(case.1, e),
2222            }
2223        }
2224    }
2225
2226    #[test]
2227    fn circular_references() {
2228        let p = data_file_path(&["derived", "test.cfg"]);
2229        let cfg = Config::from_file(&p).expect("failed to load test.cfg");
2230
2231        let cases = vec![
2232            (
2233                "circ_list[1]",
2234                ConfigError::CircularReferenceError(vec![(
2235                    "circ_list[1]".to_string(),
2236                    loc!(46, 5),
2237                )]),
2238            ),
2239            (
2240                "circ_map.a",
2241                ConfigError::CircularReferenceError(vec![
2242                    (
2243                        "circ_map.a".to_string(),
2244                        Location {
2245                            line: 53,
2246                            column: 8,
2247                        },
2248                    ),
2249                    ("circ_map.b".to_string(), loc!(51, 8)),
2250                    ("circ_map.c".to_string(), loc!(52, 8)),
2251                ]),
2252            ),
2253        ];
2254
2255        for case in cases {
2256            match cfg.get(case.0) {
2257                Ok(v) => panic!("unexpected success {:?}", v),
2258                Err(e) => assert_eq!(case.1, e),
2259            }
2260        }
2261    }
2262
2263    #[test]
2264    fn sources() {
2265        let cases = vec![
2266            "foo[::2]",
2267            "foo[:]",
2268            "foo[:2]",
2269            "foo[2:]",
2270            "foo[::1]",
2271            "foo[::-1]",
2272            "foo[2]",
2273        ];
2274
2275        for case in cases {
2276            let node = parse_path(case).unwrap();
2277            let s = to_source(&node);
2278
2279            assert_eq!(case, s);
2280        }
2281    }
2282
2283    #[test]
2284    fn path_across_includes() {
2285        let p = data_file_path(&["base", "main.cfg"]);
2286        let cfg = Config::from_file(&p).expect("failed to load base main.cfg");
2287
2288        let cases = vec![
2289            ("logging.appenders.file.filename", CV!("run/server.log")),
2290            ("logging.appenders.file.append", CV!(true)),
2291            (
2292                "logging.appenders.error.filename",
2293                CV!("run/server-errors.log"),
2294            ),
2295            ("logging.appenders.error.append", CV!(false)),
2296            ("redirects.freeotp.url", CV!("https://freeotp.github.io/")),
2297            ("redirects.freeotp.permanent", CV!(false)),
2298        ];
2299
2300        for case in cases {
2301            let msg = format!("failed for {}", case.0);
2302            assert_eq!(case.1, cfg.get(case.0).unwrap(), "{}", msg);
2303        }
2304    }
2305
2306    #[test]
2307    fn forms() {
2308        let mut cfg = Config::new();
2309        let ip = data_file_path(&["base"]);
2310
2311        cfg.add_include(&ip);
2312        let p = data_file_path(&["derived", "forms.cfg"]);
2313        cfg.load_from_file(&p).expect("failed to load forms.cfg");
2314
2315        let cases = vec![
2316            ("modals.deletion.contents[0].id", CV!("frm-deletion")),
2317            (
2318                "refs.delivery_address_field",
2319                make_map!(
2320                    "kind" => "field",
2321                    "type" => "textarea",
2322                    "name" => "postal_address",
2323                    "label" => "Postal address",
2324                    "label_i18n" => "postal-address",
2325                    "short_name" => "address",
2326                    "placeholder" => "We need this for delivering to you",
2327                    "ph_i18n" => "your-postal-address",
2328                    "message" => " ",
2329                    "required" => true,
2330                    "attrs" => make_map!("minlength" => 10),
2331                    "grpclass" => "col-md-6"
2332                ),
2333            ),
2334            (
2335                "refs.delivery_instructions_field",
2336                make_map!(
2337                    "kind" => "field",
2338                    "type" => "textarea",
2339                    "name" => "delivery_instructions",
2340                    "label" => "Delivery Instructions",
2341                    "short_name" => "notes",
2342                    "placeholder" => "Any special delivery instructions?",
2343                    "message" => " ",
2344                    "label_i18n" => "delivery-instructions",
2345                    "ph_i18n" => "any-special-delivery-instructions",
2346                    "grpclass" => "col-md-6"
2347                ),
2348            ),
2349            (
2350                "refs.verify_field",
2351                make_map!(
2352                    "kind" => "field",
2353                    "type" => "input",
2354                    "name" => "verification_code",
2355                    "label" => "Verification code",
2356                    "label_i18n" => "verification-code",
2357                    "short_name" => "verification code",
2358                    "placeholder" => "Your verification code (NOT a backup code)",
2359                    "ph_i18n" => "verification-not-backup-code",
2360                    "attrs" => make_map!(
2361                            "minlength" => 6,
2362                            "maxlength" => 6,
2363                            "autofocus" => true),
2364                    "append" => make_map!(
2365                            "label" => "Verify",
2366                            "type" => "submit",
2367                            "classes" => "btn-primary"),
2368                    "message" => " ",
2369                    "required" => true
2370                ),
2371            ),
2372            (
2373                "refs.signup_password_field",
2374                make_map!(
2375                    "kind" => "field",
2376                    "type" => "password",
2377                    "label" => "Password",
2378                    "label_i18n" => "password",
2379                    "message" => " ",
2380                    "name" => "password",
2381                    "ph_i18n" => "password-wanted-on-site",
2382                    "placeholder" => "The password you want to use on this site",
2383                    "required" => true,
2384                    "toggle" => true
2385                ),
2386            ),
2387            (
2388                "refs.signup_password_conf_field",
2389                make_map!(
2390                    "kind" => "field",
2391                    "type" => "password",
2392                    "name" => "password_conf",
2393                    "label" => "Password confirmation",
2394                    "label_i18n" => "password-confirmation",
2395                    "placeholder" => "The same password, again, to guard against mistyping",
2396                    "ph_i18n" => "same-password-again",
2397                    "message" => " ",
2398                    "toggle" => true,
2399                    "required" => true
2400                ),
2401            ),
2402            (
2403                "fieldsets.signup_ident[0].contents[0]",
2404                make_map!(
2405                    "kind" => "field",
2406                    "type" => "input",
2407                    "name" => "display_name",
2408                    "label" => "Your name",
2409                    "label_i18n" => "your-name",
2410                    "placeholder" => "Your full name",
2411                    "ph_i18n" => "your-full-name",
2412                    "message" => " ",
2413                    "data_source" => "user.display_name",
2414                    "required" => true,
2415                    "attrs" => make_map!("autofocus" => true),
2416                    "grpclass" => "col-md-6"
2417                ),
2418            ),
2419            (
2420                "fieldsets.signup_ident[0].contents[1]",
2421                make_map!(
2422                    "kind" => "field",
2423                    "type" => "input",
2424                    "name" => "familiar_name",
2425                    "label" => "Familiar name",
2426                    "label_i18n" => "familiar-name",
2427                    "placeholder" => "If not just the first word in your full name",
2428                    "ph_i18n" => "if-not-first-word",
2429                    "data_source" => "user.familiar_name",
2430                    "message" => " ",
2431                    "grpclass" => "col-md-6"
2432                ),
2433            ),
2434            (
2435                "fieldsets.signup_ident[1].contents[0]",
2436                make_map!(
2437                    "kind" => "field",
2438                    "type" => "email",
2439                    "name" => "email",
2440                    "label" => "Email address (used to sign in)",
2441                    "label_i18n" => "email-address",
2442                    "short_name" => "email address",
2443                    "placeholder" => "Your email address",
2444                    "ph_i18n" => "your-email-address",
2445                    "message" => " ",
2446                    "required" => true,
2447                    "data_source" => "user.email",
2448                    "grpclass" => "col-md-6"
2449                ),
2450            ),
2451            (
2452                "fieldsets.signup_ident[1].contents[1]",
2453                make_map!(
2454                    "kind" => "field",
2455                    "type" => "input",
2456                    "name" => "mobile_phone",
2457                    "label" => "Phone number",
2458                    "label_i18n" => "phone-number",
2459                    "short_name" => "phone number",
2460                    "placeholder" => "Your phone number",
2461                    "ph_i18n" => "your-phone-number",
2462                    "classes" => "numeric",
2463                    "message" => " ",
2464                    "prepend" => make_map!("icon" => "phone"),
2465                    "attrs" => make_map!("maxlength" => 10),
2466                    "required" => true,
2467                    "data_source" => "customer.mobile_phone",
2468                    "grpclass" => "col-md-6"
2469                ),
2470            ),
2471        ];
2472
2473        for case in cases {
2474            match cfg.get(case.0) {
2475                Err(e) => panic!("unexpected failure {:?}", e),
2476                Ok(v) => assert_eq!(case.1, v),
2477            }
2478        }
2479    }
2480
2481    #[test]
2482    fn absolute_include_path() {
2483        let mut p = data_file_path(&["derived", "test.cfg"]);
2484        p = canonicalize(&p)
2485            .unwrap()
2486            .to_str()
2487            .unwrap()
2488            .replace("\\", "/") // for Windows - avoid escape sequence-like stuff
2489            .to_string();
2490
2491        let source = "test: @'foo'".replace("foo", &p);
2492        let mut cfg = Config::new();
2493
2494        cfg.load(Box::new(Cursor::new(source)))
2495            .expect("couldn't load from source");
2496        match cfg.get("test.computed6") {
2497            Err(e) => panic!("unexpected failure {:?}", e),
2498            Ok(v) => assert_eq!(2i64, v.as_i64()),
2499        }
2500    }
2501
2502    #[test]
2503    fn nested_include_path() {
2504        let p = data_file_path(&["base", "top.cfg"]);
2505        let mut ip = data_file_path(&["derived"]);
2506        let mut cfg = Config::new();
2507
2508        cfg.add_include(&ip);
2509        ip = data_file_path(&["another"]);
2510        cfg.add_include(&ip);
2511        cfg.load_from_file(&p).expect("failed to load forms.cfg");
2512
2513        match cfg.get("level1.level2.final") {
2514            Err(e) => panic!("unexpected failure {:?}", e),
2515            Ok(v) => assert_eq!(42i64, v.as_i64()),
2516        }
2517    }
2518
2519    #[test]
2520    fn windows_line_endings() {
2521        let p = data_file_path(&["derived", "testwin.cfg"]);
2522        let _cfg = Config::from_file(&p).expect("failed to load testwin.cfg");
2523    }
2524
2525    #[test]
2526    fn wip_locations() {
2527        /*
2528                let p = data_file_path(&["derived", "test.cfg"]);
2529                let mut f = File::open(&p).expect("Couldn't open test.cfg");
2530                let mut tokenizer = Tokenizer::new(Box::new(f));
2531                let mut t = tokenizer.get_token().expect("a token was expected");
2532
2533                while t.kind != TokenKind::EOF {
2534                    let kind = format!("{:?}", t.kind);
2535                    println!(
2536                        "({:>3} {:>3}) - ({:>3} {:>3}) {:>12} {}",
2537                        t.start.line, t.start.column, t.end.line, t.end.column, kind, t.text
2538                    );
2539                    t = tokenizer.get_token().expect("a token was expected");
2540                }
2541                println!("Done.");
2542        */
2543    }
2544}