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 sequence() -> SequenceBuilder {
32 SequenceBuilder::new()
33 }
34
35 pub fn mapping() -> MappingBuilder {
37 MappingBuilder::new()
38 }
39
40 pub fn build(self) -> YamlFile {
42 self.file
43 }
44}
45
46impl Default for YamlBuilder {
47 fn default() -> Self {
48 Self::mapping().build()
49 }
50}
51
52pub struct SequenceBuilder {
54 builder: GreenNodeBuilder<'static>,
55 indent: usize,
56 count: usize,
57 last_item_ended_with_newline: bool,
59}
60
61impl SequenceBuilder {
62 pub fn new() -> Self {
64 let mut builder = GreenNodeBuilder::new();
65 builder.start_node(SyntaxKind::ROOT.into());
66 builder.start_node(SyntaxKind::DOCUMENT.into());
67 builder.start_node(SyntaxKind::SEQUENCE.into());
68 SequenceBuilder {
69 builder,
70 indent: 0,
71 count: 0,
72 last_item_ended_with_newline: false,
73 }
74 }
75
76 fn at_indent(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
77 SequenceBuilder {
78 builder,
79 indent,
80 count: 0,
81 last_item_ended_with_newline: false,
82 }
83 }
84
85 fn emit_item_preamble(&mut self) {
86 if self.count > 0 && !self.last_item_ended_with_newline {
88 self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
89 }
90 if self.indent > 0 {
91 self.builder
92 .token(SyntaxKind::WHITESPACE.into(), &" ".repeat(self.indent));
93 }
94 self.builder.token(SyntaxKind::DASH.into(), "-");
95 self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
96 }
97
98 pub fn item(mut self, value: impl AsYaml) -> Self {
101 self.emit_item_preamble();
102
103 let ends_with_newline = match (value.is_inline(), value.kind()) {
105 (true, _) => value.build_content(&mut self.builder, self.indent, false),
107 (false, YamlKind::Mapping) | (false, YamlKind::Sequence) => {
110 value.build_content(&mut self.builder, self.indent + 2, false)
111 }
112 (false, _) => {
114 self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
115 value.build_content(&mut self.builder, self.indent + 2, false)
116 }
117 };
118
119 self.count += 1;
120 self.last_item_ended_with_newline = ends_with_newline;
121 self
122 }
123
124 pub fn sequence<F>(self, f: F) -> Self
126 where
127 F: FnOnce(SequenceBuilder) -> SequenceBuilder,
128 {
129 let SequenceBuilder {
130 mut builder,
131 indent,
132 count,
133 ..
134 } = self;
135
136 if count > 0 {
137 builder.token(SyntaxKind::NEWLINE.into(), "\n");
138 }
139 if indent > 0 {
140 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
141 }
142 builder.token(SyntaxKind::DASH.into(), "-");
143 builder.token(SyntaxKind::WHITESPACE.into(), " ");
144 builder.token(SyntaxKind::NEWLINE.into(), "\n");
145
146 builder.start_node(SyntaxKind::SEQUENCE.into());
147 let nested = SequenceBuilder::at_indent(builder, indent + 2);
148 let filled = f(nested);
149 let SequenceBuilder { mut builder, .. } = filled;
150 builder.finish_node(); SequenceBuilder {
153 builder,
154 indent,
155 count: count + 1,
156 last_item_ended_with_newline: true,
157 }
158 }
159
160 pub fn mapping<F>(self, f: F) -> Self
162 where
163 F: FnOnce(MappingBuilder) -> MappingBuilder,
164 {
165 let SequenceBuilder {
166 mut builder,
167 indent,
168 count,
169 ..
170 } = self;
171
172 if count > 0 {
173 builder.token(SyntaxKind::NEWLINE.into(), "\n");
174 }
175 if indent > 0 {
176 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
177 }
178 builder.token(SyntaxKind::DASH.into(), "-");
179 builder.token(SyntaxKind::WHITESPACE.into(), " ");
180 builder.token(SyntaxKind::NEWLINE.into(), "\n");
181
182 builder.start_node(SyntaxKind::MAPPING.into());
183 let nested = MappingBuilder::at_indent(builder, indent + 2);
184 let filled = f(nested);
185 let MappingBuilder { mut builder, .. } = filled;
186 builder.finish_node(); SequenceBuilder {
189 builder,
190 indent,
191 count: count + 1,
192 last_item_ended_with_newline: true,
193 }
194 }
195
196 pub fn insert_sequence(self, other: SequenceBuilder) -> Self {
198 let SequenceBuilder {
200 builder: mut other_builder,
201 ..
202 } = other;
203 other_builder.finish_node(); other_builder.finish_node(); other_builder.finish_node(); let green = other_builder.finish();
207 let root = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
208
209 use rowan::ast::AstNode;
211 if let Some(doc) = crate::yaml::Document::cast(root.first_child().unwrap()) {
212 if let Some(seq_node) = doc.syntax().children().next() {
213 let SequenceBuilder {
214 mut builder,
215 indent,
216 count,
217 ..
218 } = self;
219
220 if count > 0 {
221 builder.token(SyntaxKind::NEWLINE.into(), "\n");
222 }
223 if indent > 0 {
224 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
225 }
226 builder.token(SyntaxKind::DASH.into(), "-");
227 builder.token(SyntaxKind::WHITESPACE.into(), " ");
228 builder.token(SyntaxKind::NEWLINE.into(), "\n");
229
230 crate::as_yaml::copy_node_content(&mut builder, &seq_node);
231
232 return SequenceBuilder {
233 builder,
234 indent,
235 count: count + 1,
236 last_item_ended_with_newline: true,
237 };
238 }
239 }
240 self
241 }
242
243 pub fn insert_mapping(self, other: MappingBuilder) -> Self {
245 let MappingBuilder {
247 builder: mut other_builder,
248 ..
249 } = other;
250 other_builder.finish_node(); other_builder.finish_node(); other_builder.finish_node(); let green = other_builder.finish();
254 let root = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
255
256 use rowan::ast::AstNode;
258 if let Some(doc) = crate::yaml::Document::cast(root.first_child().unwrap()) {
259 if let Some(map_node) = doc.syntax().children().next() {
260 let SequenceBuilder {
261 mut builder,
262 indent,
263 count,
264 ..
265 } = self;
266
267 if count > 0 {
268 builder.token(SyntaxKind::NEWLINE.into(), "\n");
269 }
270 if indent > 0 {
271 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
272 }
273 builder.token(SyntaxKind::DASH.into(), "-");
274 builder.token(SyntaxKind::WHITESPACE.into(), " ");
275 builder.token(SyntaxKind::NEWLINE.into(), "\n");
276
277 crate::as_yaml::copy_node_content(&mut builder, &map_node);
278
279 return SequenceBuilder {
280 builder,
281 indent,
282 count: count + 1,
283 last_item_ended_with_newline: true,
284 };
285 }
286 }
287 self
288 }
289
290 pub fn build(mut self) -> YamlBuilder {
292 self.builder.finish_node(); self.builder.finish_node(); self.builder.finish_node(); let green = self.builder.finish();
296 YamlBuilder {
297 file: YamlFile(rowan::SyntaxNode::new_root_mut(green)),
298 }
299 }
300
301 pub fn build_document(self) -> Document {
303 self.build()
304 .build()
305 .document()
306 .expect("YamlBuilder always produces a document node")
307 }
308}
309
310impl Default for SequenceBuilder {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316pub struct MappingBuilder {
318 builder: GreenNodeBuilder<'static>,
319 indent: usize,
320 count: usize,
321}
322
323impl MappingBuilder {
324 pub fn new() -> Self {
326 let mut builder = GreenNodeBuilder::new();
327 builder.start_node(SyntaxKind::ROOT.into());
328 builder.start_node(SyntaxKind::DOCUMENT.into());
329 builder.start_node(SyntaxKind::MAPPING.into());
330 MappingBuilder {
331 builder,
332 indent: 0,
333 count: 0,
334 }
335 }
336
337 fn at_indent(builder: GreenNodeBuilder<'static>, indent: usize) -> Self {
338 MappingBuilder {
339 builder,
340 indent,
341 count: 0,
342 }
343 }
344
345 fn emit_key_preamble(&mut self, key: &str) {
346 if self.count > 0 {
347 self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
348 }
349 if self.indent > 0 {
350 self.builder
351 .token(SyntaxKind::WHITESPACE.into(), &" ".repeat(self.indent));
352 }
353 self.builder.start_node(SyntaxKind::SCALAR.into());
354 self.builder.token(SyntaxKind::VALUE.into(), key);
355 self.builder.finish_node();
356 self.builder.token(SyntaxKind::COLON.into(), ":");
357 self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
358 }
359
360 pub fn pair(mut self, key: impl Into<String>, value: impl AsYaml) -> Self {
363 self.emit_key_preamble(&key.into());
364 if value.is_inline() {
365 value.build_content(&mut self.builder, self.indent, false);
366 } else {
367 self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
368 value.build_content(&mut self.builder, self.indent + 2, false);
369 }
370 self.count += 1;
371 self
372 }
373
374 pub fn sequence<F>(self, key: impl Into<String>, f: F) -> Self
376 where
377 F: FnOnce(SequenceBuilder) -> SequenceBuilder,
378 {
379 let MappingBuilder {
380 mut builder,
381 indent,
382 count,
383 } = self;
384
385 if count > 0 {
386 builder.token(SyntaxKind::NEWLINE.into(), "\n");
387 }
388 if indent > 0 {
389 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
390 }
391 builder.start_node(SyntaxKind::SCALAR.into());
392 builder.token(SyntaxKind::VALUE.into(), &key.into());
393 builder.finish_node();
394 builder.token(SyntaxKind::COLON.into(), ":");
395 builder.token(SyntaxKind::WHITESPACE.into(), " ");
396 builder.token(SyntaxKind::NEWLINE.into(), "\n");
397
398 builder.start_node(SyntaxKind::SEQUENCE.into());
399 let nested = SequenceBuilder::at_indent(builder, indent + 2);
400 let filled = f(nested);
401 let SequenceBuilder { mut builder, .. } = filled;
402 builder.finish_node(); MappingBuilder {
405 builder,
406 indent,
407 count: count + 1,
408 }
409 }
410
411 pub fn mapping<F>(self, key: impl Into<String>, f: F) -> Self
413 where
414 F: FnOnce(MappingBuilder) -> MappingBuilder,
415 {
416 let MappingBuilder {
417 mut builder,
418 indent,
419 count,
420 } = self;
421
422 if count > 0 {
423 builder.token(SyntaxKind::NEWLINE.into(), "\n");
424 }
425 if indent > 0 {
426 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
427 }
428 builder.start_node(SyntaxKind::SCALAR.into());
429 builder.token(SyntaxKind::VALUE.into(), &key.into());
430 builder.finish_node();
431 builder.token(SyntaxKind::COLON.into(), ":");
432 builder.token(SyntaxKind::WHITESPACE.into(), " ");
433 builder.token(SyntaxKind::NEWLINE.into(), "\n");
434
435 builder.start_node(SyntaxKind::MAPPING.into());
436 let nested = MappingBuilder::at_indent(builder, indent + 2);
437 let filled = f(nested);
438 let MappingBuilder { mut builder, .. } = filled;
439 builder.finish_node(); MappingBuilder {
442 builder,
443 indent,
444 count: count + 1,
445 }
446 }
447
448 pub fn insert_sequence(self, key: impl Into<String>, other: SequenceBuilder) -> Self {
450 let SequenceBuilder {
452 builder: mut other_builder,
453 ..
454 } = other;
455 other_builder.finish_node(); other_builder.finish_node(); other_builder.finish_node(); let green = other_builder.finish();
459 let root = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
460
461 use rowan::ast::AstNode;
463 if let Some(doc) = crate::yaml::Document::cast(root.first_child().unwrap()) {
464 if let Some(seq_node) = doc.syntax().children().next() {
465 let MappingBuilder {
466 mut builder,
467 indent,
468 count,
469 } = self;
470
471 if count > 0 {
472 builder.token(SyntaxKind::NEWLINE.into(), "\n");
473 }
474 if indent > 0 {
475 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
476 }
477 builder.start_node(SyntaxKind::SCALAR.into());
478 builder.token(SyntaxKind::VALUE.into(), &key.into());
479 builder.finish_node();
480 builder.token(SyntaxKind::COLON.into(), ":");
481 builder.token(SyntaxKind::WHITESPACE.into(), " ");
482 builder.token(SyntaxKind::NEWLINE.into(), "\n");
483
484 crate::as_yaml::copy_node_content(&mut builder, &seq_node);
485
486 return MappingBuilder {
487 builder,
488 indent,
489 count: count + 1,
490 };
491 }
492 }
493 self
494 }
495
496 pub fn insert_mapping(self, key: impl Into<String>, other: MappingBuilder) -> Self {
498 let MappingBuilder {
500 builder: mut other_builder,
501 ..
502 } = other;
503 other_builder.finish_node(); other_builder.finish_node(); other_builder.finish_node(); let green = other_builder.finish();
507 let root = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
508
509 use rowan::ast::AstNode;
511 if let Some(doc) = crate::yaml::Document::cast(root.first_child().unwrap()) {
512 if let Some(map_node) = doc.syntax().children().next() {
513 let MappingBuilder {
514 mut builder,
515 indent,
516 count,
517 } = self;
518
519 if count > 0 {
520 builder.token(SyntaxKind::NEWLINE.into(), "\n");
521 }
522 if indent > 0 {
523 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
524 }
525 builder.start_node(SyntaxKind::SCALAR.into());
526 builder.token(SyntaxKind::VALUE.into(), &key.into());
527 builder.finish_node();
528 builder.token(SyntaxKind::COLON.into(), ":");
529 builder.token(SyntaxKind::WHITESPACE.into(), " ");
530 builder.token(SyntaxKind::NEWLINE.into(), "\n");
531
532 crate::as_yaml::copy_node_content_with_indent(&mut builder, &map_node, indent + 2);
533
534 return MappingBuilder {
535 builder,
536 indent,
537 count: count + 1,
538 };
539 }
540 }
541 self
542 }
543
544 pub fn build(mut self) -> YamlBuilder {
546 self.builder.finish_node(); self.builder.finish_node(); self.builder.finish_node(); let green = self.builder.finish();
550 YamlBuilder {
551 file: YamlFile(rowan::SyntaxNode::new_root_mut(green)),
552 }
553 }
554
555 pub fn build_document(self) -> Document {
557 self.build()
558 .build()
559 .document()
560 .expect("YamlBuilder always produces a document node")
561 }
562}
563
564impl Default for MappingBuilder {
565 fn default() -> Self {
566 Self::new()
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn test_scalar_builder() {
576 let yaml = YamlBuilder::scalar("hello world").build();
577 assert_eq!(yaml.to_string(), "hello world");
578 }
579
580 #[test]
581 fn test_sequence_builder() {
582 let yaml = YamlBuilder::sequence()
583 .item("first")
584 .item("second")
585 .item("third")
586 .build()
587 .build();
588 assert_eq!(yaml.to_string(), "- first\n- second\n- third");
589 }
590
591 #[test]
592 fn test_mapping_builder() {
593 let yaml = YamlBuilder::mapping()
594 .pair("name", "John Doe")
595 .pair("age", "30")
596 .pair("city", "New York")
597 .build()
598 .build();
599 assert_eq!(
600 yaml.to_string(),
601 "name: John Doe\nage: '30'\ncity: New York"
602 );
603 }
604
605 #[test]
606 fn test_nested_structure() {
607 let yaml = YamlBuilder::mapping()
608 .pair("version", "1.0")
609 .sequence("dependencies", |s| {
610 s.item("serde").item("tokio").item("reqwest")
611 })
612 .mapping("database", |m| {
613 m.pair("host", "localhost")
614 .pair("port", "5432")
615 .pair("name", "myapp")
616 })
617 .build()
618 .build();
619 assert_eq!(
620 yaml.to_string(),
621 "version: '1.0'\ndependencies: \n - serde\n - tokio\n - reqwest\ndatabase: \n host: localhost\n port: '5432'\n name: myapp"
622 );
623 }
624
625 #[test]
626 fn test_deeply_nested() {
627 let yaml = YamlBuilder::mapping()
628 .mapping("level1", |m| {
629 m.mapping("level2", |m| {
630 m.mapping("level3", |m| m.pair("deep", "value"))
631 })
632 })
633 .build()
634 .build();
635 assert_eq!(
636 yaml.to_string(),
637 "level1: \n level2: \n level3: \n deep: value"
638 );
639 }
640
641 #[test]
642 fn test_empty_collections() {
643 let empty_seq = YamlBuilder::sequence().build().build();
645 let text = empty_seq.to_string();
646 assert_eq!(text.trim(), "");
647
648 let empty_map = YamlBuilder::mapping().build().build();
650 let text = empty_map.to_string();
651 assert_eq!(text.trim(), "");
652 }
653
654 #[test]
655 fn test_special_characters_in_values() {
656 let yaml = YamlBuilder::mapping()
657 .pair("url", "https://example.com:8080/path?query=value")
658 .pair("email", "user@example.com")
659 .pair("path", "/usr/local/bin")
660 .pair("special", "value: with: colons")
661 .build()
662 .build();
663 assert_eq!(
664 yaml.to_string(),
665 "url: https://example.com:8080/path?query=value\nemail: user@example.com\npath: /usr/local/bin\nspecial: 'value: with: colons'"
666 );
667 }
668
669 #[test]
670 fn test_numeric_string_values() {
671 let yaml = YamlBuilder::mapping()
672 .pair("int_string", "42")
673 .pair("float_string", "3.14")
674 .pair("hex_string", "0xFF")
675 .pair("octal_string", "0o755")
676 .pair("binary_string", "0b1010")
677 .build()
678 .build();
679 assert_eq!(
680 yaml.to_string(),
681 "int_string: '42'\nfloat_string: '3.14'\nhex_string: '0xFF'\noctal_string: '0o755'\nbinary_string: '0b1010'"
682 );
683 }
684
685 #[test]
686 fn test_sequences_with_nested_mappings() {
687 let yaml = YamlBuilder::sequence()
688 .mapping(|m| m.pair("id", "1").pair("name", "Alice"))
689 .mapping(|m| m.pair("id", "2").pair("name", "Bob"))
690 .mapping(|m| m.pair("id", "3").pair("name", "Charlie"))
691 .build()
692 .build();
693 assert_eq!(
694 yaml.to_string(),
695 "- \n id: '1'\n name: Alice\n- \n id: '2'\n name: Bob\n- \n id: '3'\n name: Charlie"
696 );
697 }
698
699 #[test]
700 fn test_sequences_with_nested_sequences() {
701 let yaml = YamlBuilder::sequence()
702 .sequence(|s| s.item("1").item("2").item("3"))
703 .sequence(|s| s.item("a").item("b").item("c"))
704 .sequence(|s| s.item("x").item("y").item("z"))
705 .build()
706 .build();
707 assert_eq!(
708 yaml.to_string(),
709 "- \n - '1'\n - '2'\n - '3'\n- \n - a\n - b\n - c\n- \n - x\n - y\n - z"
710 );
711 }
712
713 #[test]
714 fn test_mixed_nesting_depth() {
715 let yaml = YamlBuilder::mapping()
716 .sequence("list", |s| {
717 s.item("simple")
718 .mapping(|m| m.pair("key", "value"))
719 .sequence(|s2| s2.item("nested1").item("nested2"))
720 })
721 .mapping("object", |m| {
722 m.pair("simple", "value")
723 .sequence("list", |s| s.item("item1").item("item2"))
724 .mapping("nested", |m2| m2.pair("deep", "value"))
725 })
726 .build()
727 .build();
728 assert_eq!(
729 yaml.to_string(),
730 "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"
731 );
732 }
733
734 #[test]
735 fn test_boolean_and_null_strings() {
736 let yaml = YamlBuilder::mapping()
737 .pair("bool_true", "true")
738 .pair("bool_false", "false")
739 .pair("yes", "yes")
740 .pair("no", "no")
741 .pair("null_value", "null")
742 .pair("tilde", "~")
743 .build()
744 .build();
745 assert_eq!(
746 yaml.to_string(),
747 "bool_true: 'true'\nbool_false: 'false'\nyes: 'yes'\nno: 'no'\nnull_value: 'null'\ntilde: '~'"
748 );
749 }
750
751 #[test]
752 fn test_long_strings() {
753 let yaml = YamlBuilder::mapping()
754 .pair("short", "test")
755 .pair("long", "a".repeat(100))
756 .build()
757 .build();
758 assert_eq!(
759 yaml.to_string(),
760 format!("short: test\nlong: {}", "a".repeat(100))
761 );
762 }
763
764 #[test]
765 fn test_unicode_values() {
766 let yaml = YamlBuilder::mapping()
767 .pair("emoji", "🎉🚀💻")
768 .pair("chinese", "你好世界")
769 .pair("arabic", "مرحبا بالعالم")
770 .pair("mixed", "Hello 世界 🌍")
771 .build()
772 .build();
773 assert_eq!(
774 yaml.to_string(),
775 "emoji: 🎉🚀💻\nchinese: 你好世界\narabic: مرحبا بالعالم\nmixed: Hello 世界 🌍"
776 );
777 }
778
779 #[test]
780 fn test_build_document_convenience_sequence() {
781 let doc = YamlBuilder::sequence()
782 .item("first")
783 .item("second")
784 .build_document();
785
786 let text = doc.to_string();
787 assert_eq!(text.trim(), "- first\n- second");
788 }
789
790 #[test]
791 fn test_build_document_convenience_mapping() {
792 let doc = YamlBuilder::mapping()
793 .pair("name", "test")
794 .pair("version", "1.0")
795 .build_document();
796
797 let text = doc.to_string();
798 assert_eq!(text.trim(), "name: test\nversion: '1.0'");
799 }
800
801 #[test]
802 fn test_insert_pre_built_sequence() {
803 let doc = YamlBuilder::mapping()
805 .pair("name", "my-app")
806 .sequence("dependencies", |s| s.item("serde").item("tokio"))
807 .build_document();
808
809 let text = doc.to_string();
810 assert_eq!(
811 text.trim(),
812 "name: my-app\ndependencies: \n - serde\n - tokio"
813 );
814 }
815
816 #[test]
817 fn test_insert_pre_built_mapping() {
818 let doc = YamlBuilder::mapping()
820 .pair("name", "my-app")
821 .mapping("database", |m| {
822 m.pair("host", "localhost").pair("port", 5432)
823 })
824 .build_document();
825
826 let text = doc.to_string();
827 assert_eq!(
828 text.trim(),
829 "name: my-app\ndatabase: \n host: localhost\n port: 5432"
830 );
831 }
832
833 #[test]
834 fn test_insert_in_sequence() {
835 let doc = YamlBuilder::sequence()
837 .item("first")
838 .sequence(|s| s.item("a").item("b"))
839 .mapping(|m| m.pair("key", "value"))
840 .build_document();
841
842 let text = doc.to_string();
843 assert_eq!(text.trim(), "- first\n- \n - a\n - b\n- \n key: value");
844 }
845
846 #[test]
847 fn test_complex_pre_built_structure() {
848 let doc = YamlBuilder::mapping()
850 .pair("version", "1.0")
851 .pair("name", "my-application")
852 .mapping("database", |m| {
853 m.pair("host", "localhost")
854 .pair("port", 5432)
855 .pair("name", "myapp")
856 })
857 .sequence("dependencies", |s| {
858 s.item("serde").item("tokio").item("reqwest")
859 })
860 .build_document();
861
862 let text = doc.to_string();
863 assert_eq!(
864 text.trim(),
865 "version: '1.0'\nname: my-application\ndatabase: \n host: localhost\n port: 5432\n name: myapp\ndependencies: \n - serde\n - tokio\n - reqwest"
866 );
867 }
868
869 #[test]
870 fn test_pair_with_typed_values() {
871 let yaml = YamlBuilder::mapping()
872 .pair("port", 5432_i64)
873 .pair("debug", true)
874 .pair("ratio", 1.5_f64)
875 .build()
876 .build();
877 assert_eq!(yaml.to_string(), "port: 5432\ndebug: true\nratio: 1.5");
878 }
879
880 #[test]
883 fn test_sequence_builder_with_block_mappings() {
884 use crate::Document;
885 use std::str::FromStr;
886
887 let yaml = r#"
889Reference:
890 Author: Stefan Kurze
891 Title: Wörterbücher und Textdateien durchsuchen mit grafischem Frontend
892 Journal: LinuxUser
893 Year: 2003
894Reference:
895 Author: Michael Vogelbacher
896 Title: Service und Informationen aus dem Netz
897 Journal: LinuxUser
898 Year: 2001
899"#;
900
901 let doc = Document::from_str(yaml).unwrap();
902 let mapping = doc.as_mapping().unwrap();
903
904 let mut reference_values = Vec::new();
906 for (key, value) in &mapping {
907 if let Some(key_scalar) = key.as_scalar() {
908 if key_scalar.as_string() == "Reference" {
909 reference_values.push(value);
910 }
911 }
912 }
913
914 while mapping.remove("Reference").is_some() {}
916
917 let mut seq_builder = SequenceBuilder::new();
919 for value in &reference_values {
920 seq_builder = seq_builder.item(value);
921 }
922 let seq_doc = seq_builder.build_document();
923
924 if let Some(seq) = seq_doc.as_sequence() {
926 mapping.set("Reference", seq);
927 }
928
929 let result = doc.to_string();
930
931 let expected = r#"Reference:
934- Author: Stefan Kurze
935 Title: Wörterbücher und Textdateien durchsuchen mit grafischem Frontend
936 Journal: LinuxUser
937 Year: 2003
938- Author: Michael Vogelbacher
939 Title: Service und Informationen aus dem Netz
940 Journal: LinuxUser
941 Year: 2001
942"#;
943
944 assert_eq!(result.trim(), expected.trim());
945 }
946
947 #[test]
948 fn test_sequence_builder_simple_mapping() {
949 use crate::Document;
950 use std::str::FromStr;
951
952 let yaml = r#"
953item:
954 key: value
955 foo: bar
956"#;
957
958 let doc = Document::from_str(yaml).unwrap();
959 let mapping = doc.as_mapping().unwrap();
960 let item_value = mapping.get("item").unwrap();
961 let item_mapping = item_value.as_mapping().unwrap();
962
963 let seq = SequenceBuilder::new().item(item_mapping).build_document();
965
966 let result = seq.to_string();
967
968 let expected = "- key: value\n foo: bar";
972 assert_eq!(result.trim(), expected);
973 }
974}