1use crate::event::{CollectionStyle, 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 pub explicit_start: bool,
29 pub explicit_end: bool,
31}
32
33#[derive(Debug, Clone, PartialEq)]
35pub enum Node<Loc = Span> {
36 Scalar {
38 value: String,
40 style: ScalarStyle,
42 anchor: Option<String>,
44 tag: Option<String>,
46 loc: Loc,
48 leading_comments: Option<Vec<String>>,
53 trailing_comment: Option<String>,
55 },
56 Mapping {
58 entries: Vec<(Self, Self)>,
60 style: CollectionStyle,
62 anchor: Option<String>,
64 tag: Option<String>,
66 loc: Loc,
68 leading_comments: Option<Vec<String>>,
70 trailing_comment: Option<String>,
72 },
73 Sequence {
75 items: Vec<Self>,
77 style: CollectionStyle,
79 anchor: Option<String>,
81 tag: Option<String>,
83 loc: Loc,
85 leading_comments: Option<Vec<String>>,
87 trailing_comment: Option<String>,
89 },
90 Alias {
92 name: String,
94 loc: Loc,
96 leading_comments: Option<Vec<String>>,
98 trailing_comment: Option<String>,
100 },
101}
102
103impl<Loc> Node<Loc> {
104 pub fn anchor(&self) -> Option<&str> {
106 match self {
107 Self::Scalar { anchor, .. }
108 | Self::Mapping { anchor, .. }
109 | Self::Sequence { anchor, .. } => anchor.as_deref(),
110 Self::Alias { .. } => None,
111 }
112 }
113
114 pub fn leading_comments(&self) -> &[String] {
116 match self {
117 Self::Scalar {
118 leading_comments, ..
119 }
120 | Self::Mapping {
121 leading_comments, ..
122 }
123 | Self::Sequence {
124 leading_comments, ..
125 }
126 | Self::Alias {
127 leading_comments, ..
128 } => leading_comments.as_deref().unwrap_or(&[]),
129 }
130 }
131
132 pub fn trailing_comment(&self) -> Option<&str> {
134 match self {
135 Self::Scalar {
136 trailing_comment, ..
137 }
138 | Self::Mapping {
139 trailing_comment, ..
140 }
141 | Self::Sequence {
142 trailing_comment, ..
143 }
144 | Self::Alias {
145 trailing_comment, ..
146 } => trailing_comment.as_deref(),
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::event::ScalarStyle;
155 use crate::pos::{Pos, Span};
156
157 fn zero_span() -> Span {
158 Span {
159 start: Pos::ORIGIN,
160 end: Pos::ORIGIN,
161 }
162 }
163
164 fn plain_scalar(value: &str) -> Node<Span> {
165 Node::Scalar {
166 value: value.to_owned(),
167 style: ScalarStyle::Plain,
168 anchor: None,
169 tag: None,
170 loc: zero_span(),
171 leading_comments: None,
172 trailing_comment: None,
173 }
174 }
175
176 #[test]
178 fn node_debug_includes_leading_comments() {
179 let node = Node::Scalar {
180 value: "val".to_owned(),
181 style: ScalarStyle::Plain,
182 anchor: None,
183 tag: None,
184 loc: zero_span(),
185 leading_comments: Some(vec!["# note".to_owned()]),
186 trailing_comment: None,
187 };
188 let debug = format!("{node:?}");
189 assert!(debug.contains("# note"), "debug output: {debug}");
190 }
191
192 #[test]
194 fn node_partial_eq_considers_leading_comments() {
195 let a = Node::Scalar {
196 value: "val".to_owned(),
197 style: ScalarStyle::Plain,
198 anchor: None,
199 tag: None,
200 loc: zero_span(),
201 leading_comments: Some(vec!["# a".to_owned()]),
202 trailing_comment: None,
203 };
204 let b = Node::Scalar {
205 value: "val".to_owned(),
206 style: ScalarStyle::Plain,
207 anchor: None,
208 tag: None,
209 loc: zero_span(),
210 leading_comments: Some(vec!["# b".to_owned()]),
211 trailing_comment: None,
212 };
213 assert_ne!(a, b);
214 }
215
216 #[test]
218 fn node_clone_preserves_comments() {
219 let node = Node::Scalar {
220 value: "val".to_owned(),
221 style: ScalarStyle::Plain,
222 anchor: None,
223 tag: None,
224 loc: zero_span(),
225 leading_comments: Some(vec!["# x".to_owned()]),
226 trailing_comment: Some("# y".to_owned()),
227 };
228 let cloned = node.clone();
229 assert_eq!(node, cloned);
230 assert_eq!(cloned.leading_comments(), &["# x"]);
231 assert_eq!(cloned.trailing_comment(), Some("# y"));
232 }
233
234 #[test]
236 fn plain_scalar_has_empty_comments() {
237 let n = plain_scalar("hello");
238 assert!(n.leading_comments().is_empty());
239 assert!(n.trailing_comment().is_none());
240 }
241
242 #[test]
243 fn node_accessor_returns_empty_slice_for_none() {
244 let node = Node::Scalar {
245 value: "v".to_owned(),
246 style: ScalarStyle::Plain,
247 anchor: None,
248 tag: None,
249 loc: zero_span(),
250 leading_comments: None,
251 trailing_comment: None,
252 };
253 assert_eq!(node.leading_comments(), &[] as &[String]);
254 }
255
256 #[test]
257 fn node_accessor_returns_slice_for_some() {
258 let node = Node::Scalar {
259 value: "v".to_owned(),
260 style: ScalarStyle::Plain,
261 anchor: None,
262 tag: None,
263 loc: zero_span(),
264 leading_comments: Some(vec!["# x".to_owned()]),
265 trailing_comment: None,
266 };
267 assert_eq!(node.leading_comments(), &["# x"]);
268 }
269
270 fn bare_document(explicit_start: bool, explicit_end: bool) -> Document<Span> {
271 Document {
272 root: plain_scalar("val"),
273 version: None,
274 tags: Vec::new(),
275 comments: Vec::new(),
276 explicit_start,
277 explicit_end,
278 }
279 }
280
281 #[test]
283 fn document_explicit_flags_in_equality() {
284 let a = bare_document(false, false);
285 let b = bare_document(false, false);
286 assert_eq!(a, b);
287 }
288
289 #[test]
291 fn document_partial_eq_distinguishes_explicit_start() {
292 let a = bare_document(true, false);
293 let b = bare_document(false, false);
294 assert_ne!(a, b);
295 }
296
297 #[test]
299 fn document_partial_eq_distinguishes_explicit_end() {
300 let a = bare_document(false, true);
301 let b = bare_document(false, false);
302 assert_ne!(a, b);
303 }
304
305 #[test]
307 fn document_clone_preserves_explicit_flags() {
308 let doc = bare_document(true, true);
309 let cloned = doc.clone();
310 assert_eq!(doc, cloned);
311 assert!(cloned.explicit_start);
312 assert!(cloned.explicit_end);
313 }
314}