1use crate::event::ScalarStyle;
10use crate::pos::Span;
11
12#[derive(Debug, Clone, PartialEq)]
18pub struct Document<Loc = Span> {
19 pub root: Node<Loc>,
21 pub version: Option<(u8, u8)>,
23 pub tags: Vec<(String, String)>,
25 pub comments: Vec<String>,
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub enum Node<Loc = Span> {
32 Scalar {
34 value: String,
35 style: ScalarStyle,
36 anchor: Option<String>,
37 tag: Option<String>,
38 loc: Loc,
39 leading_comments: Vec<String>,
44 trailing_comment: Option<String>,
46 },
47 Mapping {
49 entries: Vec<(Self, Self)>,
50 anchor: Option<String>,
51 tag: Option<String>,
52 loc: Loc,
53 leading_comments: Vec<String>,
55 trailing_comment: Option<String>,
57 },
58 Sequence {
60 items: Vec<Self>,
61 anchor: Option<String>,
62 tag: Option<String>,
63 loc: Loc,
64 leading_comments: Vec<String>,
66 trailing_comment: Option<String>,
68 },
69 Alias {
71 name: String,
72 loc: Loc,
73 leading_comments: Vec<String>,
75 trailing_comment: Option<String>,
77 },
78}
79
80impl<Loc> Node<Loc> {
81 pub fn anchor(&self) -> Option<&str> {
83 match self {
84 Self::Scalar { anchor, .. }
85 | Self::Mapping { anchor, .. }
86 | Self::Sequence { anchor, .. } => anchor.as_deref(),
87 Self::Alias { .. } => None,
88 }
89 }
90
91 pub fn leading_comments(&self) -> &[String] {
93 match self {
94 Self::Scalar {
95 leading_comments, ..
96 }
97 | Self::Mapping {
98 leading_comments, ..
99 }
100 | Self::Sequence {
101 leading_comments, ..
102 }
103 | Self::Alias {
104 leading_comments, ..
105 } => leading_comments,
106 }
107 }
108
109 pub fn trailing_comment(&self) -> Option<&str> {
111 match self {
112 Self::Scalar {
113 trailing_comment, ..
114 }
115 | Self::Mapping {
116 trailing_comment, ..
117 }
118 | Self::Sequence {
119 trailing_comment, ..
120 }
121 | Self::Alias {
122 trailing_comment, ..
123 } => trailing_comment.as_deref(),
124 }
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::indexing_slicing, clippy::expect_used, clippy::unwrap_used)]
130mod tests {
131 use super::*;
132 use crate::event::ScalarStyle;
133 use crate::pos::{Pos, Span};
134
135 fn zero_span() -> Span {
136 Span {
137 start: Pos::ORIGIN,
138 end: Pos::ORIGIN,
139 }
140 }
141
142 fn plain_scalar(value: &str) -> Node<Span> {
143 Node::Scalar {
144 value: value.to_owned(),
145 style: ScalarStyle::Plain,
146 anchor: None,
147 tag: None,
148 loc: zero_span(),
149 leading_comments: Vec::new(),
150 trailing_comment: None,
151 }
152 }
153
154 #[test]
156 fn node_debug_includes_leading_comments() {
157 let node = Node::Scalar {
158 value: "val".to_owned(),
159 style: ScalarStyle::Plain,
160 anchor: None,
161 tag: None,
162 loc: zero_span(),
163 leading_comments: vec!["# note".to_owned()],
164 trailing_comment: None,
165 };
166 let debug = format!("{node:?}");
167 assert!(debug.contains("# note"), "debug output: {debug}");
168 }
169
170 #[test]
172 fn node_partial_eq_considers_leading_comments() {
173 let a = Node::Scalar {
174 value: "val".to_owned(),
175 style: ScalarStyle::Plain,
176 anchor: None,
177 tag: None,
178 loc: zero_span(),
179 leading_comments: vec!["# a".to_owned()],
180 trailing_comment: None,
181 };
182 let b = Node::Scalar {
183 value: "val".to_owned(),
184 style: ScalarStyle::Plain,
185 anchor: None,
186 tag: None,
187 loc: zero_span(),
188 leading_comments: vec!["# b".to_owned()],
189 trailing_comment: None,
190 };
191 assert_ne!(a, b);
192 }
193
194 #[test]
196 fn node_clone_preserves_comments() {
197 let node = Node::Scalar {
198 value: "val".to_owned(),
199 style: ScalarStyle::Plain,
200 anchor: None,
201 tag: None,
202 loc: zero_span(),
203 leading_comments: vec!["# x".to_owned()],
204 trailing_comment: Some("# y".to_owned()),
205 };
206 let cloned = node.clone();
207 assert_eq!(node, cloned);
208 assert_eq!(cloned.leading_comments(), &["# x"]);
209 assert_eq!(cloned.trailing_comment(), Some("# y"));
210 }
211
212 #[test]
214 fn plain_scalar_has_empty_comments() {
215 let n = plain_scalar("hello");
216 assert!(n.leading_comments().is_empty());
217 assert!(n.trailing_comment().is_none());
218 }
219}