1 use std::fmt;
2 use std::iter::FromIterator;
3 
4 use snapbox::assert_eq;
5 use toml_edit::{array, table, value, Document, Item, Key, Table, Value};
6 
7 macro_rules! parse_key {
8     ($s:expr) => {{
9         let key = $s.parse::<Key>();
10         assert!(key.is_ok());
11         key.unwrap()
12     }};
13 }
14 
15 macro_rules! as_table {
16     ($e:ident) => {{
17         assert!($e.is_table());
18         $e.as_table_mut().unwrap()
19     }};
20 }
21 
22 // Copied from https://github.com/colin-kiegel/rust-pretty-assertions/issues/24
23 /// Wrapper around string slice that makes debug output `{:?}` to print string same way as `{}`.
24 /// Used in different `assert*!` macros in combination with `pretty_assertions` crate to make
25 /// test failures to show nice diffs.
26 #[derive(PartialEq, Eq)]
27 struct PrettyString<'a>(pub &'a str);
28 /// Make diff to display string as multi-line string
29 impl<'a> fmt::Debug for PrettyString<'a> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result30     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31         f.write_str(self.0)
32     }
33 }
34 
35 struct Test {
36     doc: Document,
37 }
38 
given(input: &str) -> Test39 fn given(input: &str) -> Test {
40     let doc = input.parse::<Document>();
41     assert!(doc.is_ok());
42     Test { doc: doc.unwrap() }
43 }
44 
45 impl Test {
running<F>(&mut self, func: F) -> &mut Self where F: Fn(&mut Table),46     fn running<F>(&mut self, func: F) -> &mut Self
47     where
48         F: Fn(&mut Table),
49     {
50         {
51             let root = self.doc.as_table_mut();
52             func(root);
53         }
54         self
55     }
56 
57     #[track_caller]
produces_display(&self, expected: &str) -> &Self58     fn produces_display(&self, expected: &str) -> &Self {
59         assert_eq(expected, self.doc.to_string());
60         self
61     }
62 }
63 
64 // insertion
65 
66 #[test]
test_insert_leaf_table()67 fn test_insert_leaf_table() {
68     given(
69         r#"[servers]
70 
71         [servers.alpha]
72         ip = "10.0.0.1"
73         dc = "eqdc10"
74 
75         [other.table]"#,
76     )
77     .running(|root| {
78         root["servers"]["beta"] = table();
79         root["servers"]["beta"]["ip"] = value("10.0.0.2");
80         root["servers"]["beta"]["dc"] = value("eqdc10");
81     })
82     .produces_display(
83         r#"[servers]
84 
85         [servers.alpha]
86         ip = "10.0.0.1"
87         dc = "eqdc10"
88 
89 [servers.beta]
90 ip = "10.0.0.2"
91 dc = "eqdc10"
92 
93         [other.table]
94 "#,
95     );
96 }
97 
98 #[test]
test_inserted_leaf_table_goes_after_last_sibling()99 fn test_inserted_leaf_table_goes_after_last_sibling() {
100     given(
101         r#"
102         [package]
103         [dependencies]
104         [[example]]
105         [dependencies.opencl]
106         [dev-dependencies]"#,
107     )
108     .running(|root| {
109         root["dependencies"]["newthing"] = table();
110     })
111     .produces_display(
112         r#"
113         [package]
114         [dependencies]
115         [[example]]
116         [dependencies.opencl]
117 
118 [dependencies.newthing]
119         [dev-dependencies]
120 "#,
121     );
122 }
123 
124 #[test]
test_inserting_tables_from_different_parsed_docs()125 fn test_inserting_tables_from_different_parsed_docs() {
126     given("[a]")
127         .running(|root| {
128             let other = "[b]".parse::<Document>().unwrap();
129             root["b"] = other["b"].clone();
130         })
131         .produces_display("[a]\n[b]\n");
132 }
133 #[test]
test_insert_nonleaf_table()134 fn test_insert_nonleaf_table() {
135     given(
136         r#"
137         [other.table]"#,
138     )
139     .running(|root| {
140         root["servers"] = table();
141         root["servers"]["alpha"] = table();
142         root["servers"]["alpha"]["ip"] = value("10.0.0.1");
143         root["servers"]["alpha"]["dc"] = value("eqdc10");
144     })
145     .produces_display(
146         r#"
147         [other.table]
148 
149 [servers]
150 
151 [servers.alpha]
152 ip = "10.0.0.1"
153 dc = "eqdc10"
154 "#,
155     );
156 }
157 
158 #[test]
test_insert_array()159 fn test_insert_array() {
160     given(
161         r#"
162         [package]
163         title = "withoutarray""#,
164     )
165     .running(|root| {
166         root["bin"] = array();
167         assert!(root["bin"].is_array_of_tables());
168         let array = root["bin"].as_array_of_tables_mut().unwrap();
169         {
170             let mut table = Table::new();
171             table["hello"] = value("world");
172             array.push(table);
173         }
174         array.push(Table::new());
175     })
176     .produces_display(
177         r#"
178         [package]
179         title = "withoutarray"
180 
181 [[bin]]
182 hello = "world"
183 
184 [[bin]]
185 "#,
186     );
187 }
188 
189 #[test]
test_insert_values()190 fn test_insert_values() {
191     given(
192         r#"
193         [tbl.son]"#,
194     )
195     .running(|root| {
196         root["tbl"]["key1"] = value("value1");
197         root["tbl"]["key2"] = value(42);
198         root["tbl"]["key3"] = value(8.1415926);
199     })
200     .produces_display(
201         r#"[tbl]
202 key1 = "value1"
203 key2 = 42
204 key3 = 8.1415926
205 
206         [tbl.son]
207 "#,
208     );
209 }
210 
211 // removal
212 
213 #[test]
test_remove_leaf_table()214 fn test_remove_leaf_table() {
215     given(
216         r#"
217         [servers]
218 
219         # Indentation (tabs and/or spaces) is allowed but not required
220 [servers.alpha]
221         ip = "10.0.0.1"
222         dc = "eqdc10"
223 
224         [servers.beta]
225         ip = "10.0.0.2"
226         dc = "eqdc10""#,
227     )
228     .running(|root| {
229         let servers = root.get_mut("servers").unwrap();
230         let servers = as_table!(servers);
231         assert!(servers.remove("alpha").is_some());
232     })
233     .produces_display(
234         r#"
235         [servers]
236 
237         [servers.beta]
238         ip = "10.0.0.2"
239         dc = "eqdc10"
240 "#,
241     );
242 }
243 
244 #[test]
test_remove_nonleaf_table()245 fn test_remove_nonleaf_table() {
246     given(
247         r#"
248         title = "not relevant"
249 
250         # comment 1
251         [a.b.c] # comment 1.1
252         key1 = 1 # comment 1.2
253         # comment 2
254         [b] # comment 2.1
255         key2 = 2 # comment 2.2
256 
257         # comment 3
258         [a] # comment 3.1
259         key3 = 3 # comment 3.2
260         [[a.'array']]
261         b = 1
262 
263         [[a.b.c.trololololololo]] # ohohohohoho
264         c = 2
265         key3 = 42
266 
267            # comment on some other table
268            [some.other.table]
269 
270 
271 
272 
273         # comment 4
274         [a.b] # comment 4.1
275         key4 = 4 # comment 4.2
276         key41 = 41 # comment 4.3
277 
278 
279     "#,
280     )
281     .running(|root| {
282         assert!(root.remove("a").is_some());
283     })
284     .produces_display(
285         r#"
286         title = "not relevant"
287         # comment 2
288         [b] # comment 2.1
289         key2 = 2 # comment 2.2
290 
291            # comment on some other table
292            [some.other.table]
293 
294 
295     "#,
296     );
297 }
298 
299 #[test]
test_remove_array_entry()300 fn test_remove_array_entry() {
301     given(
302         r#"
303         [package]
304         name = "hello"
305         version = "1.0.0"
306 
307         [[bin]]
308         name = "world"
309         path = "src/bin/world/main.rs"
310 
311         [dependencies]
312         nom = "4.0" # future is here
313 
314         [[bin]]
315         name = "delete me please"
316         path = "src/bin/dmp/main.rs""#,
317     )
318     .running(|root| {
319         let dmp = root.get_mut("bin").unwrap();
320         assert!(dmp.is_array_of_tables());
321         let dmp = dmp.as_array_of_tables_mut().unwrap();
322         assert_eq!(dmp.len(), 2);
323         dmp.remove(1);
324         assert_eq!(dmp.len(), 1);
325     })
326     .produces_display(
327         r#"
328         [package]
329         name = "hello"
330         version = "1.0.0"
331 
332         [[bin]]
333         name = "world"
334         path = "src/bin/world/main.rs"
335 
336         [dependencies]
337         nom = "4.0" # future is here
338 "#,
339     );
340 }
341 
342 #[test]
test_remove_array()343 fn test_remove_array() {
344     given(
345         r#"
346         [package]
347         name = "hello"
348         version = "1.0.0"
349 
350         [[bin]]
351         name = "world"
352         path = "src/bin/world/main.rs"
353 
354         [dependencies]
355         nom = "4.0" # future is here
356 
357         [[bin]]
358         name = "delete me please"
359         path = "src/bin/dmp/main.rs""#,
360     )
361     .running(|root| {
362         assert!(root.remove("bin").is_some());
363     })
364     .produces_display(
365         r#"
366         [package]
367         name = "hello"
368         version = "1.0.0"
369 
370         [dependencies]
371         nom = "4.0" # future is here
372 "#,
373     );
374 }
375 
376 #[test]
test_remove_value()377 fn test_remove_value() {
378     given(
379         r#"
380         name = "hello"
381         # delete this
382         version = "1.0.0" # please
383         documentation = "https://docs.rs/hello""#,
384     )
385     .running(|root| {
386         let value = root.remove("version");
387         assert!(value.is_some());
388         let value = value.unwrap();
389         assert!(value.is_value());
390         let value = value.as_value().unwrap();
391         assert!(value.is_str());
392         let value = value.as_str().unwrap();
393         assert_eq(value, "1.0.0");
394     })
395     .produces_display(
396         r#"
397         name = "hello"
398         documentation = "https://docs.rs/hello"
399 "#,
400     );
401 }
402 
403 #[test]
test_remove_last_value_from_implicit()404 fn test_remove_last_value_from_implicit() {
405     given(
406         r#"
407         [a]
408         b = 1"#,
409     )
410     .running(|root| {
411         let a = root.get_mut("a").unwrap();
412         assert!(a.is_table());
413         let a = as_table!(a);
414         a.set_implicit(true);
415         let value = a.remove("b");
416         assert!(value.is_some());
417         let value = value.unwrap();
418         assert!(value.is_value());
419         let value = value.as_value().unwrap();
420         assert_eq!(value.as_integer(), Some(1));
421     })
422     .produces_display(r#""#);
423 }
424 
425 // values
426 
427 #[test]
test_sort_values()428 fn test_sort_values() {
429     given(
430         r#"
431         [a.z]
432 
433         [a]
434         # this comment is attached to b
435         b = 2 # as well as this
436         a = 1
437         c = 3
438 
439         [a.y]"#,
440     )
441     .running(|root| {
442         let a = root.get_mut("a").unwrap();
443         let a = as_table!(a);
444         a.sort_values();
445     })
446     .produces_display(
447         r#"
448         [a.z]
449 
450         [a]
451         a = 1
452         # this comment is attached to b
453         b = 2 # as well as this
454         c = 3
455 
456         [a.y]
457 "#,
458     );
459 }
460 
461 #[test]
test_sort_values_by()462 fn test_sort_values_by() {
463     given(
464         r#"
465         [a.z]
466 
467         [a]
468         # this comment is attached to b
469         b = 2 # as well as this
470         a = 1
471         "c" = 3
472 
473         [a.y]"#,
474     )
475     .running(|root| {
476         let a = root.get_mut("a").unwrap();
477         let a = as_table!(a);
478         // Sort by the representation, not the value. So "\"c\"" sorts before "a" because '"' sorts
479         // before 'a'.
480         a.sort_values_by(|k1, _, k2, _| k1.display_repr().cmp(&k2.display_repr()));
481     })
482     .produces_display(
483         r#"
484         [a.z]
485 
486         [a]
487         "c" = 3
488         a = 1
489         # this comment is attached to b
490         b = 2 # as well as this
491 
492         [a.y]
493 "#,
494     );
495 }
496 
497 #[test]
test_set_position()498 fn test_set_position() {
499     given(
500         r#"
501         [package]
502         [dependencies]
503         [dependencies.opencl]
504         [dev-dependencies]"#,
505     )
506     .running(|root| {
507         for (header, table) in root.iter_mut() {
508             if header == "dependencies" {
509                 let tab = as_table!(table);
510                 tab.set_position(0);
511                 let (_, segmented) = tab.iter_mut().next().unwrap();
512                 as_table!(segmented).set_position(5)
513             }
514         }
515     })
516     .produces_display(
517         r#"        [dependencies]
518 
519         [package]
520         [dev-dependencies]
521         [dependencies.opencl]
522 "#,
523     );
524 }
525 
526 #[test]
test_multiple_zero_positions()527 fn test_multiple_zero_positions() {
528     given(
529         r#"
530         [package]
531         [dependencies]
532         [dependencies.opencl]
533         a=""
534         [dev-dependencies]"#,
535     )
536     .running(|root| {
537         for (_, table) in root.iter_mut() {
538             as_table!(table).set_position(0)
539         }
540     })
541     .produces_display(
542         r#"
543         [package]
544         [dependencies]
545         [dev-dependencies]
546         [dependencies.opencl]
547         a=""
548 "#,
549     );
550 }
551 
552 #[test]
test_multiple_max_usize_positions()553 fn test_multiple_max_usize_positions() {
554     given(
555         r#"
556         [package]
557         [dependencies]
558         [dependencies.opencl]
559         a=""
560         [dev-dependencies]"#,
561     )
562     .running(|root| {
563         for (_, table) in root.iter_mut() {
564             as_table!(table).set_position(usize::MAX)
565         }
566     })
567     .produces_display(
568         r#"        [dependencies.opencl]
569         a=""
570 
571         [package]
572         [dependencies]
573         [dev-dependencies]
574 "#,
575     );
576 }
577 
578 macro_rules! as_array {
579     ($entry:ident) => {{
580         assert!($entry.is_value());
581         let a = $entry.as_value_mut().unwrap();
582         assert!(a.is_array());
583         a.as_array_mut().unwrap()
584     }};
585 }
586 
587 #[test]
test_insert_replace_into_array()588 fn test_insert_replace_into_array() {
589     given(
590         r#"
591         a = [1,2,3]
592         b = []"#,
593     )
594     .running(|root| {
595         {
596             let a = root.get_mut("a").unwrap();
597             let a = as_array!(a);
598             assert_eq!(a.len(), 3);
599             assert!(a.get(2).is_some());
600             a.push(4);
601             assert_eq!(a.len(), 4);
602             a.fmt();
603         }
604         let b = root.get_mut("b").unwrap();
605         let b = as_array!(b);
606         assert!(b.is_empty());
607         b.push("hello");
608         assert_eq!(b.len(), 1);
609 
610         b.push_formatted(Value::from("world").decorated("\n", "\n"));
611         b.push_formatted(Value::from("test").decorated("", ""));
612 
613         b.insert(1, "beep");
614         b.insert_formatted(2, Value::from("boop").decorated("   ", "   "));
615 
616         // This should preserve formatting.
617         assert_eq!(b.replace(2, "zoink").as_str(), Some("boop"));
618         // This should replace formatting.
619         assert_eq!(
620             b.replace_formatted(4, Value::from("yikes").decorated("  ", ""))
621                 .as_str(),
622             Some("test")
623         );
624         dbg!(root);
625     })
626     .produces_display(
627         r#"
628         a = [1, 2, 3, 4]
629         b = ["hello", "beep",   "zoink"   ,
630 "world"
631 ,  "yikes"]
632 "#,
633     );
634 }
635 
636 #[test]
test_remove_from_array()637 fn test_remove_from_array() {
638     given(
639         r#"
640         a = [1, 2, 3, 4]
641         b = ["hello"]"#,
642     )
643     .running(|root| {
644         {
645             let a = root.get_mut("a").unwrap();
646             let a = as_array!(a);
647             assert_eq!(a.len(), 4);
648             assert!(a.remove(3).is_integer());
649             assert_eq!(a.len(), 3);
650         }
651         let b = root.get_mut("b").unwrap();
652         let b = as_array!(b);
653         assert_eq!(b.len(), 1);
654         assert!(b.remove(0).is_str());
655         assert!(b.is_empty());
656     })
657     .produces_display(
658         r#"
659         a = [1, 2, 3]
660         b = []
661 "#,
662     );
663 }
664 
665 #[test]
test_format_array()666 fn test_format_array() {
667     given(
668         r#"
669     a = [
670       1,
671             "2",
672       3.0,
673     ]
674     "#,
675     )
676     .running(|root| {
677         for (_, v) in root.iter_mut() {
678             if let Item::Value(Value::Array(array)) = v {
679                 array.fmt();
680             }
681         }
682     })
683     .produces_display(
684         r#"
685     a = [1, "2", 3.0]
686     "#,
687     );
688 }
689 
690 macro_rules! as_inline_table {
691     ($entry:ident) => {{
692         assert!($entry.is_value());
693         let a = $entry.as_value_mut().unwrap();
694         assert!(a.is_inline_table());
695         a.as_inline_table_mut().unwrap()
696     }};
697 }
698 
699 #[test]
test_insert_into_inline_table()700 fn test_insert_into_inline_table() {
701     given(
702         r#"
703         a = {a=2,  c = 3}
704         b = {}"#,
705     )
706     .running(|root| {
707         {
708             let a = root.get_mut("a").unwrap();
709             let a = as_inline_table!(a);
710             assert_eq!(a.len(), 2);
711             assert!(a.contains_key("a") && a.get("c").is_some() && a.get_mut("c").is_some());
712             a.get_or_insert("b", 42);
713             assert_eq!(a.len(), 3);
714             a.fmt();
715         }
716         let b = root.get_mut("b").unwrap();
717         let b = as_inline_table!(b);
718         assert!(b.is_empty());
719         b.get_or_insert("hello", "world");
720         assert_eq!(b.len(), 1);
721         b.fmt()
722     })
723     .produces_display(
724         r#"
725         a = { a = 2, c = 3, b = 42 }
726         b = { hello = "world" }
727 "#,
728     );
729 }
730 
731 #[test]
test_remove_from_inline_table()732 fn test_remove_from_inline_table() {
733     given(
734         r#"
735         a = {a=2,  c = 3, b = 42}
736         b = {'hello' = "world"}"#,
737     )
738     .running(|root| {
739         {
740             let a = root.get_mut("a").unwrap();
741             let a = as_inline_table!(a);
742             assert_eq!(a.len(), 3);
743             assert!(a.remove("c").is_some());
744             assert_eq!(a.len(), 2);
745         }
746         let b = root.get_mut("b").unwrap();
747         let b = as_inline_table!(b);
748         assert_eq!(b.len(), 1);
749         assert!(b.remove("hello").is_some());
750         assert!(b.is_empty());
751     })
752     .produces_display(
753         r#"
754         a = {a=2, b = 42}
755         b = {}
756 "#,
757     );
758 }
759 
760 #[test]
test_as_table_like()761 fn test_as_table_like() {
762     given(
763         r#"
764         a = {a=2,  c = 3, b = 42}
765         x = {}
766         [[bin]]
767         [b]
768         x = "y"
769         [empty]"#,
770     )
771     .running(|root| {
772         let a = root["a"].as_table_like();
773         assert!(a.is_some());
774         let a = a.unwrap();
775         assert_eq!(a.iter().count(), 3);
776         assert_eq!(a.len(), 3);
777         assert_eq!(a.get("a").and_then(Item::as_integer), Some(2));
778 
779         let b = root["b"].as_table_like();
780         assert!(b.is_some());
781         let b = b.unwrap();
782         assert_eq!(b.iter().count(), 1);
783         assert_eq!(b.len(), 1);
784         assert_eq!(b.get("x").and_then(Item::as_str), Some("y"));
785 
786         assert_eq!(root["x"].as_table_like().map(|t| t.iter().count()), Some(0));
787         assert_eq!(
788             root["empty"].as_table_like().map(|t| t.is_empty()),
789             Some(true)
790         );
791 
792         assert!(root["bin"].as_table_like().is_none());
793     });
794 }
795 
796 #[test]
test_inline_table_append()797 fn test_inline_table_append() {
798     let mut a = Value::from_iter(vec![
799         (parse_key!("a"), 1),
800         (parse_key!("b"), 2),
801         (parse_key!("c"), 3),
802     ]);
803     let a = a.as_inline_table_mut().unwrap();
804 
805     let mut b = Value::from_iter(vec![
806         (parse_key!("c"), 4),
807         (parse_key!("d"), 5),
808         (parse_key!("e"), 6),
809     ]);
810     let b = b.as_inline_table_mut().unwrap();
811 
812     a.extend(b.iter());
813     assert_eq!(a.len(), 5);
814     assert!(a.contains_key("e"));
815     assert_eq!(b.len(), 3);
816 }
817 
818 #[test]
test_insert_dotted_into_std_table()819 fn test_insert_dotted_into_std_table() {
820     given("")
821         .running(|root| {
822             root["nixpkgs"] = table();
823 
824             root["nixpkgs"]["src"] = table();
825             root["nixpkgs"]["src"]
826                 .as_table_like_mut()
827                 .unwrap()
828                 .set_dotted(true);
829             root["nixpkgs"]["src"]["git"] = value("https://github.com/nixos/nixpkgs");
830         })
831         .produces_display(
832             r#"[nixpkgs]
833 src.git = "https://github.com/nixos/nixpkgs"
834 "#,
835         );
836 }
837 
838 #[test]
test_insert_dotted_into_implicit_table()839 fn test_insert_dotted_into_implicit_table() {
840     given("")
841         .running(|root| {
842             root["nixpkgs"] = table();
843 
844             root["nixpkgs"]["src"]["git"] = value("https://github.com/nixos/nixpkgs");
845             root["nixpkgs"]["src"]
846                 .as_table_like_mut()
847                 .unwrap()
848                 .set_dotted(true);
849         })
850         .produces_display(
851             r#"[nixpkgs]
852 src.git = "https://github.com/nixos/nixpkgs"
853 "#,
854         );
855 }
856 
857 #[test]
sorting_with_references()858 fn sorting_with_references() {
859     let values = vec!["foo", "qux", "bar"];
860     let mut array = toml_edit::Array::from_iter(values);
861     array.sort_by(|lhs, rhs| lhs.as_str().cmp(&rhs.as_str()));
862 }
863