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::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 use crate::event::CollectionStyle;
434 let span = zero_span();
435 let node = Node::Mapping {
436 entries: vec![],
437 style: CollectionStyle::Block,
438 anchor: Some("m".to_owned()),
439 anchor_loc: Some(span),
440 tag: None,
441 tag_loc: None,
442 loc: zero_span(),
443 leading_comments: None,
444 trailing_comment: None,
445 };
446 assert_eq!(node.anchor_loc(), Some(span));
447 }
448
449 #[test]
451 fn anchor_loc_accessor_returns_some_for_anchored_sequence() {
452 use crate::event::CollectionStyle;
453 let span = zero_span();
454 let node = Node::Sequence {
455 items: vec![],
456 style: CollectionStyle::Block,
457 anchor: Some("s".to_owned()),
458 anchor_loc: Some(span),
459 tag: None,
460 tag_loc: None,
461 loc: zero_span(),
462 leading_comments: None,
463 trailing_comment: None,
464 };
465 assert_eq!(node.anchor_loc(), Some(span));
466 }
467
468 #[test]
474 fn tag_loc_accessor_returns_some_for_tagged_scalar() {
475 let span = zero_span();
476 let node = Node::Scalar {
477 value: "v".to_owned(),
478 style: ScalarStyle::Plain,
479 anchor: None,
480 anchor_loc: None,
481 tag: Some("!t".to_owned()),
482 tag_loc: Some(span),
483 loc: zero_span(),
484 leading_comments: None,
485 trailing_comment: None,
486 };
487 assert_eq!(node.tag_loc(), Some(span));
488 }
489
490 #[test]
492 fn tag_loc_accessor_returns_none_for_untagged_scalar() {
493 let node = Node::Scalar {
494 value: "v".to_owned(),
495 style: ScalarStyle::Plain,
496 anchor: None,
497 anchor_loc: None,
498 tag: None,
499 tag_loc: None,
500 loc: zero_span(),
501 leading_comments: None,
502 trailing_comment: None,
503 };
504 assert_eq!(node.tag_loc(), None);
505 }
506
507 #[test]
509 fn tag_loc_accessor_returns_none_for_alias() {
510 let node = Node::Alias {
511 name: "x".to_owned(),
512 loc: zero_span(),
513 leading_comments: None,
514 trailing_comment: None,
515 };
516 assert_eq!(node.tag_loc(), None);
517 }
518
519 #[test]
521 fn tag_loc_accessor_returns_some_for_tagged_mapping() {
522 use crate::event::CollectionStyle;
523 let span = zero_span();
524 let node = Node::Mapping {
525 entries: vec![],
526 style: CollectionStyle::Block,
527 anchor: None,
528 anchor_loc: None,
529 tag: Some("!!map".to_owned()),
530 tag_loc: Some(span),
531 loc: zero_span(),
532 leading_comments: None,
533 trailing_comment: None,
534 };
535 assert_eq!(node.tag_loc(), Some(span));
536 }
537
538 #[test]
540 fn tag_loc_accessor_returns_some_for_tagged_sequence() {
541 use crate::event::CollectionStyle;
542 let span = zero_span();
543 let node = Node::Sequence {
544 items: vec![],
545 style: CollectionStyle::Block,
546 anchor: None,
547 anchor_loc: None,
548 tag: Some("!!seq".to_owned()),
549 tag_loc: Some(span),
550 loc: zero_span(),
551 leading_comments: None,
552 trailing_comment: None,
553 };
554 assert_eq!(node.tag_loc(), Some(span));
555 }
556}