yaml_subset/
lib.rs

1extern crate pest;
2
3mod path;
4mod utils;
5pub mod yaml;
6
7pub use path::YamlPath;
8pub use yaml::{DocumentResult, YamlResult};
9
10#[cfg(test)]
11mod tests {
12    use super::yaml::parse_yaml_file;
13    use super::yaml::{AliasedYaml, Yaml, YamlInsert};
14    use super::YamlPath;
15    use crate::yaml::{DoubleQuotedStringPart, MyVec};
16
17    #[test]
18    fn basic() {
19        let inp = r#"---
20# test
21k: &anch test
22# hi
23v: &ok
24   - &ok2 test
25# comment
26   - *test
27v: test # maybe
28k: [5, 10, "e"]
29variable_groups: []
30double_quoted: "test
31
32now a space \n vs \\n
33"
34single_quoted: 'test escaping ''
35
36now a space \n vs \\n
37'
38l:
39 - 'sqrt( {d}^({r*n+m}m+{p}) * x^({r*a+p})  * y^{b*r+q} )'
40 - Read the values of the cyclometric numbers off of the circle. <br/> {gon_circle}
41
42# after
43"#;
44        let parsed = parse_yaml_file(inp);
45        insta::assert_debug_snapshot!(parsed);
46        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
47    }
48
49    #[test]
50    fn inline_hash() {
51        let inp = r#"---
52field: &test {}
53item: {}
54item2: { key: "value", other: [a, b]}
55other: # check
56  combine: { key: { key: [l, {a: "b"}]} }
57  item: {}
58"#;
59        let parsed = parse_yaml_file(inp);
60        insta::assert_debug_snapshot!(parsed);
61        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
62    }
63
64    #[test]
65    fn block_delimiters_in_text() {
66        let inp = r#"---
67content: When you press <em>Submit part</em>.
68abs: An absolute value is written as |x|
69"#;
70        let parsed = parse_yaml_file(inp);
71        insta::assert_debug_snapshot!(parsed);
72        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
73    }
74
75    #[test]
76    fn array_delimiters_in_text() {
77        let inp = r#"---
78default_value:  random(-9 .. 9 except [0, a])    
79"#;
80        let parsed = parse_yaml_file(inp);
81        insta::assert_debug_snapshot!(parsed);
82        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
83    }
84
85    #[test]
86    fn comments_before_start() {
87        let inp = r#"
88# a comment
89# another one
90
91---
92x: 5
93
94  "#;
95
96        let parsed = parse_yaml_file(inp);
97        insta::assert_debug_snapshot!(parsed);
98        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
99    }
100
101    #[test]
102    fn elab() {
103        let inp = r#"---
104# test
105k: &anch test
106# hi
107
108v: &ok
109   - &ok2 test
110# comment
111   - *test # an anchor
112v: test # maybe
113k: &kk [5, 10, "e"]
114# after
115y: - x: 5
116     z: &zfield 6 # yess
117
118   - n: 8
119
120   - j
121# between
122z: - *kk
123
124   - ke
125k: - - j
126     - k
127
128     - l
129   - - m
130     - z: k
131
132
133       t: - m: l # comment
134            p: 4 # comment
135          - m # after m
136# wow
137# something else
138"#;
139        let parsed = parse_yaml_file(inp);
140        insta::assert_debug_snapshot!(parsed);
141        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
142    }
143
144    #[test]
145    fn block_scalars() {
146        // see https://yaml-multiline.info/
147        let inp = r#"---
148        newlines: &newline |
149                  Several lines of text,
150                  with some "quotes" of various 'types',
151                  and also a blank line:
152                  
153                  and some text with
154                    extra indentation
155                  on the next line,
156                  plus another line at the end.
157                  
158        folded: &folded >
159                  Several lines of text,
160                  with some "quotes" of various 'types',
161                  and also a blank line:
162                  
163                  and some text with
164                    extra indentation
165                  on the next line,
166                  plus another line at the end.
167                  
168                  
169                  
170        test: 5"#;
171
172        let parsed = parse_yaml_file(inp);
173        insta::assert_debug_snapshot!(parsed);
174        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
175    }
176
177    #[test]
178    fn block_scalars_with_chomping() {
179        // see https://yaml-multiline.info/
180        let inp = r#"---
181        newlines: &newline |-
182                  Several lines of text,
183                  with some "quotes" of various 'types',
184                  and also a blank line:
185                  
186                  and some text with
187                    extra indentation
188                  on the next line,
189                  plus another line at the end.
190                  
191        folded: &folded >+
192                  Several lines of text,
193                  with some "quotes" of various 'types',
194                  and also a blank line:
195                  
196                  and some text with
197                    extra indentation
198                  on the next line,
199                  plus another line at the end.
200                  
201                  
202                  
203        test: 5"#;
204
205        let parsed = parse_yaml_file(inp);
206        insta::assert_debug_snapshot!(parsed);
207        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
208    }
209
210    #[test]
211    fn block_scalars_with_indent() {
212        // see https://yaml-multiline.info/
213        let inp = r#"---
214        newlines: &newline |10
215                    Several lines of text,
216                  with some "quotes" of various 'types',
217                  and also a blank line:
218                  
219                  and some text with
220                    extra indentation
221                  on the next line,
222                  plus another line at the end.
223                  
224        folded: &folded >10
225                    Several lines of text,
226                  with some "quotes" of various 'types',
227                  and also a blank line:
228                  
229                  and some text with
230                    extra indentation
231                  on the next line,
232                  plus another line at the end.
233                  
234                  
235                  
236        test: 5"#;
237
238        let parsed = parse_yaml_file(inp);
239        insta::assert_debug_snapshot!(parsed);
240        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
241    }
242
243    #[test]
244    fn block_scalars_with_indent_and_empty_lines() {
245        // see https://yaml-multiline.info/
246        let inp = r#"---
247        newlines: &newline |10
248                    Several lines of text,
249                  with some "quotes" of various 'types',
250                  and also a blank line:
251
252                  and some text with
253                    extra indentation
254                  on the next line,
255                  plus another line at the end.
256                  
257        folded: &folded >10
258                    Several lines of text,
259                  with some "quotes" of various 'types',
260                  and also a blank line:
261
262                  and some text with
263                    extra indentation
264                  on the next line,
265                  plus another line at the end.
266                  
267                  
268                  
269        test: 5"#;
270
271        let parsed = parse_yaml_file(inp);
272        insta::assert_debug_snapshot!(parsed);
273        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
274    }
275
276    #[test]
277    fn block_scalars_deeper() {
278        let inp = r#"---
279parts:
280  - 
281    type: gapfill
282    marks: 0
283    prompt:
284      content: test
285      other: |
286        8
287        10"#;
288
289        let parsed = parse_yaml_file(inp);
290        insta::assert_debug_snapshot!(parsed);
291        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
292    }
293
294    #[test]
295    fn commas_in_unquoted_string() {
296        let inp = r#"---
297inline_array: [test, 5, hi]
298s: &key test, 5, hi
299"#;
300        let parsed = parse_yaml_file(inp);
301        insta::assert_debug_snapshot!(parsed);
302        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
303    }
304
305    #[test]
306    fn alternative_hash_comment_between_first_and_second() {
307        let inp = r#"---
308parts:
309- type: choose_one
310  # prompt: "file:statement-wrong.html"
311  answer_data: tmp 
312  "#;
313
314        let parsed = parse_yaml_file(inp);
315        insta::assert_debug_snapshot!(parsed);
316        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
317    }
318
319    #[test]
320    fn comment_before_first_key() {
321        let inp = r#"---
322default_value:
323    content: 
324      # NOTE: the 'original' non-shuffled array advr is used, rather than x_advr !!!
325      # Better advice to be written ...? This one does not correctly extend to users of this template!!
326      nl: "Gebruik het merkwaardig product {if(a*b>0, advr[0], advr[1])}"
327      en: "Use the special product {if(a*b>0, advr[0], advr[1])}""#;
328        let parsed = parse_yaml_file(inp);
329        insta::assert_debug_snapshot!(parsed);
330        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
331    }
332
333    #[test]
334    fn insert_hash_top() {
335        let inp = r#"---
336inline_array: [test, 5, hi]
337s: &key test, 5, hi
338item:
339 key: value
340"#;
341        let mut parsed = parse_yaml_file(inp).unwrap();
342        let path: YamlPath = "new".parse().unwrap();
343        let data = AliasedYaml {
344            alias: None,
345            value: Yaml::UnquotedString("value".to_string()),
346        };
347        assert_eq!(1, parsed.insert_into_hash(&path, &data, false));
348
349        let out = r#"---
350inline_array: [test, 5, hi]
351s: &key test, 5, hi
352item:
353 key: value
354new: value
355"#;
356
357        let parsed_out = parse_yaml_file(out).unwrap();
358        assert_eq!(parsed_out, parsed);
359    }
360
361    #[test]
362    fn insert_hash() {
363        let inp = r#"---
364inline_array: [test, 5, hi]
365s: &key test, 5, hi
366item:
367 key: value
368"#;
369        let mut parsed = parse_yaml_file(inp).unwrap();
370        let path: YamlPath = "item.key2".parse().unwrap();
371        let data = AliasedYaml {
372            alias: None,
373            value: Yaml::DoubleQuotedString(vec![DoubleQuotedStringPart::String(
374                "test".to_string(),
375            )]),
376        };
377        assert_eq!(1, parsed.insert_into_hash(&path, &data, false));
378
379        let out = r#"---
380inline_array: [test, 5, hi]
381s: &key test, 5, hi
382item:
383 key: value
384 key2: "test"
385"#;
386
387        let parsed_out = parse_yaml_file(out).unwrap();
388        assert_eq!(parsed_out, parsed);
389
390        let path: YamlPath = "item.key2".parse().unwrap();
391        let data = AliasedYaml {
392            alias: None,
393            value: Yaml::DoubleQuotedString(vec![DoubleQuotedStringPart::String(
394                "test2".to_string(),
395            )]),
396        };
397        assert_eq!(0, parsed.insert_into_hash(&path, &data, false));
398        assert_eq!(parsed_out, parsed);
399        assert_eq!(1, parsed.insert_into_hash(&path, &data, true));
400        let out = r#"---
401inline_array: [test, 5, hi]
402s: &key test, 5, hi
403item:
404 key: value
405 key2: "test2"
406"#;
407
408        let parsed_out = parse_yaml_file(out).unwrap();
409        assert_eq!(parsed_out, parsed);
410    }
411
412    #[test]
413    fn rename_field() {
414        let inp = r#"---
415inline_array: [test, 5, hi]
416s: &key test, 5, hi
417item:
418 key: value
419"#;
420        let mut parsed = parse_yaml_file(inp).unwrap();
421        let path: YamlPath = "item.key".parse().unwrap();
422        assert_eq!(1, parsed.rename_field(&path, "newkey".to_string()));
423
424        let out = r#"---
425inline_array: [test, 5, hi]
426s: &key test, 5, hi
427item:
428 newkey: value
429"#;
430
431        let parsed_out = parse_yaml_file(out).unwrap();
432        assert_eq!(parsed_out, parsed);
433    }
434
435    #[test]
436    fn rename_field_list_in_list() {
437        let inp = r#"---
438parts:
439  - type: gapfill
440    gaps:
441      - type: jme
442        old_name: value
443      - type: jme
444        old_name: value
445  - type: random 
446    gaps:
447      - type: jme
448        old_name: value
449      - type: jme
450        old_name: value
451  - type: gapfill
452    gaps:
453      - type: jme
454        old_name: value
455      - type: jme
456        old_name: value
457"#;
458        let mut parsed = parse_yaml_file(inp).unwrap();
459        let path: YamlPath = "parts[*]|type=gapfill.gaps[*].old_name".parse().unwrap();
460        assert_eq!(4, parsed.rename_field(&path, "new_name".to_string()));
461
462        let out = r#"---
463parts:
464  - type: gapfill
465    gaps:
466      - type: jme
467        new_name: value
468      - type: jme
469        new_name: value
470  - type: random 
471    gaps:
472      - type: jme
473        old_name: value
474      - type: jme
475        old_name: value
476  - type: gapfill
477    gaps:
478      - type: jme
479        new_name: value
480      - type: jme
481        new_name: value
482"#;
483
484        let parsed_out = parse_yaml_file(out).unwrap();
485        assert_eq!(parsed_out, parsed);
486    }
487
488    #[test]
489    fn to_object() {
490        let inp = r#"---
491inline_array: [test, 5, hi]
492s: &key test, 5, hi
493item:
494 key: value
495"#;
496        let mut parsed = parse_yaml_file(inp).unwrap();
497        let path: YamlPath = "item.key".parse().unwrap();
498        assert_eq!(1, parsed.to_object(&path, "subkey".to_string()));
499
500        let out = r#"---
501inline_array: [test, 5, hi]
502s: &key test, 5, hi
503item:
504 key: 
505   subkey: value
506"#;
507
508        let parsed_out = parse_yaml_file(out).unwrap();
509        assert_eq!(parsed_out, parsed);
510    }
511    #[test]
512    fn to_object_with_condition() {
513        let inp = r#"---
514inline_array: [test, 5, hi]
515s: &key test, 5, hi
516item:
517 key: value
518"#;
519        let mut parsed = parse_yaml_file(inp).unwrap();
520        let path: YamlPath = "item.key|key=value".parse().unwrap();
521        assert_eq!(1, parsed.to_object(&path, "subkey".to_string()));
522
523        let out = r#"---
524inline_array: [test, 5, hi]
525s: &key test, 5, hi
526item:
527 key: 
528   subkey: value
529"#;
530
531        let parsed_out = parse_yaml_file(out).unwrap();
532        assert_eq!(parsed_out, parsed);
533
534        let mut parsed = parse_yaml_file(inp).unwrap();
535        let path: YamlPath = "item.key|key=other".parse().unwrap();
536        assert_eq!(0, parsed.to_object(&path, "subkey".to_string()));
537        assert_eq!(parse_yaml_file(inp).unwrap(), parsed);
538    }
539    #[test]
540    fn remove_from_hash() {
541        let inp = r#"---
542inline_array: [test, 5, hi]
543s: &key test, 5, hi
544item:
545 key: value
546"#;
547        let mut parsed = parse_yaml_file(inp).unwrap();
548        let path: YamlPath = "item".parse().unwrap();
549        assert_eq!(1, parsed.remove_from_hash(&path));
550
551        let out = r#"---
552inline_array: [test, 5, hi]
553s: &key test, 5, hi
554"#;
555
556        let parsed_out = parse_yaml_file(out).unwrap();
557        assert_eq!(parsed_out, parsed);
558    }
559    #[test]
560    fn remove_from_hash_deeper() {
561        let inp = r#"---
562inline_array: [test, 5, hi]
563s: &key test, 5, hi
564item:
565 key:
566  item: 5
567  item2: 6
568"#;
569        let mut parsed = parse_yaml_file(inp).unwrap();
570        let path: YamlPath = "item.key.item".parse().unwrap();
571        assert_eq!(1, parsed.remove_from_hash(&path));
572        let out = r#"---
573inline_array: [test, 5, hi]
574s: &key test, 5, hi
575item:
576  key:
577    item2: 6
578"#;
579
580        let parsed_out = parse_yaml_file(out).unwrap();
581        assert_eq!(parsed_out, parsed);
582    }
583    #[test]
584    fn remove_from_hash_deeper_with_condition() {
585        let inp = r#"---
586type: test
587inline_array: [test, 5, hi]
588s: &key test, 5, hi
589item:
590 key:
591  item: 5
592  item2: 6
593"#;
594        let mut parsed = parse_yaml_file(inp).unwrap();
595        let path: YamlPath = "item|type=test.key.item".parse().unwrap();
596        assert_eq!(1, parsed.remove_from_hash(&path));
597        let out = r#"---
598type: test
599inline_array: [test, 5, hi]
600s: &key test, 5, hi
601item:
602  key:
603    item2: 6
604"#;
605
606        let parsed_out = parse_yaml_file(out).unwrap();
607        assert_eq!(parsed_out, parsed);
608
609        let mut parsed = parse_yaml_file(inp).unwrap();
610        let path: YamlPath = "item|type=other.key.item".parse().unwrap();
611        assert_eq!(0, parsed.remove_from_hash(&path));
612        assert_eq!(parse_yaml_file(inp).unwrap(), parsed);
613    }
614    #[test]
615    fn move_to_subfield_in_root() {
616        let inp = r#"---
617a: vala
618b: valb
619c: valc
620d: vald
621e: vale
622"#;
623
624        let mut parsed = parse_yaml_file(inp).unwrap();
625        let path: YamlPath = "".parse().unwrap();
626        assert_eq!(
627            1,
628            parsed.move_to_subfield(
629                &path,
630                "sub".to_string(),
631                vec!["e".to_string(), "b".to_string(), "d".to_string()]
632            )
633        );
634        let out = r#"---
635a: vala
636c: valc
637sub:
638    e: vale
639    b: valb
640    d: vald
641"#;
642
643        let parsed_out = parse_yaml_file(out).unwrap();
644        assert_eq!(parsed_out, parsed);
645    }
646    #[test]
647    fn move_to_subfield() {
648        let inp = r#"---
649k:
650  a: vala
651  b: valb
652  c: valc
653  d: vald
654  e: vale
655"#;
656
657        let mut parsed = parse_yaml_file(inp).unwrap();
658        let path: YamlPath = "k".parse().unwrap();
659        assert_eq!(
660            1,
661            parsed.move_to_subfield(
662                &path,
663                "sub".to_string(),
664                vec!["e".to_string(), "b".to_string(), "d".to_string()]
665            )
666        );
667        let out = r#"---
668k:
669  a: vala
670  c: valc
671  sub:
672    e: vale
673    b: valb
674    d: vald
675"#;
676
677        let parsed_out = parse_yaml_file(out).unwrap();
678        assert_eq!(parsed_out, parsed);
679    }
680    #[test]
681    fn move_to_existing_subfield() {
682        let inp = r#"---
683k:
684  a: vala
685  b: valb
686  c: valc
687  d: vald
688  e: vale
689  l:
690    z: valz
691"#;
692
693        let mut parsed = parse_yaml_file(inp).unwrap();
694        let path: YamlPath = "k".parse().unwrap();
695        assert_eq!(
696            1,
697            parsed.move_to_subfield(
698                &path,
699                "l".to_string(),
700                vec![
701                    "e".to_string(),
702                    "b".to_string(),
703                    "d".to_string(),
704                    "kk".to_string()
705                ]
706            )
707        );
708        let out = r#"---
709k:
710  a: vala
711  c: valc
712  l:
713    z: valz
714    e: vale
715    b: valb
716    d: vald
717"#;
718
719        let parsed_out = parse_yaml_file(out).unwrap();
720        assert_eq!(parsed_out, parsed);
721    }
722    #[test]
723    fn move_to_subfield_list() {
724        let inp = r#"---
725k:
726  - item1: test0
727    result:
728      yes: three
729  - item1: test1
730"#;
731
732        let mut parsed = parse_yaml_file(inp).unwrap();
733        let path: YamlPath = "k[*]".parse().unwrap();
734        assert_eq!(
735            2,
736            parsed.move_to_subfield(&path, "result".to_string(), vec!["item1".to_string(),])
737        );
738        let out = r#"---
739k:
740  - result:
741      yes: three
742      item1: test0
743  - result:
744      item1: test1
745"#;
746
747        let parsed_out = parse_yaml_file(out).unwrap();
748        assert_eq!(parsed_out, parsed);
749    }
750    #[test]
751    fn move_to_subfield_list_item() {
752        let inp = r#"---
753k:
754  - item1: test0
755    result:
756      yes: three
757  - item1: test1
758"#;
759
760        let mut parsed = parse_yaml_file(inp).unwrap();
761        let path: YamlPath = "k[0]".parse().unwrap();
762        assert_eq!(
763            1,
764            parsed.move_to_subfield(&path, "result".to_string(), vec!["item1".to_string(),])
765        );
766        let out = r#"---
767k:
768  - result:
769      yes: three
770      item1: test0
771  - item1: test1
772"#;
773
774        let parsed_out = parse_yaml_file(out).unwrap();
775        assert_eq!(parsed_out, parsed);
776    }
777    #[test]
778    fn move_to_subfield_list_filter() {
779        let inp = r#"---
780k:
781  - item1: test0
782    result:
783      yes: three
784  - item1: test1
785"#;
786
787        let mut parsed = parse_yaml_file(inp).unwrap();
788        let path: YamlPath = "k[*]|item1=test1".parse().unwrap();
789        assert_eq!(
790            1,
791            parsed.move_to_subfield(&path, "result".to_string(), vec!["item1".to_string(),])
792        );
793        let out = r#"---
794k:
795  - item1: test0
796    result:
797      yes: three      
798  - result:
799      item1: test1
800"#;
801
802        let parsed_out = parse_yaml_file(out).unwrap();
803        assert_eq!(parsed_out, parsed);
804    }
805    #[test]
806    fn neg_number_in_list() {
807        let inp = r#"---
808vset_range:
809  - -10
810  - 10
811vset_range_points: 20
812"#;
813
814        let parsed = parse_yaml_file(inp);
815        insta::assert_debug_snapshot!(parsed);
816        insta::assert_display_snapshot!(parsed.unwrap().format().unwrap());
817    }
818    #[test]
819    fn move_to_map_with_field_as_key() {
820        let inp = r#"---
821k:
822    - item1: 
823        variables:
824            # this specifies the test
825            test: value
826            ke:
827              # comments
828              definition: result
829              group: keep
830            # this one will be moved
831            other:
832              definition: result
833              group: different
834            k: 
835              definition: value
836              group: ""
837            main:
838              definiton: field
839              group: other
840            other2:
841              definition: result2
842              group: different
843    - item2:
844        variables:
845            # this specifies the test
846            test: value
847        grouped_variables: anything
848"#;
849
850        let mut parsed = parse_yaml_file(inp).unwrap();
851        let path: YamlPath = "k[*].item1".parse().unwrap();
852        assert_eq!(
853            1,
854            parsed.move_to_map_with_field_as_key(
855                &path,
856                "variables".to_string(),
857                "group".to_string(),
858                "grouped_variables".to_string(),
859                vec!["keep".to_string()]
860            )
861        );
862        let out = r#"---
863k:
864- item1: 
865        variables:
866            # this specifies the test
867            test: value
868            ke:
869              # comments
870              definition: result
871            k: 
872              definition: value
873        grouped_variables:
874            different:
875                # this one will be moved
876                other:
877                    definition: result
878                other2:
879                    definition: result2
880            other:
881                main:
882                    definiton: field
883- item2:
884    variables:
885        # this specifies the test
886        test: value
887    grouped_variables: anything
888"#;
889        println!("{}", parsed.format().unwrap());
890        let parsed_out = parse_yaml_file(out).unwrap();
891        assert_eq!(parsed_out, parsed);
892    }
893
894    #[test]
895    fn find() {
896        let inp = r#"---
897k:
898  - item1: test0
899    result:
900      yes: three
901  - item1: test1
902"#;
903
904        let mut parsed = parse_yaml_file(inp).unwrap();
905        let path: YamlPath = "k[*].item1".parse().unwrap();
906        assert_eq!(
907            MyVec(vec![
908                Yaml::UnquotedString("test0".to_string()),
909                Yaml::UnquotedString("test1".to_string()),
910            ]),
911            parsed.find_values(&path)
912        );
913    }
914}