1use crate::yaml::{Alias, Mapping, Scalar, Sequence, SyntaxNode, TaggedNode};
23use rowan::ast::AstNode;
24use std::borrow::Cow;
25use std::fmt;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum YamlKind {
30 Mapping,
32 Sequence,
34 Scalar,
36 Alias,
38 Document,
40 Tagged(Cow<'static, str>),
44}
45
46pub trait AsYaml {
62 fn as_node(&self) -> Option<&SyntaxNode>;
67
68 fn kind(&self) -> YamlKind;
70
71 fn build_content(
79 &self,
80 builder: &mut rowan::GreenNodeBuilder,
81 indent: usize,
82 flow_context: bool,
83 ) -> bool;
84
85 fn is_inline(&self) -> bool;
90}
91
92pub fn yaml_eq<A, B>(a: &A, b: &B) -> bool
98where
99 A: AsYaml + ?Sized,
100 B: AsYaml + ?Sized,
101{
102 if let Some(node) = a.as_node() {
104 use crate::lex::SyntaxKind;
105 return match node.kind() {
106 SyntaxKind::SCALAR => Scalar::cast(node.clone()).is_some_and(|s| scalar_eq_rhs(&s, b)),
107 SyntaxKind::MAPPING => {
108 Mapping::cast(node.clone()).is_some_and(|m| mapping_eq_rhs(&m, b))
109 }
110 SyntaxKind::SEQUENCE => {
111 Sequence::cast(node.clone()).is_some_and(|s| sequence_eq_rhs(&s, b))
112 }
113 SyntaxKind::TAGGED_NODE => {
114 TaggedNode::cast(node.clone()).is_some_and(|t| tagged_eq_rhs(&t, b))
115 }
116 _ => false,
117 };
118 }
119
120 if let Some(node) = b.as_node() {
122 use crate::lex::SyntaxKind;
123 return match node.kind() {
124 SyntaxKind::SCALAR => Scalar::cast(node.clone()).is_some_and(|s| scalar_eq_rhs(&s, a)),
125 SyntaxKind::MAPPING => {
126 Mapping::cast(node.clone()).is_some_and(|m| mapping_eq_rhs(&m, a))
127 }
128 SyntaxKind::SEQUENCE => {
129 Sequence::cast(node.clone()).is_some_and(|s| sequence_eq_rhs(&s, a))
130 }
131 SyntaxKind::TAGGED_NODE => {
132 TaggedNode::cast(node.clone()).is_some_and(|t| tagged_eq_rhs(&t, a))
133 }
134 _ => false,
135 };
136 }
137
138 if a.kind() != b.kind() {
140 return false;
141 }
142 match (raw_scalar_str(a), raw_scalar_str(b)) {
143 (Some(sa), Some(sb)) => sa == sb,
144 _ => false,
145 }
146}
147
148fn raw_scalar_str<T: AsYaml + ?Sized>(v: &T) -> Option<String> {
150 if v.as_node().is_some() || v.kind() != YamlKind::Scalar {
151 return None;
152 }
153 let mut builder = rowan::GreenNodeBuilder::new();
154 v.build_content(&mut builder, 0, false);
155 let green = builder.finish();
156 let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
157 Scalar::cast(node.clone())
158 .map(|s| s.as_string())
159 .or_else(|| Some(node.text().to_string()))
160}
161
162fn scalar_semantic_value(scalar: &Scalar) -> Option<(crate::lex::SyntaxKind, String)> {
169 use crate::lex::SyntaxKind;
170 use crate::scalar::ScalarValue;
171
172 let token = scalar.0.first_token()?;
174 let kind = token.kind();
175 let text = token.text();
176
177 let normalized = match kind {
178 SyntaxKind::INT => {
179 ScalarValue::parse_integer(text)
181 .map(|v| v.to_string())
182 .unwrap_or_else(|| text.to_string())
183 }
184 SyntaxKind::FLOAT => {
185 text.parse::<f64>()
187 .map(|v| v.to_string())
188 .unwrap_or_else(|_| text.to_string())
189 }
190 SyntaxKind::BOOL => {
191 text.to_lowercase()
193 }
194 SyntaxKind::NULL => {
195 "null".to_string()
197 }
198 SyntaxKind::STRING => {
199 scalar.as_string()
201 }
202 _ => {
203 scalar.as_string()
205 }
206 };
207
208 Some((kind, normalized))
209}
210
211fn scalar_eq_rhs<B: AsYaml + ?Sized>(lhs: &Scalar, rhs: &B) -> bool {
212 let rhs_scalar = if let Some(node) = rhs.as_node() {
214 let Some(scalar) = Scalar::cast(node.clone()) else {
215 return false;
216 };
217 scalar
218 } else {
219 if rhs.kind() != YamlKind::Scalar {
221 return false;
222 }
223 let mut builder = rowan::GreenNodeBuilder::new();
224 rhs.build_content(&mut builder, 0, false);
225 let green = builder.finish();
226 let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
227 let Some(scalar) = Scalar::cast(node) else {
228 return false;
229 };
230 scalar
231 };
232
233 let Some((lhs_kind, lhs_value)) = scalar_semantic_value(lhs) else {
235 return false;
236 };
237 let Some((rhs_kind, rhs_value)) = scalar_semantic_value(&rhs_scalar) else {
238 return false;
239 };
240
241 lhs_kind == rhs_kind && lhs_value == rhs_value
245}
246
247fn mapping_eq_rhs<B: AsYaml + ?Sized>(lhs: &Mapping, rhs: &B) -> bool {
248 let Some(node) = rhs.as_node() else {
249 return false;
250 };
251 let Some(r) = Mapping::cast(node.clone()) else {
252 return false;
253 };
254 let lhs_pairs: Vec<_> = lhs.pairs().collect();
255 let rhs_pairs: Vec<_> = r.pairs().collect();
256 if lhs_pairs.len() != rhs_pairs.len() {
257 return false;
258 }
259 lhs_pairs
261 .iter()
262 .zip(rhs_pairs.iter())
263 .all(|((lk, lv), (rk, rv))| {
264 let Some(lk) = YamlNode::from_syntax_peeled(lk.clone()) else {
265 return false;
266 };
267 let Some(rk) = YamlNode::from_syntax_peeled(rk.clone()) else {
268 return false;
269 };
270 let Some(lv) = YamlNode::from_syntax_peeled(lv.clone()) else {
271 return false;
272 };
273 let Some(rv) = YamlNode::from_syntax_peeled(rv.clone()) else {
274 return false;
275 };
276 yaml_eq(&lk, &rk) && yaml_eq(&lv, &rv)
277 })
278}
279
280fn sequence_eq_rhs<B: AsYaml + ?Sized>(lhs: &Sequence, rhs: &B) -> bool {
281 let Some(node) = rhs.as_node() else {
282 return false;
283 };
284 let Some(r) = Sequence::cast(node.clone()) else {
285 return false;
286 };
287 let lhs_items: Vec<_> = lhs.items().collect();
288 let rhs_items: Vec<_> = r.items().collect();
289 if lhs_items.len() != rhs_items.len() {
290 return false;
291 }
292 lhs_items.iter().zip(rhs_items.iter()).all(|(l, r)| {
294 match (
295 YamlNode::from_syntax(l.clone()),
296 YamlNode::from_syntax(r.clone()),
297 ) {
298 (Some(l), Some(r)) => yaml_eq(&l, &r),
299 _ => false,
300 }
301 })
302}
303
304fn tagged_eq_rhs<B: AsYaml + ?Sized>(lhs: &TaggedNode, rhs: &B) -> bool {
305 let Some(node) = rhs.as_node() else {
306 return false;
307 };
308 TaggedNode::cast(node.clone())
309 .is_some_and(|r| lhs.tag() == r.tag() && lhs.as_string() == r.as_string())
310}
311
312#[derive(Debug, Clone, PartialEq)]
339pub enum YamlNode {
340 Scalar(Scalar),
342 Mapping(Mapping),
344 Sequence(Sequence),
346 Alias(Alias),
348 TaggedNode(TaggedNode),
350}
351
352impl YamlNode {
353 pub fn from_syntax(node: SyntaxNode) -> Option<Self> {
358 use crate::lex::SyntaxKind;
359 match node.kind() {
360 SyntaxKind::SCALAR => Scalar::cast(node).map(YamlNode::Scalar),
361 SyntaxKind::MAPPING => Mapping::cast(node).map(YamlNode::Mapping),
362 SyntaxKind::SEQUENCE => Sequence::cast(node).map(YamlNode::Sequence),
363 SyntaxKind::ALIAS => Alias::cast(node).map(YamlNode::Alias),
364 SyntaxKind::TAGGED_NODE => TaggedNode::cast(node).map(YamlNode::TaggedNode),
365 _ => None,
366 }
367 }
368
369 pub(crate) fn from_syntax_peeled(node: SyntaxNode) -> Option<Self> {
375 use crate::lex::SyntaxKind;
376 let inner = if matches!(node.kind(), SyntaxKind::KEY | SyntaxKind::VALUE) {
377 node.children().next()?
378 } else {
379 node
380 };
381 Self::from_syntax(inner)
382 }
383
384 pub fn kind(&self) -> YamlKind {
386 match self {
387 YamlNode::Scalar(_) => YamlKind::Scalar,
388 YamlNode::Mapping(_) => YamlKind::Mapping,
389 YamlNode::Sequence(_) => YamlKind::Sequence,
390 YamlNode::Alias(_) => YamlKind::Alias,
391 YamlNode::TaggedNode(t) => t
392 .tag()
393 .map(|tag| YamlKind::Tagged(Cow::Owned(tag)))
394 .unwrap_or(YamlKind::Scalar),
395 }
396 }
397
398 pub(crate) fn syntax(&self) -> &SyntaxNode {
400 match self {
401 YamlNode::Scalar(s) => s.syntax(),
402 YamlNode::Mapping(m) => m.syntax(),
403 YamlNode::Sequence(s) => s.syntax(),
404 YamlNode::Alias(a) => a.syntax(),
405 YamlNode::TaggedNode(t) => t.syntax(),
406 }
407 }
408
409 pub fn yaml_eq<O: AsYaml>(&self, other: &O) -> bool {
411 yaml_eq(self, other)
412 }
413
414 pub fn as_scalar(&self) -> Option<&Scalar> {
416 if let YamlNode::Scalar(s) = self {
417 Some(s)
418 } else {
419 None
420 }
421 }
422
423 pub fn as_mapping(&self) -> Option<&Mapping> {
425 if let YamlNode::Mapping(m) = self {
426 Some(m)
427 } else {
428 None
429 }
430 }
431
432 pub fn as_sequence(&self) -> Option<&Sequence> {
434 if let YamlNode::Sequence(s) = self {
435 Some(s)
436 } else {
437 None
438 }
439 }
440
441 pub fn as_tagged(&self) -> Option<&TaggedNode> {
443 if let YamlNode::TaggedNode(t) = self {
444 Some(t)
445 } else {
446 None
447 }
448 }
449
450 pub fn as_alias(&self) -> Option<&Alias> {
452 if let YamlNode::Alias(a) = self {
453 Some(a)
454 } else {
455 None
456 }
457 }
458
459 pub fn is_scalar(&self) -> bool {
461 matches!(self, YamlNode::Scalar(_))
462 }
463
464 pub fn is_mapping(&self) -> bool {
466 matches!(self, YamlNode::Mapping(_))
467 }
468
469 pub fn is_sequence(&self) -> bool {
471 matches!(self, YamlNode::Sequence(_))
472 }
473
474 pub fn is_tagged(&self) -> bool {
476 matches!(self, YamlNode::TaggedNode(_))
477 }
478
479 pub fn is_alias(&self) -> bool {
481 matches!(self, YamlNode::Alias(_))
482 }
483
484 pub fn to_i64(&self) -> Option<i64> {
488 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_i64()
489 }
490
491 pub fn to_f64(&self) -> Option<f64> {
495 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_f64()
496 }
497
498 pub fn to_bool(&self) -> Option<bool> {
502 crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_bool()
503 }
504
505 pub fn get(&self, key: impl crate::AsYaml) -> Option<YamlNode> {
509 self.as_mapping()?
510 .get_node(key)
511 .and_then(YamlNode::from_syntax)
512 }
513
514 pub fn get_item(&self, index: usize) -> Option<YamlNode> {
518 self.as_sequence()?
519 .items()
520 .nth(index)
521 .and_then(YamlNode::from_syntax)
522 }
523}
524
525impl AsYaml for YamlNode {
526 fn as_node(&self) -> Option<&SyntaxNode> {
527 Some(self.syntax())
528 }
529
530 fn kind(&self) -> YamlKind {
531 YamlNode::kind(self)
532 }
533
534 fn build_content(
535 &self,
536 builder: &mut rowan::GreenNodeBuilder,
537 _indent: usize,
538 _flow_context: bool,
539 ) -> bool {
540 let node = self.syntax();
541 copy_node_content(builder, node);
542 node.last_token()
543 .map(|t| t.kind() == crate::lex::SyntaxKind::NEWLINE)
544 .unwrap_or(false)
545 }
546
547 fn is_inline(&self) -> bool {
548 use crate::yaml::ValueNode;
549 match self {
550 YamlNode::Scalar(s) => ValueNode::is_inline(s),
551 YamlNode::Mapping(m) => ValueNode::is_inline(m),
552 YamlNode::Sequence(s) => ValueNode::is_inline(s),
553 YamlNode::Alias(_) => true,
554 YamlNode::TaggedNode(t) => ValueNode::is_inline(t),
555 }
556 }
557}
558
559impl PartialEq<str> for YamlNode {
561 fn eq(&self, other: &str) -> bool {
562 yaml_eq(self, &other)
563 }
564}
565
566impl PartialEq<&str> for YamlNode {
567 fn eq(&self, other: &&str) -> bool {
568 yaml_eq(self, other)
569 }
570}
571
572impl PartialEq<String> for YamlNode {
573 fn eq(&self, other: &String) -> bool {
574 yaml_eq(self, &other.as_str())
575 }
576}
577
578impl fmt::Display for YamlNode {
579 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580 write!(f, "{}", self.syntax().text())
581 }
582}
583
584impl<T: AsYaml + ?Sized> AsYaml for &T {
591 fn as_node(&self) -> Option<&SyntaxNode> {
592 (*self).as_node()
593 }
594
595 fn kind(&self) -> YamlKind {
596 (*self).kind()
597 }
598
599 fn build_content(
600 &self,
601 builder: &mut rowan::GreenNodeBuilder,
602 indent: usize,
603 flow_context: bool,
604 ) -> bool {
605 (*self).build_content(builder, indent, flow_context)
606 }
607
608 fn is_inline(&self) -> bool {
609 (*self).is_inline()
610 }
611}
612
613pub(crate) fn copy_node_content(builder: &mut rowan::GreenNodeBuilder, node: &SyntaxNode) {
615 for child in node.children_with_tokens() {
616 match child {
617 rowan::NodeOrToken::Node(n) => {
618 builder.start_node(n.kind().into());
619 copy_node_content(builder, &n);
620 builder.finish_node();
621 }
622 rowan::NodeOrToken::Token(t) => {
623 builder.token(t.kind().into(), t.text());
624 }
625 }
626 }
627}
628
629pub(crate) fn copy_node_content_with_indent(
635 builder: &mut rowan::GreenNodeBuilder,
636 node: &SyntaxNode,
637 indent: usize,
638) {
639 use crate::lex::SyntaxKind;
640 let mut after_newline = false;
641
642 for child in node.children_with_tokens() {
643 match child {
644 rowan::NodeOrToken::Node(n) => {
645 if after_newline && indent > 0 {
647 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
648 }
649 builder.start_node(n.kind().into());
650 copy_node_content_with_indent(builder, &n, indent);
651 builder.finish_node();
652 after_newline = false;
653 }
654 rowan::NodeOrToken::Token(t) => {
655 match t.kind() {
656 SyntaxKind::NEWLINE => {
657 builder.token(t.kind().into(), t.text());
658 after_newline = true;
659 }
660 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => {
661 if after_newline && indent > 0 {
663 let existing_indent = t.text().len();
664 let total_indent = indent + existing_indent;
665 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(total_indent));
666 } else if after_newline {
667 } else {
669 builder.token(t.kind().into(), t.text());
671 }
672 after_newline = false;
673 }
674 SyntaxKind::DASH => {
675 if after_newline && indent > 0 {
677 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
678 }
679 builder.token(t.kind().into(), t.text());
680 after_newline = false;
681 }
682 _ => {
683 if after_newline && indent > 0 {
685 builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(indent));
686 }
687 builder.token(t.kind().into(), t.text());
688 after_newline = false;
689 }
690 }
691 }
692 }
693 }
694}
695
696pub(crate) fn copy_node_content_reindent(
706 builder: &mut rowan::GreenNodeBuilder,
707 node: &SyntaxNode,
708 delta: isize,
709) -> bool {
710 use crate::lex::SyntaxKind;
711 let mut after_newline = false;
712 let pad = |n: isize| " ".repeat(n.max(0) as usize);
713
714 for child in node.children_with_tokens() {
715 match child {
716 rowan::NodeOrToken::Node(n) => {
717 if n.text_range().is_empty() {
721 builder.start_node(n.kind().into());
722 builder.finish_node();
723 continue;
724 }
725 if after_newline && delta > 0 {
726 builder.token(SyntaxKind::WHITESPACE.into(), &pad(delta));
727 }
728 builder.start_node(n.kind().into());
729 after_newline = copy_node_content_reindent(builder, &n, delta);
730 builder.finish_node();
731 }
732 rowan::NodeOrToken::Token(t) => match t.kind() {
733 SyntaxKind::NEWLINE => {
734 builder.token(t.kind().into(), t.text());
735 after_newline = true;
736 }
737 SyntaxKind::WHITESPACE | SyntaxKind::INDENT => {
738 if after_newline {
739 let total = t.text().len() as isize + delta;
740 if total > 0 {
741 builder.token(SyntaxKind::WHITESPACE.into(), &pad(total));
742 }
743 } else {
744 builder.token(t.kind().into(), t.text());
745 }
746 after_newline = false;
747 }
748 _ => {
749 if after_newline && delta > 0 {
750 builder.token(SyntaxKind::WHITESPACE.into(), &pad(delta));
751 }
752 builder.token(t.kind().into(), t.text());
753 after_newline = false;
754 }
755 },
756 }
757 }
758 after_newline
759}
760
761pub(crate) fn source_base_indent(node: &SyntaxNode) -> usize {
767 let mut column: usize = 0;
774 let mut cursor = node.prev_sibling_or_token();
775 let mut current = node.clone();
776 loop {
777 while let Some(item) = cursor {
778 match item {
779 rowan::NodeOrToken::Token(t) => {
780 let text = t.text();
781 if let Some(nl) = text.rfind('\n') {
782 column += text[nl + 1..].chars().count();
783 return column;
784 }
785 column += text.chars().count();
786 cursor = t.prev_sibling_or_token();
787 }
788 rowan::NodeOrToken::Node(n) => {
789 let text = n.text().to_string();
793 if let Some(nl) = text.rfind('\n') {
794 column += text[nl + 1..].chars().count();
795 return column;
796 }
797 column += text.chars().count();
798 cursor = n.prev_sibling_or_token();
799 }
800 }
801 }
802 match current.parent() {
804 Some(p) => {
805 cursor = p.prev_sibling_or_token();
806 current = p;
807 }
808 None => return column,
809 }
810 }
811}
812
813macro_rules! impl_as_yaml_int {
819 ($($ty:ty),+ $(,)?) => {
820 $(impl AsYaml for $ty {
821 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
822 None
823 }
824
825 fn kind(&self) -> YamlKind {
826 YamlKind::Scalar
827 }
828
829 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
830 use crate::lex::SyntaxKind;
831 builder.start_node(SyntaxKind::SCALAR.into());
832 builder.token(SyntaxKind::INT.into(), &self.to_string());
833 builder.finish_node();
834 false
835 }
836
837 fn is_inline(&self) -> bool {
838 true
839 }
840 })+
841 };
842}
843
844macro_rules! impl_as_yaml_float {
846 ($($ty:ty),+ $(,)?) => {
847 $(impl AsYaml for $ty {
848 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
849 None
850 }
851
852 fn kind(&self) -> YamlKind {
853 YamlKind::Scalar
854 }
855
856 fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, _indent: usize, _flow_context: bool) -> bool {
857 use crate::lex::SyntaxKind;
858 builder.start_node(SyntaxKind::SCALAR.into());
859 let s = self.to_string();
860 let float_str = if s.contains('.') || s.contains('e') || s.contains('E') {
862 s
863 } else {
864 format!("{}.0", s)
865 };
866 builder.token(SyntaxKind::FLOAT.into(), &float_str);
867 builder.finish_node();
868 false
869 }
870
871 fn is_inline(&self) -> bool {
872 true
873 }
874 })+
875 };
876}
877
878impl_as_yaml_int!(i64, i32, i16, i8, isize, u64, u32, u16, u8, usize);
880
881impl_as_yaml_float!(f64, f32);
883
884impl AsYaml for bool {
885 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
886 None
887 }
888
889 fn kind(&self) -> YamlKind {
890 YamlKind::Scalar
891 }
892
893 fn build_content(
894 &self,
895 builder: &mut rowan::GreenNodeBuilder,
896 _indent: usize,
897 _flow_context: bool,
898 ) -> bool {
899 use crate::lex::SyntaxKind;
900 builder.start_node(SyntaxKind::SCALAR.into());
901 builder.token(
902 SyntaxKind::BOOL.into(),
903 if *self { "true" } else { "false" },
904 );
905 builder.finish_node();
906 false
907 }
908
909 fn is_inline(&self) -> bool {
910 true
911 }
912}
913
914impl AsYaml for String {
915 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
916 None
917 }
918
919 fn kind(&self) -> YamlKind {
920 YamlKind::Scalar
921 }
922
923 fn build_content(
924 &self,
925 builder: &mut rowan::GreenNodeBuilder,
926 _indent: usize,
927 flow_context: bool,
928 ) -> bool {
929 self.as_str().build_content(builder, _indent, flow_context)
930 }
931
932 fn is_inline(&self) -> bool {
933 true
934 }
935}
936
937impl AsYaml for &str {
938 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
939 None
940 }
941
942 fn kind(&self) -> YamlKind {
943 YamlKind::Scalar
944 }
945
946 fn build_content(
947 &self,
948 builder: &mut rowan::GreenNodeBuilder,
949 _indent: usize,
950 flow_context: bool,
951 ) -> bool {
952 use crate::lex::SyntaxKind;
953 use crate::scalar::ScalarValue;
954
955 let scalar = if flow_context {
958 ScalarValue::double_quoted(*self)
959 } else {
960 ScalarValue::string(*self)
961 };
962
963 let yaml_text = scalar.to_yaml_string();
964 builder.start_node(SyntaxKind::SCALAR.into());
967 builder.token(SyntaxKind::STRING.into(), &yaml_text);
968 builder.finish_node();
969 false
970 }
971
972 fn is_inline(&self) -> bool {
973 true
974 }
975}
976
977impl<T: AsYaml> AsYaml for Option<T> {
979 fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
980 self.as_ref().and_then(AsYaml::as_node)
981 }
982
983 fn kind(&self) -> YamlKind {
984 match self {
985 Some(v) => v.kind(),
986 None => YamlKind::Scalar,
987 }
988 }
989
990 fn build_content(
991 &self,
992 builder: &mut rowan::GreenNodeBuilder,
993 indent: usize,
994 flow_context: bool,
995 ) -> bool {
996 match self {
997 Some(v) => v.build_content(builder, indent, flow_context),
998 None => crate::scalar::ScalarValue::null().build_content(builder, indent, flow_context),
999 }
1000 }
1001
1002 fn is_inline(&self) -> bool {
1003 match self {
1004 Some(v) => v.is_inline(),
1005 None => true,
1006 }
1007 }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use super::*;
1013 use crate::yaml::{Document, YamlFile};
1014 use std::str::FromStr;
1015
1016 #[test]
1017 fn test_yaml_eq_different_quoting_styles() {
1018 let yaml = r#"
1019plain: value
1020single: 'value'
1021double: "value"
1022"#;
1023
1024 let doc = Document::from_str(yaml).unwrap();
1025 let mapping = doc.as_mapping().unwrap();
1026
1027 let plain = mapping.get("plain").unwrap();
1028 let single = mapping.get("single").unwrap();
1029 let double = mapping.get("double").unwrap();
1030
1031 assert!(yaml_eq(&plain, &single));
1033 assert!(yaml_eq(&single, &double));
1034 assert!(yaml_eq(&plain, &double));
1035
1036 assert!(yaml_eq(&plain, &"value"));
1038 assert!(yaml_eq(&single, &"value"));
1039 assert!(yaml_eq(&double, &"value"));
1040 }
1041
1042 #[test]
1043 fn test_yaml_eq_escape_sequences() {
1044 let yaml = r#"
1045newline1: "line1\nline2"
1046newline2: "line1
1047line2"
1048tab1: "a\tb"
1049tab2: "a b"
1050backslash1: "path\\file"
1051backslash2: 'path\file'
1052quote1: "say \"hi\""
1053quote2: 'say "hi"'
1054"#;
1055
1056 let doc = Document::from_str(yaml).unwrap();
1057 let mapping = doc.as_mapping().unwrap();
1058
1059 let newline1 = mapping.get("newline1").unwrap();
1060 let newline2 = mapping.get("newline2").unwrap();
1061 let tab1 = mapping.get("tab1").unwrap();
1062 let tab2 = mapping.get("tab2").unwrap();
1063 let backslash1 = mapping.get("backslash1").unwrap();
1064 let backslash2 = mapping.get("backslash2").unwrap();
1065 let quote1 = mapping.get("quote1").unwrap();
1066 let quote2 = mapping.get("quote2").unwrap();
1067
1068 assert!(yaml_eq(&newline1, &newline2));
1070
1071 assert!(yaml_eq(&tab1, &tab2));
1073
1074 assert!(yaml_eq(&backslash1, &backslash2));
1079 assert!(yaml_eq(&backslash1, &"path\\file"));
1080 assert!(yaml_eq(&backslash2, &"path\\file"));
1081
1082 assert!(yaml_eq("e1, "e2));
1084 assert!(yaml_eq("e1, &r#"say "hi""#));
1085 }
1086
1087 #[test]
1088 fn test_yaml_eq_single_quote_escaping() {
1089 let yaml = r#"
1090single1: 'can''t'
1091single2: "can't"
1092"#;
1093
1094 let doc = Document::from_str(yaml).unwrap();
1095 let mapping = doc.as_mapping().unwrap();
1096
1097 let single1 = mapping.get("single1").unwrap();
1098 let single2 = mapping.get("single2").unwrap();
1099
1100 assert!(yaml_eq(&single1, &single2));
1102 assert!(yaml_eq(&single1, &"can't"));
1103 }
1104
1105 #[test]
1106 fn test_yaml_eq_unicode_escapes() {
1107 let yaml = r#"
1108unicode1: "hello\x20world"
1109unicode2: "hello world"
1110unicode3: "smiley\u0020face"
1111unicode4: "smiley face"
1112"#;
1113
1114 let doc = Document::from_str(yaml).unwrap();
1115 let mapping = doc.as_mapping().unwrap();
1116
1117 let unicode1 = mapping.get("unicode1").unwrap();
1118 let unicode2 = mapping.get("unicode2").unwrap();
1119 let unicode3 = mapping.get("unicode3").unwrap();
1120 let unicode4 = mapping.get("unicode4").unwrap();
1121
1122 assert!(yaml_eq(&unicode1, &unicode2));
1124 assert!(yaml_eq(&unicode1, &"hello world"));
1125
1126 assert!(yaml_eq(&unicode3, &unicode4));
1128 assert!(yaml_eq(&unicode3, &"smiley face"));
1129 }
1130
1131 #[test]
1132 fn test_yaml_eq_with_comments() {
1133 let yaml1 = r#"
1134# This is a comment
1135key: value # inline comment
1136"#;
1137
1138 let yaml2 = r#"
1139key: value
1140"#;
1141
1142 let doc1 = YamlFile::from_str(yaml1).unwrap().document().unwrap();
1145 let doc2 = Document::from_str(yaml2).unwrap();
1146
1147 let mapping1 = doc1.as_mapping().unwrap();
1148 let mapping2 = doc2.as_mapping().unwrap();
1149
1150 assert!(yaml_eq(&mapping1, &mapping2));
1152
1153 let value1 = mapping1.get("key").unwrap();
1154 let value2 = mapping2.get("key").unwrap();
1155
1156 assert!(yaml_eq(&value1, &value2));
1157 assert!(yaml_eq(&value1, &"value"));
1158 }
1159
1160 #[test]
1161 fn test_yaml_eq_mappings() {
1162 let yaml1 = r#"
1163a: 1
1164b: 2
1165c: 3
1166"#;
1167
1168 let yaml2 = r#"
1169a: 1
1170b: 2
1171c: 3
1172"#;
1173
1174 let yaml3 = r#"
1175a: 1
1176c: 3
1177b: 2
1178"#;
1179
1180 let doc1 = Document::from_str(yaml1).unwrap();
1181 let doc2 = Document::from_str(yaml2).unwrap();
1182 let doc3 = Document::from_str(yaml3).unwrap();
1183
1184 let mapping1 = doc1.as_mapping().unwrap();
1185 let mapping2 = doc2.as_mapping().unwrap();
1186 let mapping3 = doc3.as_mapping().unwrap();
1187
1188 assert!(yaml_eq(&mapping1, &mapping2));
1190
1191 assert!(!yaml_eq(&mapping1, &mapping3));
1193 }
1194
1195 #[test]
1196 fn test_yaml_eq_sequences() {
1197 let yaml1 = r#"
1198- one
1199- two
1200- three
1201"#;
1202
1203 let yaml2 = r#"
1204- one
1205- two
1206- three
1207"#;
1208
1209 let yaml3 = r#"
1210- one
1211- three
1212- two
1213"#;
1214
1215 let doc1 = Document::from_str(yaml1).unwrap();
1216 let doc2 = Document::from_str(yaml2).unwrap();
1217 let doc3 = Document::from_str(yaml3).unwrap();
1218
1219 let seq1 = doc1.as_sequence().unwrap();
1220 let seq2 = doc2.as_sequence().unwrap();
1221 let seq3 = doc3.as_sequence().unwrap();
1222
1223 assert!(yaml_eq(&seq1, &seq2));
1225
1226 assert!(!yaml_eq(&seq1, &seq3));
1228 }
1229
1230 #[test]
1231 fn test_yaml_eq_nested_structures() {
1232 let yaml1 = r#"
1233outer:
1234 inner:
1235 key: "value"
1236"#;
1237
1238 let yaml2 = r#"
1239outer:
1240 inner:
1241 key: 'value'
1242"#;
1243
1244 let doc1 = Document::from_str(yaml1).unwrap();
1245 let doc2 = Document::from_str(yaml2).unwrap();
1246
1247 let mapping1 = doc1.as_mapping().unwrap();
1248 let mapping2 = doc2.as_mapping().unwrap();
1249
1250 assert!(yaml_eq(&mapping1, &mapping2));
1252 }
1253
1254 #[test]
1255 fn test_yaml_eq_special_characters() {
1256 let yaml = r#"
1257bell: "\a"
1258escape: "\e"
1259"null": "\0"
1260backspace: "\b"
1261formfeed: "\f"
1262carriagereturn: "\r"
1263verticaltab: "\v"
1264"#;
1265
1266 let doc = Document::from_str(yaml).unwrap();
1267 let mapping = doc.as_mapping().unwrap();
1268
1269 assert!(yaml_eq(&mapping.get("bell").unwrap(), &"\x07"));
1272 assert!(yaml_eq(&mapping.get("escape").unwrap(), &"\x1B"));
1273 assert!(yaml_eq(&mapping.get("null").unwrap(), &"\0"));
1274 assert!(yaml_eq(&mapping.get("backspace").unwrap(), &"\x08"));
1275 assert!(yaml_eq(&mapping.get("formfeed").unwrap(), &"\x0C"));
1276 assert!(yaml_eq(&mapping.get("carriagereturn").unwrap(), &"\r"));
1277 assert!(yaml_eq(&mapping.get("verticaltab").unwrap(), &"\x0B"));
1278 }
1279
1280 #[test]
1281 fn test_yaml_eq_empty_strings() {
1282 let yaml = r#"
1283empty1: ""
1284empty2: ''
1285"#;
1286
1287 let doc = Document::from_str(yaml).unwrap();
1288 let mapping = doc.as_mapping().unwrap();
1289
1290 let empty1 = mapping.get("empty1").unwrap();
1291 let empty2 = mapping.get("empty2").unwrap();
1292
1293 assert!(yaml_eq(&empty1, &empty2));
1294 assert!(yaml_eq(&empty1, &""));
1295 }
1296
1297 #[test]
1298 fn test_yaml_eq_whitespace_handling() {
1299 let yaml = r#"
1300spaces1: " leading"
1301spaces2: "trailing "
1302spaces3: " both "
1303plain: value
1304"#;
1305
1306 let doc = Document::from_str(yaml).unwrap();
1307 let mapping = doc.as_mapping().unwrap();
1308
1309 let spaces1 = mapping.get("spaces1").unwrap();
1310 let spaces2 = mapping.get("spaces2").unwrap();
1311 let spaces3 = mapping.get("spaces3").unwrap();
1312 let plain = mapping.get("plain").unwrap();
1313
1314 assert!(yaml_eq(&spaces1, &" leading"));
1316 assert!(yaml_eq(&spaces2, &"trailing "));
1317 assert!(yaml_eq(&spaces3, &" both "));
1318 assert!(yaml_eq(&plain, &"value"));
1319
1320 assert!(!yaml_eq(&spaces1, &"leading"));
1322 assert!(!yaml_eq(&spaces2, &"trailing"));
1323 }
1324
1325 #[test]
1326 fn test_yaml_eq_different_types() {
1327 let yaml = r#"
1328string: "123"
1329number: 123
1330sequence:
1331 - item
1332mapping:
1333 key: value
1334"#;
1335
1336 let doc = Document::from_str(yaml).unwrap();
1337 let mapping = doc.as_mapping().unwrap();
1338
1339 let string_val = mapping.get("string").unwrap();
1340 let number_val = mapping.get("number").unwrap();
1341 let sequence_val = mapping.get("sequence").unwrap();
1342 let mapping_val = mapping.get("mapping").unwrap();
1343
1344 assert!(!yaml_eq(&string_val, &number_val));
1347 assert!(!yaml_eq(&string_val, &sequence_val));
1348 assert!(!yaml_eq(&string_val, &mapping_val));
1349 assert!(!yaml_eq(&number_val, &sequence_val));
1350 assert!(!yaml_eq(&sequence_val, &mapping_val));
1351 }
1352
1353 #[test]
1354 fn test_yaml_eq_line_folding_escapes() {
1355 let yaml = r#"
1356folded1: "line1\
1357 line2"
1358folded2: "line1 line2"
1359"#;
1360
1361 let doc = Document::from_str(yaml).unwrap();
1362 let mapping = doc.as_mapping().unwrap();
1363
1364 let folded1 = mapping.get("folded1").unwrap();
1365 let folded2 = mapping.get("folded2").unwrap();
1366
1367 assert!(yaml_eq(&folded1, &folded2));
1369 assert!(yaml_eq(&folded1, &"line1 line2"));
1370 }
1371
1372 #[test]
1373 fn test_yaml_eq_complex_unicode() {
1374 let yaml = r#"
1375emoji1: "😀"
1376emoji2: "\U0001F600"
1377"#;
1378
1379 let doc = Document::from_str(yaml).unwrap();
1380 let mapping = doc.as_mapping().unwrap();
1381
1382 let emoji1 = mapping.get("emoji1").unwrap();
1383 let emoji2 = mapping.get("emoji2").unwrap();
1384
1385 assert!(yaml_eq(&emoji1, &emoji2));
1387 assert!(yaml_eq(&emoji1, &"😀"));
1388 }
1389
1390 #[test]
1391 fn test_yaml_eq_block_scalars() {
1392 let yaml1 = "literal1: |\n line1\n line2\n";
1393 let yaml2 = "literal2: |\n line1\n line2\n";
1394
1395 let doc1 = Document::from_str(yaml1).unwrap();
1396 let doc2 = Document::from_str(yaml2).unwrap();
1397
1398 let mapping1 = doc1.as_mapping().unwrap();
1399 let mapping2 = doc2.as_mapping().unwrap();
1400
1401 let literal1 = mapping1.get("literal1").unwrap();
1402 let literal2 = mapping2.get("literal2").unwrap();
1403
1404 assert!(yaml_eq(&literal1, &literal2));
1406 }
1407
1408 #[test]
1409 fn test_yaml_eq_null_and_special_values() {
1410 let yaml = r#"
1411null1: null
1412null2: null
1413null3:
1414empty: ""
1415"#;
1416
1417 let doc = Document::from_str(yaml).unwrap();
1418 let mapping = doc.as_mapping().unwrap();
1419
1420 let null1 = mapping.get("null1").unwrap();
1421 let null2 = mapping.get("null2").unwrap();
1422 let null3 = mapping.get("null3").unwrap();
1423 let empty = mapping.get("empty").unwrap();
1424
1425 assert!(yaml_eq(&null1, &null2));
1427
1428 assert!(yaml_eq(&null3, &null1));
1431 assert!(yaml_eq(&null3, &null2));
1432
1433 assert!(!yaml_eq(&null1, &empty));
1435
1436 assert!(!yaml_eq(&null1, &"null"));
1438 }
1439
1440 #[test]
1441 fn test_yaml_eq_boolean_representations() {
1442 let yaml = r#"
1443true1: true
1444true2: True
1445true3: TRUE
1446false1: false
1447false2: False
1448"#;
1449
1450 let doc = Document::from_str(yaml).unwrap();
1451 let mapping = doc.as_mapping().unwrap();
1452
1453 let true1 = mapping.get("true1").unwrap();
1454 let true2 = mapping.get("true2").unwrap();
1455 let true3 = mapping.get("true3").unwrap();
1456 let false1 = mapping.get("false1").unwrap();
1457 let false2 = mapping.get("false2").unwrap();
1458
1459 assert!(yaml_eq(&true1, &true2));
1461 assert!(yaml_eq(&true1, &true3));
1462 assert!(yaml_eq(&false1, &false2));
1463
1464 assert!(!yaml_eq(&true1, &"true"));
1466 assert!(!yaml_eq(&false1, &"false"));
1467 }
1468
1469 #[test]
1470 fn test_yaml_eq_numeric_formats() {
1471 let yaml = r#"
1472decimal: 123
1473octal: 0o173
1474hex: 0x7B
1475"#;
1476
1477 let doc = Document::from_str(yaml).unwrap();
1478 let mapping = doc.as_mapping().unwrap();
1479
1480 let decimal = mapping.get("decimal").unwrap();
1481 let octal = mapping.get("octal").unwrap();
1482 let hex = mapping.get("hex").unwrap();
1483
1484 assert!(yaml_eq(&decimal, &octal));
1486 assert!(yaml_eq(&decimal, &hex));
1487 assert!(yaml_eq(&octal, &hex));
1488
1489 assert!(!yaml_eq(&decimal, &"123"));
1491
1492 assert!(yaml_eq(&decimal, &123));
1494 assert!(yaml_eq(&octal, &123));
1495 assert!(yaml_eq(&hex, &123));
1496 }
1497
1498 #[test]
1499 fn test_yaml_eq_with_anchors() {
1500 let yaml = r#"
1501original: &anchor value
1502duplicate: value
1503"#;
1504
1505 let doc = Document::from_str(yaml).unwrap();
1506 let mapping = doc.as_mapping().unwrap();
1507
1508 let original = mapping.get("original").unwrap();
1509 let duplicate = mapping.get("duplicate").unwrap();
1510
1511 assert!(yaml_eq(&original, &duplicate));
1513 assert!(yaml_eq(&original, &"value"));
1514 }
1515
1516 #[test]
1517 fn test_yaml_eq_flow_vs_block_collections() {
1518 let yaml = r#"
1519flow_seq: [1, 2, 3]
1520block_seq:
1521 - 1
1522 - 2
1523 - 3
1524flow_map: {a: 1, b: 2}
1525block_map:
1526 a: 1
1527 b: 2
1528"#;
1529
1530 let doc = Document::from_str(yaml).unwrap();
1531 let mapping = doc.as_mapping().unwrap();
1532
1533 let flow_seq = mapping.get("flow_seq").unwrap();
1534 let block_seq = mapping.get("block_seq").unwrap();
1535 let flow_map = mapping.get("flow_map").unwrap();
1536 let block_map = mapping.get("block_map").unwrap();
1537
1538 assert!(yaml_eq(&flow_seq, &block_seq));
1540 assert!(yaml_eq(&flow_map, &block_map));
1541 }
1542
1543 #[test]
1544 fn test_option_none_emits_null() {
1545 let doc = Document::from_str("key: value\n").unwrap();
1546 doc.as_mapping().unwrap().set("key", None::<&str>);
1547 assert_eq!(doc.to_string(), "key: null\n");
1548 }
1549
1550 #[test]
1551 fn test_option_some_delegates() {
1552 let doc = Document::from_str("key: value\n").unwrap();
1553 doc.as_mapping().unwrap().set("key", Some("hello"));
1554 assert_eq!(doc.to_string(), "key: hello\n");
1555
1556 let doc = Document::from_str("key: value\n").unwrap();
1557 doc.as_mapping().unwrap().set("key", Some(42i64));
1558 assert_eq!(doc.to_string(), "key: 42\n");
1559 }
1560}