1use crate::{
4 error_recovery::{ErrorBuilder, ErrorRecoveryContext, ParseContext, RecoveryStrategy},
5 lex::{lex, SyntaxKind},
6 parse::Parse,
7 ParseErrorKind, PositionedParseError,
8};
9use rowan::ast::AstNode;
10use rowan::GreenNodeBuilder;
11use std::path::Path;
12use std::str::FromStr;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub(crate) struct ParsedYaml {
21 pub(crate) green_node: rowan::GreenNode,
23 pub(crate) errors: Vec<String>,
25 pub(crate) positioned_errors: Vec<PositionedParseError>,
27}
28
29use crate::nodes::ast_node;
31pub use crate::nodes::{Lang, SyntaxNode};
32
33pub use crate::nodes::{
35 Alias, Directive, Document, Mapping, MappingEntry, Scalar, ScalarConversionError, Sequence,
36 TaggedNode,
37};
38
39ast_node!(
40 YamlFile,
41 ROOT,
42 "A YAML file containing one or more documents"
43);
44
45pub trait ValueNode: rowan::ast::AstNode<Language = Lang> {
47 fn is_inline(&self) -> bool;
49}
50
51impl ValueNode for Mapping {
52 fn is_inline(&self) -> bool {
53 if self.0.children_with_tokens().any(|c| {
55 c.as_token()
56 .map(|t| t.kind() == SyntaxKind::LEFT_BRACE || t.kind() == SyntaxKind::RIGHT_BRACE)
57 .unwrap_or(false)
58 }) {
59 return true;
60 }
61 false
62 }
63}
64
65impl ValueNode for Sequence {
66 fn is_inline(&self) -> bool {
67 if self.0.children_with_tokens().any(|c| {
69 c.as_token()
70 .map(|t| {
71 t.kind() == SyntaxKind::LEFT_BRACKET || t.kind() == SyntaxKind::RIGHT_BRACKET
72 })
73 .unwrap_or(false)
74 }) {
75 return true;
76 }
77 false
78 }
79}
80
81impl ValueNode for Scalar {
82 fn is_inline(&self) -> bool {
83 true
85 }
86}
87
88pub(crate) fn ends_with_newline(node: &SyntaxNode) -> bool {
92 node.last_token()
93 .map(|t| t.kind() == SyntaxKind::NEWLINE)
94 .unwrap_or(false)
95}
96
97pub(crate) fn add_newline_token(
99 elements: &mut Vec<rowan::NodeOrToken<rowan::SyntaxNode<Lang>, rowan::SyntaxToken<Lang>>>,
100) {
101 let mut nl_builder = rowan::GreenNodeBuilder::new();
102 nl_builder.start_node(SyntaxKind::ROOT.into());
103 nl_builder.token(SyntaxKind::NEWLINE.into(), "\n");
104 nl_builder.finish_node();
105 let nl_node = SyntaxNode::new_root_mut(nl_builder.finish());
106 if let Some(token) = nl_node.first_token() {
107 elements.push(token.into());
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113pub struct Set(SyntaxNode);
114
115impl Set {
116 pub fn cast(node: SyntaxNode) -> Option<Self> {
118 if node.kind() == SyntaxKind::TAGGED_NODE {
119 if let Some(tagged_node) = TaggedNode::cast(node.clone()) {
120 if tagged_node.tag().as_deref() == Some("!!set") {
121 return Some(Set(node));
122 }
123 }
124 }
125 None
126 }
127
128 fn inner_mapping(&self) -> Option<Mapping> {
130 self.0.children().find_map(Mapping::cast)
131 }
132
133 pub fn members(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
146 self.inner_mapping().into_iter().flat_map(|m| {
160 let mapping_node = m.syntax().clone();
161 let has_entries = mapping_node
162 .children()
163 .any(|n| n.kind() == SyntaxKind::MAPPING_ENTRY);
164
165 if has_entries {
166 mapping_node
168 .children()
169 .filter(|n| n.kind() == SyntaxKind::MAPPING_ENTRY)
170 .filter_map(|entry| {
171 entry
172 .children()
173 .find(|n| n.kind() == SyntaxKind::KEY)
174 .and_then(|key| key.children().next())
175 .and_then(crate::as_yaml::YamlNode::from_syntax)
176 })
177 .collect::<Vec<_>>()
178 } else {
179 mapping_node
181 .children()
182 .filter(|n| n.kind() == SyntaxKind::KEY)
183 .filter_map(|key| {
184 key.children()
185 .next()
186 .and_then(crate::as_yaml::YamlNode::from_syntax)
187 })
188 .collect::<Vec<_>>()
189 }
190 })
191 }
192
193 pub fn len(&self) -> usize {
195 self.members().count()
196 }
197
198 pub fn is_empty(&self) -> bool {
200 self.members().next().is_none()
201 }
202
203 pub fn contains(&self, value: impl crate::AsYaml) -> bool {
205 self.members()
206 .any(|member| crate::as_yaml::yaml_eq(&member, &value))
207 }
208}
209
210fn extract_content_node(wrapper: &SyntaxNode) -> Option<SyntaxNode> {
214 use crate::lex::SyntaxKind;
215 match wrapper.kind() {
216 SyntaxKind::VALUE | SyntaxKind::KEY => wrapper.children().next(),
217 _ => Some(wrapper.clone()),
218 }
219}
220
221fn smart_cast<T: AstNode<Language = Lang>>(node: SyntaxNode) -> Option<T> {
223 if let Some(content) = extract_content_node(&node) {
224 T::cast(content)
225 } else {
226 None
227 }
228}
229
230pub(crate) fn extract_scalar(node: &SyntaxNode) -> Option<Scalar> {
232 smart_cast(node.clone())
233}
234
235pub(crate) fn extract_mapping(node: &SyntaxNode) -> Option<Mapping> {
237 smart_cast(node.clone())
238}
239
240pub(crate) fn extract_sequence(node: &SyntaxNode) -> Option<Sequence> {
242 smart_cast(node.clone())
243}
244
245pub(crate) fn extract_tagged_node(node: &SyntaxNode) -> Option<TaggedNode> {
247 smart_cast(node.clone())
248}
249
250pub(crate) fn copy_node_to_builder(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
252 builder.start_node(node.kind().into());
253 add_node_children_to(builder, node);
254 builder.finish_node();
255}
256
257pub(crate) fn add_node_children_to(builder: &mut GreenNodeBuilder, node: &SyntaxNode) {
259 for child in node.children_with_tokens() {
260 match child {
261 rowan::NodeOrToken::Node(child_node) => {
262 builder.start_node(child_node.kind().into());
263 add_node_children_to(builder, &child_node);
264 builder.finish_node();
265 }
266 rowan::NodeOrToken::Token(token) => {
267 builder.token(token.kind().into(), token.text());
268 }
269 }
270 }
271}
272
273pub(crate) fn dump_cst_to_string(node: &SyntaxNode, indent: usize) -> String {
275 let mut result = String::new();
276 let indent_str = " ".repeat(indent);
277
278 for child in node.children_with_tokens() {
279 match child {
280 rowan::NodeOrToken::Node(n) => {
281 result.push_str(&format!("{}{:?}\n", indent_str, n.kind()));
282 result.push_str(&dump_cst_to_string(&n, indent + 1));
283 }
284 rowan::NodeOrToken::Token(t) => {
285 result.push_str(&format!("{} {:?} {:?}\n", indent_str, t.kind(), t.text()));
286 }
287 }
288 }
289 result
290}
291
292impl Default for YamlFile {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298impl YamlFile {
299 pub fn new() -> YamlFile {
301 let mut builder = GreenNodeBuilder::new();
302 builder.start_node(SyntaxKind::ROOT.into());
303 builder.finish_node();
304 YamlFile(SyntaxNode::new_root_mut(builder.finish()))
305 }
306
307 pub fn parse(text: &str) -> Parse<YamlFile> {
309 Parse::parse_yaml(text)
310 }
311
312 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<YamlFile, crate::YamlError> {
314 let contents = std::fs::read_to_string(path)?;
315 Self::from_str(&contents)
316 }
317
318 pub fn documents(&self) -> impl Iterator<Item = Document> {
320 self.0.children().filter_map(Document::cast)
321 }
322
323 pub fn document(&self) -> Option<Document> {
328 self.documents().next()
329 }
330
331 pub fn ensure_document(&self) -> Document {
335 if self.documents().next().is_none() {
336 let doc = Document::new_mapping();
338 self.push_document(doc);
339 }
340 self.documents()
341 .next()
342 .expect("Document should exist after ensuring")
343 }
344
345 pub fn directives(&self) -> impl Iterator<Item = Directive> {
347 self.0.children().filter_map(Directive::cast)
348 }
349
350 pub fn add_directive(&self, directive_text: &str) {
359 let mut builder = GreenNodeBuilder::new();
361 builder.start_node(SyntaxKind::DIRECTIVE.into());
362 builder.token(SyntaxKind::DIRECTIVE.into(), directive_text);
363 builder.finish_node();
364 let directive_node = SyntaxNode::new_root_mut(builder.finish());
365
366 self.0.splice_children(0..0, vec![directive_node.into()]);
368 }
369
370 pub fn push_document(&self, document: Document) {
372 let children_count = self.0.children_with_tokens().count();
373
374 self.0
376 .splice_children(children_count..children_count, vec![document.0.into()]);
377 }
378
379 pub fn set(&self, key: impl crate::AsYaml, value: impl crate::AsYaml) {
388 if let Some(doc) = self.document() {
389 doc.set(key, value);
390 }
391 }
392
393 pub fn insert_after(
402 &self,
403 after_key: impl crate::AsYaml,
404 key: impl crate::AsYaml,
405 value: impl crate::AsYaml,
406 ) -> bool {
407 if let Some(doc) = self.document() {
408 doc.insert_after(after_key, key, value)
409 } else {
410 false
411 }
412 }
413
414 pub fn insert_before(
423 &self,
424 before_key: impl crate::AsYaml,
425 key: impl crate::AsYaml,
426 value: impl crate::AsYaml,
427 ) -> bool {
428 if let Some(doc) = self.document() {
429 doc.insert_before(before_key, key, value)
430 } else {
431 false
432 }
433 }
434
435 pub fn move_after(
446 &self,
447 after_key: impl crate::AsYaml,
448 key: impl crate::AsYaml,
449 value: impl crate::AsYaml,
450 ) -> bool {
451 if let Some(doc) = self.document() {
452 doc.move_after(after_key, key, value)
453 } else {
454 false
455 }
456 }
457
458 pub fn move_before(
469 &self,
470 before_key: impl crate::AsYaml,
471 key: impl crate::AsYaml,
472 value: impl crate::AsYaml,
473 ) -> bool {
474 if let Some(doc) = self.document() {
475 doc.move_before(before_key, key, value)
476 } else {
477 false
478 }
479 }
480
481 pub fn insert_at_index(
490 &self,
491 index: usize,
492 key: impl crate::AsYaml,
493 value: impl crate::AsYaml,
494 ) {
495 if let Some(doc) = self.document() {
496 doc.insert_at_index(index, key, value);
497 } else {
499 let mut builder = GreenNodeBuilder::new();
502 builder.start_node(SyntaxKind::DOCUMENT.into());
503 builder.start_node(SyntaxKind::MAPPING.into());
504 builder.finish_node(); builder.finish_node(); let doc = Document(SyntaxNode::new_root_mut(builder.finish()));
507
508 self.0.splice_children(0..0, vec![doc.0.into()]);
510
511 if let Some(doc) = self.document() {
513 doc.insert_at_index(index, key, value);
514 }
515 }
516 }
517}
518
519impl FromStr for YamlFile {
520 type Err = crate::YamlError;
521
522 fn from_str(s: &str) -> Result<Self, Self::Err> {
523 let parsed = YamlFile::parse(s);
524 if !parsed.positioned_errors().is_empty() {
525 let first = &parsed.positioned_errors()[0];
526 let lc = crate::byte_offset_to_line_column(s, first.range.start as usize);
527 return Err(crate::YamlError::Parse {
528 message: first.message.clone(),
529 line: Some(lc.line),
530 column: Some(lc.column),
531 });
532 }
533 Ok(parsed.tree())
534 }
535}
536
537struct Parser {
539 tokens: Vec<(SyntaxKind, String)>,
540 current_token_index: usize,
541 builder: GreenNodeBuilder<'static>,
542 errors: Vec<String>,
543 positioned_errors: Vec<PositionedParseError>,
544 in_flow_context: bool,
545 error_context: ErrorRecoveryContext,
547 in_value_context: bool,
549 current_line_indent: usize,
551}
552
553impl Parser {
554 fn new(text: &str) -> Self {
555 let lexed = lex(text);
556 let mut tokens = Vec::new();
557
558 for (kind, token_text) in lexed {
559 tokens.push((kind, token_text.to_string()));
560 }
561
562 let token_count = tokens.len();
564 tokens.reverse();
565
566 Self {
567 tokens,
568 current_token_index: token_count,
569 builder: GreenNodeBuilder::new(),
570 errors: Vec::new(),
571 positioned_errors: Vec::new(),
572 in_flow_context: false,
573 error_context: ErrorRecoveryContext::new(text.to_string()),
574 in_value_context: false,
575 current_line_indent: 0,
576 }
577 }
578
579 fn parse(mut self) -> ParsedYaml {
580 self.builder.start_node(SyntaxKind::ROOT.into());
581
582 if self.current() == Some(SyntaxKind::BOM) {
585 self.bump(); }
587
588 self.skip_ws_and_newlines();
589
590 while self.current() == Some(SyntaxKind::DIRECTIVE) {
592 self.parse_directive();
593 self.skip_ws_and_newlines();
594 }
595
596 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
599 self.parse_document();
600 self.skip_ws_and_newlines();
601
602 while self.current() == Some(SyntaxKind::DOC_START)
604 || self.current() == Some(SyntaxKind::DIRECTIVE)
605 {
606 while self.current() == Some(SyntaxKind::DIRECTIVE) {
608 self.parse_directive();
609 self.skip_ws_and_newlines();
610 }
611
612 if self.current() == Some(SyntaxKind::DOC_START)
614 || (self.current().is_some() && self.current() != Some(SyntaxKind::EOF))
615 {
616 self.parse_document();
617 self.skip_ws_and_newlines();
618 } else {
619 break;
620 }
621 }
622 }
623
624 while self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
627 self.builder.start_node(SyntaxKind::ERROR.into());
628
629 while self.current().is_some()
631 && self.current() != Some(SyntaxKind::EOF)
632 && self.current() != Some(SyntaxKind::DOC_START)
633 && self.current() != Some(SyntaxKind::DIRECTIVE)
634 {
635 self.bump();
636 }
637
638 self.builder.finish_node();
639
640 if self.current() == Some(SyntaxKind::DOC_START)
642 || self.current() == Some(SyntaxKind::DIRECTIVE)
643 {
644 while self.current() == Some(SyntaxKind::DIRECTIVE) {
646 self.parse_directive();
647 self.skip_ws_and_newlines();
648 }
649
650 if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
652 self.parse_document();
653 self.skip_ws_and_newlines();
654 }
655 }
656 }
657
658 self.builder.finish_node();
659
660 ParsedYaml {
661 green_node: self.builder.finish(),
662 errors: self.errors,
663 positioned_errors: self.positioned_errors,
664 }
665 }
666
667 fn parse_document(&mut self) {
668 self.builder.start_node(SyntaxKind::DOCUMENT.into());
669
670 if self.current() == Some(SyntaxKind::DOC_START) {
672 self.bump();
673 self.skip_ws_and_newlines();
674 }
675
676 if self.current().is_some()
678 && self.current() != Some(SyntaxKind::DOC_END)
679 && self.current() != Some(SyntaxKind::DOC_START)
680 {
681 self.parse_value();
682 }
683
684 if self.current() == Some(SyntaxKind::DOC_END) {
686 self.bump();
687
688 self.skip_whitespace();
690 if self.current().is_some()
691 && self.current() != Some(SyntaxKind::NEWLINE)
692 && self.current() != Some(SyntaxKind::EOF)
693 && self.current() != Some(SyntaxKind::DOC_START)
694 && self.current() != Some(SyntaxKind::DIRECTIVE)
695 {
696 self.builder.start_node(SyntaxKind::ERROR.into());
698 while self.current().is_some()
699 && self.current() != Some(SyntaxKind::NEWLINE)
700 && self.current() != Some(SyntaxKind::EOF)
701 && self.current() != Some(SyntaxKind::DOC_START)
702 && self.current() != Some(SyntaxKind::DIRECTIVE)
703 {
704 self.bump();
705 }
706 self.builder.finish_node();
707 }
708 }
709
710 self.builder.finish_node();
711 }
712
713 fn parse_value(&mut self) {
714 self.parse_value_with_base_indent(0);
715 }
716
717 fn parse_value_with_base_indent(&mut self, base_indent: usize) {
718 match self.current() {
719 Some(SyntaxKind::COMMENT) => {
720 self.bump(); self.skip_ws_and_newlines(); self.parse_value_with_base_indent(base_indent);
725 }
726 Some(SyntaxKind::DASH) if !self.in_flow_context => {
727 self.parse_sequence_with_base_indent(base_indent)
728 }
729 Some(SyntaxKind::ANCHOR) => {
730 self.bump(); self.skip_whitespace();
732 self.parse_value_with_base_indent(base_indent);
733 }
734 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
735 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
736 Some(SyntaxKind::MERGE_KEY) => {
737 self.parse_mapping_with_base_indent(base_indent);
739 }
740 Some(SyntaxKind::QUESTION) => {
741 self.parse_explicit_key_mapping();
743 }
744 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
745 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
746 Some(
747 SyntaxKind::STRING
748 | SyntaxKind::INT
749 | SyntaxKind::FLOAT
750 | SyntaxKind::BOOL
751 | SyntaxKind::NULL,
752 ) => {
753 if !self.in_flow_context && !self.in_value_context && self.is_mapping_key() {
757 self.parse_mapping_with_base_indent(base_indent);
758 } else {
759 self.parse_scalar();
760 }
761 }
762 Some(SyntaxKind::LEFT_BRACKET) => {
763 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
766 {
767 self.parse_complex_key_mapping();
768 } else {
769 self.parse_flow_sequence();
770 }
771 }
772 Some(SyntaxKind::LEFT_BRACE) => {
773 if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
776 {
777 self.parse_complex_key_mapping();
778 } else {
779 self.parse_flow_mapping();
780 }
781 }
782 Some(SyntaxKind::INDENT) => {
783 self.bump(); self.parse_value(); }
787 Some(SyntaxKind::NEWLINE) => {
788 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
791 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
792 self.bump(); self.parse_value_with_base_indent(indent_level);
794 } else {
795 self.builder.start_node(SyntaxKind::SCALAR.into());
797 self.builder.finish_node();
798 }
799 }
800 _ => self.parse_scalar(),
801 }
802 }
803
804 fn parse_alias(&mut self) {
805 self.builder.start_node(SyntaxKind::ALIAS.into());
808 if self.current() == Some(SyntaxKind::REFERENCE) {
809 self.bump(); }
811 self.builder.finish_node();
812 }
813
814 fn parse_scalar(&mut self) {
815 self.builder.start_node(SyntaxKind::SCALAR.into());
816
817 if matches!(
819 self.current(),
820 Some(SyntaxKind::QUOTE | SyntaxKind::SINGLE_QUOTE)
821 ) {
822 let quote_type = self
823 .current()
824 .expect("current token is Some: checked by matches! guard above");
825 self.bump(); while self.current().is_some() && self.current() != Some(quote_type) {
829 self.bump();
830 }
831
832 if self.current() == Some(quote_type) {
833 self.bump(); } else {
835 let expected_quote = if quote_type == SyntaxKind::QUOTE {
836 "\""
837 } else {
838 "'"
839 };
840 let error_msg = self.create_detailed_error(
841 "Unterminated quoted string",
842 &format!("closing quote {}", expected_quote),
843 self.current_text(),
844 );
845 self.add_error_and_recover(
846 error_msg,
847 quote_type,
848 ParseErrorKind::UnterminatedString,
849 );
850 }
851 } else {
852 if matches!(
854 self.current(),
855 Some(
856 SyntaxKind::STRING
857 | SyntaxKind::UNTERMINATED_STRING
858 | SyntaxKind::INT
859 | SyntaxKind::FLOAT
860 | SyntaxKind::BOOL
861 | SyntaxKind::NULL
862 )
863 ) {
864 if self.current() == Some(SyntaxKind::UNTERMINATED_STRING) {
866 self.add_error(
867 "Unterminated quoted string".to_string(),
868 ParseErrorKind::UnterminatedString,
869 );
870 }
871 if !self.in_flow_context {
872 let scalar_indent = self.current_line_indent;
880
881 while let Some(kind) = self.current() {
882 if kind == SyntaxKind::COMMENT {
883 break;
885 }
886
887 if kind == SyntaxKind::NEWLINE {
888 if self.is_plain_scalar_continuation(scalar_indent) {
890 self.bump(); while matches!(
895 self.current(),
896 Some(SyntaxKind::INDENT | SyntaxKind::WHITESPACE)
897 ) {
898 self.bump();
899 }
900
901 continue;
903 } else {
904 break;
906 }
907 }
908
909 if matches!(
911 kind,
912 SyntaxKind::LEFT_BRACKET
913 | SyntaxKind::LEFT_BRACE
914 | SyntaxKind::RIGHT_BRACKET
915 | SyntaxKind::RIGHT_BRACE
916 | SyntaxKind::COMMA
917 ) {
918 break;
919 }
920
921 if kind == SyntaxKind::WHITESPACE {
923 if self.tokens.len() >= 2 {
925 let next_kind = self.tokens[self.tokens.len() - 2].0;
926 if next_kind == SyntaxKind::COMMENT {
927 break;
929 }
930 }
931 }
932
933 self.bump();
934 }
935 } else {
936 let is_quoted_string = if let Some(SyntaxKind::STRING) = self.current() {
944 self.current_text()
945 .map(|text| text.starts_with('"') || text.starts_with('\''))
946 .unwrap_or(false)
947 } else {
948 false
949 };
950
951 self.bump(); if !is_quoted_string {
956 while let Some(kind) = self.current() {
957 if matches!(
959 kind,
960 SyntaxKind::COMMA
961 | SyntaxKind::RIGHT_BRACE
962 | SyntaxKind::RIGHT_BRACKET
963 | SyntaxKind::COMMENT
964 ) {
965 break;
966 }
967
968 if kind == SyntaxKind::NEWLINE {
971 self.bump(); while matches!(
974 self.current(),
975 Some(SyntaxKind::WHITESPACE | SyntaxKind::INDENT)
976 ) {
977 self.bump();
978 }
979 continue;
981 }
982
983 if kind == SyntaxKind::WHITESPACE {
987 if self.tokens.len() >= 2 {
990 let after_whitespace = self.tokens[self.tokens.len() - 2].0;
992 if matches!(
993 after_whitespace,
994 SyntaxKind::COMMA
995 | SyntaxKind::RIGHT_BRACE
996 | SyntaxKind::RIGHT_BRACKET
997 | SyntaxKind::NEWLINE
998 | SyntaxKind::COMMENT
999 ) {
1000 break;
1002 }
1003 }
1005 }
1006
1007 if kind == SyntaxKind::COLON && self.tokens.len() >= 2 {
1009 let next_kind = self.tokens[self.tokens.len() - 2].0;
1010 if matches!(
1011 next_kind,
1012 SyntaxKind::COMMA
1013 | SyntaxKind::RIGHT_BRACE
1014 | SyntaxKind::RIGHT_BRACKET
1015 | SyntaxKind::WHITESPACE
1016 | SyntaxKind::NEWLINE
1017 ) {
1018 break;
1020 }
1021 }
1022
1023 self.bump();
1024 }
1025 }
1026 }
1027 } else {
1028 while let Some(kind) = self.current() {
1030 if matches!(
1031 kind,
1032 SyntaxKind::NEWLINE
1033 | SyntaxKind::DASH
1034 | SyntaxKind::COMMENT
1035 | SyntaxKind::DOC_START
1036 | SyntaxKind::DOC_END
1037 ) {
1038 break;
1039 }
1040
1041 if kind == SyntaxKind::COLON {
1044 if self.in_flow_context {
1045 if self.tokens.len() >= 2 {
1048 let next_kind = self.tokens[self.tokens.len() - 2].0;
1049 if matches!(
1050 next_kind,
1051 SyntaxKind::COMMA
1052 | SyntaxKind::RIGHT_BRACE
1053 | SyntaxKind::RIGHT_BRACKET
1054 | SyntaxKind::WHITESPACE
1055 | SyntaxKind::NEWLINE
1056 ) {
1057 break;
1059 }
1060 }
1061 } else {
1063 break;
1065 }
1066 }
1067
1068 if self.in_flow_context
1070 && matches!(
1071 kind,
1072 SyntaxKind::LEFT_BRACKET
1073 | SyntaxKind::RIGHT_BRACKET
1074 | SyntaxKind::LEFT_BRACE
1075 | SyntaxKind::RIGHT_BRACE
1076 | SyntaxKind::COMMA
1077 )
1078 {
1079 break;
1080 }
1081 self.bump();
1082 }
1083 }
1084 }
1085
1086 self.builder.finish_node();
1087 }
1088
1089 fn parse_tagged_value(&mut self) {
1090 let tag_text = self.peek_tag_text();
1092
1093 match tag_text {
1094 Some("!!set") => self.parse_tagged_set(),
1095 Some("!!omap") => self.parse_tagged_omap(),
1096 Some("!!pairs") => self.parse_tagged_pairs(),
1097 _ => {
1098 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1100 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1104 self.bump();
1105 }
1106
1107 self.parse_value();
1109
1110 self.builder.finish_node();
1111 }
1112 }
1113 }
1114
1115 fn peek_tag_text(&self) -> Option<&str> {
1116 self.tokens
1117 .last()
1118 .filter(|(kind, _)| *kind == SyntaxKind::TAG)
1119 .map(|(_, text)| text.as_str())
1120 }
1121
1122 fn parse_tagged_set(&mut self) {
1123 self.parse_tagged_collection(true); }
1125
1126 fn parse_tagged_omap(&mut self) {
1127 self.parse_tagged_collection(false); }
1129
1130 fn parse_tagged_pairs(&mut self) {
1131 self.parse_tagged_collection(false); }
1133
1134 fn parse_tagged_collection(&mut self, is_mapping: bool) {
1135 self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
1136
1137 self.bump(); while matches!(self.current(), Some(SyntaxKind::WHITESPACE)) {
1142 self.bump();
1143 }
1144
1145 match self.current() {
1147 Some(SyntaxKind::LEFT_BRACE) if is_mapping => self.parse_flow_mapping(),
1148 Some(SyntaxKind::LEFT_BRACKET) if !is_mapping => self.parse_flow_sequence(),
1149 Some(SyntaxKind::NEWLINE) => {
1150 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1153 self.bump(); }
1155 if is_mapping {
1156 self.parse_mapping();
1157 } else {
1158 self.parse_sequence();
1159 }
1160 }
1161 _ => {
1162 if is_mapping {
1163 self.parse_mapping();
1164 } else {
1165 self.parse_sequence();
1166 }
1167 }
1168 }
1169
1170 self.builder.finish_node();
1171 }
1172
1173 fn parse_literal_block_scalar(&mut self) {
1174 self.builder.start_node(SyntaxKind::SCALAR.into());
1175 self.bump(); self.parse_block_scalar_header();
1177 self.parse_block_scalar_content();
1178 self.builder.finish_node();
1179 }
1180
1181 fn parse_folded_block_scalar(&mut self) {
1182 self.builder.start_node(SyntaxKind::SCALAR.into());
1183 self.bump(); self.parse_block_scalar_header();
1185 self.parse_block_scalar_content();
1186 self.builder.finish_node();
1187 }
1188
1189 fn parse_block_scalar_header(&mut self) {
1190 while let Some(kind) = self.current() {
1195 match kind {
1196 SyntaxKind::NEWLINE | SyntaxKind::COMMENT => break,
1197 SyntaxKind::INT => {
1198 if let Some(text) = self.current_text() {
1200 if text.len() == 1
1201 && text
1202 .chars()
1203 .next()
1204 .expect("text is non-empty: len == 1 checked above")
1205 .is_ascii_digit()
1206 {
1207 self.bump(); } else {
1209 break;
1211 }
1212 } else {
1213 break;
1214 }
1215 }
1216 SyntaxKind::STRING => {
1217 if let Some(text) = self.current_text() {
1219 if text == "+" || text == "-" {
1220 self.bump(); } else {
1222 break;
1224 }
1225 } else {
1226 break;
1227 }
1228 }
1229 SyntaxKind::WHITESPACE => {
1230 self.bump();
1232 }
1233 _ => {
1234 break;
1236 }
1237 }
1238 }
1239
1240 if self.current() == Some(SyntaxKind::COMMENT) {
1242 self.bump();
1243 }
1244
1245 if self.current() == Some(SyntaxKind::NEWLINE) {
1247 self.bump();
1248 }
1249 }
1250
1251 fn parse_block_scalar_content(&mut self) {
1252 let mut last_was_newline = false;
1254 let mut base_indent: Option<usize> = None;
1255 let mut first_content_indent: Option<usize> = None;
1256
1257 while let Some(kind) = self.current() {
1258 if kind == SyntaxKind::INDENT && first_content_indent.is_none() {
1260 first_content_indent = self.current_text().map(|t| t.len());
1261 }
1262
1263 if base_indent.is_none() && first_content_indent.is_some() {
1265 base_indent = first_content_indent;
1266 }
1267
1268 if self.is_at_unindented_content_for_block_scalar(last_was_newline, base_indent) {
1270 break;
1271 }
1272
1273 match kind {
1274 SyntaxKind::DOC_START | SyntaxKind::DOC_END => break,
1276 SyntaxKind::NEWLINE => {
1278 self.bump();
1279 last_was_newline = true;
1280 continue;
1281 }
1282 _ => {
1284 self.bump();
1285 last_was_newline = false;
1286 }
1287 }
1288 }
1289 }
1290
1291 fn is_at_unindented_content_for_block_scalar(
1292 &self,
1293 after_newline: bool,
1294 base_indent: Option<usize>,
1295 ) -> bool {
1296 if after_newline {
1299 let current = self.current();
1301
1302 if matches!(
1304 current,
1305 Some(SyntaxKind::COLON) | Some(SyntaxKind::QUESTION)
1306 ) {
1307 return true;
1308 }
1309
1310 if let Some(base) = base_indent {
1312 if current == Some(SyntaxKind::INDENT) {
1313 if let Some(text) = self.current_text() {
1314 if text.len() < base {
1315 return true;
1317 }
1318 }
1319 }
1320 }
1321
1322 if current != Some(SyntaxKind::INDENT)
1324 && current != Some(SyntaxKind::WHITESPACE)
1325 && current != Some(SyntaxKind::NEWLINE)
1326 && current != Some(SyntaxKind::COMMENT)
1327 {
1328 return true;
1330 }
1331 }
1332 false
1333 }
1334
1335 fn parse_mapping(&mut self) {
1336 self.parse_mapping_with_base_indent(0);
1337 }
1338
1339 fn parse_mapping_with_base_indent(&mut self, base_indent: usize) {
1340 self.builder.start_node(SyntaxKind::MAPPING.into());
1341 self.error_context.push_context(ParseContext::Mapping);
1342
1343 while self.current().is_some() {
1344 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1346 break;
1347 }
1348
1349 loop {
1351 if self.current() == Some(SyntaxKind::COMMENT) {
1352 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1355 break;
1356 }
1357 self.bump();
1358 if self.current() == Some(SyntaxKind::NEWLINE) {
1359 self.bump();
1360 }
1361 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1362 break;
1363 }
1364 } else {
1365 break;
1366 }
1367 }
1368
1369 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1373 break;
1374 }
1375
1376 if !self.is_mapping_key() && !self.is_complex_mapping_key() {
1378 break;
1379 }
1380
1381 if self.current() == Some(SyntaxKind::LEFT_BRACKET)
1383 || self.current() == Some(SyntaxKind::LEFT_BRACE)
1384 {
1385 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1387
1388 self.builder.start_node(SyntaxKind::KEY.into());
1389 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
1390 self.parse_flow_sequence();
1391 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
1392 self.parse_flow_mapping();
1393 }
1394 self.builder.finish_node();
1395
1396 self.skip_ws_and_newlines();
1397
1398 if self.current() == Some(SyntaxKind::COLON) {
1399 self.bump();
1400 self.skip_whitespace();
1401
1402 self.builder.start_node(SyntaxKind::VALUE.into());
1403 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1404 self.parse_value();
1405 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1406 self.bump();
1407 if self.current() == Some(SyntaxKind::INDENT) {
1408 self.bump();
1409 self.parse_value();
1410 }
1411 }
1412 self.builder.finish_node();
1413 } else {
1414 let error_msg = self.create_detailed_error(
1415 "Missing colon in mapping",
1416 "':' after key",
1417 self.current_text(),
1418 );
1419 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1420 }
1421
1422 self.builder.finish_node();
1424 }
1425 else if self.current() == Some(SyntaxKind::QUESTION) {
1427 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1429
1430 self.bump(); self.skip_whitespace();
1433
1434 self.builder.start_node(SyntaxKind::KEY.into());
1435 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1436 self.parse_value();
1437 }
1438 self.builder.finish_node();
1439
1440 self.skip_ws_and_newlines();
1441
1442 if self.current() == Some(SyntaxKind::COLON) {
1444 self.bump(); self.skip_whitespace();
1446
1447 self.builder.start_node(SyntaxKind::VALUE.into());
1448 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1449 self.parse_value();
1450 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1451 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1453 self.bump(); self.parse_value();
1455 }
1456 }
1457 self.builder.finish_node();
1458 } else {
1459 self.builder.start_node(SyntaxKind::VALUE.into());
1461 self.builder.start_node(SyntaxKind::SCALAR.into());
1462 self.builder.token(SyntaxKind::NULL.into(), "");
1463 self.builder.finish_node();
1464 self.builder.finish_node();
1465 }
1466
1467 self.builder.finish_node();
1469 } else {
1470 self.parse_mapping_key_value_pair();
1471 }
1472 }
1473
1474 self.builder.finish_node();
1475 self.error_context.pop_context();
1476 }
1477
1478 fn parse_sequence(&mut self) {
1479 self.parse_sequence_with_base_indent(0);
1480 }
1481
1482 fn parse_sequence_with_base_indent(&mut self, base_indent: usize) {
1483 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1484 self.error_context.push_context(ParseContext::Sequence);
1485
1486 while self.current().is_some() {
1487 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1489 break;
1490 }
1491
1492 loop {
1494 if self.current() == Some(SyntaxKind::COMMENT) {
1495 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1498 break;
1499 }
1500 self.bump();
1501 if self.current() == Some(SyntaxKind::NEWLINE) {
1502 self.bump();
1503 }
1504 if self.skip_whitespace_only_with_dedent_check(base_indent) {
1505 break;
1506 }
1507 } else {
1508 break;
1509 }
1510 }
1511
1512 if base_indent > 0 && self.is_at_dedented_position(base_indent) {
1516 break;
1517 }
1518
1519 if self.current() != Some(SyntaxKind::DASH) {
1521 break;
1522 }
1523 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1525
1526 let item_indent = self.current_line_indent;
1528
1529 self.bump(); self.skip_whitespace();
1531
1532 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1533 self.parse_value_with_base_indent(item_indent);
1535 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1536 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1539 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
1540 self.bump(); self.parse_value_with_base_indent(indent_level);
1543 }
1544 }
1545
1546 if self.current() == Some(SyntaxKind::NEWLINE) {
1548 self.bump();
1549 }
1550
1551 self.builder.finish_node();
1553 }
1554
1555 self.builder.finish_node();
1556 self.error_context.pop_context();
1557 }
1558
1559 fn next_flow_element_is_implicit_mapping(&self) -> bool {
1572 let tokens = std::iter::once(self.current().unwrap_or(SyntaxKind::EOF))
1574 .chain(self.upcoming_tokens());
1575 has_implicit_mapping_pattern(tokens)
1576 }
1577
1578 fn parse_implicit_flow_mapping(&mut self) {
1581 self.builder.start_node(SyntaxKind::MAPPING.into());
1582 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1583
1584 self.builder.start_node(SyntaxKind::KEY.into());
1586 self.parse_value();
1587 self.builder.finish_node();
1588
1589 self.skip_ws_and_newlines();
1590
1591 if self.current() == Some(SyntaxKind::COLON) {
1593 self.bump();
1594 self.skip_ws_and_newlines();
1595 }
1596
1597 self.builder.start_node(SyntaxKind::VALUE.into());
1599 if matches!(
1601 self.current(),
1602 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACKET)
1603 ) {
1604 } else {
1606 self.parse_value();
1607 }
1608 self.builder.finish_node();
1609
1610 self.builder.finish_node(); self.builder.finish_node(); }
1613}
1614
1615fn has_implicit_mapping_pattern(tokens: impl Iterator<Item = SyntaxKind>) -> bool {
1619 let mut depth = 0;
1620
1621 for kind in tokens {
1622 match kind {
1623 SyntaxKind::LEFT_BRACE | SyntaxKind::LEFT_BRACKET => {
1625 depth += 1;
1626 }
1627 SyntaxKind::RIGHT_BRACE | SyntaxKind::RIGHT_BRACKET => {
1629 if depth == 0 {
1630 return false;
1632 }
1633 depth -= 1;
1634 }
1635 SyntaxKind::COLON if depth == 0 => {
1637 return true;
1639 }
1640 SyntaxKind::COMMA if depth == 0 => {
1641 return false;
1643 }
1644 _ => {}
1646 }
1647 }
1648
1649 false
1651}
1652
1653impl Parser {
1654 fn parse_flow_sequence(&mut self) {
1655 self.builder.start_node(SyntaxKind::SEQUENCE.into());
1656 self.error_context.push_context(ParseContext::FlowSequence);
1657
1658 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1662 self.in_flow_context = true;
1663
1664 while self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1665 self.builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1667
1668 if self.next_flow_element_is_implicit_mapping() {
1671 self.parse_implicit_flow_mapping();
1673 } else {
1674 self.parse_value();
1676 }
1677
1678 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
1682 self.bump();
1683 self.skip_ws_and_newlines(); }
1685
1686 self.builder.finish_node(); if self.current() != Some(SyntaxKind::RIGHT_BRACKET) && self.current().is_some() {
1689 if matches!(
1692 self.current(),
1693 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1694 ) {
1695 break;
1697 }
1698 }
1699 }
1700
1701 self.in_flow_context = prev_flow;
1702
1703 if self.current() == Some(SyntaxKind::RIGHT_BRACKET) {
1704 self.bump();
1705 } else {
1706 let error_msg = self.create_detailed_error(
1707 "Unclosed flow sequence",
1708 "']' to close sequence",
1709 self.current_text(),
1710 );
1711 self.add_error_and_recover(
1712 error_msg,
1713 SyntaxKind::RIGHT_BRACKET,
1714 ParseErrorKind::UnclosedFlowSequence,
1715 );
1716 }
1717
1718 self.builder.finish_node();
1719 self.error_context.pop_context();
1720 }
1721
1722 fn parse_flow_mapping(&mut self) {
1723 self.builder.start_node(SyntaxKind::MAPPING.into());
1724 self.error_context.push_context(ParseContext::FlowMapping);
1725
1726 self.bump(); self.skip_ws_and_newlines(); let prev_flow = self.in_flow_context;
1730 self.in_flow_context = true;
1731
1732 while self.current() != Some(SyntaxKind::RIGHT_BRACE) && self.current().is_some() {
1733 if matches!(
1735 self.current(),
1736 Some(SyntaxKind::DASH | SyntaxKind::DOC_START | SyntaxKind::DOC_END)
1737 ) {
1738 break;
1740 }
1741
1742 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1744
1745 self.builder.start_node(SyntaxKind::KEY.into());
1747
1748 if self.current() == Some(SyntaxKind::QUESTION) {
1750 self.bump(); self.skip_whitespace();
1752 }
1753
1754 self.parse_value();
1755 self.builder.finish_node();
1756
1757 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
1760 self.bump();
1761 self.skip_ws_and_newlines(); if matches!(
1766 self.current(),
1767 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1768 ) {
1769 self.builder.start_node(SyntaxKind::VALUE.into());
1771 self.builder.start_node(SyntaxKind::SCALAR.into());
1772 self.builder.token(SyntaxKind::NULL.into(), "");
1773 self.builder.finish_node(); self.builder.finish_node(); } else {
1776 self.builder.start_node(SyntaxKind::VALUE.into());
1778 self.parse_value();
1779 self.builder.finish_node();
1780 }
1781 } else if matches!(
1782 self.current(),
1783 Some(SyntaxKind::COMMA) | Some(SyntaxKind::RIGHT_BRACE)
1784 ) {
1785 self.builder.start_node(SyntaxKind::VALUE.into());
1789 self.builder.start_node(SyntaxKind::SCALAR.into());
1790 self.builder.token(SyntaxKind::NULL.into(), "");
1791 self.builder.finish_node(); self.builder.finish_node(); } else {
1794 let error_msg = self.create_detailed_error(
1795 "Missing colon in flow mapping",
1796 "':' after key",
1797 self.current_text(),
1798 );
1799 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1800 }
1801
1802 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COMMA) {
1806 self.bump();
1807 self.skip_ws_and_newlines(); }
1809
1810 self.builder.finish_node();
1812 }
1813
1814 self.in_flow_context = prev_flow;
1815
1816 if self.current() == Some(SyntaxKind::RIGHT_BRACE) {
1817 self.bump();
1818 } else {
1819 let error_msg = self.create_detailed_error(
1820 "Unclosed flow mapping",
1821 "'}' to close mapping",
1822 self.current_text(),
1823 );
1824 self.add_error_and_recover(
1825 error_msg,
1826 SyntaxKind::RIGHT_BRACE,
1827 ParseErrorKind::UnclosedFlowMapping,
1828 );
1829 }
1830
1831 self.builder.finish_node();
1832 self.error_context.pop_context();
1833 }
1834
1835 fn parse_directive(&mut self) {
1836 self.builder.start_node(SyntaxKind::DIRECTIVE.into());
1837
1838 if self.current() == Some(SyntaxKind::DIRECTIVE) {
1839 self.bump(); } else {
1841 self.add_error("Expected directive".to_string(), ParseErrorKind::Other);
1842 }
1843
1844 self.builder.finish_node();
1845 }
1846
1847 fn parse_explicit_key_mapping(&mut self) {
1848 self.builder.start_node(SyntaxKind::MAPPING.into());
1850
1851 while self.current() == Some(SyntaxKind::QUESTION) {
1852 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1854
1855 self.bump(); self.skip_whitespace();
1858
1859 self.builder.start_node(SyntaxKind::KEY.into());
1861
1862 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1864 self.parse_value();
1865 }
1866
1867 if self.current() == Some(SyntaxKind::NEWLINE) {
1870 if self.tokens.len() >= 2 {
1873 let (next_kind, _) = &self.tokens[self.tokens.len() - 2];
1874 if *next_kind == SyntaxKind::INDENT {
1875 if self.tokens.len() >= 3 {
1877 let (token_after_indent, _) = &self.tokens[self.tokens.len() - 3];
1878 if *token_after_indent != SyntaxKind::DASH {
1881 self.bump(); self.bump(); while self.current().is_some()
1887 && self.current() != Some(SyntaxKind::NEWLINE)
1888 && self.current() != Some(SyntaxKind::COLON)
1889 {
1890 self.parse_scalar();
1891 if self.current() == Some(SyntaxKind::WHITESPACE) {
1892 self.bump(); }
1894 }
1895 }
1896 }
1897 }
1898 }
1899 }
1900
1901 self.builder.finish_node();
1902
1903 self.skip_ws_and_newlines();
1904
1905 if self.current() == Some(SyntaxKind::COLON) {
1907 self.bump(); self.skip_whitespace();
1909
1910 self.builder.start_node(SyntaxKind::VALUE.into());
1911 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1912 self.parse_value();
1913 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1914 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1917 self.bump(); self.parse_value();
1919 }
1920 }
1921 self.builder.finish_node();
1922 } else {
1923 self.builder.start_node(SyntaxKind::VALUE.into());
1925 self.builder.start_node(SyntaxKind::SCALAR.into());
1926 self.builder.token(SyntaxKind::NULL.into(), "");
1927 self.builder.finish_node();
1928 self.builder.finish_node();
1929 }
1930
1931 self.builder.finish_node();
1933
1934 self.skip_ws_and_newlines();
1935
1936 if self.current() != Some(SyntaxKind::QUESTION) && !self.is_mapping_key() {
1938 break;
1939 }
1940 }
1941
1942 while self.current().is_some() && self.is_mapping_key() {
1944 self.parse_mapping_key_value_pair();
1945 self.skip_ws_and_newlines();
1946 }
1947
1948 self.builder.finish_node();
1949 }
1950
1951 fn parse_complex_key_mapping(&mut self) {
1952 self.builder.start_node(SyntaxKind::MAPPING.into());
1954
1955 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1957
1958 self.builder.start_node(SyntaxKind::KEY.into());
1960 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
1961 self.parse_flow_sequence();
1962 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
1963 self.parse_flow_mapping();
1964 }
1965 self.builder.finish_node();
1966
1967 self.skip_ws_and_newlines(); if self.current() == Some(SyntaxKind::COLON) {
1971 self.bump();
1972 self.skip_whitespace();
1973
1974 self.builder.start_node(SyntaxKind::VALUE.into());
1976 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
1977 self.parse_value();
1978 } else if self.current() == Some(SyntaxKind::NEWLINE) {
1979 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
1981 self.bump(); self.parse_value();
1983 }
1984 }
1985 self.builder.finish_node();
1986 } else {
1987 let error_msg = self.create_detailed_error(
1988 "Missing colon in complex mapping",
1989 "':' after complex key",
1990 self.current_text(),
1991 );
1992 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
1993 }
1994
1995 self.builder.finish_node();
1997
1998 self.skip_ws_and_newlines();
1999
2000 while self.current().is_some() {
2002 if self.current() == Some(SyntaxKind::QUESTION) {
2003 self.parse_explicit_key_entries();
2005 break;
2006 } else if self.is_complex_mapping_key()
2007 || (self.is_mapping_key() && self.current() != Some(SyntaxKind::QUESTION))
2008 {
2009 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2011
2012 self.builder.start_node(SyntaxKind::KEY.into());
2014
2015 if self.current() == Some(SyntaxKind::LEFT_BRACKET) {
2016 self.parse_flow_sequence();
2017 } else if self.current() == Some(SyntaxKind::LEFT_BRACE) {
2018 self.parse_flow_mapping();
2019 } else if matches!(
2020 self.current(),
2021 Some(
2022 SyntaxKind::STRING
2023 | SyntaxKind::INT
2024 | SyntaxKind::FLOAT
2025 | SyntaxKind::BOOL
2026 | SyntaxKind::NULL
2027 | SyntaxKind::MERGE_KEY
2028 )
2029 ) {
2030 self.bump();
2031 }
2032 self.builder.finish_node();
2033
2034 self.skip_whitespace();
2035
2036 if self.current() == Some(SyntaxKind::COLON) {
2037 self.bump();
2038 self.skip_whitespace();
2039
2040 self.builder.start_node(SyntaxKind::VALUE.into());
2041 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2042 self.parse_value();
2043 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2044 self.bump();
2045 if self.current() == Some(SyntaxKind::INDENT) {
2046 self.bump();
2047 self.parse_value();
2048 }
2049 }
2050 self.builder.finish_node();
2051 }
2052
2053 self.builder.finish_node();
2055
2056 self.skip_ws_and_newlines();
2057 } else {
2058 break;
2059 }
2060 }
2061
2062 self.builder.finish_node();
2063 }
2064
2065 fn parse_explicit_key_entries(&mut self) {
2066 while self.current() == Some(SyntaxKind::QUESTION) {
2068 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2070
2071 self.bump(); self.skip_whitespace();
2073
2074 self.builder.start_node(SyntaxKind::KEY.into());
2075 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2076 self.parse_value();
2077 }
2078 self.builder.finish_node();
2079
2080 self.skip_ws_and_newlines();
2081
2082 if self.current() == Some(SyntaxKind::COLON) {
2083 self.bump();
2084 self.skip_whitespace();
2085
2086 self.builder.start_node(SyntaxKind::VALUE.into());
2087 if self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2088 self.parse_value();
2089 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2090 self.bump();
2091 if self.current() == Some(SyntaxKind::INDENT) {
2092 self.bump();
2093 self.parse_value();
2094 }
2095 }
2096 self.builder.finish_node();
2097 } else {
2098 self.builder.start_node(SyntaxKind::VALUE.into());
2100 self.builder.start_node(SyntaxKind::SCALAR.into());
2101 self.builder.token(SyntaxKind::NULL.into(), "");
2102 self.builder.finish_node();
2103 self.builder.finish_node();
2104 }
2105
2106 self.builder.finish_node();
2108
2109 self.skip_ws_and_newlines();
2110 }
2111 }
2112
2113 fn is_complex_mapping_key(&self) -> bool {
2114 if !matches!(
2116 self.current(),
2117 Some(SyntaxKind::LEFT_BRACKET) | Some(SyntaxKind::LEFT_BRACE)
2118 ) {
2119 return false;
2120 }
2121
2122 let mut depth = 0;
2124 let start_kind = self.current();
2125 let close_kind = match start_kind {
2126 Some(SyntaxKind::LEFT_BRACKET) => SyntaxKind::RIGHT_BRACKET,
2127 Some(SyntaxKind::LEFT_BRACE) => SyntaxKind::RIGHT_BRACE,
2128 _ => return false,
2129 };
2130
2131 let mut found_close = false;
2132 for kind in self.upcoming_tokens() {
2133 if !found_close {
2134 if Some(kind) == start_kind {
2135 depth += 1;
2136 } else if kind == close_kind {
2137 if depth == 0 {
2138 found_close = true;
2140 } else {
2141 depth -= 1;
2142 }
2143 }
2144 } else {
2145 match kind {
2147 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => continue,
2148 SyntaxKind::COLON => return true,
2149 _ => return false,
2150 }
2151 }
2152 }
2153 false
2154 }
2155
2156 fn parse_mapping_value(&mut self) {
2157 match self.current() {
2161 Some(SyntaxKind::DASH) if !self.in_flow_context => self.parse_sequence(),
2162 Some(SyntaxKind::ANCHOR) => {
2163 self.bump(); self.skip_whitespace();
2165 self.parse_value_with_base_indent(0);
2166 }
2167 Some(SyntaxKind::REFERENCE) => self.parse_alias(),
2168 Some(SyntaxKind::TAG) => self.parse_tagged_value(),
2169 Some(SyntaxKind::QUESTION) => {
2170 self.parse_explicit_key_mapping();
2172 }
2173 Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
2174 Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
2175 Some(SyntaxKind::LEFT_BRACKET) => {
2176 if !self.in_flow_context && self.is_complex_mapping_key() {
2178 self.parse_complex_key_mapping();
2179 } else {
2180 self.parse_flow_sequence();
2181 }
2182 }
2183 Some(SyntaxKind::LEFT_BRACE) => {
2184 if !self.in_flow_context && self.is_complex_mapping_key() {
2186 self.parse_complex_key_mapping();
2187 } else {
2188 self.parse_flow_mapping();
2189 }
2190 }
2191 _ => {
2192 self.parse_scalar();
2195 }
2196 }
2197 }
2198
2199 fn is_mapping_key(&self) -> bool {
2200 if self.current() == Some(SyntaxKind::QUESTION) {
2202 return true;
2203 }
2204
2205 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2207 return true;
2208 }
2209
2210 if self.current() == Some(SyntaxKind::DASH) {
2212 return false;
2213 }
2214
2215 let upcoming = self.upcoming_tokens();
2218 for kind in upcoming {
2219 match kind {
2220 SyntaxKind::COLON => {
2221 return true;
2222 }
2223 SyntaxKind::WHITESPACE => continue,
2224 _ => {
2226 return false;
2227 }
2228 }
2229 }
2230 false
2231 }
2232
2233 fn skip_whitespace(&mut self) {
2234 self.skip_tokens(&[SyntaxKind::WHITESPACE]);
2235 }
2236
2237 fn skip_tokens(&mut self, kinds: &[SyntaxKind]) {
2238 while let Some(current) = self.current() {
2239 if kinds.contains(¤t) {
2240 self.bump();
2241 } else {
2242 break;
2243 }
2244 }
2245 }
2246
2247 fn is_plain_scalar_continuation(&self, scalar_indent: usize) -> bool {
2250 let current_idx = self.tokens.len().saturating_sub(1);
2253
2254 if current_idx == 0 {
2255 return false; }
2257
2258 let mut peek_idx = current_idx.saturating_sub(1);
2261
2262 let next_line_indent = self
2264 .tokens
2265 .get(peek_idx)
2266 .and_then(|(kind, text)| {
2267 if *kind == SyntaxKind::INDENT {
2268 peek_idx = peek_idx.saturating_sub(1);
2269 Some(text.len())
2270 } else {
2271 None
2272 }
2273 })
2274 .unwrap_or(0);
2275
2276 while self
2278 .tokens
2279 .get(peek_idx)
2280 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2281 {
2282 peek_idx = peek_idx.saturating_sub(1);
2283 }
2284
2285 let has_content = self.tokens.get(peek_idx).is_some_and(|(kind, _)| {
2287 matches!(
2288 kind,
2289 SyntaxKind::STRING
2290 | SyntaxKind::INT
2291 | SyntaxKind::FLOAT
2292 | SyntaxKind::BOOL
2293 | SyntaxKind::NULL
2294 | SyntaxKind::UNTERMINATED_STRING
2295 )
2296 });
2297
2298 if !has_content || next_line_indent <= scalar_indent {
2299 return false;
2300 }
2301
2302 if peek_idx > 0 {
2305 let mut check_idx = peek_idx.saturating_sub(1);
2306
2307 while self
2309 .tokens
2310 .get(check_idx)
2311 .is_some_and(|(kind, _)| *kind == SyntaxKind::WHITESPACE)
2312 {
2313 if check_idx == 0 {
2314 break;
2315 }
2316 check_idx = check_idx.saturating_sub(1);
2317 }
2318
2319 if self
2321 .tokens
2322 .get(check_idx)
2323 .is_some_and(|(kind, _)| *kind == SyntaxKind::COLON)
2324 {
2325 return false;
2326 }
2327 }
2328
2329 true
2330 }
2331
2332 fn is_at_dedented_position(&self, base_indent: usize) -> bool {
2336 if base_indent == 0 {
2342 self.current_line_indent > 0
2344 } else {
2345 self.current_line_indent < base_indent
2347 }
2348 }
2349
2350 fn skip_whitespace_only_with_dedent_check(&mut self, base_indent: usize) -> bool {
2353 while self.current().is_some() {
2354 match self.current() {
2355 Some(SyntaxKind::WHITESPACE) => {
2356 self.bump();
2357 }
2358 Some(SyntaxKind::NEWLINE) => {
2359 self.bump();
2360 match self.current() {
2362 Some(SyntaxKind::INDENT) => {
2363 if let Some((_, text)) = self.tokens.last() {
2364 if text.len() < base_indent {
2365 return true;
2367 }
2368 if base_indent == 0 && !text.is_empty() {
2369 return true;
2371 }
2372 }
2373 self.bump(); }
2375 Some(SyntaxKind::COMMENT) => {
2376 if base_indent > 0 {
2378 return true;
2380 }
2381 return false;
2383 }
2384 Some(SyntaxKind::WHITESPACE) | Some(SyntaxKind::NEWLINE) => {
2385 }
2387 None => {
2388 return false;
2390 }
2391 _ => {
2392 if base_indent > 0 {
2394 return true; }
2396 return false;
2398 }
2399 }
2400 }
2401 Some(SyntaxKind::INDENT) => {
2402 if let Some((_, text)) = self.tokens.last() {
2404 if text.len() < base_indent {
2405 return true; }
2407 }
2408 self.bump();
2409 }
2410 _ => {
2411 return false;
2413 }
2414 }
2415 }
2416 false
2417 }
2418
2419 fn skip_ws_and_newlines(&mut self) {
2420 self.skip_tokens(&[
2421 SyntaxKind::WHITESPACE,
2422 SyntaxKind::NEWLINE,
2423 SyntaxKind::INDENT,
2424 SyntaxKind::COMMENT,
2425 ]);
2426 }
2427
2428 fn parse_mapping_key_value_pair(&mut self) {
2429 self.builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
2431
2432 self.builder.start_node(SyntaxKind::KEY.into());
2434
2435 if self.current() == Some(SyntaxKind::ANCHOR) {
2437 self.bump(); self.skip_whitespace();
2439 }
2440
2441 if self.current() == Some(SyntaxKind::MERGE_KEY) {
2442 self.builder.start_node(SyntaxKind::SCALAR.into());
2443 self.bump(); self.builder.finish_node(); } else if self.current() == Some(SyntaxKind::REFERENCE) {
2446 self.parse_alias();
2448 } else if matches!(
2449 self.current(),
2450 Some(
2451 SyntaxKind::STRING
2452 | SyntaxKind::INT
2453 | SyntaxKind::FLOAT
2454 | SyntaxKind::BOOL
2455 | SyntaxKind::NULL
2456 )
2457 ) {
2458 self.builder.start_node(SyntaxKind::SCALAR.into());
2459 self.bump(); self.builder.finish_node(); }
2462 self.builder.finish_node(); self.skip_whitespace();
2465
2466 if self.current() == Some(SyntaxKind::COLON) {
2468 self.bump();
2469 self.skip_whitespace();
2470
2471 self.builder.start_node(SyntaxKind::VALUE.into());
2473 let mut has_value = false;
2474 if self.current().is_some()
2475 && self.current() != Some(SyntaxKind::NEWLINE)
2476 && self.current() != Some(SyntaxKind::COMMENT)
2477 {
2478 self.parse_mapping_value();
2480 has_value = true;
2481
2482 if self.current() == Some(SyntaxKind::WHITESPACE) {
2485 self.bump(); }
2487 if self.current() == Some(SyntaxKind::COMMENT) {
2488 self.bump(); }
2490 } else if self.current() == Some(SyntaxKind::COMMENT) {
2491 self.bump(); if self.current() == Some(SyntaxKind::NEWLINE) {
2497 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2500 let indent_level =
2501 self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2502 self.bump(); self.parse_value_with_base_indent(indent_level);
2505 has_value = true;
2506 }
2507 }
2508 } else if self.current() == Some(SyntaxKind::NEWLINE) {
2510 self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
2513 let indent_level = self.tokens.last().map(|(_, text)| text.len()).unwrap_or(0);
2514 self.bump(); self.parse_value_with_base_indent(indent_level);
2517 has_value = true;
2518 } else if self.current() == Some(SyntaxKind::DASH) {
2519 self.parse_sequence();
2522 has_value = true;
2523 }
2524 }
2525
2526 if !has_value {
2528 self.builder.start_node(SyntaxKind::SCALAR.into());
2529 self.builder.token(SyntaxKind::NULL.into(), "");
2530 self.builder.finish_node();
2531 }
2532
2533 self.builder.finish_node(); } else {
2535 let error_msg = self.create_detailed_error(
2536 "Missing colon in mapping",
2537 "':' after key",
2538 self.current_text(),
2539 );
2540 self.add_error_and_recover(error_msg, SyntaxKind::COLON, ParseErrorKind::Other);
2541 }
2542
2543 while self.current() == Some(SyntaxKind::WHITESPACE) {
2548 self.bump();
2549 }
2550
2551 if self.current() == Some(SyntaxKind::NEWLINE) {
2553 self.bump();
2554 }
2555
2556 self.builder.finish_node();
2558 }
2559
2560 fn bump(&mut self) {
2561 if let Some((kind, text)) = self.tokens.pop() {
2562 match kind {
2564 SyntaxKind::INDENT => {
2565 self.current_line_indent = text.len();
2566 }
2567 SyntaxKind::NEWLINE => {
2568 self.current_line_indent = 0;
2570 }
2571 _ => {}
2572 }
2573
2574 self.builder.token(kind.into(), &text);
2575 if self.current_token_index > 0 {
2576 self.current_token_index -= 1;
2577 }
2578 self.error_context.advance(text.len());
2580 }
2581 }
2582
2583 fn current(&self) -> Option<SyntaxKind> {
2584 self.tokens.last().map(|(kind, _)| *kind)
2585 }
2586
2587 fn current_text(&self) -> Option<&str> {
2588 self.tokens.last().map(|(_, text)| text.as_str())
2589 }
2590
2591 fn upcoming_tokens(&self) -> impl Iterator<Item = SyntaxKind> + '_ {
2593 let len = self.tokens.len();
2596 (0..len.saturating_sub(1))
2597 .rev()
2598 .map(move |i| self.tokens[i].0)
2599 }
2600
2601 fn add_error(&mut self, message: String, kind: ParseErrorKind) {
2602 let token_len = self.current_text().map(|s| s.len()).unwrap_or(1);
2604 let positioned_error = self.error_context.create_error(message, token_len, kind);
2605
2606 self.errors.push(positioned_error.message.clone());
2607 self.positioned_errors.push(positioned_error);
2608 }
2609
2610 fn add_error_and_recover(
2612 &mut self,
2613 message: String,
2614 expected: SyntaxKind,
2615 kind: ParseErrorKind,
2616 ) {
2617 self.add_error(message, kind);
2618
2619 let found = self.current();
2621 let strategy = self.error_context.suggest_recovery(expected, found);
2622
2623 match strategy {
2624 RecoveryStrategy::SkipToken => {
2625 if self.current().is_some() {
2627 self.bump();
2628 }
2629 }
2630 RecoveryStrategy::SkipToEndOfLine => {
2631 while self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
2633 self.bump();
2634 }
2635 }
2636 RecoveryStrategy::InsertToken(kind) => {
2637 self.builder.token(kind.into(), "");
2639 }
2640 RecoveryStrategy::SyncToSafePoint => {
2641 let sync_point = self
2643 .error_context
2644 .find_sync_point(&self.tokens, self.tokens.len() - self.current_token_index);
2645 let tokens_to_skip = sync_point - (self.tokens.len() - self.current_token_index);
2646 for _ in 0..tokens_to_skip {
2647 if self.current().is_some() {
2648 self.bump();
2649 }
2650 }
2651 }
2652 }
2653 }
2654
2655 fn create_detailed_error(
2657 &self,
2658 base_message: &str,
2659 expected: &str,
2660 found: Option<&str>,
2661 ) -> String {
2662 let mut builder = ErrorBuilder::new(base_message);
2663 builder = builder.expected(expected);
2664
2665 if let Some(found_str) = found {
2666 builder = builder.found(found_str);
2667 } else if let Some(token) = self.current_text() {
2668 builder = builder.found(format!("'{}'", token));
2669 } else {
2670 builder = builder.found("end of input");
2671 }
2672
2673 let context = match self.error_context.current_context() {
2675 ParseContext::Mapping => "in mapping",
2676 ParseContext::Sequence => "in sequence",
2677 ParseContext::FlowMapping => "in flow mapping",
2678 ParseContext::FlowSequence => "in flow sequence",
2679 ParseContext::BlockScalar => "in block scalar",
2680 ParseContext::QuotedString => "in quoted string",
2681 _ => "at document level",
2682 };
2683 builder = builder.context(context);
2684
2685 let suggestion = self.get_error_suggestion(base_message, expected, found);
2687 if let Some(suggestion_text) = suggestion {
2688 builder = builder.suggestion(suggestion_text);
2689 }
2690
2691 builder.build()
2692 }
2693
2694 fn get_error_suggestion(
2696 &self,
2697 base_message: &str,
2698 expected: &str,
2699 found: Option<&str>,
2700 ) -> Option<String> {
2701 if base_message.contains("Unterminated quoted string") {
2702 return Some(
2703 "Add closing quote or check for unescaped quotes within the string".to_string(),
2704 );
2705 }
2706
2707 if base_message.contains("Missing colon") || expected.contains("':'") {
2708 return Some("Add ':' after the key, or check for proper indentation".to_string());
2709 }
2710
2711 if base_message.contains("Unclosed flow sequence") {
2712 return Some(
2713 "Add ']' to close the array, or check for missing commas between elements"
2714 .to_string(),
2715 );
2716 }
2717
2718 if base_message.contains("Unclosed flow mapping") {
2719 return Some(
2720 "Add '}' to close the object, or check for missing commas between key-value pairs"
2721 .to_string(),
2722 );
2723 }
2724
2725 if let Some(found_text) = found {
2726 if found_text.contains('\n') {
2727 return Some(
2728 "Unexpected newline - check indentation and YAML structure".to_string(),
2729 );
2730 }
2731
2732 if found_text.contains('\t') {
2733 return Some(
2734 "Tabs are not allowed in YAML - use spaces for indentation".to_string(),
2735 );
2736 }
2737 }
2738
2739 None
2740 }
2741}
2742
2743pub(crate) fn parse(text: &str) -> ParsedYaml {
2745 let parser = Parser::new(text);
2746 parser.parse()
2747}
2748
2749#[cfg(test)]
2752mod tests {
2753 use super::*;
2754 use crate::builder::{MappingBuilder, SequenceBuilder};
2755 use crate::scalar::ScalarValue;
2756 use crate::value::YamlValue; #[test]
2759 fn test_simple_mapping() {
2760 let yaml = "key: value";
2761 let parsed = YamlFile::from_str(yaml).unwrap();
2762 let doc = parsed.document().unwrap();
2763 let mapping = doc.as_mapping().unwrap();
2764
2765 assert_eq!(parsed.to_string().trim(), "key: value");
2767
2768 let value = mapping.get("key");
2770 assert!(value.is_some());
2771 }
2772
2773 #[test]
2774 fn test_simple_sequence() {
2775 let yaml = "- item1\n- item2";
2776 let parsed = YamlFile::from_str(yaml);
2777 assert!(parsed.is_ok());
2778 }
2779
2780 #[test]
2781 fn test_complex_yaml() {
2782 let yaml = r#"
2783name: my-app
2784version: 1.0.0
2785dependencies:
2786 - serde
2787 - tokio
2788config:
2789 port: 8080
2790 enabled: true
2791"#;
2792 let parsed = YamlFile::from_str(yaml).unwrap();
2793 assert_eq!(parsed.documents().count(), 1);
2794
2795 let doc = parsed.document().unwrap();
2796 assert!(doc.as_mapping().is_some());
2797 }
2798
2799 #[test]
2800 fn test_multiple_documents() {
2801 let yaml = r#"---
2802doc: first
2803---
2804doc: second
2805...
2806"#;
2807 let parsed = YamlFile::from_str(yaml).unwrap();
2808 assert_eq!(parsed.documents().count(), 2);
2809 }
2810
2811 #[test]
2812 fn test_flow_styles() {
2813 let yaml = r#"
2814array: [1, 2, 3]
2815object: {key: value, another: 42}
2816"#;
2817 let parsed = YamlFile::from_str(yaml).unwrap();
2818 assert!(parsed.document().is_some());
2819 }
2820
2821 #[test]
2822 fn test_scalar_types_parsing() {
2823 let yaml = r#"
2824string: hello
2825integer: 42
2826float: 3.14
2827bool_true: true
2828bool_false: false
2829null_value: null
2830tilde: ~
2831"#;
2832 let parsed = YamlFile::from_str(yaml).unwrap();
2833 let doc = parsed.document().unwrap();
2834 let mapping = doc.as_mapping().unwrap();
2835
2836 assert!(mapping.get("string").is_some());
2838 assert!(mapping.get("integer").is_some());
2839 assert!(mapping.get("float").is_some());
2840 assert!(mapping.get("bool_true").is_some());
2841 assert!(mapping.get("bool_false").is_some());
2842 assert!(mapping.get("null_value").is_some());
2843 assert!(mapping.get("tilde").is_some());
2844 }
2845
2846 #[test]
2847 fn test_preserve_formatting() {
2848 let yaml = r#"# Comment at start
2849key: value # inline comment
2850
2851# Another comment
2852list:
2853 - item1
2854 - item2
2855"#;
2856 let parsed = YamlFile::from_str(yaml).unwrap();
2857
2858 let doc = parsed.document().unwrap();
2859 let mapping = doc.as_mapping().unwrap();
2860 assert_eq!(
2861 mapping.get("key").unwrap().as_scalar().unwrap().as_string(),
2862 "value"
2863 );
2864 let list = mapping.get_sequence("list").unwrap();
2865 assert_eq!(list.len(), 2);
2866 let items: Vec<String> = list
2868 .values()
2869 .map(|v| v.to_string().trim().to_string())
2870 .collect();
2871 assert_eq!(items, vec!["item1", "item2"]);
2872
2873 let output = parsed.to_string();
2875 assert_eq!(output, yaml);
2876 }
2877
2878 #[test]
2879 fn test_quoted_strings() {
2880 let yaml = r#"
2881single: 'single quoted'
2882double: "double quoted"
2883plain: unquoted
2884"#;
2885 let parsed = YamlFile::from_str(yaml).unwrap();
2886 let doc = parsed.document().unwrap();
2887 let mapping = doc.as_mapping().unwrap();
2888
2889 assert!(mapping.get("single").is_some());
2890 assert!(mapping.get("double").is_some());
2891 assert!(mapping.get("plain").is_some());
2892 }
2893
2894 #[test]
2895 fn test_nested_structures() {
2896 let yaml = r#"
2897root:
2898 nested:
2899 deeply:
2900 value: 42
2901 list:
2902 - item1
2903 - item2
2904"#;
2905 let parsed = YamlFile::from_str(yaml).unwrap();
2906 assert!(parsed.document().is_some());
2907 }
2908
2909 #[test]
2910 fn test_empty_values() {
2911 let yaml = r#"
2912empty_string: ""
2913empty_after_colon:
2914another_key: value
2915"#;
2916 let parsed = YamlFile::from_str(yaml).unwrap();
2917 let doc = parsed.document().unwrap();
2918 let mapping = doc.as_mapping().unwrap();
2919
2920 assert!(mapping.get("empty_string").is_some());
2921 assert!(mapping.get("another_key").is_some());
2922 }
2923
2924 #[test]
2925 fn test_special_characters() {
2926 let yaml = r#"
2927special: "line1\nline2"
2928unicode: "emoji 😀"
2929escaped: 'it\'s escaped'
2930"#;
2931 let result = YamlFile::from_str(yaml);
2932 assert!(result.is_ok());
2934 }
2935
2936 #[test]
2939 fn test_error_handling() {
2940 let yaml = "key: value\n invalid indentation for key";
2942 let result = YamlFile::from_str(yaml);
2943 let _ = result;
2945 }
2946
2947 #[test]
2950 fn test_anchor_exact_output() {
2951 let yaml = "key: &anchor value\nref: *anchor";
2952 let parsed = YamlFile::from_str(yaml).unwrap();
2953 let output = parsed.to_string();
2954
2955 assert_eq!(output, "key: &anchor value\nref: *anchor");
2957 }
2958
2959 #[test]
2960 fn test_anchor_with_different_value_types() {
2961 let yaml = r#"string_anchor: &str_val "hello"
2962int_anchor: &int_val 42
2963bool_anchor: &bool_val true
2964null_anchor: &null_val null
2965str_ref: *str_val
2966int_ref: *int_val
2967bool_ref: *bool_val
2968null_ref: *null_val"#;
2969
2970 let parsed = YamlFile::from_str(yaml);
2971 assert!(
2972 parsed.is_ok(),
2973 "Should parse anchors with different value types"
2974 );
2975
2976 let yaml_doc = parsed.unwrap();
2977
2978 let doc = yaml_doc.document().unwrap();
2979 let mapping = doc.as_mapping().unwrap();
2980
2981 assert_eq!(
2983 mapping
2984 .get("string_anchor")
2985 .unwrap()
2986 .as_scalar()
2987 .unwrap()
2988 .as_string(),
2989 "hello"
2990 );
2991 assert_eq!(mapping.get("int_anchor").unwrap().to_i64(), Some(42));
2992 assert_eq!(mapping.get("bool_anchor").unwrap().to_bool(), Some(true));
2993 assert!(mapping.get("null_anchor").unwrap().as_scalar().is_some());
2994
2995 let str_ref = mapping.get("str_ref").unwrap();
2997 assert!(str_ref.is_alias());
2998 assert_eq!(str_ref.as_alias().unwrap().name(), "str_val");
2999
3000 let int_ref = mapping.get("int_ref").unwrap();
3001 assert!(int_ref.is_alias());
3002 assert_eq!(int_ref.as_alias().unwrap().name(), "int_val");
3003
3004 let bool_ref = mapping.get("bool_ref").unwrap();
3005 assert!(bool_ref.is_alias());
3006 assert_eq!(bool_ref.as_alias().unwrap().name(), "bool_val");
3007
3008 let null_ref = mapping.get("null_ref").unwrap();
3009 assert!(null_ref.is_alias());
3010 assert_eq!(null_ref.as_alias().unwrap().name(), "null_val");
3011
3012 let output = yaml_doc.to_string();
3014 assert_eq!(output, yaml);
3015 }
3016
3017 #[test]
3018 fn test_undefined_alias_parses_successfully() {
3019 let yaml = "key: *undefined";
3020 let parse_result = Parse::parse_yaml(yaml);
3021
3022 assert!(
3025 !parse_result.has_errors(),
3026 "Parser should not validate undefined aliases"
3027 );
3028
3029 let output = parse_result.tree().to_string();
3031 assert_eq!(output.trim(), "key: *undefined");
3032 }
3033
3034 #[test]
3035 fn test_anchor_names_with_alphanumeric_chars() {
3036 let yaml1 = "key1: &anchor_123 val1\nref1: *anchor_123";
3038 let parsed1 = YamlFile::from_str(yaml1);
3039 assert!(
3040 parsed1.is_ok(),
3041 "Should parse anchors with underscores and numbers"
3042 );
3043
3044 let file1 = parsed1.unwrap();
3045 let doc1 = file1.document().unwrap();
3046 let map1 = doc1.as_mapping().unwrap();
3047 assert_eq!(
3048 map1.get("key1").unwrap().as_scalar().unwrap().as_string(),
3049 "val1"
3050 );
3051 assert!(map1.get("ref1").unwrap().is_alias());
3052 assert_eq!(
3053 map1.get("ref1").unwrap().as_alias().unwrap().name(),
3054 "anchor_123"
3055 );
3056 assert_eq!(file1.to_string(), yaml1);
3057
3058 let yaml2 = "key2: &AnchorName val2\nref2: *AnchorName";
3059 let parsed2 = YamlFile::from_str(yaml2);
3060 assert!(parsed2.is_ok(), "Should parse anchors with mixed case");
3061
3062 let file2 = parsed2.unwrap();
3063 let doc2 = file2.document().unwrap();
3064 let map2 = doc2.as_mapping().unwrap();
3065 assert_eq!(
3066 map2.get("key2").unwrap().as_scalar().unwrap().as_string(),
3067 "val2"
3068 );
3069 assert!(map2.get("ref2").unwrap().is_alias());
3070 assert_eq!(
3071 map2.get("ref2").unwrap().as_alias().unwrap().name(),
3072 "AnchorName"
3073 );
3074 assert_eq!(file2.to_string(), yaml2);
3075
3076 let yaml3 = "key3: &anchor123abc val3\nref3: *anchor123abc";
3077 let parsed3 = YamlFile::from_str(yaml3);
3078 assert!(
3079 parsed3.is_ok(),
3080 "Should parse anchors with letters and numbers"
3081 );
3082
3083 let file3 = parsed3.unwrap();
3084 let doc3 = file3.document().unwrap();
3085 let map3 = doc3.as_mapping().unwrap();
3086 assert_eq!(
3087 map3.get("key3").unwrap().as_scalar().unwrap().as_string(),
3088 "val3"
3089 );
3090 assert!(map3.get("ref3").unwrap().is_alias());
3091 assert_eq!(
3092 map3.get("ref3").unwrap().as_alias().unwrap().name(),
3093 "anchor123abc"
3094 );
3095 assert_eq!(file3.to_string(), yaml3);
3096 }
3097
3098 #[test]
3099 fn test_anchor_in_sequence_detailed() {
3100 let yaml = r#"items:
3101 - &first_item value1
3102 - second_item
3103 - *first_item"#;
3104
3105 let parsed = YamlFile::from_str(yaml);
3106 assert!(parsed.is_ok(), "Should parse anchors in sequences");
3107
3108 let yaml_doc = parsed.unwrap();
3109
3110 let doc = yaml_doc.document().unwrap();
3111 let mapping = doc.as_mapping().unwrap();
3112 let seq = mapping.get_sequence("items").unwrap();
3113 assert_eq!(seq.len(), 3);
3114
3115 let item0 = seq.get(0).unwrap();
3117 let item1 = seq.get(1).unwrap();
3118 let item2 = seq.get(2).unwrap();
3119
3120 assert_eq!(item0.as_scalar().unwrap().as_string(), "value1");
3122
3123 assert_eq!(item1.as_scalar().unwrap().as_string(), "second_item");
3125
3126 assert!(item2.is_alias(), "Third item should be an alias");
3128 assert_eq!(item2.as_alias().unwrap().name(), "first_item");
3129
3130 let output = yaml_doc.to_string();
3131 assert_eq!(output, yaml);
3132 }
3133
3134 #[test]
3135 fn test_preserve_whitespace_around_anchors() {
3136 let yaml = "key: &anchor value \nref: *anchor ";
3137 let parsed = YamlFile::from_str(yaml).unwrap();
3138
3139 let doc = parsed.document().unwrap();
3140 let mapping = doc.as_mapping().unwrap();
3141 assert_eq!(
3142 mapping
3143 .get("key")
3144 .unwrap()
3145 .as_scalar()
3146 .unwrap()
3147 .as_string()
3148 .trim(),
3149 "value"
3150 );
3151 let ref_node = mapping.get("ref").unwrap();
3152 assert!(ref_node.is_alias());
3153 assert_eq!(ref_node.as_alias().unwrap().name(), "anchor");
3154
3155 let output = parsed.to_string();
3157 assert_eq!(output, yaml);
3158 }
3159
3160 #[test]
3161 fn test_literal_block_scalar_basic() {
3162 let yaml = r#"literal: |
3163 Line 1
3164 Line 2
3165 Line 3
3166"#;
3167 let parsed = YamlFile::from_str(yaml);
3168 assert!(parsed.is_ok(), "Should parse basic literal block scalar");
3169
3170 let yaml_doc = parsed.unwrap();
3171 let output = yaml_doc.to_string();
3172
3173 assert_eq!(output, yaml);
3175 }
3176
3177 #[test]
3178 fn test_folded_block_scalar_basic() {
3179 let yaml = r#"folded: >
3180 This is a very long line that will be folded
3181 into a single line in the output
3182 but preserves paragraph breaks.
3183
3184 This is a new paragraph.
3185"#;
3186 let parsed = YamlFile::from_str(yaml);
3187 assert!(parsed.is_ok(), "Should parse basic folded block scalar");
3188
3189 let yaml_doc = parsed.unwrap();
3190 let output = yaml_doc.to_string();
3191
3192 assert_eq!(output, yaml);
3194 }
3195
3196 #[test]
3197 fn test_literal_block_scalar_with_chomping_indicators() {
3198 let yaml1 = r#"strip: |-
3200 Line 1
3201 Line 2
3202
3203"#;
3204 let parsed1 = YamlFile::from_str(yaml1);
3205 assert!(
3206 parsed1.is_ok(),
3207 "Should parse literal block scalar with strip indicator"
3208 );
3209
3210 let file1 = parsed1.unwrap();
3211 let doc1 = file1.document().unwrap();
3212 let mapping1 = doc1.as_mapping().unwrap();
3213 let value1 = mapping1
3214 .get("strip")
3215 .unwrap()
3216 .as_scalar()
3217 .unwrap()
3218 .as_string();
3219 assert_eq!(value1, "Line 1\nLine 2");
3220
3221 let output1 = file1.to_string();
3222 assert_eq!(output1, yaml1);
3223
3224 let yaml2 = r#"keep: |+
3226 Line 1
3227 Line 2
3228
3229"#;
3230 let parsed2 = YamlFile::from_str(yaml2);
3231 assert!(
3232 parsed2.is_ok(),
3233 "Should parse literal block scalar with keep indicator"
3234 );
3235
3236 let file2 = parsed2.unwrap();
3237 let doc2 = file2.document().unwrap();
3238 let mapping2 = doc2.as_mapping().unwrap();
3239 let value2 = mapping2
3240 .get("keep")
3241 .unwrap()
3242 .as_scalar()
3243 .unwrap()
3244 .as_string();
3245 assert_eq!(value2, "Line 1\nLine 2\n\n");
3246
3247 let output2 = file2.to_string();
3248 assert_eq!(output2, yaml2);
3249 }
3250
3251 #[test]
3252 fn test_folded_block_scalar_with_chomping_indicators() {
3253 let yaml1 = r#"strip: >-
3255 Folded content that should
3256 be stripped of final newlines
3257"#;
3258 let parsed1 = YamlFile::from_str(yaml1);
3259 assert!(
3260 parsed1.is_ok(),
3261 "Should parse folded block scalar with strip indicator"
3262 );
3263
3264 let file1 = parsed1.unwrap();
3265 let doc1 = file1.document().unwrap();
3266 let mapping1 = doc1.as_mapping().unwrap();
3267 let value1 = mapping1
3268 .get("strip")
3269 .unwrap()
3270 .as_scalar()
3271 .unwrap()
3272 .as_string();
3273 assert_eq!(
3274 value1,
3275 "Folded content that should be stripped of final newlines"
3276 );
3277
3278 let output1 = file1.to_string();
3279 assert_eq!(output1, yaml1);
3280
3281 let yaml2 = r#"keep: >+
3283 Folded content that should
3284 keep all final newlines
3285
3286"#;
3287 let parsed2 = YamlFile::from_str(yaml2);
3288 assert!(
3289 parsed2.is_ok(),
3290 "Should parse folded block scalar with keep indicator"
3291 );
3292
3293 let file2 = parsed2.unwrap();
3294 let doc2 = file2.document().unwrap();
3295 let mapping2 = doc2.as_mapping().unwrap();
3296 let value2 = mapping2
3297 .get("keep")
3298 .unwrap()
3299 .as_scalar()
3300 .unwrap()
3301 .as_string();
3302 assert_eq!(
3303 value2,
3304 "Folded content that should keep all final newlines\n\n"
3305 );
3306
3307 let output2 = file2.to_string();
3308 assert_eq!(output2, yaml2);
3309 }
3310
3311 #[test]
3312 fn test_block_scalar_with_explicit_indentation() {
3313 let yaml1 = r#"explicit: |2
3314 Two space indent
3315 Another line
3316"#;
3317 let parsed1 = YamlFile::from_str(yaml1)
3318 .expect("Should parse literal block scalar with explicit indentation");
3319
3320 let doc1 = parsed1.document().expect("Should have document");
3321 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3322 let scalar1 = mapping1
3323 .get("explicit")
3324 .expect("Should have 'explicit' key");
3325 assert_eq!(
3326 scalar1.as_scalar().unwrap().as_string(),
3327 "Two space indent\nAnother line\n"
3328 );
3329
3330 let output1 = parsed1.to_string();
3331 assert_eq!(output1, yaml1);
3332
3333 let yaml2 = r#"folded_explicit: >3
3334 Three space indent
3335 Another folded line
3336"#;
3337 let parsed2 = YamlFile::from_str(yaml2)
3338 .expect("Should parse folded block scalar with explicit indentation");
3339
3340 let doc2 = parsed2.document().expect("Should have document");
3341 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
3342 let scalar2 = mapping2
3343 .get("folded_explicit")
3344 .expect("Should have 'folded_explicit' key");
3345 assert_eq!(
3346 scalar2.as_scalar().unwrap().as_string(),
3347 "Three space indent Another folded line\n"
3348 );
3349
3350 let output2 = parsed2.to_string();
3351 assert_eq!(output2, yaml2);
3352 }
3353
3354 #[test]
3355 fn test_block_scalar_in_mapping() {
3356 let yaml = r#"description: |
3357 This is a multi-line
3358 description that should
3359 preserve line breaks.
3360
3361 It can have multiple paragraphs too.
3362
3363summary: >
3364 This is a summary that
3365 should be folded into
3366 a single line.
3367
3368version: "1.0"
3369"#;
3370 let parsed =
3371 YamlFile::from_str(yaml).expect("Should parse block scalars in mapping context");
3372
3373 let doc = parsed.document().expect("Should have document");
3374 let mapping = doc.as_mapping().expect("Should be a mapping");
3375
3376 let description = mapping
3377 .get("description")
3378 .expect("Should have 'description' key");
3379 assert_eq!(
3380 description.as_scalar().unwrap().as_string(),
3381 "This is a multi-line\ndescription that should\npreserve line breaks.\n\nIt can have multiple paragraphs too.\n"
3382 );
3383
3384 let summary = mapping.get("summary").expect("Should have 'summary' key");
3385 assert_eq!(
3386 summary.as_scalar().unwrap().as_string(),
3387 "This is a summary that should be folded into a single line.\n"
3388 );
3389
3390 let version = mapping.get("version").expect("Should have 'version' key");
3391 assert_eq!(version.as_scalar().unwrap().as_string(), "1.0");
3392
3393 let output = parsed.to_string();
3394 assert_eq!(output, yaml);
3395 }
3396
3397 #[test]
3398 fn test_mixed_block_and_regular_scalars() {
3399 let yaml = r#"config:
3400 name: "My App"
3401 description: |
3402 This application does many things:
3403 - Feature 1
3404 - Feature 2
3405 - Feature 3
3406 summary: >
3407 A brief summary that spans
3408 multiple lines but should
3409 be folded together.
3410 version: 1.0
3411 enabled: true
3412"#;
3413 let parsed =
3414 YamlFile::from_str(yaml).expect("Should parse mixed block and regular scalars");
3415
3416 let doc = parsed.document().expect("Should have document");
3417 let mapping = doc.as_mapping().expect("Should be a mapping");
3418 let config_node = mapping.get("config").expect("Should have 'config' key");
3419 let config = config_node
3420 .as_mapping()
3421 .expect("Should be a nested mapping");
3422
3423 assert_eq!(
3424 config.get("name").unwrap().as_scalar().unwrap().as_string(),
3425 "My App"
3426 );
3427 assert_eq!(
3428 config
3429 .get("description")
3430 .unwrap()
3431 .as_scalar()
3432 .unwrap()
3433 .as_string(),
3434 "This application does many things:\n- Feature 1\n- Feature 2\n- Feature 3\n"
3435 );
3436 assert_eq!(
3437 config
3438 .get("summary")
3439 .unwrap()
3440 .as_scalar()
3441 .unwrap()
3442 .as_string(),
3443 "A brief summary that spans multiple lines but should be folded together.\n"
3444 );
3445 assert_eq!(
3446 config
3447 .get("version")
3448 .unwrap()
3449 .as_scalar()
3450 .unwrap()
3451 .as_string(),
3452 "1.0"
3453 );
3454 assert_eq!(config.get("enabled").unwrap().to_bool(), Some(true));
3455
3456 let output = parsed.to_string();
3457 assert_eq!(output, yaml);
3458 }
3459
3460 #[test]
3461 fn test_block_scalar_edge_cases() {
3462 let yaml1 = r#"empty_literal: |
3466empty_folded: >
3467"#;
3468 let parsed1 = YamlFile::from_str(yaml1).expect("Should parse this edge case");
3469
3470 let doc1 = parsed1.document().expect("Should have document");
3472 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3473 assert_eq!(mapping1.len(), 1, "Should have only one key");
3474 assert_eq!(
3475 mapping1
3476 .get("empty_literal")
3477 .unwrap()
3478 .as_scalar()
3479 .unwrap()
3480 .as_string(),
3481 "empty_folded: >\n"
3482 );
3483
3484 assert_eq!(parsed1.to_string(), yaml1);
3485
3486 let yaml2 = r#"whitespace: |
3488
3489
3490"#;
3491 let parsed2 =
3492 YamlFile::from_str(yaml2).expect("Should parse block scalar with only whitespace");
3493
3494 assert_eq!(parsed2.to_string(), yaml2);
3495
3496 let yaml3 = r#"first: |
3498 Content
3499second: value
3500"#;
3501 let parsed3 =
3502 YamlFile::from_str(yaml3).expect("Should parse block scalar followed by other keys");
3503
3504 let doc3 = parsed3.document().expect("Should have document");
3505 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
3506 assert_eq!(
3507 mapping3
3508 .get("first")
3509 .unwrap()
3510 .as_scalar()
3511 .unwrap()
3512 .as_string(),
3513 "Content\n"
3514 );
3515 assert_eq!(
3516 mapping3
3517 .get("second")
3518 .unwrap()
3519 .as_scalar()
3520 .unwrap()
3521 .as_string(),
3522 "value"
3523 );
3524
3525 let output3 = parsed3.to_string();
3526 assert_eq!(output3, yaml3);
3527 }
3528
3529 #[test]
3530 fn test_literal_block_scalar_advanced_formatting() {
3531 let yaml = r#"poem: |
3532 Roses are red,
3533 Violets are blue,
3534 YAML is great,
3535 And so are you!
3536
3537 This is another stanza
3538 with different content.
3539 And this line has extra indentation.
3540 Back to normal indentation.
3541
3542 Final stanza.
3543"#;
3544 let parsed = YamlFile::from_str(yaml).expect("Should parse complex literal block scalar");
3545
3546 let doc = parsed.document().expect("Should have document");
3547 let mapping = doc.as_mapping().expect("Should be a mapping");
3548 let poem = mapping.get("poem").expect("Should have 'poem' key");
3549 let expected_content = "Roses are red,\nViolets are blue,\nYAML is great,\nAnd so are you!\n\nThis is another stanza\nwith different content.\n And this line has extra indentation.\nBack to normal indentation.\n\nFinal stanza.\n";
3550 assert_eq!(poem.as_scalar().unwrap().as_string(), expected_content);
3551
3552 let output = parsed.to_string();
3553 assert_eq!(output, yaml);
3554 }
3555
3556 #[test]
3557 fn test_folded_block_scalar_paragraph_handling() {
3558 let yaml = r#"description: >
3559 This is the first paragraph that should
3560 be folded into a single line when processed
3561 by a YAML parser.
3562
3563 This is a second paragraph that should
3564 also be folded but kept separate from
3565 the first paragraph.
3566
3567
3568 This is a third paragraph after
3569 multiple blank lines.
3570
3571 Final paragraph.
3572"#;
3573 let parsed =
3574 YamlFile::from_str(yaml).expect("Should parse folded block scalar with paragraphs");
3575
3576 let doc = parsed.document().expect("Should have document");
3577 let mapping = doc.as_mapping().expect("Should be a mapping");
3578 let description = mapping
3579 .get("description")
3580 .expect("Should have 'description' key");
3581 let expected_content = "This is the first paragraph that should be folded into a single line when processed by a YAML parser.\nThis is a second paragraph that should also be folded but kept separate from the first paragraph.\nThis is a third paragraph after multiple blank lines.\nFinal paragraph.\n";
3582 assert_eq!(
3583 description.as_scalar().unwrap().as_string(),
3584 expected_content
3585 );
3586
3587 let output = parsed.to_string();
3588 assert_eq!(output, yaml);
3589 }
3590
3591 #[test]
3592 fn test_block_scalars_with_special_characters() {
3593 let yaml = r#"special_chars: |
3594 Line with colons: key: value
3595 Line with dashes - and more - dashes
3596 Line with quotes "double" and 'single'
3597 Line with brackets [array] and braces {object}
3598 Line with pipes | and greater than >
3599 Line with at @ and hash # symbols
3600 Line with percent % and exclamation !
3601
3602backslash_test: >
3603 This line has a backslash \ in it
3604 And this line has multiple \\ backslashes
3605
3606unicode_test: |
3607 This line has unicode: 你好世界
3608 And emojis: 🚀 🎉 ✨
3609"#;
3610 let parsed =
3611 YamlFile::from_str(yaml).expect("Should parse block scalars with special characters");
3612
3613 let doc = parsed.document().expect("Should have document");
3614 let mapping = doc.as_mapping().expect("Should be a mapping");
3615
3616 let special_chars = mapping
3617 .get("special_chars")
3618 .expect("Should have 'special_chars' key");
3619 assert_eq!(
3620 special_chars.as_scalar().unwrap().as_string(),
3621 "Line with colons: key: value\nLine with dashes - and more - dashes\nLine with quotes \"double\" and 'single'\nLine with brackets [array] and braces {object}\nLine with pipes | and greater than >\nLine with at @ and hash # symbols\nLine with percent % and exclamation !\n"
3622 );
3623
3624 let backslash_test = mapping
3625 .get("backslash_test")
3626 .expect("Should have 'backslash_test' key");
3627 assert_eq!(
3628 backslash_test.as_scalar().unwrap().as_string(),
3629 "This line has a backslash \\ in it And this line has multiple \\\\ backslashes\n"
3630 );
3631
3632 let unicode_test = mapping
3633 .get("unicode_test")
3634 .expect("Should have 'unicode_test' key");
3635 assert_eq!(
3636 unicode_test.as_scalar().unwrap().as_string(),
3637 "This line has unicode: 你好世界\nAnd emojis: 🚀 🎉 ✨\n"
3638 );
3639
3640 let output = parsed.to_string();
3641 assert_eq!(output, yaml);
3642 }
3643
3644 #[test]
3645 fn test_block_scalar_chomping_detailed() {
3646 let yaml_clip = r#"clip: |
3648 Line 1
3649 Line 2
3650
3651"#;
3652 let parsed_clip =
3653 YamlFile::from_str(yaml_clip).expect("Should parse block scalar with default clipping");
3654
3655 let doc_clip = parsed_clip.document().expect("Should have document");
3657 let mapping_clip = doc_clip.as_mapping().expect("Should be a mapping");
3658 assert_eq!(
3659 mapping_clip
3660 .get("clip")
3661 .unwrap()
3662 .as_scalar()
3663 .unwrap()
3664 .as_string(),
3665 "Line 1\nLine 2\n"
3666 );
3667
3668 assert_eq!(parsed_clip.to_string(), yaml_clip);
3669
3670 let yaml_strip = r#"strip: |-
3672 Line 1
3673 Line 2
3674
3675
3676
3677"#;
3678 let parsed_strip =
3679 YamlFile::from_str(yaml_strip).expect("Should parse block scalar with strip indicator");
3680
3681 let doc_strip = parsed_strip.document().expect("Should have document");
3683 let mapping_strip = doc_strip.as_mapping().expect("Should be a mapping");
3684 assert_eq!(
3685 mapping_strip
3686 .get("strip")
3687 .unwrap()
3688 .as_scalar()
3689 .unwrap()
3690 .as_string(),
3691 "Line 1\nLine 2"
3692 );
3693
3694 assert_eq!(parsed_strip.to_string(), yaml_strip);
3695
3696 let yaml_keep = r#"keep: |+
3698 Line 1
3699 Line 2
3700
3701
3702
3703"#;
3704 let parsed_keep =
3705 YamlFile::from_str(yaml_keep).expect("Should parse block scalar with keep indicator");
3706
3707 let doc_keep = parsed_keep.document().expect("Should have document");
3709 let mapping_keep = doc_keep.as_mapping().expect("Should be a mapping");
3710 assert_eq!(
3711 mapping_keep
3712 .get("keep")
3713 .unwrap()
3714 .as_scalar()
3715 .unwrap()
3716 .as_string(),
3717 "Line 1\nLine 2\n\n\n\n"
3718 );
3719
3720 assert_eq!(parsed_keep.to_string(), yaml_keep);
3721 }
3722
3723 #[test]
3724 fn test_block_scalar_explicit_indentation_detailed() {
3725 let yaml1 = r#"indent1: |1
3727 Single space indent
3728"#;
3729 let parsed1 = YamlFile::from_str(yaml1);
3730 assert!(parsed1.is_ok(), "Should parse |1 block scalar");
3731 let output1 = parsed1.unwrap().to_string();
3732 assert_eq!(output1, yaml1);
3733
3734 let yaml2 = r#"indent2: |2
3735 Two space indent
3736"#;
3737 let parsed2 = YamlFile::from_str(yaml2);
3738 assert!(parsed2.is_ok(), "Should parse |2 block scalar");
3739 let output2 = parsed2.unwrap().to_string();
3740 assert_eq!(output2, yaml2);
3741
3742 let yaml3 = r#"folded_indent: >2
3743 Two space folded
3744 content spans lines
3745"#;
3746 let parsed3 = YamlFile::from_str(yaml3);
3747 assert!(parsed3.is_ok(), "Should parse >2 folded block scalar");
3748 let output3 = parsed3.unwrap().to_string();
3749 assert_eq!(output3, yaml3);
3750 }
3751
3752 #[test]
3753 fn test_block_scalar_combined_indicators() {
3754 let yaml = r#"strip_with_indent: |2-
3755 Content with explicit indent
3756 and strip chomping
3757
3758
3759keep_with_indent: >3+
3760 Content with explicit indent
3761 and keep chomping
3762
3763
3764
3765folded_strip: >-
3766 Folded content
3767 with strip indicator
3768
3769literal_keep: |+
3770 Literal content
3771 with keep indicator
3772
3773
3774"#;
3775 let parsed =
3776 YamlFile::from_str(yaml).expect("Should parse block scalars with combined indicators");
3777
3778 let doc = parsed.document().expect("Should have document");
3779 let mapping = doc.as_mapping().expect("Should be a mapping");
3780
3781 assert_eq!(
3782 mapping
3783 .get("strip_with_indent")
3784 .unwrap()
3785 .as_scalar()
3786 .unwrap()
3787 .as_string(),
3788 "Content with explicit indent\nand strip chomping"
3789 );
3790 assert_eq!(
3791 mapping
3792 .get("keep_with_indent")
3793 .unwrap()
3794 .as_scalar()
3795 .unwrap()
3796 .as_string(),
3797 "Content with explicit indent and keep chomping\n\n\n\n"
3798 );
3799 assert_eq!(
3800 mapping
3801 .get("folded_strip")
3802 .unwrap()
3803 .as_scalar()
3804 .unwrap()
3805 .as_string(),
3806 "Folded content with strip indicator"
3807 );
3808 assert_eq!(
3809 mapping
3810 .get("literal_keep")
3811 .unwrap()
3812 .as_scalar()
3813 .unwrap()
3814 .as_string(),
3815 "Literal content\nwith keep indicator\n\n\n"
3816 );
3817
3818 let output = parsed.to_string();
3819 assert_eq!(output, yaml);
3820 }
3821
3822 #[test]
3823 fn test_block_scalar_whitespace_and_empty() {
3824 let yaml1 = r#"whitespace_only: |
3826
3827
3828
3829"#;
3830 let parsed1 =
3831 YamlFile::from_str(yaml1).expect("Should handle block scalar with only whitespace");
3832
3833 let doc1 = parsed1.document().expect("Should have document");
3834 let mapping1 = doc1.as_mapping().expect("Should be a mapping");
3835 assert_eq!(
3836 mapping1
3837 .get("whitespace_only")
3838 .unwrap()
3839 .as_scalar()
3840 .unwrap()
3841 .as_string(),
3842 "\n"
3843 );
3844
3845 assert_eq!(parsed1.to_string(), yaml1);
3846
3847 let yaml2 = r#"mixed_indent: |
3849 Normal line
3850 Indented line
3851 Back to normal
3852 More indented
3853 Normal again
3854"#;
3855 let parsed2 = YamlFile::from_str(yaml2).expect("Should handle mixed indentation levels");
3856
3857 let doc2 = parsed2.document().expect("Should have document");
3858 let mapping2 = doc2.as_mapping().expect("Should be a mapping");
3859 assert_eq!(
3860 mapping2
3861 .get("mixed_indent")
3862 .unwrap()
3863 .as_scalar()
3864 .unwrap()
3865 .as_string(),
3866 "Normal line\n Indented line\nBack to normal\n More indented\nNormal again\n"
3867 );
3868
3869 assert_eq!(parsed2.to_string(), yaml2);
3870
3871 let yaml3 = r#"first: |
3873 Content
3874immediate: value
3875another: |
3876 More content
3877final: end
3878"#;
3879 let parsed3 =
3880 YamlFile::from_str(yaml3).expect("Should handle multiple block scalars in mapping");
3881
3882 let doc3 = parsed3.document().expect("Should have document");
3883 let mapping3 = doc3.as_mapping().expect("Should be a mapping");
3884 assert_eq!(
3885 mapping3
3886 .get("first")
3887 .unwrap()
3888 .as_scalar()
3889 .unwrap()
3890 .as_string(),
3891 "Content\n"
3892 );
3893 assert_eq!(
3894 mapping3
3895 .get("immediate")
3896 .unwrap()
3897 .as_scalar()
3898 .unwrap()
3899 .as_string(),
3900 "value"
3901 );
3902 assert_eq!(
3903 mapping3
3904 .get("another")
3905 .unwrap()
3906 .as_scalar()
3907 .unwrap()
3908 .as_string(),
3909 "More content\n"
3910 );
3911 assert_eq!(
3912 mapping3
3913 .get("final")
3914 .unwrap()
3915 .as_scalar()
3916 .unwrap()
3917 .as_string(),
3918 "end"
3919 );
3920
3921 let output3 = parsed3.to_string();
3922 assert_eq!(output3, yaml3);
3923 }
3924
3925 #[test]
3926 fn test_block_scalar_with_comments() {
3927 let yaml = r#"# Main configuration
3928config: | # This is a literal block
3929 # This comment is inside the block
3930 line1: value1
3931 # Another internal comment
3932 line2: value2
3933
3934# Outside comment
3935other: > # Folded block comment
3936 This content spans
3937 # This hash is part of the content, not a comment
3938 multiple lines
3939"#;
3940 let parsed = YamlFile::from_str(yaml).expect("Should parse block scalars with comments");
3941
3942 let doc = parsed.document().expect("Should have document");
3943 let mapping = doc.as_mapping().expect("Should be a mapping");
3944
3945 assert_eq!(
3948 mapping.get("config").unwrap().as_scalar().unwrap().as_string(),
3949 "# This comment is inside the block\nline1: value1\n# Another internal comment\nline2: value2\n\nOutside comment\n"
3950 );
3951
3952 assert_eq!(
3953 mapping
3954 .get("other")
3955 .unwrap()
3956 .as_scalar()
3957 .unwrap()
3958 .as_string(),
3959 "This content spans # This hash is part of the content, not a comment multiple lines\n"
3960 );
3961
3962 let output = parsed.to_string();
3963 assert_eq!(output, yaml);
3964 }
3965
3966 #[test]
3967 fn test_block_scalar_empty_and_minimal() {
3968 let yaml = r#"empty_literal: |
3969
3970empty_folded: >
3971
3972minimal_literal: |
3973 x
3974
3975minimal_folded: >
3976 y
3977
3978just_newlines: |
3979
3980
3981
3982just_spaces: |
3983
3984
3985
3986"#;
3987 let parsed =
3988 YamlFile::from_str(yaml).expect("Should handle empty and minimal block scalars");
3989
3990 let doc = parsed.document().expect("Should have document");
3991 let mapping = doc.as_mapping().expect("Should be a mapping");
3992
3993 assert_eq!(
3994 mapping
3995 .get("empty_literal")
3996 .unwrap()
3997 .as_scalar()
3998 .unwrap()
3999 .as_string(),
4000 "\n"
4001 );
4002 assert_eq!(
4003 mapping
4004 .get("empty_folded")
4005 .unwrap()
4006 .as_scalar()
4007 .unwrap()
4008 .as_string(),
4009 "\n"
4010 );
4011 assert_eq!(
4012 mapping
4013 .get("minimal_literal")
4014 .unwrap()
4015 .as_scalar()
4016 .unwrap()
4017 .as_string(),
4018 "x\n"
4019 );
4020 assert_eq!(
4021 mapping
4022 .get("minimal_folded")
4023 .unwrap()
4024 .as_scalar()
4025 .unwrap()
4026 .as_string(),
4027 "y\n"
4028 );
4029 assert_eq!(
4030 mapping
4031 .get("just_newlines")
4032 .unwrap()
4033 .as_scalar()
4034 .unwrap()
4035 .as_string(),
4036 "\n"
4037 );
4038 assert_eq!(
4039 mapping
4040 .get("just_spaces")
4041 .unwrap()
4042 .as_scalar()
4043 .unwrap()
4044 .as_string(),
4045 "\n"
4046 );
4047
4048 let output = parsed.to_string();
4049 assert_eq!(output, yaml);
4050 }
4051
4052 #[test]
4053 fn test_block_scalar_with_document_markers() {
4054 let yaml = r#"---
4055doc1: |
4056 This is the first document
4057 with a literal block scalar.
4058
4059next_key: value
4060---
4061doc2: >
4062 This is the second document
4063 with a folded block scalar.
4064
4065another_key: another_value
4066...
4067"#;
4068 let parsed =
4069 YamlFile::from_str(yaml).expect("Should parse block scalars with document markers");
4070
4071 let doc = parsed.document().expect("Should have first document");
4073 let mapping = doc.as_mapping().expect("Should be a mapping");
4074
4075 assert_eq!(
4076 mapping
4077 .get("doc1")
4078 .unwrap()
4079 .as_scalar()
4080 .unwrap()
4081 .as_string(),
4082 "This is the first document\nwith a literal block scalar.\n"
4083 );
4084 assert_eq!(
4085 mapping
4086 .get("next_key")
4087 .unwrap()
4088 .as_scalar()
4089 .unwrap()
4090 .as_string(),
4091 "value"
4092 );
4093
4094 let output = parsed.to_string();
4096 assert_eq!(output, yaml);
4097 }
4098
4099 #[test]
4100 fn test_block_scalar_formatting_preservation() {
4101 let original = r#"preserve_me: |
4102 Line with multiple spaces
4103 Line with tabs here
4104 Line with trailing spaces
4105
4106 Empty line above and below
4107
4108 Final line
4109"#;
4110 let parsed = YamlFile::from_str(original).expect("Should preserve exact formatting");
4111
4112 let doc = parsed.document().expect("Should have document");
4113 let mapping = doc.as_mapping().expect("Should be a mapping");
4114
4115 let preserve_me = mapping
4116 .get("preserve_me")
4117 .expect("Should have 'preserve_me' key");
4118 let expected = "Line with multiple spaces\nLine with\ttabs\there\nLine with trailing spaces\n\nEmpty line above and below\n\nFinal line\n";
4119 assert_eq!(preserve_me.as_scalar().unwrap().as_string(), expected);
4120
4121 let output = parsed.to_string();
4123 assert_eq!(output, original);
4124 }
4125
4126 #[test]
4127 fn test_block_scalar_complex_yaml_content() {
4128 let yaml = r#"yaml_content: |
4129 # This block contains YAML-like content
4130 nested:
4131 - item: value
4132 - item: another
4133
4134 mapping:
4135 key1: |
4136 Even more nested literal content
4137 key2: value
4138
4139 anchors: &anchor
4140 anchor_content: data
4141
4142 reference: *anchor
4143
4144quoted_yaml: >
4145 This folded block contains
4146 YAML structures: {key: value, array: [1, 2, 3]}
4147 that should be treated as plain text.
4148"#;
4149 let parsed = YamlFile::from_str(yaml)
4150 .expect("Should parse block scalars containing YAML-like structures");
4151
4152 let doc = parsed.document().expect("Should have document");
4153 let mapping = doc.as_mapping().expect("Should be a mapping");
4154
4155 let expected_yaml_content = "# This block contains YAML-like content\nnested:\n - item: value\n - item: another\n\nmapping:\n key1: |\n Even more nested literal content\n key2: value\n\nanchors: &anchor\n anchor_content: data\n\nreference: *anchor\n";
4156 assert_eq!(
4157 mapping
4158 .get("yaml_content")
4159 .unwrap()
4160 .as_scalar()
4161 .unwrap()
4162 .as_string(),
4163 expected_yaml_content
4164 );
4165
4166 let expected_quoted_yaml = "This folded block contains YAML structures: {key: value, array: [1, 2, 3]} that should be treated as plain text.\n";
4167 assert_eq!(
4168 mapping
4169 .get("quoted_yaml")
4170 .unwrap()
4171 .as_scalar()
4172 .unwrap()
4173 .as_string(),
4174 expected_quoted_yaml
4175 );
4176
4177 let output = parsed.to_string();
4178 assert_eq!(output, yaml);
4179 }
4180
4181 #[test]
4182 fn test_block_scalar_performance_large_content() {
4183 let mut large_content = String::new();
4185 for i in 1..=100 {
4186 large_content.push_str(&format!(
4187 " Line number {} with some content that makes it longer\n",
4188 i
4189 ));
4190 }
4191
4192 let yaml = format!(
4193 "large_literal: |\n{}\nlarge_folded: >\n{}\n",
4194 large_content, large_content
4195 );
4196
4197 let parsed =
4198 YamlFile::from_str(&yaml).expect("Should parse large block scalars without errors");
4199
4200 let doc = parsed.document().expect("Should have document");
4201 let mapping = doc.as_mapping().expect("Should be a mapping");
4202
4203 let literal_value = mapping
4204 .get("large_literal")
4205 .expect("Should have large_literal key")
4206 .as_scalar()
4207 .expect("Should be scalar")
4208 .as_string();
4209
4210 let mut expected_literal = String::new();
4212 for i in 1..=100 {
4213 expected_literal.push_str(&format!(
4214 "Line number {} with some content that makes it longer\n",
4215 i
4216 ));
4217 }
4218 assert_eq!(literal_value, expected_literal);
4219
4220 let folded_value = mapping
4221 .get("large_folded")
4222 .expect("Should have large_folded key")
4223 .as_scalar()
4224 .expect("Should be scalar")
4225 .as_string();
4226
4227 let mut expected_folded = String::new();
4229 for i in 1..=100 {
4230 if i > 1 {
4231 expected_folded.push(' ');
4232 }
4233 expected_folded.push_str(&format!(
4234 "Line number {} with some content that makes it longer",
4235 i
4236 ));
4237 }
4238 expected_folded.push('\n');
4239 assert_eq!(folded_value, expected_folded);
4240
4241 let output = parsed.to_string();
4242 assert_eq!(output, yaml);
4243 }
4244
4245 #[test]
4246 fn test_block_scalar_error_recovery() {
4247 let yaml = r#"good_key: value
4249bad_block: |
4250incomplete_key
4251another_good: works
4252"#;
4253 let parsed = YamlFile::from_str(yaml).expect("Should parse");
4254
4255 let doc = parsed.document().expect("Should have document");
4256 let mapping = doc.as_mapping().expect("Should be a mapping");
4257
4258 assert_eq!(
4260 mapping
4261 .get("good_key")
4262 .unwrap()
4263 .as_scalar()
4264 .unwrap()
4265 .as_string(),
4266 "value"
4267 );
4268
4269 assert_eq!(
4271 mapping
4272 .get("bad_block")
4273 .unwrap()
4274 .as_scalar()
4275 .unwrap()
4276 .as_string(),
4277 "incomplete_key\n"
4278 );
4279
4280 assert_eq!(
4282 mapping
4283 .get("another_good")
4284 .unwrap()
4285 .as_scalar()
4286 .unwrap()
4287 .as_string(),
4288 "works"
4289 );
4290
4291 let output = parsed.to_string();
4292 assert_eq!(output, yaml);
4293 }
4294
4295 #[test]
4296 fn test_block_scalar_with_flow_structures() {
4297 let yaml = r#"mixed_styles: |
4298 This literal block contains:
4299 - A flow sequence: [1, 2, 3]
4300 - A flow mapping: {key: value, other: data}
4301 - Mixed content: [a, {nested: true}, c]
4302
4303flow_then_block:
4304 flow_seq: [item1, item2]
4305 block_literal: |
4306 This comes after flow style
4307 and should work fine.
4308 flow_map: {after: block}
4309"#;
4310 let parsed = YamlFile::from_str(yaml).expect("Should parse mixed flow and block styles");
4311
4312 let doc = parsed.document().expect("Should have document");
4313 let mapping = doc.as_mapping().expect("Should be a mapping");
4314
4315 let expected_mixed = "This literal block contains:\n- A flow sequence: [1, 2, 3]\n- A flow mapping: {key: value, other: data}\n- Mixed content: [a, {nested: true}, c]\n";
4317 assert_eq!(
4318 mapping
4319 .get("mixed_styles")
4320 .unwrap()
4321 .as_scalar()
4322 .unwrap()
4323 .as_string(),
4324 expected_mixed
4325 );
4326
4327 let flow_then_block_value = mapping.get("flow_then_block").unwrap();
4329 let flow_then_block = flow_then_block_value.as_mapping().unwrap();
4330
4331 let flow_seq_value = flow_then_block.get("flow_seq").unwrap();
4333 let flow_seq = flow_seq_value.as_sequence().unwrap();
4334 assert_eq!(flow_seq.len(), 2);
4335 assert_eq!(
4336 flow_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
4337 "item1"
4338 );
4339 assert_eq!(
4340 flow_seq.get(1).unwrap().as_scalar().unwrap().as_string(),
4341 "item2"
4342 );
4343
4344 assert_eq!(
4346 flow_then_block
4347 .get("block_literal")
4348 .unwrap()
4349 .as_scalar()
4350 .unwrap()
4351 .as_string(),
4352 "This comes after flow style\nand should work fine.\n"
4353 );
4354
4355 let flow_map_value = flow_then_block.get("flow_map").unwrap();
4357 let flow_map = flow_map_value.as_mapping().unwrap();
4358 assert_eq!(
4359 flow_map
4360 .get("after")
4361 .unwrap()
4362 .as_scalar()
4363 .unwrap()
4364 .as_string(),
4365 "block"
4366 );
4367
4368 let output = parsed.to_string();
4369 assert_eq!(output, yaml);
4370 }
4371
4372 #[test]
4373 fn test_block_scalar_indentation_edge_cases() {
4374 let yaml1 = r#"empty: |
4376next: value"#;
4377 let parsed1 = YamlFile::from_str(yaml1);
4378 assert!(parsed1.is_ok(), "Should handle empty block followed by key");
4379
4380 let yaml2 = r#"inconsistent: |
4382 normal indent
4383 more indent
4384 back to normal
4385 even more
4386 normal
4387"#;
4388 let parsed2 = YamlFile::from_str(yaml2);
4389 assert!(
4390 parsed2.is_ok(),
4391 "Should handle inconsistent but valid indentation"
4392 );
4393
4394 let yaml3 = "tabs: |\n\tTab indented line\n\tAnother tab line\n";
4396 let parsed3 = YamlFile::from_str(yaml3);
4397 assert!(
4398 parsed3.is_ok(),
4399 "Should handle tab characters in block scalars"
4400 );
4401 }
4402
4403 #[test]
4404 fn test_block_scalar_with_anchors_and_aliases() {
4405 let yaml = r#"template: &template |
4406 This is a template
4407 with multiple lines
4408 that can be referenced.
4409
4410instance1: *template
4411
4412instance2:
4413 content: *template
4414 other: value
4415
4416modified: |
4417 <<: *template
4418 Additional content here
4419"#;
4420 let parsed =
4421 YamlFile::from_str(yaml).expect("Should parse block scalars with anchors and aliases");
4422
4423 let doc = parsed.document().expect("Should have document");
4424 let mapping = doc.as_mapping().expect("Should be a mapping");
4425
4426 let expected_template =
4428 "This is a template\nwith multiple lines\nthat can be referenced.\n";
4429 let template_value = mapping.get("template").unwrap();
4430 assert_eq!(
4431 template_value.as_scalar().unwrap().as_string(),
4432 expected_template
4433 );
4434
4435 let instance1 = mapping.get("instance1").unwrap();
4437 assert!(
4438 instance1.is_alias(),
4439 "instance1 should be an alias, not a scalar"
4440 );
4441 assert_eq!(instance1.as_alias().unwrap().name(), "template");
4442
4443 let instance2_value = mapping.get("instance2").unwrap();
4445 let instance2 = instance2_value.as_mapping().unwrap();
4446
4447 let content = instance2.get("content").unwrap();
4449 assert!(
4450 content.is_alias(),
4451 "content should be an alias, not a scalar"
4452 );
4453 assert_eq!(content.as_alias().unwrap().name(), "template");
4454
4455 assert_eq!(
4457 instance2
4458 .get("other")
4459 .unwrap()
4460 .as_scalar()
4461 .unwrap()
4462 .as_string(),
4463 "value"
4464 );
4465
4466 let modified = mapping.get("modified").unwrap();
4469 assert!(
4470 modified.is_scalar(),
4471 "modified should be a scalar, not an alias"
4472 );
4473 assert_eq!(
4474 modified.as_scalar().unwrap().as_string(),
4475 "<<: *template\nAdditional content here\n"
4476 );
4477
4478 let output = parsed.to_string();
4479 assert_eq!(output, yaml);
4480 }
4481
4482 #[test]
4483 fn test_block_scalar_newline_variations() {
4484 let yaml_unix = "unix: |\n Line 1\n Line 2\n";
4486 let parsed_unix = YamlFile::from_str(yaml_unix).expect("Should handle Unix newlines");
4487
4488 let yaml_windows = "windows: |\r\n Line 1\r\n Line 2\r\n";
4489 let parsed_windows =
4490 YamlFile::from_str(yaml_windows).expect("Should handle Windows newlines");
4491
4492 let doc_unix = parsed_unix.document().expect("Should have document");
4494 let mapping_unix = doc_unix.as_mapping().expect("Should be a mapping");
4495 assert_eq!(
4496 mapping_unix
4497 .get("unix")
4498 .unwrap()
4499 .as_scalar()
4500 .unwrap()
4501 .as_string(),
4502 "Line 1\nLine 2\n"
4503 );
4504
4505 let doc_windows = parsed_windows.document().expect("Should have document");
4507 let mapping_windows = doc_windows.as_mapping().expect("Should be a mapping");
4508 assert_eq!(
4509 mapping_windows
4510 .get("windows")
4511 .unwrap()
4512 .as_scalar()
4513 .unwrap()
4514 .as_string(),
4515 "Line 1\nLine 2\n"
4516 );
4517
4518 assert_eq!(parsed_unix.to_string(), yaml_unix);
4519 assert_eq!(parsed_windows.to_string(), yaml_windows);
4520 }
4521
4522 #[test]
4523 fn test_block_scalar_boundary_detection() {
4524 let yaml = r#"config:
4526 description: |
4527 This is a configuration
4528 with multiple lines.
4529
4530 name: "MyApp"
4531 version: 1.0
4532
4533 settings: >
4534 These are settings that
4535 span multiple lines too.
4536
4537 debug: true
4538"#;
4539 let parsed =
4540 YamlFile::from_str(yaml).expect("Should properly detect block scalar boundaries");
4541
4542 let doc = parsed.document().expect("Should have document");
4543 let mapping = doc.as_mapping().expect("Should be a mapping");
4544 let config_value = mapping.get("config").unwrap();
4545 let config = config_value.as_mapping().unwrap();
4546
4547 assert_eq!(
4549 config
4550 .get("description")
4551 .unwrap()
4552 .as_scalar()
4553 .unwrap()
4554 .as_string(),
4555 "This is a configuration\nwith multiple lines.\n"
4556 );
4557
4558 assert_eq!(
4560 config.get("name").unwrap().as_scalar().unwrap().as_string(),
4561 "MyApp"
4562 );
4563
4564 assert_eq!(
4566 config
4567 .get("version")
4568 .unwrap()
4569 .as_scalar()
4570 .unwrap()
4571 .as_string(),
4572 "1.0"
4573 );
4574
4575 assert_eq!(
4577 config
4578 .get("settings")
4579 .unwrap()
4580 .as_scalar()
4581 .unwrap()
4582 .as_string(),
4583 "These are settings that span multiple lines too.\n"
4584 );
4585
4586 assert_eq!(
4588 config
4589 .get("debug")
4590 .unwrap()
4591 .as_scalar()
4592 .unwrap()
4593 .as_string(),
4594 "true"
4595 );
4596
4597 let output = parsed.to_string();
4598 assert_eq!(output, yaml);
4599 }
4600
4601 #[test]
4602 fn test_block_scalar_with_numeric_content() {
4603 let yaml = r#"numbers_as_text: |
4604 123
4605 45.67
4606 -89
4607 +100
4608 0xFF
4609 1e5
4610 true
4611 false
4612 null
4613
4614calculations: >
4615 The result is: 2 + 2 = 4
4616 And 10 * 5 = 50
4617 Also: 100% complete
4618"#;
4619 let parsed = YamlFile::from_str(yaml)
4620 .expect("Should parse numeric content as text in block scalars");
4621
4622 let doc = parsed.document().expect("Should have document");
4623 let mapping = doc.as_mapping().expect("Should be a mapping");
4624
4625 let expected_numbers = "123\n45.67\n-89\n+100\n0xFF\n1e5\ntrue\nfalse\nnull\n";
4627 assert_eq!(
4628 mapping
4629 .get("numbers_as_text")
4630 .unwrap()
4631 .as_scalar()
4632 .unwrap()
4633 .as_string(),
4634 expected_numbers
4635 );
4636
4637 let expected_calculations =
4639 "The result is: 2 + 2 = 4 And 10 * 5 = 50 Also: 100% complete\n";
4640 assert_eq!(
4641 mapping
4642 .get("calculations")
4643 .unwrap()
4644 .as_scalar()
4645 .unwrap()
4646 .as_string(),
4647 expected_calculations
4648 );
4649
4650 let output = parsed.to_string();
4651 assert_eq!(output, yaml);
4652 }
4653
4654 #[test]
4655 fn test_block_scalar_exact_preservation() {
4656 let test_cases = [
4658 r#"simple: |
4660 Hello World
4661"#,
4662 r#"folded: >
4664 Hello World
4665"#,
4666 r#"strip: |-
4668 Content
4669
4670keep: |+
4671 Content
4672
4673"#,
4674 r#"explicit: |2
4676 Two space indent
4677"#,
4678 r#"config:
4680 script: |
4681 #!/bin/bash
4682 echo "Starting deployment"
4683
4684 for service in api web worker; do
4685 echo "Deploying $service"
4686 kubectl apply -f $service.yaml
4687 done
4688
4689 description: >
4690 This configuration defines a deployment
4691 script that will be executed during
4692 the CI/CD pipeline.
4693"#,
4694 ];
4695
4696 for (i, yaml) in test_cases.iter().enumerate() {
4697 let parsed = YamlFile::from_str(yaml);
4698 assert!(parsed.is_ok(), "Test case {} should parse successfully", i);
4699
4700 let output = parsed.unwrap().to_string();
4701 assert_eq!(
4702 output, *yaml,
4703 "Test case {} should preserve exact formatting",
4704 i
4705 );
4706 }
4707 }
4708
4709 #[test]
4710 fn test_block_scalar_chomping_exact() {
4711 let yaml_strip = r#"strip: |-
4712 Content
4713"#;
4714 let parsed_strip = YamlFile::from_str(yaml_strip).unwrap();
4715 assert_eq!(parsed_strip.to_string(), yaml_strip);
4716
4717 let yaml_keep = r#"keep: |+
4718 Content
4719
4720"#;
4721 let parsed_keep = YamlFile::from_str(yaml_keep).unwrap();
4722 assert_eq!(parsed_keep.to_string(), yaml_keep);
4723
4724 let yaml_folded_strip = r#"folded: >-
4725 Content
4726"#;
4727 let parsed_folded_strip = YamlFile::from_str(yaml_folded_strip).unwrap();
4728 assert_eq!(parsed_folded_strip.to_string(), yaml_folded_strip);
4729 }
4730
4731 #[test]
4732 fn test_block_scalar_indentation_exact() {
4733 let yaml1 = r#"indent1: |1
4734 Single space
4735"#;
4736 let parsed1 = YamlFile::from_str(yaml1).unwrap();
4737 assert_eq!(parsed1.to_string(), yaml1);
4738
4739 let yaml2 = r#"indent2: |2
4740 Two spaces
4741"#;
4742 let parsed2 = YamlFile::from_str(yaml2).unwrap();
4743 assert_eq!(parsed2.to_string(), yaml2);
4744
4745 let yaml3 = r#"combined: |3+
4746 Content with keep
4747
4748"#;
4749 let parsed3 = YamlFile::from_str(yaml3).unwrap();
4750 assert_eq!(parsed3.to_string(), yaml3);
4751 }
4752
4753 #[test]
4754 fn test_block_scalar_mapping_exact() {
4755 let yaml = r#"description: |
4756 Line 1
4757 Line 2
4758
4759summary: >
4760 Folded content
4761
4762version: "1.0"
4763"#;
4764 let parsed = YamlFile::from_str(yaml).unwrap();
4765 assert_eq!(parsed.to_string(), yaml);
4766 }
4767
4768 #[test]
4769 fn test_block_scalar_sequence_exact() {
4770 let yaml = r#"items:
4771 - |
4772 First item content
4773 with multiple lines
4774
4775 - >
4776 Second item folded
4777 content
4778
4779 - regular_item
4780"#;
4781 let parsed = YamlFile::from_str(yaml).unwrap();
4782 assert_eq!(parsed.to_string(), yaml);
4783 }
4784
4785 #[test]
4786 fn test_block_scalar_empty_exact() {
4787 let yaml1 = r#"empty: |
4788
4789"#;
4790 let parsed1 = YamlFile::from_str(yaml1).unwrap();
4791 assert_eq!(parsed1.to_string(), yaml1);
4792
4793 let yaml2 = r#"empty_folded: >
4794
4795"#;
4796 let parsed2 = YamlFile::from_str(yaml2).unwrap();
4797 assert_eq!(parsed2.to_string(), yaml2);
4798 }
4799
4800 #[test]
4801 fn test_empty_documents_in_stream() {
4802 let yaml = "---\n---\nkey: value\n---\n...\n";
4804 let parsed = YamlFile::from_str(yaml).unwrap();
4805 assert_eq!(parsed.documents().count(), 3);
4806 assert_eq!(parsed.to_string(), yaml);
4807 }
4808
4809 #[test]
4810 fn test_mixed_document_end_markers() {
4811 let yaml = "---\nfirst: doc\n...\n---\nsecond: doc\n---\nthird: doc\n...\n";
4813 let parsed = YamlFile::from_str(yaml).unwrap();
4814 assert_eq!(parsed.documents().count(), 3);
4815 assert_eq!(parsed.to_string(), yaml);
4816 }
4817
4818 #[test]
4819 fn test_complex_document_stream() {
4820 let yaml = r#"%YAML 1.2
4821%TAG ! tag:example.com,2000:app/
4822---
4823template: &anchor
4824 key: !custom value
4825instance:
4826 <<: *anchor
4827 extra: data
4828...
4829%YAML 1.2
4830---
4831- item1
4832- item2: nested
4833...
4834---
4835literal: |
4836 Block content
4837 Multiple lines
4838folded: >
4839 Folded content
4840 on multiple lines
4841...
4842"#;
4843 let parsed = YamlFile::from_str(yaml).unwrap();
4844 assert_eq!(parsed.documents().count(), 3);
4845 assert_eq!(parsed.to_string(), yaml);
4846 }
4847
4848 #[test]
4849 fn test_number_format_parsing() {
4850 let yaml = YamlFile::from_str("value: 0b1010").unwrap();
4852 assert_eq!(yaml.to_string().trim(), "value: 0b1010");
4853
4854 let yaml = YamlFile::from_str("value: 0B1111").unwrap();
4855 assert_eq!(yaml.to_string().trim(), "value: 0B1111");
4856
4857 let yaml = YamlFile::from_str("value: 0o755").unwrap();
4859 assert_eq!(yaml.to_string().trim(), "value: 0o755");
4860
4861 let yaml = YamlFile::from_str("value: 0O644").unwrap();
4862 assert_eq!(yaml.to_string().trim(), "value: 0O644");
4863
4864 let yaml = YamlFile::from_str("value: -0b1010").unwrap();
4866 assert_eq!(yaml.to_string().trim(), "value: -0b1010");
4867
4868 let yaml = YamlFile::from_str("value: +0o755").unwrap();
4869 assert_eq!(yaml.to_string().trim(), "value: +0o755");
4870
4871 let yaml = YamlFile::from_str("value: 0755").unwrap();
4873 assert_eq!(yaml.to_string().trim(), "value: 0755");
4874
4875 let yaml = YamlFile::from_str("value: 0xFF").unwrap();
4876 assert_eq!(yaml.to_string().trim(), "value: 0xFF");
4877 }
4878
4879 #[test]
4880 fn test_invalid_number_formats_as_strings() {
4881 let yaml = YamlFile::from_str("value: 0b2").unwrap();
4883 assert_eq!(yaml.to_string().trim(), "value: 0b2");
4884
4885 let yaml = YamlFile::from_str("value: 0o9").unwrap();
4886 assert_eq!(yaml.to_string().trim(), "value: 0o9");
4887
4888 let yaml = YamlFile::from_str("value: 0xGH").unwrap();
4889 assert_eq!(yaml.to_string().trim(), "value: 0xGH");
4890 }
4891
4892 #[test]
4893 fn test_number_formats_in_complex_structures() {
4894 let input = r#"
4895config:
4896 permissions: 0o755
4897 flags: 0b11010
4898 color: 0xFF00FF
4899 count: 42"#;
4900
4901 let yaml = YamlFile::from_str(input).unwrap();
4902
4903 let doc = yaml.document().expect("Should have document");
4904 let mapping = doc.as_mapping().expect("Should be a mapping");
4905 let config_value = mapping.get("config").unwrap();
4906 let config = config_value.as_mapping().unwrap();
4907
4908 assert_eq!(
4909 config
4910 .get("permissions")
4911 .unwrap()
4912 .as_scalar()
4913 .unwrap()
4914 .as_string(),
4915 "0o755"
4916 );
4917 assert_eq!(
4918 config
4919 .get("flags")
4920 .unwrap()
4921 .as_scalar()
4922 .unwrap()
4923 .as_string(),
4924 "0b11010"
4925 );
4926 assert_eq!(
4927 config
4928 .get("color")
4929 .unwrap()
4930 .as_scalar()
4931 .unwrap()
4932 .as_string(),
4933 "0xFF00FF"
4934 );
4935 assert_eq!(
4936 config
4937 .get("count")
4938 .unwrap()
4939 .as_scalar()
4940 .unwrap()
4941 .as_string(),
4942 "42"
4943 );
4944
4945 let output = yaml.to_string();
4946 assert_eq!(output, input);
4947 }
4948
4949 #[test]
4950 fn test_editing_operations() {
4951 let yaml = YamlFile::from_str("name: old-name\nversion: 1.0.0").unwrap();
4953 if let Some(doc) = yaml.document() {
4954 doc.set("name", "new-name");
4955 doc.set("version", "2.0.0");
4956
4957 assert_eq!(doc.get_string("name"), Some("new-name".to_string()));
4959 assert_eq!(doc.get_string("version"), Some("2.0.0".to_string()));
4960
4961 let output = doc.to_string();
4963 assert_eq!(output, "name: new-name\nversion: 2.0.0");
4964 }
4965 }
4966
4967 #[test]
4968 fn test_timestamp_parsing_and_validation() {
4969 use crate::scalar::{ScalarType, ScalarValue};
4970
4971 let test_cases = vec![
4973 ("2001-12-14 21:59:43.10 -5", true), ("2001-12-15T02:59:43.1Z", true), ("2002-12-14", true), ("2001-12-14t21:59:43.10-05:00", true), ("2001-12-14 21:59:43.10", true), ("2001-12-14T21:59:43", true), ("not-a-timestamp", false), ("2001-13-14", false), ("2001-12-32", false), ];
4983
4984 for (timestamp_str, should_be_valid) in test_cases {
4985 let scalar = ScalarValue::parse(timestamp_str);
4986
4987 if should_be_valid {
4988 assert_eq!(
4989 scalar.scalar_type(),
4990 ScalarType::Timestamp,
4991 "Failed to recognize '{}' as timestamp",
4992 timestamp_str
4993 );
4994 assert!(scalar.is_timestamp());
4995
4996 assert_eq!(scalar.value(), timestamp_str);
4998
4999 let yaml = format!("timestamp: {}", timestamp_str);
5001 let parsed = YamlFile::from_str(&yaml).unwrap();
5002
5003 let doc = parsed.document().expect("Should have document");
5004 let mapping = doc.as_mapping().expect("Should be a mapping");
5005 assert_eq!(
5006 mapping
5007 .get("timestamp")
5008 .unwrap()
5009 .as_scalar()
5010 .unwrap()
5011 .as_string(),
5012 timestamp_str,
5013 "Timestamp '{}' not preserved",
5014 timestamp_str
5015 );
5016
5017 let output = parsed.to_string();
5018 assert_eq!(output, yaml);
5019 } else {
5020 assert_ne!(
5021 scalar.scalar_type(),
5022 ScalarType::Timestamp,
5023 "'{}' should not be recognized as timestamp",
5024 timestamp_str
5025 );
5026 }
5027 }
5028
5029 let yaml_with_timestamps = r#"
5031created_at: 2001-12-14 21:59:43.10 -5
5032updated_at: 2001-12-15T02:59:43.1Z
5033date_only: 2002-12-14
5034timestamps_in_array:
5035 - 2001-12-14 21:59:43.10 -5
5036 - 2001-12-15T02:59:43.1Z
5037 - 2002-12-14"#;
5038
5039 let parsed = YamlFile::from_str(yaml_with_timestamps).unwrap();
5040
5041 let doc = parsed.document().expect("Should have document");
5042 let mapping = doc.as_mapping().expect("Should be a mapping");
5043 assert_eq!(
5044 mapping
5045 .get("created_at")
5046 .unwrap()
5047 .as_scalar()
5048 .unwrap()
5049 .as_string(),
5050 "2001-12-14 21:59:43.10 -5"
5051 );
5052 assert_eq!(
5053 mapping
5054 .get("updated_at")
5055 .unwrap()
5056 .as_scalar()
5057 .unwrap()
5058 .as_string(),
5059 "2001-12-15T02:59:43.1Z"
5060 );
5061 assert_eq!(
5062 mapping
5063 .get("date_only")
5064 .unwrap()
5065 .as_scalar()
5066 .unwrap()
5067 .as_string(),
5068 "2002-12-14"
5069 );
5070
5071 let array_value = mapping.get("timestamps_in_array").unwrap();
5072 let array = array_value.as_sequence().unwrap();
5073 assert_eq!(
5074 array.get(0).unwrap().as_scalar().unwrap().as_string(),
5075 "2001-12-14 21:59:43.10 -5"
5076 );
5077 assert_eq!(
5078 array.get(1).unwrap().as_scalar().unwrap().as_string(),
5079 "2001-12-15T02:59:43.1Z"
5080 );
5081 assert_eq!(
5082 array.get(2).unwrap().as_scalar().unwrap().as_string(),
5083 "2002-12-14"
5084 );
5085
5086 let output = parsed.to_string();
5087 assert_eq!(output, yaml_with_timestamps);
5088 }
5089
5090 #[test]
5091 fn test_regex_support_in_yaml() {
5092 use crate::scalar::{ScalarType, ScalarValue};
5093
5094 let yaml_with_regex = r#"
5096patterns:
5097 digits: !!regex '\d+'
5098 word: !!regex '\w+'
5099 simple: !!regex 'test'"#;
5100
5101 let parsed = YamlFile::from_str(yaml_with_regex).unwrap();
5102
5103 let output = parsed.to_string();
5105 assert_eq!(output, yaml_with_regex);
5106
5107 let regex_scalar = ScalarValue::regex(r"^\d{4}-\d{2}-\d{2}$");
5109 assert_eq!(regex_scalar.scalar_type(), ScalarType::Regex);
5110 assert!(regex_scalar.is_regex());
5111 assert_eq!(regex_scalar.value(), r"^\d{4}-\d{2}-\d{2}$");
5112 assert_eq!(
5113 regex_scalar.to_yaml_string(),
5114 r"!!regex ^\d{4}-\d{2}-\d{2}$"
5115 );
5116
5117 let yaml_simple = "pattern: !!regex '\\d+'";
5119 let parsed_simple = YamlFile::from_str(yaml_simple).unwrap();
5120
5121 let doc_simple = parsed_simple.document().expect("Should have document");
5122 let mapping_simple = doc_simple.as_mapping().expect("Should be a mapping");
5123 let pattern_value = mapping_simple
5124 .get("pattern")
5125 .expect("Should have pattern key");
5126
5127 assert!(pattern_value.is_tagged(), "pattern should be a tagged node");
5129 let tagged = pattern_value.as_tagged().expect("Should be tagged");
5130 assert_eq!(tagged.tag(), Some("!!regex".to_string()));
5131 assert_eq!(tagged.as_string(), Some("\\d+".to_string()));
5132
5133 let output_simple = parsed_simple.to_string();
5134 assert_eq!(output_simple, yaml_simple);
5135
5136 let complex_regex = r#"validation: !!regex '^https?://(?:[-\w.])+(?:\:[0-9]+)?'"#;
5138 let parsed_complex = YamlFile::from_str(complex_regex).unwrap();
5139
5140 let doc_complex = parsed_complex.document().expect("Should have document");
5141 let mapping_complex = doc_complex.as_mapping().expect("Should be a mapping");
5142 let validation_value = mapping_complex
5143 .get("validation")
5144 .expect("Should have validation key");
5145
5146 assert!(
5147 validation_value.is_tagged(),
5148 "validation should be a tagged node"
5149 );
5150 let tagged_complex = validation_value.as_tagged().expect("Should be tagged");
5151 assert_eq!(tagged_complex.tag(), Some("!!regex".to_string()));
5152 assert_eq!(
5153 tagged_complex.as_string(),
5154 Some("^https?://(?:[-\\w.])+(?:\\:[0-9]+)?".to_string())
5155 );
5156
5157 let output_complex = parsed_complex.to_string();
5158 assert_eq!(output_complex, complex_regex);
5159 }
5160
5161 #[test]
5162 fn test_regex_in_different_contexts() {
5163 let yaml_sequence = r#"
5165patterns:
5166 - !!regex '\d+'
5167 - !!regex '[a-z]+'
5168 - normal_string
5169 - !!regex '.*@.*\..*'
5170"#;
5171
5172 let parsed_seq = YamlFile::from_str(yaml_sequence).unwrap();
5173
5174 let doc_seq = parsed_seq.document().expect("Should have document");
5175 let mapping_seq = doc_seq.as_mapping().expect("Should be a mapping");
5176 let patterns_value = mapping_seq
5177 .get("patterns")
5178 .expect("Should have patterns key");
5179 let patterns = patterns_value.as_sequence().expect("Should be a sequence");
5180
5181 assert!(patterns.get(0).unwrap().is_tagged());
5183 assert_eq!(
5184 patterns.get(0).unwrap().as_tagged().unwrap().tag(),
5185 Some("!!regex".to_string())
5186 );
5187
5188 assert!(patterns.get(1).unwrap().is_tagged());
5190 assert_eq!(
5191 patterns.get(1).unwrap().as_tagged().unwrap().tag(),
5192 Some("!!regex".to_string())
5193 );
5194
5195 assert!(!patterns.get(2).unwrap().is_tagged());
5197 assert_eq!(
5198 patterns.get(2).unwrap().as_scalar().unwrap().as_string(),
5199 "normal_string"
5200 );
5201
5202 assert!(patterns.get(3).unwrap().is_tagged());
5204 assert_eq!(
5205 patterns.get(3).unwrap().as_tagged().unwrap().tag(),
5206 Some("!!regex".to_string())
5207 );
5208
5209 let output_seq = parsed_seq.to_string();
5210 assert_eq!(output_seq, yaml_sequence);
5211
5212 let yaml_nested = r#"
5214validation:
5215 email: !!regex '[^@]+@[^@]+\.[a-z]+'
5216 phone: !!regex '\d{3}-\d{3}-\d{4}'
5217 config:
5218 debug_pattern: !!regex 'DEBUG:.*'
5219 nested:
5220 deep_pattern: !!regex 'ERROR'
5221"#;
5222
5223 let parsed_nested = YamlFile::from_str(yaml_nested).unwrap();
5224 let output_nested = parsed_nested.to_string();
5226 assert_eq!(output_nested, yaml_nested);
5227
5228 let yaml_mixed = r#"
5230mixed_collection:
5231 - name: "test"
5232 patterns: [!!regex '\d+', !!regex '\w+']
5233 - patterns:
5234 simple: !!regex 'test'
5235 complex: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5236"#;
5237
5238 let parsed_mixed = YamlFile::from_str(yaml_mixed).unwrap();
5239 let output_mixed = parsed_mixed.to_string();
5241 assert_eq!(output_mixed, yaml_mixed);
5242
5243 let yaml_flow =
5245 r#"inline_patterns: {email: !!regex '[^@]+@[^@]+', phone: !!regex '\d{3}-\d{4}'}"#;
5246
5247 let parsed_flow = YamlFile::from_str(yaml_flow).unwrap();
5248 let output_flow = parsed_flow.to_string();
5250 assert_eq!(output_flow, yaml_flow);
5251 }
5252
5253 #[test]
5254 fn test_regex_parsing_edge_cases() {
5255 let yaml_quotes = r#"
5258patterns:
5259 single_quoted: !!regex 'pattern with spaces'
5260 double_quoted: !!regex "pattern_without_escapes"
5261 unquoted: !!regex simple_pattern
5262"#;
5263
5264 let parsed_quotes = YamlFile::from_str(yaml_quotes).unwrap();
5265
5266 let output_quotes = parsed_quotes.to_string();
5267 assert_eq!(output_quotes, yaml_quotes);
5268
5269 let yaml_empty = r#"
5271empty: !!regex ''
5272whitespace: !!regex ' '
5273tabs: !!regex ' '
5274"#;
5275
5276 let parsed_empty =
5277 YamlFile::from_str(yaml_empty).expect("Should parse empty/whitespace regex patterns");
5278
5279 let output_empty = parsed_empty.to_string();
5280 assert_eq!(output_empty, yaml_empty);
5281
5282 let yaml_special = r#"special: !!regex 'pattern_with_underscores_and_123'"#;
5284
5285 let parsed_special = YamlFile::from_str(yaml_special)
5286 .expect("Should parse regex with safe special characters");
5287
5288 let output_special = parsed_special.to_string();
5289 assert_eq!(output_special, yaml_special);
5290
5291 let yaml_verify = r#"test_pattern: !!regex '\d{4}-\d{2}-\d{2}'"#;
5293 let parsed_verify = YamlFile::from_str(yaml_verify).unwrap();
5294
5295 let output_verify = parsed_verify.to_string();
5296 assert_eq!(output_verify, yaml_verify);
5297
5298 let yaml_multiple = r#"
5300patterns:
5301 email: !!regex '^[^\s@]+@[^\s@]+\.[^\s@]+$'
5302 phone: !!regex '^\+?[\d\s\-\(\)]{10,}$'
5303 url: !!regex '^https?://[^\s]+$'
5304 ipv4: !!regex '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$'
5305 uuid: !!regex '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
5306"#;
5307
5308 let parsed_multiple =
5309 YamlFile::from_str(yaml_multiple).expect("Should parse multiple regex patterns");
5310
5311 let output_multiple = parsed_multiple.to_string();
5313 assert_eq!(output_multiple, yaml_multiple);
5314 }
5315
5316 #[test]
5317 fn test_enhanced_comment_support() {
5318 let yaml1 = r#"flow_seq: [
5323 item1, # comment after item1
5324 item2, # comment after item2
5325 item3 # comment after item3
5326]"#;
5327 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5328 let output1 = parsed1.to_string();
5329
5330 assert_eq!(output1, yaml1);
5331
5332 let yaml2 = r#"flow_map: {
5334 key1: val1, # comment after first pair
5335 key2: val2, # comment after second pair
5336 key3: val3 # comment after third pair
5337}"#;
5338 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5339 let output2 = parsed2.to_string();
5340
5341 assert_eq!(output2, yaml2);
5342
5343 let yaml3 = r#"config:
5345 servers: [
5346 {name: web1, port: 80}, # Web server 1
5347 {name: web2, port: 80}, # Web server 2
5348 {name: db1, port: 5432} # Database server
5349 ] # End servers array"#;
5350 let parsed3 = YamlFile::from_str(yaml3).unwrap();
5351 let output3 = parsed3.to_string();
5352
5353 assert_eq!(output3, yaml3);
5354
5355 let yaml4 = r#"items:
5357 - first # First item comment
5358 - second # Second item comment
5359 # Comment between items
5360 - third # Third item comment"#;
5361 let parsed4 = YamlFile::from_str(yaml4).unwrap();
5362 let output4 = parsed4.to_string();
5363
5364 assert_eq!(output4, yaml4);
5365
5366 for yaml in [yaml1, yaml2, yaml3, yaml4] {
5368 let parsed = YamlFile::from_str(yaml).unwrap();
5369 let output = parsed.to_string();
5370 let reparsed = YamlFile::from_str(&output);
5371 assert!(reparsed.is_ok(), "Round-trip parsing should succeed");
5372 }
5373 }
5374
5375 #[test]
5376 fn test_insert_after_with_sequence() {
5377 let yaml = "name: project\nversion: 1.0.0";
5378 let parsed = YamlFile::from_str(yaml).unwrap();
5379 let doc = parsed.document().expect("Should have a document");
5380
5381 let features = SequenceBuilder::new()
5383 .item("feature1")
5384 .item("feature2")
5385 .item("feature3")
5386 .build_document()
5387 .as_sequence()
5388 .unwrap();
5389 let success = doc.insert_after("name", "features", features);
5390 assert!(success, "insert_after should succeed");
5391
5392 let output = doc.to_string();
5393
5394 let expected = r#"name: project
5396features:
5397 - feature1
5398 - feature2
5399 - feature3
5400version: 1.0.0"#;
5401 assert_eq!(output.trim(), expected);
5402
5403 let reparsed = YamlFile::from_str(&output);
5404 assert!(reparsed.is_ok(), "Output should be valid YAML");
5405 }
5406
5407 #[test]
5408 fn test_insert_before_with_mapping() {
5409 let yaml = "name: project\nversion: 1.0.0";
5410 let parsed = YamlFile::from_str(yaml).unwrap();
5411 let doc = parsed.document().expect("Should have a document");
5412
5413 let database = MappingBuilder::new()
5415 .pair("host", "localhost")
5416 .pair("port", 5432)
5417 .pair("database", "mydb")
5418 .build_document()
5419 .as_mapping()
5420 .unwrap();
5421 let success = doc.insert_before("version", "database", database);
5422 assert!(success, "insert_before should succeed");
5423
5424 let output = doc.to_string();
5425
5426 let expected = r#"name: project
5428database:
5429 host: localhost
5430 port: 5432
5431 database: mydb
5432version: 1.0.0"#;
5433 assert_eq!(output.trim(), expected);
5434
5435 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5437 let reparsed_doc = reparsed.document().expect("Should have document");
5438 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5439
5440 let db_value = reparsed_mapping
5441 .get("database")
5442 .expect("Should have database key");
5443 let db_mapping = db_value.as_mapping().expect("database should be mapping");
5444 assert_eq!(
5445 db_mapping
5446 .get("host")
5447 .unwrap()
5448 .as_scalar()
5449 .unwrap()
5450 .as_string(),
5451 "localhost"
5452 );
5453 assert_eq!(
5454 db_mapping
5455 .get("port")
5456 .unwrap()
5457 .as_scalar()
5458 .unwrap()
5459 .as_string(),
5460 "5432"
5461 );
5462 }
5463
5464 #[test]
5465 fn test_insert_at_index_with_mixed_types() {
5466 let yaml = "name: project";
5467 let parsed = YamlFile::from_str(yaml).unwrap();
5468 let doc = parsed.document().expect("Should have a document");
5469
5470 doc.insert_at_index(1, "version", "1.0.0");
5472 doc.insert_at_index(2, "active", true);
5473 doc.insert_at_index(3, "count", 42);
5474
5475 let features = SequenceBuilder::new()
5476 .item("auth")
5477 .item("logging")
5478 .build_document()
5479 .as_sequence()
5480 .unwrap();
5481 doc.insert_at_index(4, "features", features);
5482
5483 let output = doc.to_string();
5484
5485 let expected = r#"name: project
5486version: 1.0.0
5487active: true
5488count: 42
5489features:
5490- auth
5491- logging"#;
5492 assert_eq!(output.trim(), expected);
5493
5494 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5495 let reparsed_doc = reparsed.document().expect("Should have document");
5496 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5497
5498 assert_eq!(
5499 reparsed_mapping
5500 .get("version")
5501 .unwrap()
5502 .as_scalar()
5503 .unwrap()
5504 .as_string(),
5505 "1.0.0"
5506 );
5507 assert_eq!(
5508 reparsed_mapping.get("active").unwrap().to_bool(),
5509 Some(true)
5510 );
5511 assert_eq!(reparsed_mapping.get("count").unwrap().to_i64(), Some(42));
5512
5513 let features_value = reparsed_mapping.get("features").unwrap();
5514 let features = features_value.as_sequence().unwrap();
5515 assert_eq!(
5516 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5517 "auth"
5518 );
5519 assert_eq!(
5520 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5521 "logging"
5522 );
5523 }
5524
5525 #[test]
5526 fn test_insert_with_null_and_special_scalars() {
5527 let yaml = "name: project";
5528 let parsed = YamlFile::from_str(yaml).unwrap();
5529 let doc = parsed.document().expect("Should have a document");
5530
5531 doc.insert_after("name", "null_value", ScalarValue::null());
5533 doc.insert_after("null_value", "empty_string", "");
5534 doc.insert_after("empty_string", "number", 1.234);
5535 doc.insert_after("number", "boolean", false);
5536
5537 let output = doc.to_string();
5538
5539 let expected = r#"name: project
5540null_value: null
5541empty_string: ''
5542number: 1.234
5543boolean: false"#;
5544 assert_eq!(output.trim(), expected);
5545
5546 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5547 let reparsed_doc = reparsed.document().expect("Should have document");
5548 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5549
5550 assert_eq!(
5551 reparsed_mapping
5552 .get("name")
5553 .unwrap()
5554 .as_scalar()
5555 .unwrap()
5556 .as_string(),
5557 "project"
5558 );
5559 assert!(reparsed_mapping
5560 .get("null_value")
5561 .unwrap()
5562 .as_scalar()
5563 .unwrap()
5564 .is_null());
5565 assert_eq!(
5566 reparsed_mapping
5567 .get("empty_string")
5568 .unwrap()
5569 .as_scalar()
5570 .unwrap()
5571 .as_string(),
5572 ""
5573 );
5574 assert_eq!(
5575 reparsed_mapping.get("number").unwrap().to_f64(),
5576 Some(1.234)
5577 );
5578 assert_eq!(
5579 reparsed_mapping.get("boolean").unwrap().to_bool(),
5580 Some(false)
5581 );
5582 }
5583
5584 #[test]
5585 fn test_insert_ordering_preservation() {
5586 let yaml = "first: 1\nthird: 3\nfifth: 5";
5587 let parsed = YamlFile::from_str(yaml).unwrap();
5588 let doc = parsed.document().expect("Should have a document");
5589
5590 doc.insert_after("first", "second", 2);
5592 doc.insert_before("fifth", "fourth", 4);
5593
5594 let output = doc.to_string();
5595
5596 let expected = r#"first: 1
5598second: 2
5599third: 3
5600fourth: 4
5601fifth: 5"#;
5602 assert_eq!(output.trim(), expected);
5603
5604 let reparsed = YamlFile::from_str(&output);
5605 assert!(reparsed.is_ok(), "Output should be valid YAML");
5606 }
5607
5608 #[test]
5609 fn test_insert_with_yamlvalue_positioning() {
5610 let yaml = "name: project\nversion: 1.0\nactive: true";
5611 let parsed = YamlFile::from_str(yaml).unwrap();
5612 let doc = parsed.document().expect("Should have a document");
5613
5614 let success = doc.insert_after("name", "description", "A sample project");
5618 assert!(success, "Should find string key");
5619
5620 let success = doc.insert_after(1.0, "build", "gradle");
5622 assert!(
5623 !success,
5624 "Should not find numeric key (1.0) when actual key is string 'version'"
5625 );
5626
5627 let success = doc.insert_after(true, "test", "enabled");
5629 assert!(
5630 !success,
5631 "Should not find boolean key (true) when actual key is string 'active'"
5632 );
5633
5634 let bool_string_key = "true";
5636 let success = doc.insert_after(bool_string_key, "test_mode", "development");
5637 assert!(!success, "Should not find 'true' key when value is true");
5638
5639 let output = doc.to_string();
5640
5641 let expected = "name: project\ndescription: A sample project\nversion: 1.0\nactive: true";
5643 assert_eq!(output, expected);
5644 }
5645
5646 #[test]
5647 fn test_insert_complex_nested_structure() {
5648 let yaml = "name: project";
5649 let parsed = YamlFile::from_str(yaml).unwrap();
5650 let doc = parsed.document().expect("Should have a document");
5651
5652 let config = MappingBuilder::new()
5654 .mapping("server", |m| m.pair("host", "0.0.0.0").pair("port", 8080))
5655 .pair("debug", true)
5656 .sequence("features", |s| s.item("api").item("web").item("cli"))
5657 .build_document()
5658 .as_mapping()
5659 .unwrap();
5660
5661 doc.insert_after("name", "config", config);
5662
5663 let output = doc.to_string();
5664
5665 let expected = "name: project\nconfig:\n server: \n host: 0.0.0.0\n port: 8080\n debug: true\n features: \n - api\n - web\n - cli\n";
5667 assert_eq!(output, expected);
5668
5669 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5670 let reparsed_doc = reparsed.document().expect("Should have document");
5671 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5672
5673 let config_value = reparsed_mapping
5674 .get("config")
5675 .expect("Should have config key");
5676 let config_mapping = config_value.as_mapping().expect("config should be mapping");
5677
5678 let server_value = config_mapping
5679 .get("server")
5680 .expect("Should have server key");
5681 let server = server_value.as_mapping().expect("server should be mapping");
5682 assert_eq!(
5683 server.get("host").unwrap().as_scalar().unwrap().as_string(),
5684 "0.0.0.0"
5685 );
5686 assert_eq!(server.get("port").unwrap().to_i64(), Some(8080));
5687
5688 assert_eq!(config_mapping.get("debug").unwrap().to_bool(), Some(true));
5689
5690 let features_value = config_mapping
5691 .get("features")
5692 .expect("Should have features key");
5693 let features = features_value
5694 .as_sequence()
5695 .expect("features should be sequence");
5696 assert_eq!(features.len(), 3);
5697 assert_eq!(
5698 features.get(0).unwrap().as_scalar().unwrap().as_string(),
5699 "api"
5700 );
5701 assert_eq!(
5702 features.get(1).unwrap().as_scalar().unwrap().as_string(),
5703 "web"
5704 );
5705 assert_eq!(
5706 features.get(2).unwrap().as_scalar().unwrap().as_string(),
5707 "cli"
5708 );
5709 }
5710
5711 #[test]
5712 fn test_insert_with_yaml_sets() {
5713 let yaml = "name: project";
5714 let parsed = YamlFile::from_str(yaml).unwrap();
5715 let doc = parsed.document().expect("Should have a document");
5716
5717 let mut tags = std::collections::BTreeSet::new();
5719 tags.insert("production".to_string());
5720 tags.insert("database".to_string());
5721 tags.insert("web".to_string());
5722
5723 let yaml_set = YamlValue::from_set(tags);
5724 doc.insert_after("name", "tags", yaml_set);
5725
5726 let output = doc.to_string();
5727
5728 let expected =
5730 "name: project\ntags: !!set\n database: null\n production: null\n web: null\n";
5731 assert_eq!(output, expected);
5732
5733 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5734 let reparsed_doc = reparsed.document().expect("Should have document");
5735 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5736
5737 let tags_value = reparsed_mapping.get("tags").expect("Should have tags key");
5738 assert!(tags_value.is_tagged(), "tags should be tagged");
5739 let tagged = tags_value.as_tagged().expect("Should be tagged node");
5740 assert_eq!(tagged.tag(), Some("!!set".to_string()));
5741
5742 let tags_syntax = tagged.syntax();
5745 let tags_mapping = tags_syntax
5746 .children()
5747 .find_map(Mapping::cast)
5748 .expect("Set should have mapping child");
5749
5750 assert!(tags_mapping
5751 .get("database")
5752 .unwrap()
5753 .as_scalar()
5754 .unwrap()
5755 .is_null());
5756 assert!(tags_mapping
5757 .get("production")
5758 .unwrap()
5759 .as_scalar()
5760 .unwrap()
5761 .is_null());
5762 assert!(tags_mapping
5763 .get("web")
5764 .unwrap()
5765 .as_scalar()
5766 .unwrap()
5767 .is_null());
5768 }
5769
5770 #[test]
5771 fn test_insert_with_ordered_mappings() {
5772 let yaml = "name: project";
5773 let parsed = YamlFile::from_str(yaml).unwrap();
5774 let doc = parsed.document().expect("Should have a document");
5775
5776 let ordered_steps = vec![
5778 ("compile".to_string(), YamlValue::from("gcc main.c")),
5779 ("test".to_string(), YamlValue::from("./a.out test")),
5780 (
5781 "package".to_string(),
5782 YamlValue::from("tar -czf app.tar.gz ."),
5783 ),
5784 ];
5785
5786 let yaml_omap = YamlValue::from_ordered_mapping(ordered_steps);
5787 doc.insert_after("name", "build_steps", yaml_omap);
5788
5789 let output = doc.to_string();
5790
5791 let expected = "name: project\nbuild_steps: !!omap\n - compile: gcc main.c\n - test: ./a.out test\n - package: tar -czf app.tar.gz .\n";
5793 assert_eq!(output, expected);
5794
5795 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5796 let reparsed_doc = reparsed.document().expect("Should have document");
5797 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5798
5799 let steps_value = reparsed_mapping
5800 .get("build_steps")
5801 .expect("Should have build_steps key");
5802 assert!(steps_value.is_tagged(), "build_steps should be tagged");
5803 let tagged = steps_value.as_tagged().expect("Should be tagged node");
5804 assert_eq!(tagged.tag(), Some("!!omap".to_string()));
5805
5806 let steps_syntax = tagged.syntax();
5808 let steps_seq = steps_syntax
5809 .children()
5810 .find_map(Sequence::cast)
5811 .expect("Omap should have sequence child");
5812
5813 assert_eq!(steps_seq.len(), 3);
5814 let compile_value = steps_seq.get(0).unwrap();
5816 let compile_item = compile_value.as_mapping().expect("Should be mapping");
5817 assert_eq!(
5818 compile_item
5819 .get("compile")
5820 .unwrap()
5821 .as_scalar()
5822 .unwrap()
5823 .as_string(),
5824 "gcc main.c"
5825 );
5826
5827 let test_value = steps_seq.get(1).unwrap();
5828 let test_item = test_value.as_mapping().expect("Should be mapping");
5829 assert_eq!(
5830 test_item
5831 .get("test")
5832 .unwrap()
5833 .as_scalar()
5834 .unwrap()
5835 .as_string(),
5836 "./a.out test"
5837 );
5838
5839 let package_value = steps_seq.get(2).unwrap();
5840 let package_item = package_value.as_mapping().expect("Should be mapping");
5841 assert_eq!(
5842 package_item
5843 .get("package")
5844 .unwrap()
5845 .as_scalar()
5846 .unwrap()
5847 .as_string(),
5848 "tar -czf app.tar.gz ."
5849 );
5850 }
5851
5852 #[test]
5853 fn test_insert_with_pairs() {
5854 let yaml = "name: project";
5855 let parsed = YamlFile::from_str(yaml).unwrap();
5856 let doc = parsed.document().expect("Should have a document");
5857
5858 let connection_attempts = vec![
5860 ("server".to_string(), YamlValue::from("primary.db")),
5861 ("server".to_string(), YamlValue::from("secondary.db")), ("server".to_string(), YamlValue::from("tertiary.db")), ("timeout".to_string(), YamlValue::from(30)),
5864 ];
5865
5866 let yaml_pairs = YamlValue::from_pairs(connection_attempts);
5867 doc.insert_after("name", "connections", yaml_pairs);
5868
5869 let output = doc.to_string();
5870
5871 let expected = "name: project\nconnections: !!pairs\n - server: primary.db\n - server: secondary.db\n - server: tertiary.db\n - timeout: 30\n";
5873 assert_eq!(output, expected);
5874
5875 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
5876 let reparsed_doc = reparsed.document().expect("Should have document");
5877 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
5878
5879 let connections_value = reparsed_mapping
5880 .get("connections")
5881 .expect("Should have connections key");
5882 assert!(
5883 connections_value.is_tagged(),
5884 "connections should be tagged"
5885 );
5886 let tagged = connections_value
5887 .as_tagged()
5888 .expect("Should be tagged node");
5889 assert_eq!(tagged.tag(), Some("!!pairs".to_string()));
5890
5891 let connections_syntax = tagged.syntax();
5893 let connections_seq = connections_syntax
5894 .children()
5895 .find_map(Sequence::cast)
5896 .expect("Pairs should have sequence child");
5897
5898 assert_eq!(connections_seq.len(), 4);
5899
5900 let server1_val = connections_seq.get(0).unwrap();
5901 let server1 = server1_val.as_mapping().expect("Should be mapping");
5902 assert_eq!(
5903 server1
5904 .get("server")
5905 .unwrap()
5906 .as_scalar()
5907 .unwrap()
5908 .as_string(),
5909 "primary.db"
5910 );
5911
5912 let server2_val = connections_seq.get(1).unwrap();
5913 let server2 = server2_val.as_mapping().expect("Should be mapping");
5914 assert_eq!(
5915 server2
5916 .get("server")
5917 .unwrap()
5918 .as_scalar()
5919 .unwrap()
5920 .as_string(),
5921 "secondary.db"
5922 );
5923
5924 let server3_val = connections_seq.get(2).unwrap();
5925 let server3 = server3_val.as_mapping().expect("Should be mapping");
5926 assert_eq!(
5927 server3
5928 .get("server")
5929 .unwrap()
5930 .as_scalar()
5931 .unwrap()
5932 .as_string(),
5933 "tertiary.db"
5934 );
5935
5936 let timeout_val = connections_seq.get(3).unwrap();
5937 let timeout = timeout_val.as_mapping().expect("Should be mapping");
5938 assert_eq!(timeout.get("timeout").unwrap().to_i64(), Some(30));
5939 }
5940
5941 #[test]
5942 fn test_insert_with_empty_collections() {
5943 let yaml1 = "name: project";
5947 let parsed1 = YamlFile::from_str(yaml1).unwrap();
5948 let doc1 = parsed1.document().expect("Should have a document");
5949
5950 let empty_seq_yaml = YamlFile::from_str("[]").unwrap();
5952 let empty_list = empty_seq_yaml.document().unwrap().as_sequence().unwrap();
5953
5954 doc1.insert_after("name", "empty_list", empty_list);
5955 let output1 = doc1.to_string();
5956 assert_eq!(output1, "name: project\nempty_list: []\n");
5957
5958 let yaml2 = "name: project";
5960 let parsed2 = YamlFile::from_str(yaml2).unwrap();
5961 let doc2 = parsed2.document().expect("Should have a document");
5962
5963 let empty_map_yaml = YamlFile::from_str("{}").unwrap();
5965 let empty_map = empty_map_yaml.document().unwrap().as_mapping().unwrap();
5966
5967 doc2.insert_after("name", "empty_map", empty_map);
5968 let output2 = doc2.to_string();
5969 assert_eq!(output2, "name: project\nempty_map: {}\n");
5970
5971 let yaml3 = "name: project";
5973 let parsed3 = YamlFile::from_str(yaml3).unwrap();
5974 let doc3 = parsed3.document().expect("Should have a document");
5975 doc3.insert_after("name", "empty_set", YamlValue::set());
5976 let output3 = doc3.to_string();
5977 assert_eq!(output3, "name: project\nempty_set: !!set {}\n");
5978
5979 assert!(
5981 YamlFile::from_str(&output1).is_ok(),
5982 "Empty sequence output should be valid YAML"
5983 );
5984 assert!(
5985 YamlFile::from_str(&output2).is_ok(),
5986 "Empty mapping output should be valid YAML"
5987 );
5988 assert!(
5989 YamlFile::from_str(&output3).is_ok(),
5990 "Empty set output should be valid YAML"
5991 );
5992 }
5993
5994 #[test]
5995 fn test_insert_with_deeply_nested_sequences() {
5996 let yaml = "name: project";
5997 let parsed = YamlFile::from_str(yaml).unwrap();
5998 let doc = parsed.document().expect("Should have a document");
5999
6000 let nested_data = SequenceBuilder::new()
6002 .item("item1")
6003 .mapping(|m| {
6004 m.sequence("config", |s| s.item("debug").item(true).item(8080))
6005 .pair("name", "service")
6006 })
6007 .item(42)
6008 .build_document()
6009 .as_sequence()
6010 .unwrap();
6011
6012 doc.insert_after("name", "nested_data", nested_data);
6013
6014 let output = doc.to_string();
6015
6016 let expected = "name: project\nnested_data:\n - item1\n - \n config: \n - debug\n - true\n - 8080\n name: service- 42\n";
6017 assert_eq!(output, expected);
6018
6019 let reparsed = YamlFile::from_str(&output).expect("Output should be valid YAML");
6020 let reparsed_doc = reparsed.document().expect("Should have document");
6021 let reparsed_mapping = reparsed_doc.as_mapping().expect("Should be mapping");
6022
6023 let nested_value = reparsed_mapping
6024 .get("nested_data")
6025 .expect("Should have nested_data key");
6026 let nested_seq = nested_value.as_sequence().expect("Should be sequence");
6027 assert_eq!(nested_seq.len(), 2);
6030
6031 assert_eq!(
6033 nested_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6034 "item1"
6035 );
6036
6037 let second_item = nested_seq.get(1).unwrap();
6039 let second_mapping = second_item.as_mapping().expect("Should be mapping");
6040
6041 let config_value = second_mapping.get("config").expect("Should have config");
6042 let config_seq = config_value
6043 .as_sequence()
6044 .expect("config should be sequence");
6045 assert_eq!(config_seq.len(), 3);
6046 assert_eq!(
6047 config_seq.get(0).unwrap().as_scalar().unwrap().as_string(),
6048 "debug"
6049 );
6050 assert_eq!(config_seq.get(1).unwrap().to_bool(), Some(true));
6051 assert_eq!(config_seq.get(2).unwrap().to_i64(), Some(8080));
6052
6053 assert_eq!(
6055 second_mapping
6056 .get("name")
6057 .unwrap()
6058 .as_scalar()
6059 .unwrap()
6060 .as_string(),
6061 "service- 42"
6062 );
6063 }
6064
6065 #[test]
6066 fn test_ast_preservation_comments_in_mapping() {
6067 let yaml = r#"---
6069# Header comment
6070key1: value1 # Inline comment 1
6071# Middle comment
6072key2: value2 # Inline comment 2
6073# Footer comment
6074"#;
6075 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6076
6077 if let Some(mapping) = doc.as_mapping() {
6079 mapping.move_after("key1", "new_key", "new_value");
6080 }
6081
6082 let result = doc.to_string();
6083
6084 let expected = r#"---
6085# Header comment
6086key1: value1 # Inline comment 1
6087new_key: new_value
6088# Middle comment
6089key2: value2 # Inline comment 2
6090# Footer comment
6091"#;
6092 assert_eq!(result, expected);
6093 }
6094
6095 #[test]
6096 fn test_ast_preservation_whitespace_in_mapping() {
6097 let yaml = r#"---
6099key1: value1
6100
6101
6102key2: value2
6103"#;
6104 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6105
6106 if let Some(mapping) = doc.as_mapping() {
6108 mapping.move_after("key1", "new_key", "new_value");
6109 }
6110
6111 let result = doc.to_string();
6112
6113 let expected = r#"---
6114key1: value1
6115new_key: new_value
6116
6117
6118key2: value2
6119"#;
6120 assert_eq!(result, expected);
6121 }
6122
6123 #[test]
6124 fn test_ast_preservation_complex_structure() {
6125 let yaml = r#"---
6127# Configuration file
6128database: # Database settings
6129 host: localhost # Default host
6130 port: 5432 # Default port
6131 # Connection pool settings
6132 pool:
6133 min: 1 # Minimum connections
6134 max: 10 # Maximum connections
6135
6136# Server configuration
6137server:
6138 port: 8080 # HTTP port
6139"#;
6140 let doc = YamlFile::from_str(yaml).unwrap().document().unwrap();
6141
6142 eprintln!("===========\n");
6143
6144 if let Some(mapping) = doc.as_mapping() {
6146 mapping.move_after("database", "logging", "debug");
6147 }
6148
6149 let result = doc.to_string();
6150
6151 let expected = r#"---
6153# Configuration file
6154database: # Database settings
6155 host: localhost # Default host
6156 port: 5432 # Default port
6157 # Connection pool settings
6158 pool:
6159 min: 1 # Minimum connections
6160 max: 10 # Maximum connections
6161
6162logging: debug
6163# Server configuration
6164server:
6165 port: 8080 # HTTP port
6166"#;
6167 assert_eq!(result, expected);
6168 }
6169
6170 mod lookahead_tests {
6172 use super::*;
6173 use crate::lex::lex;
6174
6175 fn check_implicit_mapping(yaml: &str) -> bool {
6177 let tokens: Vec<(SyntaxKind, &str)> = lex(yaml);
6178 let kinds: Vec<SyntaxKind> = tokens.iter().rev().map(|(kind, _)| *kind).collect();
6180 has_implicit_mapping_pattern(kinds.into_iter())
6181 }
6182
6183 #[test]
6184 fn test_simple_implicit_mapping() {
6185 assert!(check_implicit_mapping("'key' : value"));
6188 }
6189
6190 #[test]
6191 fn test_simple_value_no_mapping() {
6192 assert!(!check_implicit_mapping("value ,"));
6194 }
6195
6196 #[test]
6197 fn test_value_with_comma() {
6198 assert!(!check_implicit_mapping("value , more"));
6200 }
6201
6202 #[test]
6203 fn test_nested_sequence_as_key() {
6204 assert!(check_implicit_mapping("[a, b]: value"));
6206 }
6207
6208 #[test]
6209 fn test_nested_mapping_not_implicit() {
6210 assert!(!check_implicit_mapping("{a: 1}, b"));
6212 }
6213
6214 #[test]
6215 fn test_deeply_nested_key() {
6216 assert!(check_implicit_mapping("[[a]]: value"));
6218 }
6219
6220 #[test]
6221 fn test_with_whitespace() {
6222 assert!(check_implicit_mapping("'key' : value"));
6224 }
6225
6226 #[test]
6227 fn test_mapping_value_in_sequence() {
6228 assert!(!check_implicit_mapping("a, {key: value}"));
6231 }
6232
6233 #[test]
6234 fn test_multiple_colons() {
6235 assert!(check_implicit_mapping("key1: value1, key2: value2"));
6237 }
6238
6239 #[test]
6240 fn test_url_not_mapping() {
6241 assert!(!check_implicit_mapping("http://example.com"));
6244 }
6245 }
6246}