1#![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();
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();
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 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 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 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()); }
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 #[test]
1419 fn basic() {
1420 let cfg = Config::new();
1421
1422 assert!(cfg.no_duplicates);
1423 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 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 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 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 match the_list {
1752 Value::List(items) => {
1753 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 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 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 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 ("decimal_integer", CV!(123)),
2065 ("hexadecimal_integer", CV!(0x123)),
2066 ("octal_integer", CV!(83)),
2067 ("binary_integer", CV!(0b000100100011)),
2068 ("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 ("expression_2", CV!(Complex64::new(3.0, 2.0))),
2077 ("list_value[4]", CV!(Complex64::new(1.0, 3.0))),
2078 ("boolean_value", CV!(true)),
2080 ("opposite_boolean_value", CV!(false)),
2081 ("computed_boolean_2", CV!(false)),
2082 ("computed_boolean_1", CV!(true)),
2083 ("incl_list", make_seq!("a", "b", "c")),
2085 (
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("\\", "/") .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 }
2544}