1use crate::{
4 as_yaml::{AsYaml, YamlKind},
5 lex::SyntaxKind,
6 yaml::{Document, YamlFile},
7};
8use rowan::GreenNodeBuilder;
9
10pub struct YamlBuilder {
12 file: YamlFile,
13}
14
15impl YamlBuilder {
16 pub fn scalar(value: impl AsYaml) -> Self {
18 let mut builder = GreenNodeBuilder::new();
19 builder.start_node(SyntaxKind::ROOT.into());
20 builder.start_node(SyntaxKind::DOCUMENT.into());
21 value.build_content(&mut builder, 0, false);
22 builder.finish_node();
23 builder.finish_node();
24 let green = builder.finish();
25 YamlBuilder {
26 file: YamlFile(rowan::SyntaxNode::new_root_mut(green)),
27 }
28 }
29
30 pub fn alias(name: impl AsRef<str>) -> Self {
43 Self::scalar(crate::yaml::Alias::new(name))
44 }
45
46 pub fn sequence() -> SequenceBuilder {
48 SequenceBuilder::new()
49 }
50
51 pub fn mapping() -> MappingBuilder {
53 MappingBuilder::new()
54 }
55
56 pub fn build(self) -> YamlFile {
58 self.file
59 }
60}
61
62impl Default for YamlBuilder {
63 fn default() -> Self {
64 Self::mapping().build()
65 }
66}
67
68pub struct SequenceBuilder {
70 builder: GreenNodeBuilder<'static>,
71 indent: usize,
72 count: usize,
73 last_item_ended_with_newline: bool,
75}
76
77fn emit_dash(
80 builder: &mut GreenNodeBuilder<'static>,
81 newline_first: bool,
82 indent: usize,
83 trailing_newline: bool,
84) {
85 if newline_first {
86 builder.token(SyntaxKind::NEWLINE.into(), "\n");
87 }
88 if indent > 0 {
89 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
90 }
91 builder.token(SyntaxKind::DASH.into(), "-");
92 builder.token(SyntaxKind::WHITESPACE.into(), " ");
93 if trailing_newline {
94 builder.token(SyntaxKind::NEWLINE.into(), "\n");
95 }
96}
97
98fn emit_key(
101 builder: &mut GreenNodeBuilder<'static>,
102 newline_first: bool,
103 indent: usize,
104 key: &str,
105 trailing_newline: bool,
106) {
107 if newline_first {
108 builder.token(SyntaxKind::NEWLINE.into(), "\n");
109 }
110 if indent > 0 {
111 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
112 }
113 builder.start_node(SyntaxKind::SCALAR.into());
114 builder.token(SyntaxKind::VALUE.into(), key);
115 builder.finish_node();
116 builder.token(SyntaxKind::COLON.into(), ":");
117 builder.token(SyntaxKind::WHITESPACE.into(), " ");
118 if trailing_newline {
119 builder.token(SyntaxKind::NEWLINE.into(), "\n");
120 }
121}
122
123fn copy_nested_at(
148 builder: &mut GreenNodeBuilder<'static>,
149 node: &rowan::SyntaxNode<crate::yaml::Lang>,
150 indent: usize,
151) {
152 let nested = indent + 2;
153 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(nested));
154 crate::as_yaml::copy_node_content_reindent(builder, node, nested as isize);
155}
156
157fn finish_into_inner_node(
164 mut builder: GreenNodeBuilder<'static>,
165) -> Option<rowan::SyntaxNode<crate::yaml::Lang>> {
166 use rowan::ast::AstNode;
167 builder.finish_node(); builder.finish_node(); builder.finish_node(); let root = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(builder.finish());
171 let doc = Document::cast(root.first_child()?)?;
172 doc.syntax().children().next()
173}
174
175impl SequenceBuilder {
176 pub fn new() -> Self {
178 let mut builder = GreenNodeBuilder::new();
179 builder.start_node(SyntaxKind::ROOT.into());
180 builder.start_node(SyntaxKind::DOCUMENT.into());
181 builder.start_node(SyntaxKind::SEQUENCE.into());
182 SequenceBuilder {
183 builder,
184 indent: 0,
185 count: 0,
186 last_item_ended_with_newline: false,
187 }
188 }
189
190 fn at_indent(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
191 SequenceBuilder {
192 builder,
193 indent,
194 count: 0,
195 last_item_ended_with_newline: false,
196 }
197 }
198
199 fn emit_item_preamble(&mut self) {
200 let newline_first = self.count > 0 && !self.last_item_ended_with_newline;
202 emit_dash(&mut self.builder, newline_first, self.indent, false);
203 }
204
205 pub fn item(mut self, value: impl AsYaml) -> Self {
208 self.emit_item_preamble();
209
210 let ends_with_newline = match (value.is_inline(), value.kind()) {
212 (true, _) => value.build_content(&mut self.builder, self.indent, false),
214 (false, YamlKind::Mapping | YamlKind::Sequence) => {
217 value.build_content(&mut self.builder, self.indent + 2, false)
218 }
219 (false, _) => {
221 self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
222 value.build_content(&mut self.builder, self.indent + 2, false)
223 }
224 };
225
226 self.count += 1;
227 self.last_item_ended_with_newline = ends_with_newline;
228 self
229 }
230
231 pub fn sequence<F>(self, f: F) -> Self
233 where
234 F: FnOnce(SequenceBuilder) -> SequenceBuilder,
235 {
236 let SequenceBuilder {
237 mut builder,
238 indent,
239 count,
240 ..
241 } = self;
242
243 emit_dash(&mut builder, count > 0, indent, true);
244
245 builder.start_node(SyntaxKind::SEQUENCE.into());
246 let nested = SequenceBuilder::at_indent(builder, indent + 2);
247 let filled = f(nested);
248 let SequenceBuilder { mut builder, .. } = filled;
249 builder.finish_node(); SequenceBuilder {
252 builder,
253 indent,
254 count: count + 1,
255 last_item_ended_with_newline: true,
256 }
257 }
258
259 pub fn mapping<F>(self, f: F) -> Self
261 where
262 F: FnOnce(MappingBuilder) -> MappingBuilder,
263 {
264 let SequenceBuilder {
265 mut builder,
266 indent,
267 count,
268 ..
269 } = self;
270
271 emit_dash(&mut builder, count > 0, indent, true);
272
273 builder.start_node(SyntaxKind::MAPPING.into());
274 let nested = MappingBuilder::at_indent(builder, indent + 2);
275 let filled = f(nested);
276 let MappingBuilder { mut builder, .. } = filled;
277 builder.finish_node(); SequenceBuilder {
280 builder,
281 indent,
282 count: count + 1,
283 last_item_ended_with_newline: true,
284 }
285 }
286
287 pub fn insert_sequence(self, other: SequenceBuilder) -> Self {
289 let SequenceBuilder {
290 builder: other_builder,
291 ..
292 } = other;
293 let Some(seq_node) = finish_into_inner_node(other_builder) else {
294 return self;
295 };
296 let SequenceBuilder {
297 mut builder,
298 indent,
299 count,
300 ..
301 } = self;
302
303 emit_dash(&mut builder, count > 0, indent, true);
304 copy_nested_at(&mut builder, &seq_node, indent);
305
306 SequenceBuilder {
307 builder,
308 indent,
309 count: count + 1,
310 last_item_ended_with_newline: true,
311 }
312 }
313
314 pub fn insert_mapping(self, other: MappingBuilder) -> Self {
316 let MappingBuilder {
317 builder: other_builder,
318 ..
319 } = other;
320 let Some(map_node) = finish_into_inner_node(other_builder) else {
321 return self;
322 };
323 let SequenceBuilder {
324 mut builder,
325 indent,
326 count,
327 ..
328 } = self;
329
330 emit_dash(&mut builder, count > 0, indent, true);
331 copy_nested_at(&mut builder, &map_node, indent);
332
333 SequenceBuilder {
334 builder,
335 indent,
336 count: count + 1,
337 last_item_ended_with_newline: true,
338 }
339 }
340
341 pub fn build(mut self) -> YamlBuilder {
343 if self.count == 0 {
346 self.builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
347 self.builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
348 }
349 self.builder.finish_node(); self.builder.finish_node(); self.builder.finish_node(); let green = self.builder.finish();
353 YamlBuilder {
354 file: YamlFile(rowan::SyntaxNode::new_root_mut(green)),
355 }
356 }
357
358 pub fn build_document(self) -> Document {
360 self.build()
361 .build()
362 .document()
363 .expect("YamlBuilder always produces a document node")
364 }
365}
366
367impl Default for SequenceBuilder {
368 fn default() -> Self {
369 Self::new()
370 }
371}
372
373pub struct MappingBuilder {
375 builder: GreenNodeBuilder<'static>,
376 indent: usize,
377 count: usize,
378}
379
380impl MappingBuilder {
381 pub fn new() -> Self {
383 let mut builder = GreenNodeBuilder::new();
384 builder.start_node(SyntaxKind::ROOT.into());
385 builder.start_node(SyntaxKind::DOCUMENT.into());
386 builder.start_node(SyntaxKind::MAPPING.into());
387 MappingBuilder {
388 builder,
389 indent: 0,
390 count: 0,
391 }
392 }
393
394 fn at_indent(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
395 MappingBuilder {
396 builder,
397 indent,
398 count: 0,
399 }
400 }
401
402 fn emit_key_preamble(&mut self, key: &str) {
403 emit_key(&mut self.builder, self.count > 0, self.indent, key, false);
404 }
405
406 pub fn pair(mut self, key: impl Into<String>, value: impl AsYaml) -> Self {
409 self.emit_key_preamble(&key.into());
410 if value.is_inline() {
411 value.build_content(&mut self.builder, self.indent, false);
412 } else {
413 self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
414 value.build_content(&mut self.builder, self.indent + 2, false);
415 }
416 self.count += 1;
417 self
418 }
419
420 pub fn sequence<F>(self, key: impl Into<String>, f: F) -> Self
422 where
423 F: FnOnce(SequenceBuilder) -> SequenceBuilder,
424 {
425 let MappingBuilder {
426 mut builder,
427 indent,
428 count,
429 } = self;
430
431 emit_key(&mut builder, count > 0, indent, &key.into(), true);
432
433 builder.start_node(SyntaxKind::SEQUENCE.into());
434 let nested = SequenceBuilder::at_indent(builder, indent + 2);
435 let filled = f(nested);
436 let SequenceBuilder { mut builder, .. } = filled;
437 builder.finish_node(); MappingBuilder {
440 builder,
441 indent,
442 count: count + 1,
443 }
444 }
445
446 pub fn mapping<F>(self, key: impl Into<String>, f: F) -> Self
448 where
449 F: FnOnce(MappingBuilder) -> MappingBuilder,
450 {
451 let MappingBuilder {
452 mut builder,
453 indent,
454 count,
455 } = self;
456
457 emit_key(&mut builder, count > 0, indent, &key.into(), true);
458
459 builder.start_node(SyntaxKind::MAPPING.into());
460 let nested = MappingBuilder::at_indent(builder, indent + 2);
461 let filled = f(nested);
462 let MappingBuilder { mut builder, .. } = filled;
463 builder.finish_node(); MappingBuilder {
466 builder,
467 indent,
468 count: count + 1,
469 }
470 }
471
472 pub fn insert_sequence(self, key: impl Into<String>, other: SequenceBuilder) -> Self {
474 let SequenceBuilder {
475 builder: other_builder,
476 ..
477 } = other;
478 let Some(seq_node) = finish_into_inner_node(other_builder) else {
479 return self;
480 };
481 let MappingBuilder {
482 mut builder,
483 indent,
484 count,
485 } = self;
486
487 emit_key(&mut builder, count > 0, indent, &key.into(), true);
488 copy_nested_at(&mut builder, &seq_node, indent);
489
490 MappingBuilder {
491 builder,
492 indent,
493 count: count + 1,
494 }
495 }
496
497 pub fn insert_mapping(self, key: impl Into<String>, other: MappingBuilder) -> Self {
499 let MappingBuilder {
500 builder: other_builder,
501 ..
502 } = other;
503 let Some(map_node) = finish_into_inner_node(other_builder) else {
504 return self;
505 };
506 let MappingBuilder {
507 mut builder,
508 indent,
509 count,
510 } = self;
511
512 emit_key(&mut builder, count > 0, indent, &key.into(), true);
513 copy_nested_at(&mut builder, &map_node, indent);
514
515 MappingBuilder {
516 builder,
517 indent,
518 count: count + 1,
519 }
520 }
521
522 pub fn build(mut self) -> YamlBuilder {
524 if self.count == 0 {
526 self.builder.token(SyntaxKind::LEFT_BRACE.into(), "{");
527 self.builder.token(SyntaxKind::RIGHT_BRACE.into(), "}");
528 }
529 self.builder.finish_node(); self.builder.finish_node(); self.builder.finish_node(); let green = self.builder.finish();
533 YamlBuilder {
534 file: YamlFile(rowan::SyntaxNode::new_root_mut(green)),
535 }
536 }
537
538 pub fn build_document(self) -> Document {
540 self.build()
541 .build()
542 .document()
543 .expect("YamlBuilder always produces a document node")
544 }
545}
546
547impl Default for MappingBuilder {
548 fn default() -> Self {
549 Self::new()
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556
557 #[test]
558 fn test_scalar_builder() {
559 let yaml = YamlBuilder::scalar("hello world").build();
560 assert_eq!(yaml.to_string(), "hello world");
561 }
562
563 #[test]
564 fn test_alias_builder() {
565 let yaml = YamlBuilder::alias("shared").build();
566 assert_eq!(yaml.to_string(), "*shared");
567 }
568
569 #[test]
570 fn test_mapping_builder_alias_value() {
571 let yaml = YamlBuilder::mapping()
572 .pair("ref", crate::yaml::Alias::new("shared"))
573 .build()
574 .build();
575 assert_eq!(yaml.to_string(), "ref: *shared");
576 }
577
578 #[test]
579 fn test_sequence_builder() {
580 let yaml = YamlBuilder::sequence()
581 .item("first")
582 .item("second")
583 .item("third")
584 .build()
585 .build();
586 assert_eq!(yaml.to_string(), "- first\n- second\n- third");
587 }
588
589 #[test]
590 fn test_mapping_builder() {
591 let yaml = YamlBuilder::mapping()
592 .pair("name", "John Doe")
593 .pair("age", "30")
594 .pair("city", "New York")
595 .build()
596 .build();
597 assert_eq!(
598 yaml.to_string(),
599 "name: John Doe\nage: '30'\ncity: New York"
600 );
601 }
602
603 #[test]
604 fn test_nested_structure() {
605 let yaml = YamlBuilder::mapping()
606 .pair("version", "1.0")
607 .sequence("dependencies", |s| {
608 s.item("serde").item("tokio").item("reqwest")
609 })
610 .mapping("database", |m| {
611 m.pair("host", "localhost")
612 .pair("port", "5432")
613 .pair("name", "myapp")
614 })
615 .build()
616 .build();
617 assert_eq!(
618 yaml.to_string(),
619 "version: '1.0'\ndependencies: \n - serde\n - tokio\n - reqwest\ndatabase: \n host: localhost\n port: '5432'\n name: myapp"
620 );
621 }
622
623 #[test]
624 fn test_deeply_nested() {
625 let yaml = YamlBuilder::mapping()
626 .mapping("level1", |m| {
627 m.mapping("level2", |m| {
628 m.mapping("level3", |m| m.pair("deep", "value"))
629 })
630 })
631 .build()
632 .build();
633 assert_eq!(
634 yaml.to_string(),
635 "level1: \n level2: \n level3: \n deep: value"
636 );
637 }
638
639 #[test]
640 fn test_empty_collections() {
641 let empty_seq = YamlBuilder::sequence().build().build();
644 assert_eq!(empty_seq.to_string().trim(), "[]");
645
646 let empty_map = YamlBuilder::mapping().build().build();
647 assert_eq!(empty_map.to_string().trim(), "{}");
648 }
649
650 #[test]
651 fn test_special_characters_in_values() {
652 let yaml = YamlBuilder::mapping()
653 .pair("url", "https://example.com:8080/path?query=value")
654 .pair("email", "user@example.com")
655 .pair("path", "/usr/local/bin")
656 .pair("special", "value: with: colons")
657 .build()
658 .build();
659 assert_eq!(
660 yaml.to_string(),
661 "url: https://example.com:8080/path?query=value\nemail: user@example.com\npath: /usr/local/bin\nspecial: 'value: with: colons'"
662 );
663 }
664
665 #[test]
666 fn test_numeric_string_values() {
667 let yaml = YamlBuilder::mapping()
668 .pair("int_string", "42")
669 .pair("float_string", "3.14")
670 .pair("hex_string", "0xFF")
671 .pair("octal_string", "0o755")
672 .pair("binary_string", "0b1010")
673 .build()
674 .build();
675 assert_eq!(
676 yaml.to_string(),
677 "int_string: '42'\nfloat_string: '3.14'\nhex_string: '0xFF'\noctal_string: '0o755'\nbinary_string: '0b1010'"
678 );
679 }
680
681 #[test]
682 fn test_sequences_with_nested_mappings() {
683 let yaml = YamlBuilder::sequence()
684 .mapping(|m| m.pair("id", "1").pair("name", "Alice"))
685 .mapping(|m| m.pair("id", "2").pair("name", "Bob"))
686 .mapping(|m| m.pair("id", "3").pair("name", "Charlie"))
687 .build()
688 .build();
689 assert_eq!(
690 yaml.to_string(),
691 "- \n id: '1'\n name: Alice\n- \n id: '2'\n name: Bob\n- \n id: '3'\n name: Charlie"
692 );
693 }
694
695 #[test]
696 fn test_sequences_with_nested_sequences() {
697 let yaml = YamlBuilder::sequence()
698 .sequence(|s| s.item("1").item("2").item("3"))
699 .sequence(|s| s.item("a").item("b").item("c"))
700 .sequence(|s| s.item("x").item("y").item("z"))
701 .build()
702 .build();
703 assert_eq!(
704 yaml.to_string(),
705 "- \n - '1'\n - '2'\n - '3'\n- \n - a\n - b\n - c\n- \n - x\n - y\n - z"
706 );
707 }
708
709 #[test]
710 fn test_mixed_nesting_depth() {
711 let yaml = YamlBuilder::mapping()
712 .sequence("list", |s| {
713 s.item("simple")
714 .mapping(|m| m.pair("key", "value"))
715 .sequence(|s2| s2.item("nested1").item("nested2"))
716 })
717 .mapping("object", |m| {
718 m.pair("simple", "value")
719 .sequence("list", |s| s.item("item1").item("item2"))
720 .mapping("nested", |m2| m2.pair("deep", "value"))
721 })
722 .build()
723 .build();
724 assert_eq!(
725 yaml.to_string(),
726 "list: \n - simple\n - \n key: value\n - \n - nested1\n - nested2\nobject: \n simple: value\n list: \n - item1\n - item2\n nested: \n deep: value"
727 );
728 }
729
730 #[test]
731 fn test_boolean_and_null_strings() {
732 let yaml = YamlBuilder::mapping()
733 .pair("bool_true", "true")
734 .pair("bool_false", "false")
735 .pair("yes", "yes")
736 .pair("no", "no")
737 .pair("null_value", "null")
738 .pair("tilde", "~")
739 .build()
740 .build();
741 assert_eq!(
742 yaml.to_string(),
743 "bool_true: 'true'\nbool_false: 'false'\nyes: 'yes'\nno: 'no'\nnull_value: 'null'\ntilde: '~'"
744 );
745 }
746
747 #[test]
748 fn test_long_strings() {
749 let yaml = YamlBuilder::mapping()
750 .pair("short", "test")
751 .pair("long", "a".repeat(100))
752 .build()
753 .build();
754 assert_eq!(
755 yaml.to_string(),
756 format!("short: test\nlong: {}", "a".repeat(100))
757 );
758 }
759
760 #[test]
761 fn test_unicode_values() {
762 let yaml = YamlBuilder::mapping()
763 .pair("emoji", "🎉🚀💻")
764 .pair("chinese", "你好世界")
765 .pair("arabic", "مرحبا بالعالم")
766 .pair("mixed", "Hello 世界 🌍")
767 .build()
768 .build();
769 assert_eq!(
770 yaml.to_string(),
771 "emoji: 🎉🚀💻\nchinese: 你好世界\narabic: مرحبا بالعالم\nmixed: Hello 世界 🌍"
772 );
773 }
774
775 #[test]
776 fn test_build_document_convenience_sequence() {
777 let doc = YamlBuilder::sequence()
778 .item("first")
779 .item("second")
780 .build_document();
781
782 let text = doc.to_string();
783 assert_eq!(text.trim(), "- first\n- second");
784 }
785
786 #[test]
787 fn test_build_document_convenience_mapping() {
788 let doc = YamlBuilder::mapping()
789 .pair("name", "test")
790 .pair("version", "1.0")
791 .build_document();
792
793 let text = doc.to_string();
794 assert_eq!(text.trim(), "name: test\nversion: '1.0'");
795 }
796
797 #[test]
798 fn test_insert_pre_built_sequence() {
799 let doc = YamlBuilder::mapping()
800 .pair("name", "my-app")
801 .sequence("dependencies", |s| s.item("serde").item("tokio"))
802 .build_document();
803
804 let text = doc.to_string();
805 assert_eq!(
806 text.trim(),
807 "name: my-app\ndependencies: \n - serde\n - tokio"
808 );
809 }
810
811 #[test]
812 fn test_insert_pre_built_mapping() {
813 let doc = YamlBuilder::mapping()
814 .pair("name", "my-app")
815 .mapping("database", |m| {
816 m.pair("host", "localhost").pair("port", 5432)
817 })
818 .build_document();
819
820 let text = doc.to_string();
821 assert_eq!(
822 text.trim(),
823 "name: my-app\ndatabase: \n host: localhost\n port: 5432"
824 );
825 }
826
827 #[test]
828 fn test_insert_in_sequence() {
829 let doc = YamlBuilder::sequence()
830 .item("first")
831 .sequence(|s| s.item("a").item("b"))
832 .mapping(|m| m.pair("key", "value"))
833 .build_document();
834
835 let text = doc.to_string();
836 assert_eq!(text.trim(), "- first\n- \n - a\n - b\n- \n key: value");
837 }
838
839 #[test]
840 fn test_insert_sequence_indents_like_closure() {
841 let inserted = YamlBuilder::mapping()
842 .pair("name", "my-app")
843 .insert_sequence(
844 "dependencies",
845 SequenceBuilder::new().item("serde").item("tokio"),
846 )
847 .build_document()
848 .to_string();
849 let closure = YamlBuilder::mapping()
850 .pair("name", "my-app")
851 .sequence("dependencies", |s| s.item("serde").item("tokio"))
852 .build_document()
853 .to_string();
854
855 assert_eq!(
856 inserted.trim(),
857 "name: my-app\ndependencies: \n - serde\n - tokio"
858 );
859 assert_eq!(inserted, closure);
860 }
861
862 #[test]
863 fn test_insert_mapping_indents_like_closure() {
864 let inserted = YamlBuilder::mapping()
865 .pair("name", "my-app")
866 .insert_mapping(
867 "database",
868 MappingBuilder::new()
869 .pair("host", "localhost")
870 .pair("port", 5432),
871 )
872 .build_document()
873 .to_string();
874 let closure = YamlBuilder::mapping()
875 .pair("name", "my-app")
876 .mapping("database", |m| {
877 m.pair("host", "localhost").pair("port", 5432)
878 })
879 .build_document()
880 .to_string();
881
882 assert_eq!(
883 inserted.trim(),
884 "name: my-app\ndatabase: \n host: localhost\n port: 5432"
885 );
886 assert_eq!(inserted, closure);
887 }
888
889 #[test]
890 fn test_insert_into_sequence_indents_like_closure() {
891 let inserted = YamlBuilder::sequence()
892 .item("first")
893 .insert_sequence(SequenceBuilder::new().item("a").item("b"))
894 .insert_mapping(MappingBuilder::new().pair("key", "value"))
895 .build_document()
896 .to_string();
897 let closure = YamlBuilder::sequence()
898 .item("first")
899 .sequence(|s| s.item("a").item("b"))
900 .mapping(|m| m.pair("key", "value"))
901 .build_document()
902 .to_string();
903
904 assert_eq!(
905 inserted.trim(),
906 "- first\n- \n - a\n - b\n- \n key: value"
907 );
908 assert_eq!(inserted, closure);
909 }
910
911 #[test]
912 fn test_insert_mapping_with_nested_collection() {
913 let doc = YamlBuilder::mapping()
916 .insert_mapping(
917 "outer",
918 MappingBuilder::new()
919 .pair("x", "1")
920 .mapping("deep", |m| m.pair("p", "9")),
921 )
922 .build_document();
923
924 assert_eq!(
925 doc.to_string().trim(),
926 "outer: \n x: '1'\n deep: \n p: '9'"
927 );
928 }
929
930 #[test]
931 fn test_complex_pre_built_structure() {
932 let doc = YamlBuilder::mapping()
933 .pair("version", "1.0")
934 .pair("name", "my-application")
935 .mapping("database", |m| {
936 m.pair("host", "localhost")
937 .pair("port", 5432)
938 .pair("name", "myapp")
939 })
940 .sequence("dependencies", |s| {
941 s.item("serde").item("tokio").item("reqwest")
942 })
943 .build_document();
944
945 let text = doc.to_string();
946 assert_eq!(
947 text.trim(),
948 "version: '1.0'\nname: my-application\ndatabase: \n host: localhost\n port: 5432\n name: myapp\ndependencies: \n - serde\n - tokio\n - reqwest"
949 );
950 }
951
952 #[test]
953 fn test_pair_with_typed_values() {
954 let yaml = YamlBuilder::mapping()
955 .pair("port", 5432_i64)
956 .pair("debug", true)
957 .pair("ratio", 1.5_f64)
958 .build()
959 .build();
960 assert_eq!(yaml.to_string(), "port: 5432\ndebug: true\nratio: 1.5");
961 }
962
963 #[test]
966 fn test_sequence_builder_with_block_mappings() {
967 use crate::Document;
968 use std::str::FromStr;
969
970 let yaml = r#"
972Reference:
973 Author: Stefan Kurze
974 Title: Wörterbücher und Textdateien durchsuchen mit grafischem Frontend
975 Journal: LinuxUser
976 Year: 2003
977Reference:
978 Author: Michael Vogelbacher
979 Title: Service und Informationen aus dem Netz
980 Journal: LinuxUser
981 Year: 2001
982"#;
983
984 let doc = Document::from_str(yaml).unwrap();
985 let mapping = doc.as_mapping().unwrap();
986
987 let mut reference_values = Vec::new();
989 for (key, value) in &mapping {
990 if let Some(key_scalar) = key.as_scalar() {
991 if key_scalar.as_string() == "Reference" {
992 reference_values.push(value);
993 }
994 }
995 }
996
997 while mapping.remove("Reference").is_some() {}
999
1000 let mut seq_builder = SequenceBuilder::new();
1002 for value in &reference_values {
1003 seq_builder = seq_builder.item(value);
1004 }
1005 let seq_doc = seq_builder.build_document();
1006
1007 if let Some(seq) = seq_doc.as_sequence() {
1009 mapping.set("Reference", seq);
1010 }
1011
1012 let result = doc.to_string();
1013
1014 let expected = r#"Reference:
1017- Author: Stefan Kurze
1018 Title: Wörterbücher und Textdateien durchsuchen mit grafischem Frontend
1019 Journal: LinuxUser
1020 Year: 2003
1021- Author: Michael Vogelbacher
1022 Title: Service und Informationen aus dem Netz
1023 Journal: LinuxUser
1024 Year: 2001
1025"#;
1026
1027 assert_eq!(result.trim(), expected.trim());
1028 }
1029
1030 #[test]
1031 fn test_sequence_builder_simple_mapping() {
1032 use crate::Document;
1033 use std::str::FromStr;
1034
1035 let yaml = r#"
1036item:
1037 key: value
1038 foo: bar
1039"#;
1040
1041 let doc = Document::from_str(yaml).unwrap();
1042 let mapping = doc.as_mapping().unwrap();
1043 let item_value = mapping.get("item").unwrap();
1044 let item_mapping = item_value.as_mapping().unwrap();
1045
1046 let seq = SequenceBuilder::new().item(item_mapping).build_document();
1048
1049 let result = seq.to_string();
1050
1051 let expected = "- key: value\n foo: bar";
1055 assert_eq!(result.trim(), expected);
1056 }
1057}