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 anchor_loc: Option<Loc>,
47 tag: Option<String>,
49 tag_loc: Option<Loc>,
52 loc: Loc,
54 leading_comments: Option<Vec<String>>,
59 trailing_comment: Option<String>,
61 },
62 Mapping {
64 entries: Vec<(Self, Self)>,
66 style: CollectionStyle,
68 anchor: Option<String>,
70 anchor_loc: Option<Loc>,
73 tag: Option<String>,
75 tag_loc: Option<Loc>,
78 loc: Loc,
80 leading_comments: Option<Vec<String>>,
82 trailing_comment: Option<String>,
84 },
85 Sequence {
87 items: Vec<Self>,
89 style: CollectionStyle,
91 anchor: Option<String>,
93 anchor_loc: Option<Loc>,
96 tag: Option<String>,
98 tag_loc: Option<Loc>,
101 loc: Loc,
103 leading_comments: Option<Vec<String>>,
105 trailing_comment: Option<String>,
107 },
108 Alias {
110 name: String,
112 loc: Loc,
114 leading_comments: Option<Vec<String>>,
116 trailing_comment: Option<String>,
118 },
119}
120
121impl<Loc> Node<Loc> {
122 pub fn anchor(&self) -> Option<&str> {
124 match self {
125 Self::Scalar { anchor, .. }
126 | Self::Mapping { anchor, .. }
127 | Self::Sequence { anchor, .. } => anchor.as_deref(),
128 Self::Alias { .. } => None,
129 }
130 }
131
132 pub const fn anchor_loc(&self) -> Option<Loc>
137 where
138 Loc: Copy,
139 {
140 match self {
141 Self::Scalar { anchor_loc, .. }
142 | Self::Mapping { anchor_loc, .. }
143 | Self::Sequence { anchor_loc, .. } => *anchor_loc,
144 Self::Alias { .. } => None,
145 }
146 }
147
148 pub const fn tag_loc(&self) -> Option<Loc>
153 where
154 Loc: Copy,
155 {
156 match self {
157 Self::Scalar { tag_loc, .. }
158 | Self::Mapping { tag_loc, .. }
159 | Self::Sequence { tag_loc, .. } => *tag_loc,
160 Self::Alias { .. } => None,
161 }
162 }
163
164 pub fn leading_comments(&self) -> &[String] {
166 match self {
167 Self::Scalar {
168 leading_comments, ..
169 }
170 | Self::Mapping {
171 leading_comments, ..
172 }
173 | Self::Sequence {
174 leading_comments, ..
175 }
176 | Self::Alias {
177 leading_comments, ..
178 } => leading_comments.as_deref().unwrap_or(&[]),
179 }
180 }
181
182 pub fn trailing_comment(&self) -> Option<&str> {
184 match self {
185 Self::Scalar {
186 trailing_comment, ..
187 }
188 | Self::Mapping {
189 trailing_comment, ..
190 }
191 | Self::Sequence {
192 trailing_comment, ..
193 }
194 | Self::Alias {
195 trailing_comment, ..
196 } => trailing_comment.as_deref(),
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::event::{CollectionStyle, ScalarStyle};
205 use crate::pos::{Pos, Span};
206
207 fn zero_span() -> Span {
208 Span {
209 start: Pos::ORIGIN,
210 end: Pos::ORIGIN,
211 }
212 }
213
214 fn plain_scalar(value: &str) -> Node<Span> {
215 Node::Scalar {
216 value: value.to_owned(),
217 style: ScalarStyle::Plain,
218 anchor: None,
219 anchor_loc: None,
220 tag: None,
221 tag_loc: None,
222 loc: zero_span(),
223 leading_comments: None,
224 trailing_comment: None,
225 }
226 }
227
228 #[test]
230 fn node_debug_includes_leading_comments() {
231 let node = Node::Scalar {
232 value: "val".to_owned(),
233 style: ScalarStyle::Plain,
234 anchor: None,
235 anchor_loc: None,
236 tag: None,
237 tag_loc: None,
238 loc: zero_span(),
239 leading_comments: Some(vec!["# note".to_owned()]),
240 trailing_comment: None,
241 };
242 let debug = format!("{node:?}");
243 assert!(debug.contains("# note"), "debug output: {debug}");
244 }
245
246 #[test]
248 fn node_partial_eq_considers_leading_comments() {
249 let a = Node::Scalar {
250 value: "val".to_owned(),
251 style: ScalarStyle::Plain,
252 anchor: None,
253 anchor_loc: None,
254 tag: None,
255 tag_loc: None,
256 loc: zero_span(),
257 leading_comments: Some(vec!["# a".to_owned()]),
258 trailing_comment: None,
259 };
260 let b = Node::Scalar {
261 value: "val".to_owned(),
262 style: ScalarStyle::Plain,
263 anchor: None,
264 anchor_loc: None,
265 tag: None,
266 tag_loc: None,
267 loc: zero_span(),
268 leading_comments: Some(vec!["# b".to_owned()]),
269 trailing_comment: None,
270 };
271 assert_ne!(a, b);
272 }
273
274 #[test]
276 fn node_clone_preserves_comments() {
277 let node = Node::Scalar {
278 value: "val".to_owned(),
279 style: ScalarStyle::Plain,
280 anchor: None,
281 anchor_loc: None,
282 tag: None,
283 tag_loc: None,
284 loc: zero_span(),
285 leading_comments: Some(vec!["# x".to_owned()]),
286 trailing_comment: Some("# y".to_owned()),
287 };
288 let cloned = node.clone();
289 assert_eq!(node, cloned);
290 assert_eq!(cloned.leading_comments(), &["# x"]);
291 assert_eq!(cloned.trailing_comment(), Some("# y"));
292 }
293
294 #[test]
296 fn plain_scalar_has_empty_comments() {
297 let n = plain_scalar("hello");
298 assert!(n.leading_comments().is_empty());
299 assert!(n.trailing_comment().is_none());
300 }
301
302 #[test]
303 fn node_accessor_returns_empty_slice_for_none() {
304 let node = Node::Scalar {
305 value: "v".to_owned(),
306 style: ScalarStyle::Plain,
307 anchor: None,
308 anchor_loc: None,
309 tag: None,
310 tag_loc: None,
311 loc: zero_span(),
312 leading_comments: None,
313 trailing_comment: None,
314 };
315 assert_eq!(node.leading_comments(), &[] as &[String]);
316 }
317
318 #[test]
319 fn node_accessor_returns_slice_for_some() {
320 let node = Node::Scalar {
321 value: "v".to_owned(),
322 style: ScalarStyle::Plain,
323 anchor: None,
324 anchor_loc: None,
325 tag: None,
326 tag_loc: None,
327 loc: zero_span(),
328 leading_comments: Some(vec!["# x".to_owned()]),
329 trailing_comment: None,
330 };
331 assert_eq!(node.leading_comments(), &["# x"]);
332 }
333
334 fn bare_document(explicit_start: bool, explicit_end: bool) -> Document<Span> {
335 Document {
336 root: plain_scalar("val"),
337 version: None,
338 tags: Vec::new(),
339 comments: Vec::new(),
340 explicit_start,
341 explicit_end,
342 }
343 }
344
345 #[test]
347 fn document_explicit_flags_in_equality() {
348 let a = bare_document(false, false);
349 let b = bare_document(false, false);
350 assert_eq!(a, b);
351 }
352
353 #[test]
355 fn document_partial_eq_distinguishes_explicit_start() {
356 let a = bare_document(true, false);
357 let b = bare_document(false, false);
358 assert_ne!(a, b);
359 }
360
361 #[test]
363 fn document_partial_eq_distinguishes_explicit_end() {
364 let a = bare_document(false, true);
365 let b = bare_document(false, false);
366 assert_ne!(a, b);
367 }
368
369 #[test]
371 fn document_clone_preserves_explicit_flags() {
372 let doc = bare_document(true, true);
373 let cloned = doc.clone();
374 assert_eq!(doc, cloned);
375 assert!(cloned.explicit_start);
376 assert!(cloned.explicit_end);
377 }
378
379 #[test]
385 fn anchor_loc_accessor_returns_some_for_anchored_scalar() {
386 let span = zero_span();
387 let node = Node::Scalar {
388 value: "v".to_owned(),
389 style: ScalarStyle::Plain,
390 anchor: Some("a".to_owned()),
391 anchor_loc: Some(span),
392 tag: None,
393 tag_loc: None,
394 loc: zero_span(),
395 leading_comments: None,
396 trailing_comment: None,
397 };
398 assert_eq!(node.anchor_loc(), Some(span));
399 }
400
401 #[test]
403 fn anchor_loc_accessor_returns_none_for_unanchored_scalar() {
404 let node = Node::Scalar {
405 value: "v".to_owned(),
406 style: ScalarStyle::Plain,
407 anchor: None,
408 anchor_loc: None,
409 tag: None,
410 tag_loc: None,
411 loc: zero_span(),
412 leading_comments: None,
413 trailing_comment: None,
414 };
415 assert_eq!(node.anchor_loc(), None);
416 }
417
418 #[test]
420 fn anchor_loc_accessor_returns_none_for_alias() {
421 let node = Node::Alias {
422 name: "x".to_owned(),
423 loc: zero_span(),
424 leading_comments: None,
425 trailing_comment: None,
426 };
427 assert_eq!(node.anchor_loc(), None);
428 }
429
430 #[test]
432 fn anchor_loc_accessor_returns_some_for_anchored_mapping() {
433 let span = zero_span();
434 let node = Node::Mapping {
435 entries: vec![],
436 style: CollectionStyle::Block,
437 anchor: Some("m".to_owned()),
438 anchor_loc: Some(span),
439 tag: None,
440 tag_loc: None,
441 loc: zero_span(),
442 leading_comments: None,
443 trailing_comment: None,
444 };
445 assert_eq!(node.anchor_loc(), Some(span));
446 }
447
448 #[test]
450 fn anchor_loc_accessor_returns_some_for_anchored_sequence() {
451 let span = zero_span();
452 let node = Node::Sequence {
453 items: vec![],
454 style: CollectionStyle::Block,
455 anchor: Some("s".to_owned()),
456 anchor_loc: Some(span),
457 tag: None,
458 tag_loc: None,
459 loc: zero_span(),
460 leading_comments: None,
461 trailing_comment: None,
462 };
463 assert_eq!(node.anchor_loc(), Some(span));
464 }
465
466 #[test]
472 fn tag_loc_accessor_returns_some_for_tagged_scalar() {
473 let span = zero_span();
474 let node = Node::Scalar {
475 value: "v".to_owned(),
476 style: ScalarStyle::Plain,
477 anchor: None,
478 anchor_loc: None,
479 tag: Some("!t".to_owned()),
480 tag_loc: Some(span),
481 loc: zero_span(),
482 leading_comments: None,
483 trailing_comment: None,
484 };
485 assert_eq!(node.tag_loc(), Some(span));
486 }
487
488 #[test]
490 fn tag_loc_accessor_returns_none_for_untagged_scalar() {
491 let node = Node::Scalar {
492 value: "v".to_owned(),
493 style: ScalarStyle::Plain,
494 anchor: None,
495 anchor_loc: None,
496 tag: None,
497 tag_loc: None,
498 loc: zero_span(),
499 leading_comments: None,
500 trailing_comment: None,
501 };
502 assert_eq!(node.tag_loc(), None);
503 }
504
505 #[test]
507 fn tag_loc_accessor_returns_none_for_alias() {
508 let node = Node::Alias {
509 name: "x".to_owned(),
510 loc: zero_span(),
511 leading_comments: None,
512 trailing_comment: None,
513 };
514 assert_eq!(node.tag_loc(), None);
515 }
516
517 #[test]
519 fn tag_loc_accessor_returns_some_for_tagged_mapping() {
520 let span = zero_span();
521 let node = Node::Mapping {
522 entries: vec![],
523 style: CollectionStyle::Block,
524 anchor: None,
525 anchor_loc: None,
526 tag: Some("!!map".to_owned()),
527 tag_loc: Some(span),
528 loc: zero_span(),
529 leading_comments: None,
530 trailing_comment: None,
531 };
532 assert_eq!(node.tag_loc(), Some(span));
533 }
534
535 #[test]
537 fn tag_loc_accessor_returns_some_for_tagged_sequence() {
538 let span = zero_span();
539 let node = Node::Sequence {
540 items: vec![],
541 style: CollectionStyle::Block,
542 anchor: None,
543 anchor_loc: None,
544 tag: Some("!!seq".to_owned()),
545 tag_loc: Some(span),
546 loc: zero_span(),
547 leading_comments: None,
548 trailing_comment: None,
549 };
550 assert_eq!(node.tag_loc(), Some(span));
551 }
552}