Skip to main content

tstring_yaml/
lib.rs

1use saphyr::{ScalarOwned, YamlOwned};
2use saphyr_parser::{ScalarStyle, Tag};
3use std::borrow::Cow;
4use std::str::FromStr;
5use tstring_syntax::{
6    BackendError, BackendResult, NormalizedDocument, NormalizedEntry, NormalizedFloat,
7    NormalizedKey, NormalizedKeyEntry, NormalizedStream, NormalizedValue, SourcePosition,
8    SourceSpan, StreamItem, TemplateInput,
9};
10
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12pub enum YamlProfile {
13    V1_2_2,
14}
15
16impl YamlProfile {
17    #[must_use]
18    pub const fn as_str(self) -> &'static str {
19        match self {
20            Self::V1_2_2 => "1.2.2",
21        }
22    }
23}
24
25impl Default for YamlProfile {
26    fn default() -> Self {
27        Self::V1_2_2
28    }
29}
30
31impl FromStr for YamlProfile {
32    type Err = String;
33
34    fn from_str(value: &str) -> Result<Self, Self::Err> {
35        match value {
36            "1.2.2" => Ok(Self::V1_2_2),
37            other => Err(format!(
38                "Unsupported YAML profile {other:?}. Supported profiles: \"1.2.2\"."
39            )),
40        }
41    }
42}
43
44#[derive(Clone, Debug)]
45pub struct YamlInterpolationNode {
46    pub span: SourceSpan,
47    pub interpolation_index: usize,
48    pub role: String,
49}
50
51#[derive(Clone, Debug)]
52pub struct YamlTextChunkNode {
53    pub span: SourceSpan,
54    pub value: String,
55}
56
57#[derive(Clone, Debug)]
58pub enum YamlChunk {
59    Text(YamlTextChunkNode),
60    Interpolation(YamlInterpolationNode),
61}
62
63#[derive(Clone, Debug)]
64pub struct YamlTagNode {
65    pub span: SourceSpan,
66    pub chunks: Vec<YamlChunk>,
67}
68
69#[derive(Clone, Debug)]
70pub struct YamlAnchorNode {
71    pub span: SourceSpan,
72    pub chunks: Vec<YamlChunk>,
73}
74
75#[derive(Clone, Debug)]
76pub struct YamlPlainScalarNode {
77    pub span: SourceSpan,
78    pub chunks: Vec<YamlChunk>,
79}
80
81#[derive(Clone, Debug)]
82pub struct YamlDoubleQuotedScalarNode {
83    pub span: SourceSpan,
84    pub chunks: Vec<YamlChunk>,
85}
86
87#[derive(Clone, Debug)]
88pub struct YamlSingleQuotedScalarNode {
89    pub span: SourceSpan,
90    pub chunks: Vec<YamlChunk>,
91}
92
93#[derive(Clone, Debug)]
94pub struct YamlBlockScalarNode {
95    pub span: SourceSpan,
96    pub style: String,
97    pub chomping: Option<String>,
98    pub indent_indicator: Option<usize>,
99    pub chunks: Vec<YamlChunk>,
100}
101
102#[derive(Clone, Debug)]
103pub struct YamlAliasNode {
104    pub span: SourceSpan,
105    pub chunks: Vec<YamlChunk>,
106}
107
108#[derive(Clone, Debug)]
109pub enum YamlKeyValue {
110    Scalar(YamlScalarNode),
111    Interpolation(YamlInterpolationNode),
112    Complex(Box<YamlValueNode>),
113}
114
115#[derive(Clone, Debug)]
116pub struct YamlKeyNode {
117    pub span: SourceSpan,
118    pub value: YamlKeyValue,
119}
120
121#[derive(Clone, Debug)]
122pub struct YamlMappingEntryNode {
123    pub span: SourceSpan,
124    pub key: YamlKeyNode,
125    pub value: YamlValueNode,
126}
127
128#[derive(Clone, Debug)]
129pub struct YamlMappingNode {
130    pub span: SourceSpan,
131    pub entries: Vec<YamlMappingEntryNode>,
132    pub flow: bool,
133}
134
135#[derive(Clone, Debug)]
136pub struct YamlSequenceNode {
137    pub span: SourceSpan,
138    pub items: Vec<YamlValueNode>,
139    pub flow: bool,
140}
141
142#[derive(Clone, Debug)]
143pub struct YamlDecoratedNode {
144    pub span: SourceSpan,
145    pub value: Box<YamlValueNode>,
146    pub tag: Option<YamlTagNode>,
147    pub anchor: Option<YamlAnchorNode>,
148}
149
150#[derive(Clone, Debug)]
151pub struct YamlDocumentNode {
152    pub span: SourceSpan,
153    pub directives: Vec<String>,
154    pub explicit_start: bool,
155    pub explicit_end: bool,
156    pub value: YamlValueNode,
157}
158
159#[derive(Clone, Debug)]
160pub struct YamlStreamNode {
161    pub span: SourceSpan,
162    pub documents: Vec<YamlDocumentNode>,
163}
164
165#[derive(Clone, Debug)]
166pub enum YamlScalarNode {
167    Plain(YamlPlainScalarNode),
168    DoubleQuoted(YamlDoubleQuotedScalarNode),
169    SingleQuoted(YamlSingleQuotedScalarNode),
170    Block(YamlBlockScalarNode),
171    Alias(YamlAliasNode),
172}
173
174#[derive(Clone, Debug)]
175pub enum YamlValueNode {
176    Scalar(YamlScalarNode),
177    Interpolation(YamlInterpolationNode),
178    Mapping(YamlMappingNode),
179    Sequence(YamlSequenceNode),
180    Decorated(YamlDecoratedNode),
181}
182
183pub struct YamlParser {
184    items: Vec<StreamItem>,
185    index: usize,
186}
187
188impl YamlParser {
189    #[must_use]
190    pub fn new(template: &TemplateInput) -> Self {
191        Self {
192            items: template.flatten(),
193            index: 0,
194        }
195    }
196
197    #[must_use]
198    pub fn from_items(items: Vec<StreamItem>) -> Self {
199        Self { items, index: 0 }
200    }
201
202    pub fn parse(&mut self) -> BackendResult<YamlDocumentNode> {
203        let start = self.mark();
204        self.skip_blank_lines();
205        self.consume_line_start_tabs()?;
206        let value = self.parse_block_node(self.current_line_indent())?;
207        self.skip_trailing_document_space();
208        self.skip_blank_lines();
209        if self.current_kind() != "eof" {
210            return Err(self.error("Unexpected trailing YAML content."));
211        }
212        Ok(YamlDocumentNode {
213            span: self.span_from(start),
214            directives: Vec::new(),
215            explicit_start: false,
216            explicit_end: false,
217            value,
218        })
219    }
220
221    fn current(&self) -> &StreamItem {
222        &self.items[self.index]
223    }
224
225    fn current_kind(&self) -> &'static str {
226        self.current().kind()
227    }
228
229    fn current_char(&self) -> Option<char> {
230        self.current().char()
231    }
232
233    fn mark(&self) -> SourcePosition {
234        self.current().span().start.clone()
235    }
236
237    fn previous_end(&self) -> SourcePosition {
238        if self.index == 0 {
239            return self.current().span().start.clone();
240        }
241        self.items[self.index - 1].span().end.clone()
242    }
243
244    fn span_from(&self, start: SourcePosition) -> SourceSpan {
245        SourceSpan::between(start, self.previous_end())
246    }
247
248    fn error(&self, message: impl Into<String>) -> BackendError {
249        BackendError::parse_at("yaml.parse", message, Some(self.current().span().clone()))
250    }
251
252    fn advance(&mut self) {
253        if self.current_kind() != "eof" {
254            self.index += 1;
255        }
256    }
257
258    fn is_line_start(&self) -> bool {
259        self.index == 0 || self.items[self.index - 1].char() == Some('\n')
260    }
261
262    fn current_line_indent(&self) -> usize {
263        let mut probe = self.index;
264        while probe > 0 && self.items[probe - 1].char() != Some('\n') {
265            probe -= 1;
266        }
267        let mut indent = 0;
268        while matches!(self.items[probe].char(), Some(' ')) {
269            indent += 1;
270            probe += 1;
271        }
272        indent
273    }
274
275    fn skip_blank_lines(&mut self) {
276        loop {
277            if self.current_kind() == "eof" || !self.is_line_start() {
278                return;
279            }
280            let mut probe = self.index;
281            while matches!(self.items[probe].char(), Some(' ')) {
282                probe += 1;
283            }
284            match &self.items[probe] {
285                StreamItem::Eof { .. } => {
286                    self.index = probe;
287                    return;
288                }
289                StreamItem::Char { ch: '#', .. } => {
290                    self.index = probe;
291                    while !matches!(self.current_char(), None | Some('\n')) {
292                        self.advance();
293                    }
294                    if self.current_char() == Some('\n') {
295                        self.advance();
296                    }
297                }
298                _ if self.items[probe].char() == Some('\n') => {
299                    self.index = probe;
300                    self.advance();
301                }
302                _ => return,
303            }
304        }
305    }
306
307    fn skip_trailing_document_space(&mut self) {
308        loop {
309            while self.current_char() == Some(' ') {
310                self.advance();
311            }
312            if self.current_starts_comment() {
313                while !matches!(self.current_char(), None | Some('\n')) {
314                    self.advance();
315                }
316            }
317            if self.current_char() == Some('\n') {
318                self.advance();
319                continue;
320            }
321            return;
322        }
323    }
324
325    fn consume_indent(&mut self, indent: usize) -> BackendResult<()> {
326        if !self.is_line_start() {
327            return Ok(());
328        }
329        for _ in 0..indent {
330            if self.current_char() != Some(' ') {
331                return Err(self.error("Incorrect YAML indentation."));
332            }
333            self.advance();
334        }
335        Ok(())
336    }
337
338    fn parse_block_node(&mut self, indent: usize) -> BackendResult<YamlValueNode> {
339        self.skip_blank_lines();
340        self.consume_line_start_tabs()?;
341        if self.current_kind() == "eof" || self.current_line_indent() < indent {
342            return Ok(YamlValueNode::Scalar(YamlScalarNode::Plain(
343                null_plain_scalar(),
344            )));
345        }
346        self.consume_indent(indent)?;
347        if self.starts_sequence_item() {
348            return Ok(YamlValueNode::Sequence(self.parse_block_sequence(indent)?));
349        }
350        if self.line_has_mapping_key() {
351            return Ok(YamlValueNode::Mapping(self.parse_block_mapping(indent)?));
352        }
353        let decorated = self.parse_decorators()?;
354        if decorated.is_some() && self.starts_sequence_item() {
355            return Err(self.error("Unexpected trailing YAML content."));
356        }
357        let value = if decorated.is_some() && self.decorator_line_break_ahead() {
358            self.consume_inline_comment_and_line_break();
359            self.skip_blank_lines();
360            self.consume_line_start_tabs()?;
361            let next_indent = self.current_line_indent();
362            let has_nested_value = self.current_kind() != "eof"
363                && (next_indent >= indent || (indent > 0 && next_indent + 1 == indent));
364            if has_nested_value {
365                self.parse_block_node(next_indent)?
366            } else {
367                YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
368            }
369        } else {
370            self.parse_inline_value(None, indent, false)?
371        };
372        wrap_decorators(decorated, value)
373    }
374
375    fn value_owns_following_lines(value: &YamlValueNode) -> bool {
376        match value {
377            YamlValueNode::Mapping(node) => !node.flow,
378            YamlValueNode::Sequence(node) => !node.flow,
379            YamlValueNode::Scalar(YamlScalarNode::Block(_)) => true,
380            YamlValueNode::Decorated(node) => Self::value_owns_following_lines(node.value.as_ref()),
381            _ => false,
382        }
383    }
384
385    fn parse_decorators(
386        &mut self,
387    ) -> BackendResult<Option<(Option<YamlTagNode>, Option<YamlAnchorNode>)>> {
388        let mut tag = None;
389        let mut anchor = None;
390        let mut consumed = false;
391        loop {
392            if self.current_char() == Some('!') {
393                tag = Some(self.parse_tag()?);
394                consumed = true;
395                self.skip_inline_spaces();
396                continue;
397            }
398            if self.current_char() == Some('&') {
399                anchor = Some(self.parse_anchor()?);
400                consumed = true;
401                self.skip_inline_spaces();
402                continue;
403            }
404            break;
405        }
406        Ok(consumed.then_some((tag, anchor)))
407    }
408
409    fn parse_tag(&mut self) -> BackendResult<YamlTagNode> {
410        let start = self.mark();
411        self.advance();
412        let chunks = if self.current_char() == Some('<') {
413            self.parse_verbatim_tag_chunks()?
414        } else {
415            self.parse_symbol_chunks(&[' ', '\t', '\n', '[', ']', '{', '}', ',', ':'])?
416        };
417        Ok(YamlTagNode {
418            span: self.span_from(start),
419            chunks,
420        })
421    }
422
423    fn parse_anchor(&mut self) -> BackendResult<YamlAnchorNode> {
424        let start = self.mark();
425        self.advance();
426        let chunks = self.parse_symbol_chunks(&[' ', '\t', '\n', '[', ']', '{', '}', ','])?;
427        Ok(YamlAnchorNode {
428            span: self.span_from(start),
429            chunks,
430        })
431    }
432
433    fn parse_alias(&mut self) -> BackendResult<YamlAliasNode> {
434        let start = self.mark();
435        self.advance();
436        let chunks = self.parse_symbol_chunks(&[' ', '\t', '\n', '[', ']', '{', '}', ','])?;
437        Ok(YamlAliasNode {
438            span: self.span_from(start),
439            chunks,
440        })
441    }
442
443    fn parse_symbol_chunks(&mut self, stop_chars: &[char]) -> BackendResult<Vec<YamlChunk>> {
444        let mut chunks = Vec::new();
445        let mut buffer = String::new();
446        while self.current_kind() != "eof" {
447            if self.current_kind() == "interpolation" {
448                self.flush_buffer(&mut buffer, &mut chunks);
449                chunks.push(YamlChunk::Interpolation(
450                    self.consume_interpolation("metadata_fragment")?,
451                ));
452                continue;
453            }
454            let Some(ch) = self.current_char() else {
455                break;
456            };
457            if stop_chars.contains(&ch) {
458                break;
459            }
460            buffer.push(ch);
461            self.advance();
462        }
463        self.flush_buffer(&mut buffer, &mut chunks);
464        Ok(chunks)
465    }
466
467    fn parse_verbatim_tag_chunks(&mut self) -> BackendResult<Vec<YamlChunk>> {
468        let mut chunks = Vec::new();
469        let mut buffer = String::new();
470        buffer.push('<');
471        self.advance();
472
473        while self.current_kind() != "eof" {
474            if self.current_kind() == "interpolation" {
475                self.flush_buffer(&mut buffer, &mut chunks);
476                chunks.push(YamlChunk::Interpolation(
477                    self.consume_interpolation("metadata_fragment")?,
478                ));
479                continue;
480            }
481
482            let Some(ch) = self.current_char() else {
483                break;
484            };
485            buffer.push(ch);
486            self.advance();
487            if ch == '>' {
488                self.flush_buffer(&mut buffer, &mut chunks);
489                return Ok(chunks);
490            }
491        }
492
493        Err(self.error("Unterminated YAML verbatim tag."))
494    }
495
496    fn skip_inline_spaces(&mut self) {
497        while matches!(self.current_char(), Some(' ' | '\t')) {
498            self.advance();
499        }
500    }
501
502    fn skip_flow_separation(&mut self) {
503        self.skip_flow_separation_with_breaks();
504    }
505
506    fn skip_flow_separation_with_breaks(&mut self) -> bool {
507        let mut saw_line_break = false;
508        loop {
509            while matches!(self.current_char(), Some(' ' | '\t' | '\r')) {
510                self.advance();
511            }
512            if self.current_starts_comment() {
513                while !matches!(self.current_char(), None | Some('\n')) {
514                    self.advance();
515                }
516            }
517            if self.current_char() == Some('\n') {
518                saw_line_break = true;
519                self.advance();
520                continue;
521            }
522            break;
523        }
524        saw_line_break
525    }
526
527    fn starts_sequence_item(&self) -> bool {
528        self.starts_sequence_item_at(self.index)
529    }
530
531    fn starts_sequence_item_at(&self, mut probe: usize) -> bool {
532        while matches!(self.items.get(probe).and_then(StreamItem::char), Some(' ')) {
533            probe += 1;
534        }
535        matches!(self.items.get(probe).and_then(StreamItem::char), Some('-'))
536            && matches!(
537                self.items.get(probe + 1).and_then(StreamItem::char),
538                Some(' ' | '\t' | '\n')
539            )
540    }
541
542    fn peek_char(&self, offset: usize) -> Option<char> {
543        self.items
544            .get(self.index + offset)
545            .and_then(StreamItem::char)
546    }
547
548    fn line_has_mapping_key(&self) -> bool {
549        self.line_has_mapping_key_at(self.index)
550    }
551
552    fn line_has_mapping_key_at(&self, probe: usize) -> bool {
553        if matches!(self.items.get(probe).and_then(StreamItem::char), Some('?'))
554            && matches!(
555                self.items.get(probe + 1).and_then(StreamItem::char),
556                Some(' ' | '\t' | '\n')
557            )
558        {
559            return true;
560        }
561        let mut parser = Self {
562            items: self.items.clone(),
563            index: probe,
564        };
565        if parser.parse_key().is_err() {
566            return false;
567        }
568        parser.skip_key_value_spaces();
569        if parser.current_char() != Some(':') {
570            return false;
571        }
572        matches!(parser.peek_char(1), Some(' ' | '\t' | '\n') | None)
573    }
574
575    fn skip_key_value_spaces(&mut self) {
576        while matches!(self.current_char(), Some(' ' | '\t')) {
577            self.advance();
578        }
579    }
580
581    fn decorator_line_break_ahead(&self) -> bool {
582        let mut probe = self.index;
583        while matches!(
584            self.items.get(probe).and_then(StreamItem::char),
585            Some(' ' | '\t')
586        ) {
587            probe += 1;
588        }
589        if self.items.get(probe).and_then(StreamItem::char) == Some('#') {
590            while !matches!(
591                self.items.get(probe).and_then(StreamItem::char),
592                None | Some('\n')
593            ) {
594                probe += 1;
595            }
596        }
597        matches!(self.items.get(probe).and_then(StreamItem::char), Some('\n'))
598            || matches!(self.items.get(probe), Some(StreamItem::Eof { .. }))
599    }
600
601    fn consume_inline_comment_and_line_break(&mut self) {
602        self.skip_inline_spaces();
603        if self.current_starts_comment() {
604            while !matches!(self.current_char(), None | Some('\n')) {
605                self.advance();
606            }
607        }
608        if self.current_char() == Some('\n') {
609            self.advance();
610        }
611    }
612
613    fn consume_line_end_required(&mut self) -> BackendResult<()> {
614        if self.current_kind() == "eof" || self.is_line_start() {
615            return Ok(());
616        }
617        self.skip_inline_spaces();
618        if self.current_starts_comment() {
619            while !matches!(self.current_char(), None | Some('\n')) {
620                self.advance();
621            }
622        }
623        match self.current_char() {
624            Some('\n') => {
625                self.advance();
626                Ok(())
627            }
628            None => Ok(()),
629            _ if self.current_kind() == "eof" => Ok(()),
630            _ => Err(self.error("Unexpected trailing YAML content.")),
631        }
632    }
633
634    fn consume_line_start_tabs(&mut self) -> BackendResult<()> {
635        if !self.is_line_start() || self.current_char() != Some('\t') {
636            return Ok(());
637        }
638
639        let mut probe = self.index;
640        while self.items.get(probe).and_then(StreamItem::char) == Some('\t') {
641            probe += 1;
642        }
643
644        if self.starts_sequence_item_at(probe) || self.line_has_mapping_key_at(probe) {
645            return Err(self.error("Tabs are not allowed as YAML indentation."));
646        }
647
648        self.index = probe;
649        Ok(())
650    }
651
652    fn current_starts_comment(&self) -> bool {
653        self.current_char() == Some('#')
654            && (self.index == 0
655                || matches!(
656                    self.items
657                        .get(self.index.saturating_sub(1))
658                        .and_then(StreamItem::char),
659                    Some(' ' | '\t' | '\n' | '\r')
660                ))
661    }
662
663    fn parse_key_value_separator(&mut self) -> BackendResult<()> {
664        self.skip_key_value_spaces();
665        self.consume_char(':')?;
666        if self.current_char() == Some('\t') {
667            let mut probe = self.index;
668            while self.items.get(probe).and_then(StreamItem::char) == Some('\t') {
669                probe += 1;
670            }
671            if !matches!(
672                self.items.get(probe).and_then(StreamItem::char),
673                Some(
674                    ' ' | '\n'
675                        | '\r'
676                        | '#'
677                        | '['
678                        | '{'
679                        | '"'
680                        | '\''
681                        | '!'
682                        | '&'
683                        | '*'
684                        | '|'
685                        | '>'
686                        | '?'
687                ) | None
688            ) {
689                return Err(self.error("Tabs are not allowed as YAML indentation."));
690            }
691        }
692        Ok(())
693    }
694
695    fn classify_key_value(&self, start: SourcePosition, value: YamlValueNode) -> YamlKeyNode {
696        let value = match value {
697            YamlValueNode::Interpolation(node) => YamlKeyValue::Interpolation(node),
698            YamlValueNode::Scalar(
699                node @ (YamlScalarNode::Plain(_)
700                | YamlScalarNode::DoubleQuoted(_)
701                | YamlScalarNode::SingleQuoted(_)),
702            ) => YamlKeyValue::Scalar(node),
703            other => YamlKeyValue::Complex(Box::new(other)),
704        };
705        YamlKeyNode {
706            span: self.span_from(start),
707            value,
708        }
709    }
710
711    fn parse_block_sequence(&mut self, indent: usize) -> BackendResult<YamlSequenceNode> {
712        let start = self.mark();
713        let mut items = Vec::new();
714        let mut first_item = true;
715        loop {
716            if !first_item || self.is_line_start() {
717                self.consume_indent(indent)?;
718            }
719            first_item = false;
720            if !self.starts_sequence_item() {
721                break;
722            }
723            self.advance();
724            if matches!(self.current_char(), Some(' ' | '\t')) {
725                self.skip_inline_spaces();
726            }
727            if self.current_starts_comment() {
728                while !matches!(self.current_char(), None | Some('\n')) {
729                    self.advance();
730                }
731                if self.current_char() == Some('\n') {
732                    self.advance();
733                }
734                self.skip_blank_lines();
735                if self.is_line_start() && self.current_char() == Some('\t') {
736                    self.consume_line_start_tabs()?;
737                }
738                let item = if self.current_kind() == "eof" || self.current_line_indent() <= indent {
739                    YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
740                } else {
741                    self.parse_block_node(self.current_line_indent())?
742                };
743                let owns_following_lines = Self::value_owns_following_lines(&item);
744                items.push(item);
745                if !owns_following_lines && self.current_kind() != "eof" {
746                    self.consume_line_end_required()?;
747                }
748            } else if self.current_kind() == "eof" || self.current_char() == Some('\n') {
749                if self.current_char() == Some('\n') {
750                    self.advance();
751                }
752                self.skip_blank_lines();
753                let item = self.parse_block_node(self.current_line_indent())?;
754                let owns_following_lines = Self::value_owns_following_lines(&item);
755                items.push(item);
756                if !owns_following_lines {
757                    self.consume_line_end_required()?;
758                }
759            } else if self.starts_sequence_item() {
760                items.push(YamlValueNode::Sequence(
761                    self.parse_compact_sequence(indent + 2)?,
762                ));
763            } else if self.line_has_mapping_key() {
764                items.push(YamlValueNode::Mapping(
765                    self.parse_compact_mapping(indent + 2)?,
766                ));
767            } else {
768                let item = self.parse_inline_value(None, indent, true)?;
769                let owns_following_lines = Self::value_owns_following_lines(&item);
770                items.push(item);
771                if !owns_following_lines {
772                    self.consume_line_end_required()?;
773                }
774            }
775            self.skip_blank_lines();
776            if self.current_kind() == "eof"
777                || self.current_line_indent() != indent
778                || !self.starts_sequence_item()
779            {
780                break;
781            }
782        }
783        Ok(YamlSequenceNode {
784            span: self.span_from(start),
785            items,
786            flow: false,
787        })
788    }
789
790    fn parse_compact_sequence(&mut self, indent: usize) -> BackendResult<YamlSequenceNode> {
791        let start = self.mark();
792        let mut items = Vec::new();
793        let mut first_item = true;
794        loop {
795            if !first_item || self.is_line_start() {
796                self.consume_indent(indent)?;
797            }
798            first_item = false;
799            if !self.starts_sequence_item() {
800                break;
801            }
802            self.advance();
803            if matches!(self.current_char(), Some(' ' | '\t')) {
804                self.skip_inline_spaces();
805            }
806            if self.current_starts_comment() {
807                while !matches!(self.current_char(), None | Some('\n')) {
808                    self.advance();
809                }
810                if self.current_char() == Some('\n') {
811                    self.advance();
812                }
813                self.skip_blank_lines();
814                if self.is_line_start() && self.current_char() == Some('\t') {
815                    self.consume_line_start_tabs()?;
816                }
817                let item = if self.current_kind() == "eof" || self.current_line_indent() <= indent {
818                    YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
819                } else {
820                    self.parse_block_node(self.current_line_indent())?
821                };
822                let owns_following_lines = Self::value_owns_following_lines(&item);
823                items.push(item);
824                if !owns_following_lines && self.current_kind() != "eof" {
825                    self.consume_line_end_required()?;
826                }
827            } else if self.current_kind() == "eof" || self.current_char() == Some('\n') {
828                if self.current_char() == Some('\n') {
829                    self.advance();
830                }
831                self.skip_blank_lines();
832                let item = self.parse_block_node(self.current_line_indent())?;
833                let owns_following_lines = Self::value_owns_following_lines(&item);
834                items.push(item);
835                if !owns_following_lines {
836                    self.consume_line_end_required()?;
837                }
838            } else if self.starts_sequence_item() {
839                items.push(YamlValueNode::Sequence(
840                    self.parse_compact_sequence(indent + 2)?,
841                ));
842            } else if self.line_has_mapping_key() {
843                items.push(YamlValueNode::Mapping(
844                    self.parse_compact_mapping(indent + 2)?,
845                ));
846            } else {
847                let item = self.parse_inline_value(None, indent, true)?;
848                let owns_following_lines = Self::value_owns_following_lines(&item);
849                items.push(item);
850                if !owns_following_lines {
851                    self.consume_line_end_required()?;
852                }
853            }
854            self.skip_blank_lines();
855            if self.current_kind() == "eof"
856                || self.current_line_indent() < indent
857                || !self.starts_sequence_item()
858            {
859                break;
860            }
861        }
862        Ok(YamlSequenceNode {
863            span: self.span_from(start),
864            items,
865            flow: false,
866        })
867    }
868
869    fn parse_compact_mapping(&mut self, indent: usize) -> BackendResult<YamlMappingNode> {
870        let start = self.mark();
871        let mut entries = Vec::new();
872        loop {
873            let entry_start = self.mark();
874            let explicit_key =
875                self.current_char() == Some('?') && matches!(self.peek_char(1), Some(' ' | '\n'));
876            let key = if explicit_key {
877                self.parse_explicit_key(indent)?
878            } else {
879                self.parse_key()?
880            };
881            let value = if explicit_key {
882                self.parse_explicit_mapping_entry_value(indent)?
883            } else {
884                self.parse_key_value_separator()?;
885                self.parse_mapping_value_after_separator(indent, false)?
886            };
887            if !Self::value_owns_following_lines(&value) {
888                self.consume_line_end_required()?;
889            }
890            entries.push(YamlMappingEntryNode {
891                span: self.span_from(entry_start),
892                key,
893                value,
894            });
895            self.skip_blank_lines();
896            if self.current_kind() == "eof"
897                || self.current_line_indent() != indent
898                || self.starts_sequence_item()
899                || !self.line_has_mapping_key()
900            {
901                break;
902            }
903            self.consume_indent(indent)?;
904        }
905        Ok(YamlMappingNode {
906            span: self.span_from(start),
907            entries,
908            flow: false,
909        })
910    }
911
912    fn parse_block_mapping(&mut self, indent: usize) -> BackendResult<YamlMappingNode> {
913        let start = self.mark();
914        let mut entries = Vec::new();
915        let mut first_entry = true;
916        loop {
917            if !first_entry {
918                self.consume_indent(indent)?;
919            }
920            first_entry = false;
921            let entry_start = self.mark();
922            let explicit_key =
923                self.current_char() == Some('?') && matches!(self.peek_char(1), Some(' ' | '\n'));
924            let key = if explicit_key {
925                self.parse_explicit_key(indent)?
926            } else {
927                self.parse_key()?
928            };
929            let value = if explicit_key {
930                self.parse_explicit_mapping_entry_value(indent)?
931            } else {
932                self.parse_key_value_separator()?;
933                self.parse_mapping_value_after_separator(indent, false)?
934            };
935            if !Self::value_owns_following_lines(&value) {
936                self.consume_line_end_required()?;
937            }
938            entries.push(YamlMappingEntryNode {
939                span: self.span_from(entry_start),
940                key,
941                value,
942            });
943            self.skip_blank_lines();
944            if self.current_kind() == "eof"
945                || self.current_line_indent() != indent
946                || self.starts_sequence_item()
947                || !self.line_has_mapping_key()
948            {
949                break;
950            }
951        }
952        Ok(YamlMappingNode {
953            span: self.span_from(start),
954            entries,
955            flow: false,
956        })
957    }
958
959    fn parse_explicit_mapping_entry_value(
960        &mut self,
961        indent: usize,
962    ) -> BackendResult<YamlValueNode> {
963        if self.current_kind() == "eof"
964            || (self.is_line_start() && self.current_line_indent() < indent)
965        {
966            return Ok(YamlValueNode::Scalar(YamlScalarNode::Plain(
967                null_plain_scalar(),
968            )));
969        }
970
971        self.consume_indent(indent)?;
972        if self.current_char() != Some(':') {
973            return Ok(YamlValueNode::Scalar(YamlScalarNode::Plain(
974                null_plain_scalar(),
975            )));
976        }
977
978        self.parse_key_value_separator()?;
979        self.parse_mapping_value_after_separator(indent, true)
980    }
981
982    fn parse_mapping_value_after_separator(
983        &mut self,
984        indent: usize,
985        allow_inline_compact_sequence: bool,
986    ) -> BackendResult<YamlValueNode> {
987        if matches!(self.current_char(), Some(' ' | '\t')) {
988            if matches!(self.current_char(), Some(' ' | '\t')) {
989                self.skip_inline_spaces();
990            }
991            return self.parse_mapping_value(indent, allow_inline_compact_sequence);
992        }
993        if matches!(self.current_kind(), "eof") || self.current_char() == Some('\n') {
994            if self.current_char() == Some('\n') {
995                self.advance();
996            }
997            self.skip_blank_lines();
998            self.consume_line_start_tabs()?;
999            if self.starts_sequence_item() && self.current_line_indent() == indent {
1000                return Ok(YamlValueNode::Sequence(self.parse_block_sequence(indent)?));
1001            }
1002            if self.current_kind() == "eof" || self.current_line_indent() <= indent {
1003                return Ok(YamlValueNode::Scalar(YamlScalarNode::Plain(
1004                    null_plain_scalar(),
1005                )));
1006            }
1007            return self.parse_block_node(self.current_line_indent());
1008        }
1009        self.parse_inline_value(None, indent, true)
1010    }
1011
1012    fn parse_mapping_value(
1013        &mut self,
1014        indent: usize,
1015        allow_inline_compact_sequence: bool,
1016    ) -> BackendResult<YamlValueNode> {
1017        if self.current_starts_comment() {
1018            while !matches!(self.current_char(), None | Some('\n')) {
1019                self.advance();
1020            }
1021            if self.current_char() == Some('\n') {
1022                self.advance();
1023            }
1024            self.skip_blank_lines();
1025            if self.is_line_start() && self.current_char() == Some('\t') {
1026                self.consume_line_start_tabs()?;
1027            }
1028            if self.starts_sequence_item() && self.current_line_indent() == indent {
1029                return Ok(YamlValueNode::Sequence(self.parse_block_sequence(indent)?));
1030            }
1031            if self.current_kind() == "eof" || self.current_line_indent() <= indent {
1032                return Ok(YamlValueNode::Scalar(YamlScalarNode::Plain(
1033                    null_plain_scalar(),
1034                )));
1035            }
1036            return self.parse_block_node(self.current_line_indent());
1037        }
1038        if self.starts_sequence_item() {
1039            if self.is_line_start() && self.current_line_indent() == indent {
1040                return Ok(YamlValueNode::Sequence(self.parse_block_sequence(indent)?));
1041            }
1042            if allow_inline_compact_sequence {
1043                return Ok(YamlValueNode::Sequence(
1044                    self.parse_compact_sequence(indent + 2)?,
1045                ));
1046            }
1047            return Err(self.error("Unexpected trailing YAML content."));
1048        }
1049        if allow_inline_compact_sequence && self.line_has_mapping_key() {
1050            return Ok(YamlValueNode::Mapping(
1051                self.parse_compact_mapping(indent + 2)?,
1052            ));
1053        }
1054        if matches!(self.current_char(), Some(' ' | '\t')) {
1055            self.skip_inline_spaces();
1056            if self.current_starts_comment() {
1057                while !matches!(self.current_char(), None | Some('\n')) {
1058                    self.advance();
1059                }
1060                if self.current_char() == Some('\n') {
1061                    self.advance();
1062                }
1063                self.skip_blank_lines();
1064                if self.is_line_start() && self.current_char() == Some('\t') {
1065                    self.consume_line_start_tabs()?;
1066                }
1067                if self.starts_sequence_item() && self.current_line_indent() == indent {
1068                    return Ok(YamlValueNode::Sequence(self.parse_block_sequence(indent)?));
1069                }
1070                if self.current_kind() == "eof" || self.current_line_indent() <= indent {
1071                    return Ok(YamlValueNode::Scalar(YamlScalarNode::Plain(
1072                        null_plain_scalar(),
1073                    )));
1074                }
1075                return self.parse_block_node(self.current_line_indent());
1076            }
1077            if self.starts_sequence_item() {
1078                if self.is_line_start() && self.current_line_indent() == indent {
1079                    return Ok(YamlValueNode::Sequence(self.parse_block_sequence(indent)?));
1080                }
1081                if allow_inline_compact_sequence {
1082                    return Ok(YamlValueNode::Sequence(
1083                        self.parse_compact_sequence(indent + 2)?,
1084                    ));
1085                }
1086                return Err(self.error("Unexpected trailing YAML content."));
1087            }
1088            if allow_inline_compact_sequence && self.line_has_mapping_key() {
1089                return Ok(YamlValueNode::Mapping(
1090                    self.parse_compact_mapping(indent + 2)?,
1091                ));
1092            }
1093            return self.parse_inline_value(None, indent, true);
1094        }
1095        if self.current_kind() == "eof" || self.current_char() == Some('\n') {
1096            if self.current_char() == Some('\n') {
1097                self.advance();
1098            }
1099            self.skip_blank_lines();
1100            if self.is_line_start() && self.current_char() == Some('\t') {
1101                self.consume_line_start_tabs()?;
1102            }
1103            if self.starts_sequence_item() && self.current_line_indent() == indent {
1104                return Ok(YamlValueNode::Sequence(self.parse_block_sequence(indent)?));
1105            }
1106            if self.current_kind() == "eof" || self.current_line_indent() <= indent {
1107                return Ok(YamlValueNode::Scalar(YamlScalarNode::Plain(
1108                    null_plain_scalar(),
1109                )));
1110            }
1111            return self.parse_block_node(self.current_line_indent());
1112        }
1113        self.parse_inline_value(None, indent, true)
1114    }
1115
1116    fn parse_explicit_key(&mut self, indent: usize) -> BackendResult<YamlKeyNode> {
1117        let start = self.mark();
1118        self.consume_char('?')?;
1119        if matches!(self.current_char(), Some(' ' | '\t')) {
1120            self.skip_inline_spaces();
1121        }
1122        let value = if self.current_starts_comment() {
1123            while !matches!(self.current_char(), None | Some('\n')) {
1124                self.advance();
1125            }
1126            if self.current_char() == Some('\n') {
1127                self.advance();
1128            }
1129            self.skip_blank_lines();
1130            if self.is_line_start() && self.current_char() == Some('\t') {
1131                self.consume_line_start_tabs()?;
1132            }
1133            if self.current_kind() == "eof" || self.current_char() == Some(':') {
1134                YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
1135            } else {
1136                self.parse_block_node(self.current_line_indent())?
1137            }
1138        } else if self.current_kind() == "eof" || self.current_char() == Some('\n') {
1139            if self.current_char() == Some('\n') {
1140                self.advance();
1141            }
1142            self.skip_blank_lines();
1143            if self.current_kind() == "eof" || self.current_char() == Some(':') {
1144                YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
1145            } else {
1146                self.parse_block_node(self.current_line_indent())?
1147            }
1148        } else if self.starts_sequence_item() {
1149            YamlValueNode::Sequence(self.parse_compact_sequence(indent + 2)?)
1150        } else if self.line_has_mapping_key() {
1151            YamlValueNode::Mapping(self.parse_compact_mapping(indent + 2)?)
1152        } else {
1153            let value = self.parse_inline_value(None, indent, true)?;
1154            self.consume_line_end_required()?;
1155            value
1156        };
1157        Ok(self.classify_key_value(start, value))
1158    }
1159
1160    fn parse_key(&mut self) -> BackendResult<YamlKeyNode> {
1161        let start = self.mark();
1162        let key_start = self.index;
1163        let value = self.parse_inline_value(Some(&[':']), 0, false)?;
1164        self.ensure_single_line_implicit_key(key_start)?;
1165        Ok(self.classify_key_value(start, value))
1166    }
1167
1168    fn parse_inline_value(
1169        &mut self,
1170        stop_chars: Option<&[char]>,
1171        parent_indent: usize,
1172        requires_indented_continuation: bool,
1173    ) -> BackendResult<YamlValueNode> {
1174        let decorated = self.parse_decorators()?;
1175        if decorated.is_some() && self.starts_sequence_item() {
1176            return Err(self.error("Unexpected trailing YAML content."));
1177        }
1178        if decorated.is_some() && self.decorator_line_break_ahead() {
1179            self.consume_inline_comment_and_line_break();
1180            self.skip_blank_lines();
1181            self.consume_line_start_tabs()?;
1182            let has_nested_value = self.current_kind() != "eof"
1183                && (self.current_line_indent() > parent_indent
1184                    || (self.current_line_indent() == parent_indent
1185                        && self.starts_sequence_item()));
1186            let value = if has_nested_value {
1187                self.parse_block_node(self.current_line_indent())?
1188            } else {
1189                YamlValueNode::Scalar(YamlScalarNode::Plain(YamlPlainScalarNode {
1190                    span: SourceSpan::point(0, 0),
1191                    chunks: vec![YamlChunk::Text(YamlTextChunkNode {
1192                        span: SourceSpan::point(0, 0),
1193                        value: "null".to_owned(),
1194                    })],
1195                }))
1196            };
1197            return wrap_decorators(decorated, value);
1198        }
1199        if decorated.is_some() && self.inline_value_terminator(stop_chars) {
1200            return wrap_decorators(
1201                decorated,
1202                YamlValueNode::Scalar(YamlScalarNode::Plain(empty_plain_scalar())),
1203            );
1204        }
1205        let value = if self.current_kind() == "interpolation" {
1206            let interpolation = self.consume_interpolation("value")?;
1207            if self.inline_value_terminator(stop_chars) {
1208                YamlValueNode::Interpolation(interpolation)
1209            } else {
1210                YamlValueNode::Scalar(YamlScalarNode::Plain(self.parse_plain_scalar(
1211                    stop_chars,
1212                    Some(vec![YamlChunk::Interpolation(interpolation)]),
1213                    false,
1214                    parent_indent,
1215                    requires_indented_continuation,
1216                )?))
1217            }
1218        } else if self.current_char() == Some('[') {
1219            YamlValueNode::Sequence(self.parse_flow_sequence()?)
1220        } else if self.current_char() == Some('{') {
1221            YamlValueNode::Mapping(self.parse_flow_mapping()?)
1222        } else if self.current_char() == Some('"') {
1223            YamlValueNode::Scalar(YamlScalarNode::DoubleQuoted(
1224                self.parse_double_quoted(parent_indent, requires_indented_continuation)?,
1225            ))
1226        } else if self.current_char() == Some('\'') {
1227            YamlValueNode::Scalar(YamlScalarNode::SingleQuoted(self.parse_single_quoted()?))
1228        } else if self.current_char() == Some('*') {
1229            YamlValueNode::Scalar(YamlScalarNode::Alias(self.parse_alias()?))
1230        } else if matches!(self.current_char(), Some('|' | '>')) {
1231            YamlValueNode::Scalar(YamlScalarNode::Block(
1232                self.parse_block_scalar(parent_indent)?,
1233            ))
1234        } else if matches!(self.current_char(), Some(',' | ']' | '}')) {
1235            return Err(self.error("Expected a YAML value."));
1236        } else {
1237            YamlValueNode::Scalar(YamlScalarNode::Plain(self.parse_plain_scalar(
1238                stop_chars,
1239                None,
1240                false,
1241                parent_indent,
1242                requires_indented_continuation,
1243            )?))
1244        };
1245        wrap_decorators(decorated, value)
1246    }
1247
1248    fn inline_value_terminator(&self, stop_chars: Option<&[char]>) -> bool {
1249        let mut probe = self.index;
1250        while matches!(self.items[probe].char(), Some(' ' | '\t')) {
1251            probe += 1;
1252        }
1253        match &self.items[probe] {
1254            StreamItem::Eof { .. } => true,
1255            StreamItem::Char { ch, .. } => {
1256                stop_chars.is_some_and(|chars| chars.contains(ch)) || matches!(ch, '\n' | '#')
1257            }
1258            _ => false,
1259        }
1260    }
1261
1262    fn parse_flow_sequence(&mut self) -> BackendResult<YamlSequenceNode> {
1263        let start = self.mark();
1264        let line_value_start = self.is_line_value_start();
1265        self.consume_char('[')?;
1266        let mut items = Vec::new();
1267        self.skip_flow_separation();
1268        if self.current_char() == Some(']') {
1269            self.advance();
1270            return Ok(YamlSequenceNode {
1271                span: self.span_from(start),
1272                items,
1273                flow: true,
1274            });
1275        }
1276        loop {
1277            if self.current_char() == Some(',') {
1278                return Err(self.error("Expected a YAML value."));
1279            }
1280            if self.current_char() == Some('-') && matches!(self.peek_char(1), Some(',' | ']')) {
1281                return Err(self.error("Expected a YAML value."));
1282            }
1283            items.push(self.parse_flow_sequence_item()?);
1284            self.skip_flow_separation();
1285            if self.current_char() == Some(']') {
1286                self.advance();
1287                break;
1288            }
1289            self.consume_char(',')?;
1290            let saw_line_break = self.skip_flow_separation_with_breaks();
1291            if saw_line_break
1292                && !line_value_start
1293                && self.current_char() != Some(']')
1294                && self.current_line_indent() == 0
1295            {
1296                return Err(self.error("Unexpected trailing YAML content."));
1297            }
1298            if self.current_char() == Some(']') {
1299                self.advance();
1300                break;
1301            }
1302        }
1303        Ok(YamlSequenceNode {
1304            span: self.span_from(start),
1305            items,
1306            flow: true,
1307        })
1308    }
1309
1310    fn is_line_value_start(&self) -> bool {
1311        let mut probe = self.index;
1312        while probe > 0 && self.items[probe - 1].char() != Some('\n') {
1313            probe -= 1;
1314        }
1315        while probe < self.index {
1316            match self.items[probe].char() {
1317                Some(' ' | '\t') => probe += 1,
1318                _ => return false,
1319            }
1320        }
1321        true
1322    }
1323
1324    fn parse_flow_sequence_item(&mut self) -> BackendResult<YamlValueNode> {
1325        let entry_start = self.mark();
1326        let explicit =
1327            self.current_char() == Some('?') && matches!(self.peek_char(1), Some(' ' | '[' | '{'));
1328        if explicit {
1329            self.consume_char('?')?;
1330            self.skip_inline_spaces();
1331        }
1332
1333        let key_value = self.parse_inline_value(Some(&[':', ',', ']']), 0, false)?;
1334        let saw_line_break = self.skip_flow_separation_with_breaks();
1335        if self.current_char() != Some(':') {
1336            if explicit {
1337                return Err(self.error("Expected ':' in YAML template."));
1338            }
1339            return Ok(key_value);
1340        }
1341        if saw_line_break {
1342            return Err(self.error("Expected ':' in YAML template."));
1343        }
1344
1345        let key = self.classify_key_value(entry_start.clone(), key_value);
1346        self.parse_key_value_separator()?;
1347        self.skip_flow_separation();
1348        let value = if matches!(self.current_char(), Some(',' | ']')) {
1349            YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
1350        } else {
1351            self.parse_inline_value(Some(&[',', ']']), 0, false)?
1352        };
1353        let pair_span = self.span_from(entry_start);
1354        Ok(YamlValueNode::Mapping(YamlMappingNode {
1355            span: pair_span.clone(),
1356            entries: vec![YamlMappingEntryNode {
1357                span: pair_span,
1358                key,
1359                value,
1360            }],
1361            flow: true,
1362        }))
1363    }
1364
1365    fn parse_flow_mapping(&mut self) -> BackendResult<YamlMappingNode> {
1366        let start = self.mark();
1367        self.consume_char('{')?;
1368        let mut entries = Vec::new();
1369        self.skip_flow_separation();
1370        if self.current_char() == Some(',') {
1371            return Err(self.error("Expected ':' in YAML template."));
1372        }
1373        if self.current_char() == Some('}') {
1374            self.advance();
1375            return Ok(YamlMappingNode {
1376                span: self.span_from(start),
1377                entries,
1378                flow: true,
1379            });
1380        }
1381        loop {
1382            let entry_start = self.mark();
1383            let key = self.parse_flow_key()?;
1384            self.skip_flow_separation();
1385            let value = if matches!(self.current_char(), Some(',' | '}')) {
1386                if self.current_char() == Some('}') && flow_implicit_plain_key_needs_separator(&key)
1387                {
1388                    return Err(self.error("Expected ':' in YAML template."));
1389                }
1390                YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
1391            } else {
1392                self.parse_key_value_separator()?;
1393                self.skip_flow_separation();
1394                if matches!(self.current_char(), Some(',' | '}')) {
1395                    YamlValueNode::Scalar(YamlScalarNode::Plain(null_plain_scalar()))
1396                } else {
1397                    self.parse_inline_value(Some(&[',', '}']), 0, false)?
1398                }
1399            };
1400            entries.push(YamlMappingEntryNode {
1401                span: self.span_from(entry_start),
1402                key,
1403                value,
1404            });
1405            self.skip_flow_separation();
1406            if self.current_char() == Some('}') {
1407                self.advance();
1408                break;
1409            }
1410            self.consume_char(',')?;
1411            self.skip_flow_separation();
1412            if self.current_char() == Some(',') {
1413                return Err(self.error("Expected ':' in YAML template."));
1414            }
1415            if self.current_char() == Some('}') {
1416                self.advance();
1417                break;
1418            }
1419        }
1420        Ok(YamlMappingNode {
1421            span: self.span_from(start),
1422            entries,
1423            flow: true,
1424        })
1425    }
1426
1427    fn parse_flow_key(&mut self) -> BackendResult<YamlKeyNode> {
1428        let start = self.mark();
1429        let explicit =
1430            self.current_char() == Some('?') && matches!(self.peek_char(1), Some(' ' | '[' | '{'));
1431        if explicit {
1432            self.consume_char('?')?;
1433            self.skip_inline_spaces();
1434        }
1435        let value = self.parse_inline_value(Some(&[':', ',', '}']), 0, false)?;
1436        Ok(self.classify_key_value(start, value))
1437    }
1438
1439    fn ensure_single_line_implicit_key(&self, start_index: usize) -> BackendResult<()> {
1440        let saw_line_break = self.items[start_index..self.index]
1441            .iter()
1442            .any(|item| item.char() == Some('\n'));
1443        if saw_line_break {
1444            return Err(BackendError::parse_at(
1445                "yaml.parse",
1446                "Implicit YAML keys must be on a single line.",
1447                Some(self.current().span().clone()),
1448            ));
1449        }
1450        Ok(())
1451    }
1452
1453    fn parse_double_quoted(
1454        &mut self,
1455        parent_indent: usize,
1456        requires_indented_continuation: bool,
1457    ) -> BackendResult<YamlDoubleQuotedScalarNode> {
1458        let start = self.mark();
1459        self.consume_char('"')?;
1460        let mut chunks = Vec::new();
1461        let mut buffer = String::new();
1462        loop {
1463            if self.current_kind() == "eof" {
1464                return Err(self.error("Unterminated YAML double-quoted scalar."));
1465            }
1466            if self.current_kind() == "interpolation" {
1467                self.flush_buffer(&mut buffer, &mut chunks);
1468                chunks.push(YamlChunk::Interpolation(
1469                    self.consume_interpolation("string_fragment")?,
1470                ));
1471                continue;
1472            }
1473            if self.current_char() == Some('"') {
1474                self.flush_buffer(&mut buffer, &mut chunks);
1475                self.advance();
1476                break;
1477            }
1478            if matches!(self.current_char(), Some('\r' | '\n')) {
1479                buffer.push_str(&self.parse_quoted_line_folding(
1480                    '"',
1481                    parent_indent,
1482                    requires_indented_continuation,
1483                )?);
1484                continue;
1485            }
1486            if self.current_char() == Some('\\') {
1487                buffer.push_str(&self.parse_double_quoted_escape()?);
1488                continue;
1489            }
1490            if let Some(ch) = self.current_char() {
1491                buffer.push(ch);
1492                self.advance();
1493            }
1494        }
1495        Ok(YamlDoubleQuotedScalarNode {
1496            span: self.span_from(start),
1497            chunks,
1498        })
1499    }
1500
1501    fn parse_quoted_line_folding(
1502        &mut self,
1503        terminator: char,
1504        parent_indent: usize,
1505        requires_indented_continuation: bool,
1506    ) -> BackendResult<String> {
1507        let mut breaks = 0usize;
1508        loop {
1509            if self.current_char() == Some('\r') {
1510                self.advance();
1511            }
1512            if self.current_char() != Some('\n') {
1513                return Err(self.error("Invalid YAML quoted-scalar line break."));
1514            }
1515            self.advance();
1516            breaks += 1;
1517            let mut indent = 0usize;
1518            while matches!(self.current_char(), Some(' ' | '\t')) {
1519                indent += 1;
1520                self.advance();
1521            }
1522            let has_required_indent = if requires_indented_continuation {
1523                indent > parent_indent
1524            } else {
1525                indent >= parent_indent
1526            };
1527            if !has_required_indent
1528                && !matches!(self.current_char(), Some('\r' | '\n'))
1529                && self.current_char() != Some(terminator)
1530            {
1531                return Err(self.error("Invalid YAML quoted-scalar line break."));
1532            }
1533            if !matches!(self.current_char(), Some('\r' | '\n')) {
1534                break;
1535            }
1536        }
1537        if breaks == 1 {
1538            Ok(" ".to_owned())
1539        } else {
1540            Ok("\n".repeat(breaks - 1))
1541        }
1542    }
1543
1544    fn parse_single_quoted(&mut self) -> BackendResult<YamlSingleQuotedScalarNode> {
1545        let start = self.mark();
1546        self.consume_char('\'')?;
1547        let mut chunks = Vec::new();
1548        let mut buffer = String::new();
1549        loop {
1550            if self.current_kind() == "eof" {
1551                return Err(self.error("Unterminated YAML single-quoted scalar."));
1552            }
1553            if self.current_kind() == "interpolation" {
1554                self.flush_buffer(&mut buffer, &mut chunks);
1555                chunks.push(YamlChunk::Interpolation(
1556                    self.consume_interpolation("string_fragment")?,
1557                ));
1558                continue;
1559            }
1560            if self.current_char() == Some('\'') {
1561                if self.peek_char(1) == Some('\'') {
1562                    buffer.push('\'');
1563                    self.advance();
1564                    self.advance();
1565                    continue;
1566                }
1567                self.flush_buffer(&mut buffer, &mut chunks);
1568                self.advance();
1569                break;
1570            }
1571            if let Some(ch) = self.current_char() {
1572                buffer.push(ch);
1573                self.advance();
1574            }
1575        }
1576        Ok(YamlSingleQuotedScalarNode {
1577            span: self.span_from(start),
1578            chunks,
1579        })
1580    }
1581
1582    fn parse_plain_scalar(
1583        &mut self,
1584        stop_chars: Option<&[char]>,
1585        leading: Option<Vec<YamlChunk>>,
1586        key_mode: bool,
1587        parent_indent: usize,
1588        requires_indented_continuation: bool,
1589    ) -> BackendResult<YamlPlainScalarNode> {
1590        let start = self.mark();
1591        let mut chunks = leading.unwrap_or_default();
1592        let mut buffer = String::new();
1593        while self.current_kind() != "eof" {
1594            if self.current_kind() == "interpolation" {
1595                self.flush_buffer(&mut buffer, &mut chunks);
1596                chunks.push(YamlChunk::Interpolation(
1597                    self.consume_interpolation("string_fragment")?,
1598                ));
1599                continue;
1600            }
1601            let Some(ch) = self.current_char() else {
1602                break;
1603            };
1604            if ch == '#' && buffer.is_empty() {
1605                return Err(self.error("Expected a YAML value."));
1606            }
1607            if ch == '#'
1608                && self
1609                    .items
1610                    .get(self.index.wrapping_sub(1))
1611                    .and_then(StreamItem::char)
1612                    .is_none_or(char::is_whitespace)
1613            {
1614                break;
1615            }
1616            if ch == '\n' {
1617                if !key_mode {
1618                    if stop_chars.is_none()
1619                        && self.consume_plain_scalar_continuation(
1620                            &mut buffer,
1621                            parent_indent,
1622                            requires_indented_continuation,
1623                        )
1624                    {
1625                        continue;
1626                    }
1627                    if let Some(stop_chars) = stop_chars {
1628                        if self.consume_flow_plain_scalar_continuation(
1629                            &mut buffer,
1630                            stop_chars,
1631                            parent_indent,
1632                        ) {
1633                            continue;
1634                        }
1635                    }
1636                }
1637                break;
1638            }
1639            if stop_chars.is_some_and(|chars| chars.contains(&ch)) {
1640                let colon_without_separator = ch == ':'
1641                    && !matches!(self.peek_char(1), Some(' ' | '\t' | '\n' | ',' | ']' | '}'));
1642                if !colon_without_separator
1643                    && (!key_mode || matches!(self.peek_char(1), Some(' ' | '\t' | '\n')))
1644                {
1645                    break;
1646                }
1647            }
1648            if !key_mode
1649                && stop_chars.is_some()
1650                && ch == ':'
1651                && matches!(self.peek_char(1), Some(' ' | '\n' | ',' | ']' | '}'))
1652            {
1653                break;
1654            }
1655            if !key_mode
1656                && stop_chars.is_none()
1657                && ch == ':'
1658                && matches!(self.peek_char(1), Some(' ' | '\t'))
1659            {
1660                break;
1661            }
1662            if ch == '\t' && stop_chars.is_none() && buffer.contains(':') {
1663                return Err(self.error("Tabs are not allowed as YAML indentation."));
1664            }
1665            buffer.push(ch);
1666            self.advance();
1667        }
1668        self.flush_buffer(&mut buffer, &mut chunks);
1669        Ok(YamlPlainScalarNode {
1670            span: self.span_from(start),
1671            chunks,
1672        })
1673    }
1674
1675    fn consume_plain_scalar_continuation(
1676        &mut self,
1677        buffer: &mut String,
1678        parent_indent: usize,
1679        requires_indented_continuation: bool,
1680    ) -> bool {
1681        let mut probe = self.index;
1682        let mut blank_lines = 0usize;
1683        while self.items.get(probe).and_then(StreamItem::char) == Some('\n') {
1684            probe += 1;
1685            let mut indent = 0usize;
1686            while self.items.get(probe).and_then(StreamItem::char) == Some(' ') {
1687                indent += 1;
1688                probe += 1;
1689            }
1690            match self.items.get(probe).and_then(StreamItem::char) {
1691                Some('\n') => blank_lines += 1,
1692                Some('#') | None => return false,
1693                Some(_)
1694                    if if requires_indented_continuation {
1695                        indent > parent_indent
1696                    } else {
1697                        indent >= parent_indent
1698                    } =>
1699                {
1700                    if indent == parent_indent
1701                        && (self.starts_sequence_item_at(probe)
1702                            || self.line_has_mapping_key_at(probe))
1703                    {
1704                        return false;
1705                    }
1706                    self.index = probe;
1707                    if blank_lines == 0 {
1708                        if !buffer.ends_with(' ') && !buffer.is_empty() {
1709                            buffer.push(' ');
1710                        }
1711                    } else {
1712                        for _ in 0..blank_lines {
1713                            buffer.push('\n');
1714                        }
1715                    }
1716                    return true;
1717                }
1718                _ => return false,
1719            }
1720        }
1721        false
1722    }
1723
1724    fn consume_flow_plain_scalar_continuation(
1725        &mut self,
1726        buffer: &mut String,
1727        stop_chars: &[char],
1728        parent_indent: usize,
1729    ) -> bool {
1730        let mut probe = self.index;
1731        let mut breaks = 0usize;
1732        let continuation_indent;
1733
1734        loop {
1735            if self.items.get(probe).and_then(StreamItem::char) != Some('\n') {
1736                return false;
1737            }
1738            probe += 1;
1739            breaks += 1;
1740
1741            let mut indent = 0usize;
1742            while matches!(
1743                self.items.get(probe).and_then(StreamItem::char),
1744                Some(' ' | '\t')
1745            ) {
1746                indent += 1;
1747                probe += 1;
1748            }
1749
1750            if self.items.get(probe).and_then(StreamItem::char) == Some('#') {
1751                return false;
1752            }
1753
1754            if self.items.get(probe).and_then(StreamItem::char) == Some('\n') {
1755                continue;
1756            }
1757
1758            continuation_indent = indent;
1759            break;
1760        }
1761
1762        let Some(next_char) = self.items.get(probe).and_then(StreamItem::char) else {
1763            return false;
1764        };
1765        if continuation_indent < parent_indent {
1766            return false;
1767        }
1768        if stop_chars.contains(&next_char) {
1769            return false;
1770        }
1771
1772        self.index = probe;
1773        if breaks == 1 {
1774            if !buffer.ends_with(' ') && !buffer.is_empty() {
1775                buffer.push(' ');
1776            }
1777        } else {
1778            for _ in 0..(breaks - 1) {
1779                buffer.push('\n');
1780            }
1781        }
1782        true
1783    }
1784
1785    fn parse_block_scalar(&mut self, parent_indent: usize) -> BackendResult<YamlBlockScalarNode> {
1786        let start = self.mark();
1787        let style = self.current_char().unwrap_or('|').to_string();
1788        self.advance();
1789        let mut chomping = None;
1790        let mut indent_indicator = None;
1791        for _ in 0..2 {
1792            if chomping.is_none() && matches!(self.current_char(), Some('+' | '-')) {
1793                chomping = self.current_char().map(|ch| ch.to_string());
1794                self.advance();
1795                continue;
1796            }
1797            if indent_indicator.is_none()
1798                && self.current_char().is_some_and(|ch| ch.is_ascii_digit())
1799            {
1800                indent_indicator = self
1801                    .current_char()
1802                    .and_then(|ch| ch.to_digit(10))
1803                    .map(|value| value as usize);
1804                self.advance();
1805                continue;
1806            }
1807            break;
1808        }
1809        self.consume_line_end_required()?;
1810        let block_indent = if let Some(indent_indicator) = indent_indicator {
1811            indent_indicator
1812        } else {
1813            self.infer_block_scalar_indent(parent_indent)?
1814        };
1815        let mut chunks = Vec::new();
1816        let mut buffer = String::new();
1817        while self.current_kind() != "eof" {
1818            if self.line_is_blank() {
1819                while self.current_char() == Some(' ') {
1820                    self.advance();
1821                }
1822                if self.current_char() == Some('\n') {
1823                    buffer.push('\n');
1824                    self.advance();
1825                    continue;
1826                }
1827            }
1828            if self.current_line_indent() < block_indent {
1829                break;
1830            }
1831            self.consume_indent(block_indent)?;
1832            while self.current_kind() != "eof" && self.current_char() != Some('\n') {
1833                if self.current_kind() == "interpolation" {
1834                    self.flush_buffer(&mut buffer, &mut chunks);
1835                    chunks.push(YamlChunk::Interpolation(
1836                        self.consume_interpolation("string_fragment")?,
1837                    ));
1838                    continue;
1839                }
1840                if let Some(ch) = self.current_char() {
1841                    buffer.push(ch);
1842                    self.advance();
1843                }
1844            }
1845            if self.current_char() == Some('\n') {
1846                buffer.push('\n');
1847                self.advance();
1848            }
1849        }
1850        self.flush_buffer(&mut buffer, &mut chunks);
1851        Ok(YamlBlockScalarNode {
1852            span: self.span_from(start),
1853            style,
1854            chomping,
1855            indent_indicator,
1856            chunks,
1857        })
1858    }
1859
1860    fn infer_block_scalar_indent(&self, parent_indent: usize) -> BackendResult<usize> {
1861        let mut probe = self.index;
1862        let mut leading_blank_indent = 0usize;
1863        while probe < self.items.len() {
1864            let mut indent = 0usize;
1865            while self.items.get(probe).and_then(StreamItem::char) == Some(' ') {
1866                indent += 1;
1867                probe += 1;
1868            }
1869            match self.items.get(probe).and_then(StreamItem::char) {
1870                Some('\n') => {
1871                    leading_blank_indent = leading_blank_indent.max(indent);
1872                    probe += 1;
1873                }
1874                Some(_) => {
1875                    let is_zero_indented_comment_like_content = parent_indent == 0
1876                        && indent == 0
1877                        && self.items.get(probe).and_then(StreamItem::char) == Some('#');
1878                    if !is_zero_indented_comment_like_content && leading_blank_indent > indent {
1879                        return Err(self.error("Incorrect YAML indentation."));
1880                    }
1881                    return Ok(if parent_indent == 0 {
1882                        indent
1883                    } else {
1884                        indent.max(parent_indent + 1)
1885                    });
1886                }
1887                None => break,
1888            }
1889        }
1890        if leading_blank_indent > parent_indent {
1891            return Err(self.error("Incorrect YAML indentation."));
1892        }
1893        if parent_indent == 0 {
1894            Ok(0)
1895        } else {
1896            Ok(parent_indent + 1)
1897        }
1898    }
1899
1900    fn line_is_blank(&self) -> bool {
1901        let mut probe = self.index;
1902        while self.items.get(probe).and_then(StreamItem::char) == Some(' ') {
1903            probe += 1;
1904        }
1905        matches!(
1906            self.items.get(probe),
1907            Some(StreamItem::Char { ch: '\n', .. }) | Some(StreamItem::Eof { .. })
1908        )
1909    }
1910
1911    fn flush_buffer(&self, buffer: &mut String, chunks: &mut Vec<YamlChunk>) {
1912        if buffer.is_empty() {
1913            return;
1914        }
1915        chunks.push(YamlChunk::Text(YamlTextChunkNode {
1916            span: SourceSpan::point(0, 0),
1917            value: std::mem::take(buffer),
1918        }));
1919    }
1920
1921    fn parse_double_quoted_escape(&mut self) -> BackendResult<String> {
1922        self.consume_char('\\')?;
1923        if self.current_char() == Some('\r') {
1924            self.advance();
1925        }
1926        if self.current_char() == Some('\n') {
1927            self.advance();
1928            while matches!(self.current_char(), Some(' ' | '\t')) {
1929                self.advance();
1930            }
1931            return Ok(String::new());
1932        }
1933        let escape = self
1934            .current_char()
1935            .ok_or_else(|| self.error("Incomplete YAML escape sequence."))?;
1936        self.advance();
1937        let mapped = match escape {
1938            '0' => Some("\0".to_owned()),
1939            'a' => Some("\u{0007}".to_owned()),
1940            'b' => Some("\u{0008}".to_owned()),
1941            't' | '\t' => Some("\t".to_owned()),
1942            'n' => Some("\n".to_owned()),
1943            'v' => Some("\u{000b}".to_owned()),
1944            'f' => Some("\u{000c}".to_owned()),
1945            'r' => Some("\r".to_owned()),
1946            'e' => Some("\u{001b}".to_owned()),
1947            ' ' => Some(" ".to_owned()),
1948            '"' => Some("\"".to_owned()),
1949            '/' => Some("/".to_owned()),
1950            '\\' => Some("\\".to_owned()),
1951            'N' => Some("\u{0085}".to_owned()),
1952            '_' => Some("\u{00a0}".to_owned()),
1953            'L' => Some("\u{2028}".to_owned()),
1954            'P' => Some("\u{2029}".to_owned()),
1955            _ => None,
1956        };
1957        if let Some(value) = mapped {
1958            return Ok(value);
1959        }
1960        let (digits, radix_name) = match escape {
1961            'x' => (2, "hex"),
1962            'u' => (4, "unicode"),
1963            'U' => (8, "unicode"),
1964            _ => {
1965                return Err(self.error("Invalid YAML escape sequence."));
1966            }
1967        };
1968        let chars = self.collect_exact_chars(digits)?;
1969        let codepoint = u32::from_str_radix(&chars, 16)
1970            .map_err(|_| self.error(format!("Invalid YAML {radix_name} escape.")))?;
1971        char::from_u32(codepoint)
1972            .map(|value| value.to_string())
1973            .ok_or_else(|| self.error("Invalid YAML unicode escape."))
1974    }
1975
1976    fn collect_exact_chars(&mut self, count: usize) -> BackendResult<String> {
1977        let mut chars = String::new();
1978        for _ in 0..count {
1979            let ch = self
1980                .current_char()
1981                .ok_or_else(|| self.error("Unexpected end of YAML escape sequence."))?;
1982            chars.push(ch);
1983            self.advance();
1984        }
1985        Ok(chars)
1986    }
1987
1988    fn consume_char(&mut self, expected: char) -> BackendResult<()> {
1989        if self.current_char() != Some(expected) {
1990            return Err(self.error(format!("Expected {expected:?} in YAML template.")));
1991        }
1992        self.advance();
1993        Ok(())
1994    }
1995
1996    fn consume_interpolation(&mut self, role: &str) -> BackendResult<YamlInterpolationNode> {
1997        let (interpolation_index, span) = match self.current() {
1998            StreamItem::Interpolation {
1999                interpolation_index,
2000                span,
2001                ..
2002            } => (*interpolation_index, span.clone()),
2003            _ => return Err(self.error("Expected an interpolation.")),
2004        };
2005        self.advance();
2006        Ok(YamlInterpolationNode {
2007            span,
2008            interpolation_index,
2009            role: role.to_owned(),
2010        })
2011    }
2012}
2013
2014#[derive(Clone, Debug)]
2015struct YamlDocumentFragment {
2016    directives: Vec<String>,
2017    explicit_start: bool,
2018    explicit_end: bool,
2019    items: Vec<StreamItem>,
2020}
2021
2022pub fn parse_template_with_profile(
2023    template: &TemplateInput,
2024    _profile: YamlProfile,
2025) -> BackendResult<YamlStreamNode> {
2026    // YAML only exposes 1.2.2 in this phase, but the parameter keeps the
2027    // parser wired for profile-aware dispatch once additional variants land.
2028    let fragments = split_stream(template)?;
2029    let mut documents = Vec::new();
2030    for fragment in fragments {
2031        let mut parser = YamlParser::from_items(fragment.items);
2032        let mut document = parser.parse()?;
2033        document.directives = fragment.directives;
2034        document.explicit_start = fragment.explicit_start;
2035        document.explicit_end = fragment.explicit_end;
2036        documents.push(document);
2037    }
2038    Ok(YamlStreamNode {
2039        span: documents
2040            .first()
2041            .map(|document| document.span.clone())
2042            .unwrap_or_else(|| SourceSpan::point(0, 0)),
2043        documents,
2044    })
2045}
2046
2047pub fn parse_template(template: &TemplateInput) -> BackendResult<YamlStreamNode> {
2048    parse_template_with_profile(template, YamlProfile::default())
2049}
2050
2051pub fn check_template_with_profile(
2052    template: &TemplateInput,
2053    profile: YamlProfile,
2054) -> BackendResult<()> {
2055    parse_template_with_profile(template, profile).map(|_| ())
2056}
2057
2058pub fn check_template(template: &TemplateInput) -> BackendResult<()> {
2059    check_template_with_profile(template, YamlProfile::default())
2060}
2061
2062pub fn format_template_with_profile(
2063    template: &TemplateInput,
2064    profile: YamlProfile,
2065) -> BackendResult<String> {
2066    let stream = parse_template_with_profile(template, profile)?;
2067    format_yaml_stream(template, &stream)
2068}
2069
2070pub fn format_template(template: &TemplateInput) -> BackendResult<String> {
2071    format_template_with_profile(template, YamlProfile::default())
2072}
2073
2074pub fn normalize_documents_with_profile(
2075    documents: &[YamlOwned],
2076    _profile: YamlProfile,
2077) -> BackendResult<NormalizedStream> {
2078    // YAML normalization does not vary by profile yet, but the signature does
2079    // so future profile-specific semantics can plug in without reshaping APIs.
2080    documents
2081        .iter()
2082        .map(normalize_document)
2083        .collect::<BackendResult<Vec<_>>>()
2084        .map(NormalizedStream::new)
2085}
2086
2087pub fn normalize_documents(documents: &[YamlOwned]) -> BackendResult<NormalizedStream> {
2088    normalize_documents_with_profile(documents, YamlProfile::default())
2089}
2090
2091pub fn align_normalized_stream_with_ast(
2092    stream: &YamlStreamNode,
2093    normalized: &mut NormalizedStream,
2094) {
2095    for (document_node, document) in stream.documents.iter().zip(normalized.documents.iter_mut()) {
2096        align_document_with_ast(document_node, document);
2097    }
2098}
2099
2100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2101enum CollectionRenderContext {
2102    BlockAllowed,
2103    FlowRequired,
2104}
2105
2106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2107enum RenderedLayout {
2108    Inline,
2109    Block,
2110    Flow,
2111}
2112
2113struct RenderedYamlValue {
2114    text: String,
2115    layout: RenderedLayout,
2116    empty_collection: bool,
2117}
2118
2119impl RenderedYamlValue {
2120    fn inline(text: String) -> Self {
2121        Self {
2122            text,
2123            layout: RenderedLayout::Inline,
2124            empty_collection: false,
2125        }
2126    }
2127
2128    fn block(text: String) -> Self {
2129        Self {
2130            text,
2131            layout: RenderedLayout::Block,
2132            empty_collection: false,
2133        }
2134    }
2135
2136    fn flow(text: String, empty_collection: bool) -> Self {
2137        Self {
2138            text,
2139            layout: RenderedLayout::Flow,
2140            empty_collection,
2141        }
2142    }
2143}
2144
2145fn format_yaml_stream(template: &TemplateInput, stream: &YamlStreamNode) -> BackendResult<String> {
2146    let mut parts = Vec::with_capacity(stream.documents.len());
2147    for document in &stream.documents {
2148        let mut lines = Vec::new();
2149        lines.extend(document.directives.iter().cloned());
2150        if document.explicit_start || !document.directives.is_empty() || stream.documents.len() > 1
2151        {
2152            lines.push("---".to_owned());
2153        }
2154        lines.push(
2155            format_yaml_value(
2156                template,
2157                &document.value,
2158                0,
2159                CollectionRenderContext::BlockAllowed,
2160            )?
2161            .text,
2162        );
2163        if document.explicit_end {
2164            lines.push("...".to_owned());
2165        }
2166        parts.push(lines.join("\n"));
2167    }
2168    Ok(parts.join("\n"))
2169}
2170
2171fn format_yaml_value(
2172    template: &TemplateInput,
2173    node: &YamlValueNode,
2174    indent: usize,
2175    context: CollectionRenderContext,
2176) -> BackendResult<RenderedYamlValue> {
2177    match node {
2178        YamlValueNode::Decorated(node) => {
2179            let mut prefix = String::new();
2180            if let Some(tag) = &node.tag {
2181                prefix.push('!');
2182                prefix.push_str(&assemble_yaml_chunks(template, &tag.chunks, true)?);
2183            }
2184            if let Some(anchor) = &node.anchor {
2185                if !prefix.is_empty() {
2186                    prefix.push(' ');
2187                }
2188                prefix.push('&');
2189                prefix.push_str(&assemble_yaml_chunks(template, &anchor.chunks, true)?);
2190            }
2191            let rendered = format_yaml_value(template, node.value.as_ref(), indent, context)?;
2192            if prefix.is_empty() {
2193                Ok(rendered)
2194            } else {
2195                Ok(apply_rendered_prefix(prefix, rendered))
2196            }
2197        }
2198        YamlValueNode::Mapping(node) => format_yaml_mapping(template, node, indent, context),
2199        YamlValueNode::Sequence(node) => format_yaml_sequence(template, node, indent, context),
2200        YamlValueNode::Interpolation(node) => Ok(RenderedYamlValue::inline(
2201            interpolation_raw_source(template, node.interpolation_index, &node.span, "YAML value")?,
2202        )),
2203        YamlValueNode::Scalar(YamlScalarNode::Alias(node)) => Ok(RenderedYamlValue::inline(
2204            format!("*{}", assemble_yaml_chunks(template, &node.chunks, true)?),
2205        )),
2206        YamlValueNode::Scalar(YamlScalarNode::Block(node)) => Ok(RenderedYamlValue::inline(
2207            format_yaml_block_scalar(template, node, indent)?,
2208        )),
2209        YamlValueNode::Scalar(YamlScalarNode::DoubleQuoted(node)) => Ok(RenderedYamlValue::inline(
2210            serde_json::to_string(&assemble_yaml_chunks(template, &node.chunks, false)?).unwrap(),
2211        )),
2212        YamlValueNode::Scalar(YamlScalarNode::SingleQuoted(node)) => {
2213            Ok(RenderedYamlValue::inline(format!(
2214                "'{}'",
2215                assemble_yaml_chunks(template, &node.chunks, false)?.replace('\'', "''")
2216            )))
2217        }
2218        YamlValueNode::Scalar(YamlScalarNode::Plain(node)) => Ok(RenderedYamlValue::inline(
2219            format_yaml_plain_scalar(template, node)?,
2220        )),
2221    }
2222}
2223
2224fn format_yaml_mapping(
2225    template: &TemplateInput,
2226    node: &YamlMappingNode,
2227    indent: usize,
2228    context: CollectionRenderContext,
2229) -> BackendResult<RenderedYamlValue> {
2230    if node.flow || context == CollectionRenderContext::FlowRequired {
2231        let mut entries = Vec::with_capacity(node.entries.len());
2232        for entry in &node.entries {
2233            let rendered_key = match &entry.key.value {
2234                YamlKeyValue::Complex(key) => {
2235                    format_yaml_value(template, key, indent, CollectionRenderContext::FlowRequired)?
2236                        .text
2237                }
2238                _ => format_yaml_key(template, &entry.key)?,
2239            };
2240            let rendered_key = normalize_flow_key_text(rendered_key);
2241            let rendered_value = format_yaml_value(
2242                template,
2243                &entry.value,
2244                indent,
2245                CollectionRenderContext::FlowRequired,
2246            )?;
2247            entries.push(format!("{rendered_key}: {}", rendered_value.text));
2248        }
2249        return Ok(RenderedYamlValue::flow(
2250            format!("{{ {} }}", entries.join(", ")),
2251            node.entries.is_empty(),
2252        ));
2253    }
2254
2255    let mut rendered = String::new();
2256    for entry in &node.entries {
2257        if let YamlKeyValue::Complex(key) = &entry.key.value {
2258            let rendered_key = format_yaml_value(
2259                template,
2260                key,
2261                indent + 2,
2262                CollectionRenderContext::FlowRequired,
2263            )?;
2264            let rendered_value = format_yaml_value(
2265                template,
2266                &entry.value,
2267                indent + 2,
2268                CollectionRenderContext::BlockAllowed,
2269            )?;
2270            push_yaml_section(
2271                &mut rendered,
2272                format!("{}? {}", " ".repeat(indent), rendered_key.text),
2273            );
2274            push_rendered_value_with_prefix(
2275                &mut rendered,
2276                format!("{}:", " ".repeat(indent)),
2277                rendered_value,
2278            );
2279            continue;
2280        }
2281        let key = format_yaml_key(template, &entry.key)?;
2282        let rendered_value = format_yaml_value(
2283            template,
2284            &entry.value,
2285            indent + 2,
2286            CollectionRenderContext::BlockAllowed,
2287        )?;
2288        push_rendered_value_with_prefix(
2289            &mut rendered,
2290            format!("{}{}:", " ".repeat(indent), key),
2291            rendered_value,
2292        );
2293    }
2294    Ok(RenderedYamlValue::block(rendered))
2295}
2296
2297fn format_yaml_sequence(
2298    template: &TemplateInput,
2299    node: &YamlSequenceNode,
2300    indent: usize,
2301    context: CollectionRenderContext,
2302) -> BackendResult<RenderedYamlValue> {
2303    if node.flow || context == CollectionRenderContext::FlowRequired {
2304        let items = node
2305            .items
2306            .iter()
2307            .map(|item| {
2308                format_yaml_value(
2309                    template,
2310                    item,
2311                    indent,
2312                    CollectionRenderContext::FlowRequired,
2313                )
2314                .map(|item| item.text)
2315            })
2316            .collect::<BackendResult<Vec<_>>>()?;
2317        return Ok(RenderedYamlValue::flow(
2318            format!("[ {} ]", items.join(", ")),
2319            node.items.is_empty(),
2320        ));
2321    }
2322
2323    let mut rendered = String::new();
2324    for item in &node.items {
2325        let rendered_item = format_yaml_value(
2326            template,
2327            item,
2328            indent + 2,
2329            CollectionRenderContext::BlockAllowed,
2330        )?;
2331        push_rendered_value_with_prefix(
2332            &mut rendered,
2333            format!("{}-", " ".repeat(indent)),
2334            rendered_item,
2335        );
2336    }
2337    Ok(RenderedYamlValue::block(rendered))
2338}
2339
2340fn format_yaml_key(template: &TemplateInput, node: &YamlKeyNode) -> BackendResult<String> {
2341    match &node.value {
2342        YamlKeyValue::Interpolation(value) => {
2343            interpolation_raw_source(template, value.interpolation_index, &value.span, "YAML key")
2344        }
2345        YamlKeyValue::Scalar(YamlScalarNode::DoubleQuoted(value)) => Ok(serde_json::to_string(
2346            &assemble_yaml_chunks(template, &value.chunks, false)?,
2347        )
2348        .unwrap()),
2349        YamlKeyValue::Scalar(YamlScalarNode::SingleQuoted(value)) => Ok(format!(
2350            "'{}'",
2351            assemble_yaml_chunks(template, &value.chunks, false)?.replace('\'', "''")
2352        )),
2353        YamlKeyValue::Scalar(YamlScalarNode::Plain(value)) => {
2354            format_yaml_plain_scalar(template, value)
2355        }
2356        YamlKeyValue::Scalar(YamlScalarNode::Alias(node)) => Ok(format!(
2357            "*{}",
2358            assemble_yaml_chunks(template, &node.chunks, true)?
2359        )),
2360        YamlKeyValue::Scalar(YamlScalarNode::Block(node)) => {
2361            format_yaml_block_scalar(template, node, 0)
2362        }
2363        YamlKeyValue::Complex(value) => {
2364            format_yaml_value(template, value, 0, CollectionRenderContext::FlowRequired)
2365                .map(|value| value.text)
2366        }
2367    }
2368}
2369
2370fn format_yaml_plain_scalar(
2371    template: &TemplateInput,
2372    node: &YamlPlainScalarNode,
2373) -> BackendResult<String> {
2374    let text = assemble_yaml_chunks(template, &node.chunks, false)?
2375        .trim()
2376        .to_owned();
2377    if text.is_empty() {
2378        return Ok("null".to_owned());
2379    }
2380    if text.contains('\n') {
2381        return Ok(serde_json::to_string(&text).unwrap());
2382    }
2383    Ok(text)
2384}
2385
2386fn format_yaml_block_scalar(
2387    template: &TemplateInput,
2388    node: &YamlBlockScalarNode,
2389    indent: usize,
2390) -> BackendResult<String> {
2391    let mut header = node.style.clone();
2392    if let Some(chomping) = &node.chomping {
2393        header.push_str(chomping);
2394    }
2395    if let Some(indent_indicator) = node.indent_indicator {
2396        header.push_str(&indent_indicator.to_string());
2397    }
2398    let content = assemble_yaml_chunks(template, &node.chunks, false)?;
2399    if content.is_empty() {
2400        let mut rendered = header;
2401        rendered.push('\n');
2402        return Ok(rendered);
2403    }
2404
2405    let indentation_width = node
2406        .indent_indicator
2407        .map_or(indent, |indicator| indent.saturating_sub(2) + indicator);
2408    let indentation = " ".repeat(indentation_width);
2409    let trailing_breaks = content.chars().rev().take_while(|&ch| ch == '\n').count();
2410    let body_content = content.trim_end_matches('\n');
2411    let body = if body_content.is_empty() {
2412        String::new()
2413    } else {
2414        body_content
2415            .split('\n')
2416            .map(|line| format!("{indentation}{line}"))
2417            .collect::<Vec<_>>()
2418            .join("\n")
2419    };
2420    let trailing = match node.chomping.as_deref() {
2421        Some("-") => 0,
2422        Some("+") => trailing_breaks.max(1),
2423        _ => usize::from(!content.is_empty()),
2424    };
2425
2426    let mut rendered = header;
2427    rendered.push('\n');
2428    rendered.push_str(&body);
2429    for _ in 0..trailing {
2430        rendered.push('\n');
2431    }
2432    Ok(rendered)
2433}
2434
2435fn assemble_yaml_chunks(
2436    template: &TemplateInput,
2437    chunks: &[YamlChunk],
2438    _metadata: bool,
2439) -> BackendResult<String> {
2440    let mut text = String::new();
2441    for chunk in chunks {
2442        match chunk {
2443            YamlChunk::Text(chunk) => text.push_str(&chunk.value),
2444            YamlChunk::Interpolation(chunk) => text.push_str(&interpolation_raw_source(
2445                template,
2446                chunk.interpolation_index,
2447                &chunk.span,
2448                "YAML fragment",
2449            )?),
2450        }
2451    }
2452    Ok(text)
2453}
2454
2455fn apply_rendered_prefix(prefix: String, rendered: RenderedYamlValue) -> RenderedYamlValue {
2456    if rendered.layout == RenderedLayout::Block {
2457        return RenderedYamlValue {
2458            text: format!("{prefix}\n{}", rendered.text),
2459            layout: RenderedLayout::Block,
2460            empty_collection: rendered.empty_collection,
2461        };
2462    }
2463    RenderedYamlValue {
2464        text: format!("{prefix} {}", rendered.text),
2465        layout: rendered.layout,
2466        empty_collection: rendered.empty_collection,
2467    }
2468}
2469
2470fn push_rendered_value_with_prefix(
2471    output: &mut String,
2472    prefix: String,
2473    rendered: RenderedYamlValue,
2474) {
2475    if rendered.layout == RenderedLayout::Block && !rendered.empty_collection {
2476        if let Some((first, rest)) = rendered.text.split_once('\n')
2477            && !first.starts_with(' ')
2478        {
2479            push_yaml_section(output, format!("{prefix} {first}"));
2480            if !rest.is_empty() {
2481                push_yaml_section(output, rest.to_owned());
2482            }
2483            return;
2484        }
2485        push_yaml_section(output, prefix);
2486        push_yaml_section(output, rendered.text);
2487        return;
2488    }
2489
2490    push_yaml_section(output, format!("{prefix} {}", rendered.text));
2491}
2492
2493fn push_yaml_section(output: &mut String, section: String) {
2494    if output.is_empty() {
2495        output.push_str(&section);
2496        return;
2497    }
2498    if !output.ends_with('\n') {
2499        output.push('\n');
2500    }
2501    output.push_str(&section);
2502}
2503
2504fn normalize_flow_key_text(rendered_key: String) -> String {
2505    if rendered_key.starts_with('?') && !rendered_key.starts_with("? ") {
2506        return format!("? {}", &rendered_key[1..]);
2507    }
2508    rendered_key
2509}
2510
2511fn interpolation_raw_source(
2512    template: &TemplateInput,
2513    interpolation_index: usize,
2514    span: &SourceSpan,
2515    context: &str,
2516) -> BackendResult<String> {
2517    template
2518        .interpolation_raw_source(interpolation_index)
2519        .map(str::to_owned)
2520        .ok_or_else(|| {
2521            let expression = template.interpolation(interpolation_index).map_or_else(
2522                || format!("slot {interpolation_index}"),
2523                |value| value.expression_label().to_owned(),
2524            );
2525            BackendError::semantic_at(
2526                "yaml.format",
2527                format!(
2528                    "Cannot format {context} interpolation {expression:?} without raw source text."
2529                ),
2530                Some(span.clone()),
2531            )
2532        })
2533}
2534
2535fn normalize_document(document: &YamlOwned) -> BackendResult<NormalizedDocument> {
2536    if matches!(document, YamlOwned::BadValue) {
2537        return Ok(NormalizedDocument::Empty);
2538    }
2539    Ok(NormalizedDocument::Value(normalize_value(document)?))
2540}
2541
2542fn align_document_with_ast(node: &YamlDocumentNode, document: &mut NormalizedDocument) {
2543    if let NormalizedDocument::Value(value) = document {
2544        align_value_with_ast(&node.value, value);
2545    }
2546}
2547
2548fn normalize_value(value: &YamlOwned) -> BackendResult<NormalizedValue> {
2549    match value {
2550        YamlOwned::Value(value) => normalize_scalar(value),
2551        YamlOwned::Representation(value, style, tag) => {
2552            normalize_representation_value(value, *style, tag.as_ref())
2553        }
2554        YamlOwned::Sequence(values) => values
2555            .iter()
2556            .map(normalize_value)
2557            .collect::<BackendResult<Vec<_>>>()
2558            .map(NormalizedValue::Sequence),
2559        YamlOwned::Mapping(values) => normalize_mapping(values),
2560        YamlOwned::Tagged(tag, value) => {
2561            normalize_tagged_value(tag.handle.as_str(), &tag.suffix, value)
2562        }
2563        YamlOwned::Alias(_) => Err(BackendError::semantic(
2564            "Rendered YAML still contains an unresolved alias after validation.",
2565        )),
2566        YamlOwned::BadValue => Ok(NormalizedValue::Null),
2567    }
2568}
2569
2570fn normalize_representation_value(
2571    value: &str,
2572    style: ScalarStyle,
2573    tag: Option<&Tag>,
2574) -> BackendResult<NormalizedValue> {
2575    let parsed = ScalarOwned::parse_from_cow_and_metadata(
2576        Cow::Borrowed(value),
2577        style,
2578        tag.map(Cow::Borrowed).as_ref(),
2579    )
2580    .ok_or_else(|| {
2581        BackendError::semantic(format!(
2582            "Rendered YAML representation {value:?} could not be normalized with the active tag/schema."
2583        ))
2584    })?;
2585    normalize_scalar(&parsed)
2586}
2587
2588fn normalize_scalar(value: &saphyr::ScalarOwned) -> BackendResult<NormalizedValue> {
2589    match value {
2590        saphyr::ScalarOwned::Null => Ok(NormalizedValue::Null),
2591        saphyr::ScalarOwned::Boolean(value) => Ok(NormalizedValue::Bool(*value)),
2592        saphyr::ScalarOwned::Integer(value) => Ok(NormalizedValue::Integer((*value).into())),
2593        saphyr::ScalarOwned::FloatingPoint(value) => {
2594            let value = value.into_inner();
2595            if !value.is_finite() {
2596                return Err(BackendError::semantic(
2597                    "Rendered YAML contained a non-finite float, which this backend does not normalize.",
2598                ));
2599            }
2600            Ok(NormalizedValue::Float(NormalizedFloat::finite(value)))
2601        }
2602        saphyr::ScalarOwned::String(value) => Ok(NormalizedValue::String(value.clone())),
2603    }
2604}
2605
2606fn normalize_mapping(values: &saphyr::MappingOwned) -> BackendResult<NormalizedValue> {
2607    let mut entries = Vec::new();
2608    for (key, value) in values {
2609        if is_merge_key(key) {
2610            apply_merge_entries(value, &mut entries)?;
2611            continue;
2612        }
2613        insert_mapping_entry(
2614            &mut entries,
2615            normalize_key(key)?,
2616            normalize_value(value)?,
2617            true,
2618        );
2619    }
2620    Ok(NormalizedValue::Mapping(entries))
2621}
2622
2623fn align_value_with_ast(node: &YamlValueNode, normalized: &mut NormalizedValue) {
2624    match node {
2625        YamlValueNode::Decorated(decorated) => {
2626            if decorated.tag.as_ref().is_some_and(is_set_tag_literal)
2627                && let NormalizedValue::Mapping(entries) = normalized
2628            {
2629                let keys = entries.iter().map(|entry| entry.key.clone()).collect();
2630                *normalized = NormalizedValue::Set(keys);
2631                return;
2632            }
2633            align_value_with_ast(&decorated.value, normalized);
2634        }
2635        YamlValueNode::Mapping(mapping) => {
2636            if let NormalizedValue::Mapping(entries) = normalized {
2637                for (entry_node, entry) in mapping.entries.iter().zip(entries.iter_mut()) {
2638                    align_key_with_ast(mapping.flow, &entry_node.key, &mut entry.key);
2639                    align_value_with_ast(&entry_node.value, &mut entry.value);
2640                }
2641            }
2642        }
2643        YamlValueNode::Sequence(sequence) => {
2644            if let NormalizedValue::Sequence(values) = normalized {
2645                for (item_node, value) in sequence.items.iter().zip(values.iter_mut()) {
2646                    align_value_with_ast(item_node, value);
2647                }
2648            }
2649        }
2650        YamlValueNode::Scalar(_) | YamlValueNode::Interpolation(_) => {}
2651    }
2652}
2653
2654fn align_key_with_ast(flow_mapping: bool, key: &YamlKeyNode, normalized: &mut NormalizedKey) {
2655    match &key.value {
2656        YamlKeyValue::Scalar(YamlScalarNode::Plain(node))
2657            if flow_mapping && plain_scalar_starts_with_question(node) =>
2658        {
2659            if let NormalizedKey::String(value) = normalized
2660                && let Some(stripped) = value.strip_prefix('?')
2661            {
2662                *value = stripped.to_owned();
2663            }
2664        }
2665        YamlKeyValue::Complex(value) => align_key_value_with_ast(value, normalized),
2666        YamlKeyValue::Scalar(_) | YamlKeyValue::Interpolation(_) => {}
2667    }
2668}
2669
2670fn align_key_value_with_ast(node: &YamlValueNode, normalized: &mut NormalizedKey) {
2671    match node {
2672        YamlValueNode::Decorated(decorated) => {
2673            align_key_value_with_ast(&decorated.value, normalized)
2674        }
2675        YamlValueNode::Sequence(sequence) => {
2676            if let NormalizedKey::Sequence(keys) = normalized {
2677                for (item_node, key) in sequence.items.iter().zip(keys.iter_mut()) {
2678                    align_key_value_with_ast(item_node, key);
2679                }
2680            }
2681        }
2682        YamlValueNode::Mapping(mapping) => {
2683            if let NormalizedKey::Mapping(entries) = normalized {
2684                for (entry_node, entry) in mapping.entries.iter().zip(entries.iter_mut()) {
2685                    align_key_with_ast(mapping.flow, &entry_node.key, &mut entry.key);
2686                    align_key_value_with_ast(&entry_node.value, &mut entry.value);
2687                }
2688            }
2689        }
2690        YamlValueNode::Scalar(_) | YamlValueNode::Interpolation(_) => {}
2691    }
2692}
2693
2694fn plain_scalar_starts_with_question(node: &YamlPlainScalarNode) -> bool {
2695    let Some(YamlChunk::Text(text)) = node.chunks.first() else {
2696        return false;
2697    };
2698    text.value.starts_with('?')
2699}
2700
2701fn is_set_tag_literal(tag: &YamlTagNode) -> bool {
2702    let mut literal = String::new();
2703    for chunk in &tag.chunks {
2704        let YamlChunk::Text(text) = chunk else {
2705            return false;
2706        };
2707        literal.push_str(&text.value);
2708    }
2709    matches!(
2710        literal.as_str(),
2711        "!set" | "!!set" | "<tag:yaml.org,2002:set>" | "tag:yaml.org,2002:set"
2712    )
2713}
2714
2715fn normalize_key(value: &YamlOwned) -> BackendResult<NormalizedKey> {
2716    match value {
2717        YamlOwned::Value(value) => match value {
2718            saphyr::ScalarOwned::Null => Ok(NormalizedKey::Null),
2719            saphyr::ScalarOwned::Boolean(value) => Ok(NormalizedKey::Bool(*value)),
2720            saphyr::ScalarOwned::Integer(value) => Ok(NormalizedKey::Integer((*value).into())),
2721            saphyr::ScalarOwned::FloatingPoint(value) => {
2722                let value = value.into_inner();
2723                if !value.is_finite() {
2724                    return Err(BackendError::semantic(
2725                        "Rendered YAML contained a non-finite float mapping key, which this backend does not normalize.",
2726                    ));
2727                }
2728                Ok(NormalizedKey::Float(NormalizedFloat::finite(value)))
2729            }
2730            saphyr::ScalarOwned::String(value) => Ok(NormalizedKey::String(value.clone())),
2731        },
2732        YamlOwned::Representation(value, style, tag) => {
2733            normalize_representation_key(value, *style, tag.as_ref())
2734        }
2735        YamlOwned::Sequence(values) => values
2736            .iter()
2737            .map(normalize_key)
2738            .collect::<BackendResult<Vec<_>>>()
2739            .map(NormalizedKey::Sequence),
2740        YamlOwned::Mapping(values) => values
2741            .iter()
2742            .map(|(key, value)| {
2743                Ok(NormalizedKeyEntry {
2744                    key: normalize_key(key)?,
2745                    value: normalize_key(value)?,
2746                })
2747            })
2748            .collect::<BackendResult<Vec<_>>>()
2749            .map(NormalizedKey::Mapping),
2750        YamlOwned::Tagged(tag, value) => {
2751            normalize_tagged_key(tag.handle.as_str(), &tag.suffix, value)
2752        }
2753        YamlOwned::Alias(_) => Err(BackendError::semantic(
2754            "Rendered YAML still contains an unresolved alias after validation.",
2755        )),
2756        YamlOwned::BadValue => Ok(NormalizedKey::Null),
2757    }
2758}
2759
2760fn normalize_representation_key(
2761    value: &str,
2762    style: ScalarStyle,
2763    tag: Option<&Tag>,
2764) -> BackendResult<NormalizedKey> {
2765    let parsed = ScalarOwned::parse_from_cow_and_metadata(
2766        Cow::Borrowed(value),
2767        style,
2768        tag.map(Cow::Borrowed).as_ref(),
2769    )
2770    .ok_or_else(|| {
2771        BackendError::semantic(format!(
2772            "Rendered YAML key representation {value:?} could not be normalized with the active tag/schema."
2773        ))
2774    })?;
2775    match parsed {
2776        ScalarOwned::Null => Ok(NormalizedKey::Null),
2777        ScalarOwned::Boolean(value) => Ok(NormalizedKey::Bool(value)),
2778        ScalarOwned::Integer(value) => Ok(NormalizedKey::Integer(value.into())),
2779        ScalarOwned::FloatingPoint(value) => {
2780            let value = value.into_inner();
2781            if !value.is_finite() {
2782                return Err(BackendError::semantic(
2783                    "Rendered YAML contained a non-finite float mapping key, which this backend does not normalize.",
2784                ));
2785            }
2786            Ok(NormalizedKey::Float(NormalizedFloat::finite(value)))
2787        }
2788        ScalarOwned::String(value) => Ok(NormalizedKey::String(value)),
2789    }
2790}
2791
2792fn normalize_tagged_value(
2793    handle: &str,
2794    suffix: &str,
2795    value: &YamlOwned,
2796) -> BackendResult<NormalizedValue> {
2797    if is_yaml_core_tag(handle, suffix, "set") {
2798        let YamlOwned::Mapping(values) = value else {
2799            return Err(BackendError::semantic(
2800                "YAML !!set nodes must normalize from a mapping value.",
2801            ));
2802        };
2803        return values
2804            .iter()
2805            .map(|(key, _)| normalize_key(key))
2806            .collect::<BackendResult<Vec<_>>>()
2807            .map(NormalizedValue::Set);
2808    }
2809    if let Some(normalized) = normalize_core_tagged_scalar_value(handle, suffix, value)? {
2810        return Ok(normalized);
2811    }
2812    normalize_value(value)
2813}
2814
2815fn normalize_tagged_key(
2816    handle: &str,
2817    suffix: &str,
2818    value: &YamlOwned,
2819) -> BackendResult<NormalizedKey> {
2820    if is_yaml_core_tag(handle, suffix, "set") {
2821        return Err(BackendError::semantic(
2822            "YAML set values cannot be used as mapping keys in normalized output.",
2823        ));
2824    }
2825    if let Some(normalized) = normalize_core_tagged_scalar_key(handle, suffix, value)? {
2826        return Ok(normalized);
2827    }
2828    normalize_key(value)
2829}
2830
2831fn normalize_core_tagged_scalar_value(
2832    handle: &str,
2833    suffix: &str,
2834    value: &YamlOwned,
2835) -> BackendResult<Option<NormalizedValue>> {
2836    if !matches!(suffix, "null" | "bool" | "int" | "float" | "str")
2837        || !matches!(handle, "" | "!!" | "tag:yaml.org,2002:")
2838    {
2839        return Ok(None);
2840    }
2841    if is_empty_tagged_scalar(value) {
2842        return Ok(match suffix {
2843            "null" => Some(NormalizedValue::Null),
2844            "str" => Some(NormalizedValue::String(String::new())),
2845            _ => None,
2846        });
2847    }
2848    let Some(text) = scalar_text_for_core_tagged_value(value) else {
2849        return Ok(None);
2850    };
2851    normalize_representation_value(
2852        text.as_ref(),
2853        ScalarStyle::Plain,
2854        Some(&Tag {
2855            handle: canonical_core_tag_handle(handle).to_owned(),
2856            suffix: suffix.to_owned(),
2857        }),
2858    )
2859    .map(Some)
2860}
2861
2862fn normalize_core_tagged_scalar_key(
2863    handle: &str,
2864    suffix: &str,
2865    value: &YamlOwned,
2866) -> BackendResult<Option<NormalizedKey>> {
2867    if !matches!(suffix, "null" | "bool" | "int" | "float" | "str")
2868        || !matches!(handle, "" | "!!" | "tag:yaml.org,2002:")
2869    {
2870        return Ok(None);
2871    }
2872    if is_empty_tagged_scalar(value) {
2873        return Ok(match suffix {
2874            "null" => Some(NormalizedKey::Null),
2875            "str" => Some(NormalizedKey::String(String::new())),
2876            _ => None,
2877        });
2878    }
2879    let Some(text) = scalar_text_for_core_tagged_value(value) else {
2880        return Ok(None);
2881    };
2882    normalize_representation_key(
2883        text.as_ref(),
2884        ScalarStyle::Plain,
2885        Some(&Tag {
2886            handle: canonical_core_tag_handle(handle).to_owned(),
2887            suffix: suffix.to_owned(),
2888        }),
2889    )
2890    .map(Some)
2891}
2892
2893fn canonical_core_tag_handle(handle: &str) -> &str {
2894    match handle {
2895        "" | "!!" | "tag:yaml.org,2002:" => "tag:yaml.org,2002:",
2896        other => other,
2897    }
2898}
2899
2900fn scalar_text_for_tagged_value(value: &YamlOwned) -> Option<Cow<'_, str>> {
2901    match value {
2902        YamlOwned::Representation(value, _, _) => Some(Cow::Borrowed(value.as_str())),
2903        YamlOwned::Value(ScalarOwned::Null) => Some(Cow::Borrowed("null")),
2904        YamlOwned::Value(ScalarOwned::Boolean(value)) => {
2905            Some(Cow::Borrowed(if *value { "true" } else { "false" }))
2906        }
2907        YamlOwned::Value(ScalarOwned::Integer(value)) => Some(Cow::Owned(value.to_string())),
2908        YamlOwned::Value(ScalarOwned::FloatingPoint(value)) => {
2909            Some(Cow::Owned(value.into_inner().to_string()))
2910        }
2911        YamlOwned::Value(ScalarOwned::String(value)) => Some(Cow::Borrowed(value.as_str())),
2912        YamlOwned::Tagged(_, value) => scalar_text_for_tagged_value(value),
2913        YamlOwned::Sequence(_)
2914        | YamlOwned::Mapping(_)
2915        | YamlOwned::Alias(_)
2916        | YamlOwned::BadValue => None,
2917    }
2918}
2919
2920fn scalar_text_for_core_tagged_value(value: &YamlOwned) -> Option<Cow<'_, str>> {
2921    match value {
2922        YamlOwned::Value(ScalarOwned::Null) => Some(Cow::Borrowed("")),
2923        YamlOwned::Tagged(_, value) => scalar_text_for_core_tagged_value(value),
2924        other => scalar_text_for_tagged_value(other),
2925    }
2926}
2927
2928fn is_empty_tagged_scalar(value: &YamlOwned) -> bool {
2929    match value {
2930        YamlOwned::Value(ScalarOwned::Null) => true,
2931        YamlOwned::Representation(value, _, _) => value.is_empty(),
2932        YamlOwned::Tagged(_, value) => is_empty_tagged_scalar(value),
2933        _ => false,
2934    }
2935}
2936
2937fn is_yaml_core_tag(handle: &str, suffix: &str, expected_suffix: &str) -> bool {
2938    suffix == expected_suffix && matches!(handle, "" | "!!" | "tag:yaml.org,2002:")
2939}
2940
2941fn is_merge_key(value: &YamlOwned) -> bool {
2942    match value {
2943        YamlOwned::Value(saphyr::ScalarOwned::String(value)) => value == "<<",
2944        YamlOwned::Representation(value, _, _) => value == "<<",
2945        YamlOwned::Tagged(_, value) => is_merge_key(value),
2946        _ => false,
2947    }
2948}
2949
2950fn apply_merge_entries(value: &YamlOwned, entries: &mut Vec<NormalizedEntry>) -> BackendResult<()> {
2951    match value {
2952        YamlOwned::Mapping(values) => merge_mapping_entries(values, entries),
2953        YamlOwned::Sequence(values) => {
2954            for value in values {
2955                let YamlOwned::Mapping(values) = value else {
2956                    return Err(BackendError::semantic(
2957                        "YAML merge sequences must contain only mappings.",
2958                    ));
2959                };
2960                merge_mapping_entries(values, entries)?;
2961            }
2962            Ok(())
2963        }
2964        YamlOwned::Tagged(_, value) => apply_merge_entries(value, entries),
2965        _ => Err(BackendError::semantic(
2966            "YAML merge values must be a mapping or sequence of mappings.",
2967        )),
2968    }
2969}
2970
2971fn merge_mapping_entries(
2972    values: &saphyr::MappingOwned,
2973    entries: &mut Vec<NormalizedEntry>,
2974) -> BackendResult<()> {
2975    for (key, value) in values {
2976        insert_mapping_entry(entries, normalize_key(key)?, normalize_value(value)?, false);
2977    }
2978    Ok(())
2979}
2980
2981fn insert_mapping_entry(
2982    entries: &mut Vec<NormalizedEntry>,
2983    key: NormalizedKey,
2984    value: NormalizedValue,
2985    override_existing: bool,
2986) {
2987    if let Some(existing) = entries.iter_mut().find(|entry| entry.key == key) {
2988        if override_existing {
2989            existing.value = value;
2990        }
2991        return;
2992    }
2993    entries.push(NormalizedEntry { key, value });
2994}
2995
2996fn wrap_decorators(
2997    decorated: Option<(Option<YamlTagNode>, Option<YamlAnchorNode>)>,
2998    value: YamlValueNode,
2999) -> BackendResult<YamlValueNode> {
3000    if let Some((tag, anchor)) = decorated {
3001        let decorator_span = tag
3002            .as_ref()
3003            .map(|tag| tag.span.clone())
3004            .or_else(|| anchor.as_ref().map(|anchor| anchor.span.clone()))
3005            .unwrap_or_else(|| value_span(&value).clone());
3006        let span = decorator_span.merge(value_span(&value));
3007
3008        match value {
3009            YamlValueNode::Decorated(inner) => {
3010                if anchor.is_some() && inner.anchor.is_some() {
3011                    return Err(BackendError::parse_at(
3012                        "yaml.parse",
3013                        "YAML nodes cannot define more than one anchor.",
3014                        Some(span),
3015                    ));
3016                }
3017                if tag.is_some() && inner.tag.is_some() {
3018                    return Err(BackendError::parse_at(
3019                        "yaml.parse",
3020                        "YAML nodes cannot define more than one tag.",
3021                        Some(span),
3022                    ));
3023                }
3024
3025                Ok(YamlValueNode::Decorated(YamlDecoratedNode {
3026                    span,
3027                    value: inner.value,
3028                    tag: tag.or(inner.tag),
3029                    anchor: anchor.or(inner.anchor),
3030                }))
3031            }
3032            YamlValueNode::Scalar(YamlScalarNode::Alias(_))
3033                if tag.is_some() || anchor.is_some() =>
3034            {
3035                Err(BackendError::parse_at(
3036                    "yaml.parse",
3037                    "YAML aliases cannot define tags or anchors.",
3038                    Some(span),
3039                ))
3040            }
3041            value => Ok(YamlValueNode::Decorated(YamlDecoratedNode {
3042                span,
3043                value: Box::new(value),
3044                tag,
3045                anchor,
3046            })),
3047        }
3048    } else {
3049        Ok(value)
3050    }
3051}
3052
3053fn null_plain_scalar() -> YamlPlainScalarNode {
3054    YamlPlainScalarNode {
3055        span: SourceSpan::point(0, 0),
3056        chunks: vec![YamlChunk::Text(YamlTextChunkNode {
3057            span: SourceSpan::point(0, 0),
3058            value: "null".to_owned(),
3059        })],
3060    }
3061}
3062
3063fn empty_plain_scalar() -> YamlPlainScalarNode {
3064    YamlPlainScalarNode {
3065        span: SourceSpan::point(0, 0),
3066        chunks: Vec::new(),
3067    }
3068}
3069
3070fn flow_implicit_plain_key_needs_separator(key: &YamlKeyNode) -> bool {
3071    matches!(
3072        &key.value,
3073        YamlKeyValue::Scalar(YamlScalarNode::Plain(node))
3074            if node.chunks.iter().any(|chunk| match chunk {
3075                YamlChunk::Text(text) => text.value.chars().any(char::is_whitespace),
3076                YamlChunk::Interpolation(_) => true,
3077            })
3078    )
3079}
3080
3081fn split_stream(template: &TemplateInput) -> BackendResult<Vec<YamlDocumentFragment>> {
3082    let items = template.flatten();
3083    let mut fragments = Vec::new();
3084    let mut directives = Vec::new();
3085    let mut current_start = None;
3086    let mut explicit_start = false;
3087    let mut explicit_end = false;
3088    let mut line_start = 0;
3089
3090    while line_start < items.len() {
3091        let (line_end, next_line) = line_bounds(&items, line_start);
3092        let line = collect_line(&items[line_start..line_end]);
3093        let trimmed = line.trim_end_matches(['\r', '\n']);
3094        let document_start_payload = document_start_payload_start(&items, line_start, line_end);
3095        let document_end = is_document_end_marker(trimmed);
3096        let malformed_document_end = trimmed.starts_with("...") && !document_end;
3097
3098        if malformed_document_end {
3099            let span = items
3100                .get(line_start)
3101                .map(|item| item.span().clone())
3102                .unwrap_or_else(|| SourceSpan::point(0, 0));
3103            return Err(BackendError::parse_at(
3104                "yaml.parse",
3105                "Unexpected trailing YAML content.",
3106                Some(span),
3107            ));
3108        }
3109
3110        if current_start.is_none() {
3111            if trimmed.is_empty() || trimmed.starts_with('#') {
3112                line_start = next_line;
3113                continue;
3114            }
3115            if trimmed.starts_with('%') {
3116                if !is_valid_directive(trimmed) {
3117                    let span = items
3118                        .get(line_start)
3119                        .map(|item| item.span().clone())
3120                        .unwrap_or_else(|| SourceSpan::point(0, 0));
3121                    return Err(BackendError::parse_at(
3122                        "yaml.parse",
3123                        "Unexpected trailing YAML content.",
3124                        Some(span),
3125                    ));
3126                }
3127                if duplicates_existing_directive(&directives, trimmed) {
3128                    let span = items
3129                        .get(line_start)
3130                        .map(|item| item.span().clone())
3131                        .unwrap_or_else(|| SourceSpan::point(0, 0));
3132                    return Err(BackendError::parse_at(
3133                        "yaml.parse",
3134                        "Unexpected trailing YAML content.",
3135                        Some(span),
3136                    ));
3137                }
3138                directives.push(trimmed.to_owned());
3139                line_start = next_line;
3140                continue;
3141            }
3142            if trimmed == "---" {
3143                current_start = Some(next_line);
3144                explicit_start = true;
3145                line_start = next_line;
3146                continue;
3147            }
3148            if let Some(payload_start) = document_start_payload {
3149                if explicit_start_payload_looks_like_block_mapping(trimmed) {
3150                    let span = items
3151                        .get(line_start)
3152                        .map(|item| item.span().clone())
3153                        .unwrap_or_else(|| SourceSpan::point(0, 0));
3154                    return Err(BackendError::parse_at(
3155                        "yaml.parse",
3156                        "Unexpected trailing YAML content.",
3157                        Some(span),
3158                    ));
3159                }
3160                current_start = Some(payload_start.unwrap_or(next_line));
3161                explicit_start = true;
3162                line_start = next_line;
3163                continue;
3164            }
3165            if document_end {
3166                line_start = next_line;
3167                continue;
3168            }
3169            current_start = Some(line_start);
3170        } else if trimmed == "---" {
3171            fragments.push(build_fragment(
3172                &items,
3173                current_start.unwrap_or(line_start),
3174                line_start,
3175                std::mem::take(&mut directives),
3176                explicit_start,
3177                explicit_end,
3178            ));
3179            current_start = Some(next_line);
3180            explicit_start = true;
3181            explicit_end = false;
3182            line_start = next_line;
3183            continue;
3184        } else if let Some(payload_start) = document_start_payload {
3185            fragments.push(build_fragment(
3186                &items,
3187                current_start.unwrap_or(line_start),
3188                line_start,
3189                std::mem::take(&mut directives),
3190                explicit_start,
3191                explicit_end,
3192            ));
3193            current_start = Some(payload_start.unwrap_or(next_line));
3194            explicit_start = true;
3195            explicit_end = false;
3196            line_start = next_line;
3197            continue;
3198        } else if document_end {
3199            fragments.push(build_fragment(
3200                &items,
3201                current_start.unwrap_or(line_start),
3202                line_start,
3203                std::mem::take(&mut directives),
3204                explicit_start,
3205                true,
3206            ));
3207            current_start = None;
3208            explicit_start = false;
3209            explicit_end = false;
3210            line_start = next_line;
3211            continue;
3212        }
3213
3214        line_start = next_line;
3215    }
3216
3217    if let Some(start) = current_start {
3218        fragments.push(build_fragment(
3219            &items,
3220            start,
3221            items.len().saturating_sub(1),
3222            std::mem::take(&mut directives),
3223            explicit_start,
3224            explicit_end,
3225        ));
3226    }
3227
3228    if current_start.is_none() && !directives.is_empty() {
3229        let span = items
3230            .iter()
3231            .rev()
3232            .find(|item| item.kind() != "eof")
3233            .map(|item| item.span().clone())
3234            .unwrap_or_else(|| SourceSpan::point(0, 0));
3235        return Err(BackendError::parse_at(
3236            "yaml.parse",
3237            "Unexpected trailing YAML content.",
3238            Some(span),
3239        ));
3240    }
3241
3242    if fragments.is_empty() {
3243        fragments.push(YamlDocumentFragment {
3244            directives: Vec::new(),
3245            explicit_start: false,
3246            explicit_end: false,
3247            items: vec![StreamItem::Eof {
3248                span: SourceSpan::point(0, 0),
3249            }],
3250        });
3251    }
3252
3253    Ok(fragments)
3254}
3255
3256fn build_fragment(
3257    items: &[StreamItem],
3258    start: usize,
3259    end: usize,
3260    directives: Vec<String>,
3261    explicit_start: bool,
3262    explicit_end: bool,
3263) -> YamlDocumentFragment {
3264    let mut fragment_items = items[start..end].to_vec();
3265    let eof_span = fragment_items
3266        .last()
3267        .map_or_else(|| SourceSpan::point(0, 0), |item| item.span().clone());
3268    fragment_items.push(StreamItem::Eof { span: eof_span });
3269    YamlDocumentFragment {
3270        directives,
3271        explicit_start,
3272        explicit_end,
3273        items: fragment_items,
3274    }
3275}
3276
3277fn line_bounds(items: &[StreamItem], start: usize) -> (usize, usize) {
3278    let mut probe = start;
3279    while probe < items.len() {
3280        if items[probe].char() == Some('\n') {
3281            return (probe, probe + 1);
3282        }
3283        if items[probe].kind() == "eof" {
3284            return (probe, probe + 1);
3285        }
3286        probe += 1;
3287    }
3288    (items.len(), items.len())
3289}
3290
3291fn document_start_payload_start(
3292    items: &[StreamItem],
3293    line_start: usize,
3294    line_end: usize,
3295) -> Option<Option<usize>> {
3296    let mut probe = line_start;
3297    for expected in ['-', '-', '-'] {
3298        if items.get(probe).and_then(StreamItem::char) != Some(expected) {
3299            return None;
3300        }
3301        probe += 1;
3302    }
3303    if probe >= line_end {
3304        return Some(None);
3305    }
3306    if !matches!(
3307        items.get(probe).and_then(StreamItem::char),
3308        Some(' ' | '\t')
3309    ) {
3310        return None;
3311    }
3312    while probe < line_end
3313        && matches!(
3314            items.get(probe).and_then(StreamItem::char),
3315            Some(' ' | '\t')
3316        )
3317    {
3318        probe += 1;
3319    }
3320    if probe >= line_end || items.get(probe).and_then(StreamItem::char) == Some('#') {
3321        return Some(None);
3322    }
3323    Some(Some(probe))
3324}
3325
3326fn is_document_end_marker(trimmed: &str) -> bool {
3327    if !trimmed.starts_with("...") {
3328        return false;
3329    }
3330    let Some(remainder) = trimmed.get(3..) else {
3331        return true;
3332    };
3333    let remainder = remainder.trim_start_matches([' ', '\t']);
3334    remainder.is_empty() || remainder.starts_with('#')
3335}
3336
3337fn explicit_start_payload_looks_like_block_mapping(trimmed: &str) -> bool {
3338    let Some(rest) = trimmed.strip_prefix("---") else {
3339        return false;
3340    };
3341    let rest = rest.trim_start_matches([' ', '\t']);
3342    if !(rest.starts_with('&') || rest.starts_with('!')) {
3343        return false;
3344    }
3345    if rest.starts_with('[') || rest.starts_with('{') {
3346        return false;
3347    }
3348    let Some((before_colon, after_colon)) = rest.split_once(':') else {
3349        return false;
3350    };
3351    !before_colon.contains('[')
3352        && !before_colon.contains('{')
3353        && !before_colon.ends_with(':')
3354        && matches!(after_colon.chars().next(), Some(' ' | '\t') | None)
3355}
3356
3357fn is_valid_directive(trimmed: &str) -> bool {
3358    let directive = trimmed
3359        .split_once('#')
3360        .map(|(directive, _)| directive)
3361        .unwrap_or(trimmed)
3362        .trim_end();
3363    let mut parts = directive.split_whitespace();
3364    match parts.next() {
3365        Some("%YAML") => matches!(
3366            (parts.next(), parts.next(), parts.next()),
3367            (Some(_version), None, None)
3368        ),
3369        Some("%TAG") => matches!(
3370            (parts.next(), parts.next(), parts.next(), parts.next()),
3371            (Some(_handle), Some(_prefix), None, None)
3372        ),
3373        Some(_) => true,
3374        None => false,
3375    }
3376}
3377
3378fn duplicates_existing_directive(existing: &[String], candidate: &str) -> bool {
3379    let Some((name, handle)) = directive_identity(candidate) else {
3380        return false;
3381    };
3382    existing.iter().any(|directive| {
3383        directive_identity(directive).is_some_and(|(existing_name, existing_handle)| {
3384            existing_name == name && existing_handle == handle
3385        })
3386    })
3387}
3388
3389fn directive_identity(trimmed: &str) -> Option<(&str, Option<&str>)> {
3390    let directive = trimmed
3391        .split_once('#')
3392        .map(|(directive, _)| directive)
3393        .unwrap_or(trimmed)
3394        .trim_end();
3395    let mut parts = directive.split_whitespace();
3396    match parts.next() {
3397        Some("%YAML") => Some(("%YAML", None)),
3398        Some("%TAG") => parts.next().map(|handle| ("%TAG", Some(handle))),
3399        _ => None,
3400    }
3401}
3402
3403fn collect_line(items: &[StreamItem]) -> String {
3404    let mut line = String::new();
3405    for item in items {
3406        if let Some(ch) = item.char() {
3407            line.push(ch);
3408        } else {
3409            line.push('\u{fffc}');
3410        }
3411    }
3412    line
3413}
3414
3415fn value_span(value: &YamlValueNode) -> &SourceSpan {
3416    match value {
3417        YamlValueNode::Scalar(YamlScalarNode::Plain(node)) => &node.span,
3418        YamlValueNode::Scalar(YamlScalarNode::DoubleQuoted(node)) => &node.span,
3419        YamlValueNode::Scalar(YamlScalarNode::SingleQuoted(node)) => &node.span,
3420        YamlValueNode::Scalar(YamlScalarNode::Block(node)) => &node.span,
3421        YamlValueNode::Scalar(YamlScalarNode::Alias(node)) => &node.span,
3422        YamlValueNode::Interpolation(node) => &node.span,
3423        YamlValueNode::Mapping(node) => &node.span,
3424        YamlValueNode::Sequence(node) => &node.span,
3425        YamlValueNode::Decorated(node) => &node.span,
3426    }
3427}
3428
3429#[cfg(test)]
3430mod tests {
3431    use super::{YamlValueNode, parse_template};
3432    use pyo3::prelude::*;
3433    use saphyr::{LoadableYamlNode, ScalarOwned, YamlOwned};
3434    use tstring_pyo3_bindings::{extract_template, yaml::render_document};
3435    use tstring_syntax::{BackendError, BackendResult, ErrorKind};
3436
3437    fn parse_rendered_yaml(text: &str) -> BackendResult<Vec<YamlOwned>> {
3438        YamlOwned::load_from_str(text).map_err(|err| {
3439            BackendError::parse(format!(
3440                "Rendered YAML could not be reparsed during test verification: {err}"
3441            ))
3442        })
3443    }
3444
3445    fn yaml_scalar_text(value: &YamlOwned) -> Option<&str> {
3446        match value {
3447            YamlOwned::Value(value) => value.as_str(),
3448            YamlOwned::Representation(value, _, _) => Some(value.as_str()),
3449            YamlOwned::Tagged(_, value) => yaml_scalar_text(value),
3450            _ => None,
3451        }
3452    }
3453
3454    fn yaml_integer(value: &YamlOwned) -> Option<i64> {
3455        match value {
3456            YamlOwned::Value(value) => value.as_integer(),
3457            YamlOwned::Tagged(_, value) => yaml_integer(value),
3458            _ => None,
3459        }
3460    }
3461
3462    fn yaml_float(value: &YamlOwned) -> Option<f64> {
3463        match value {
3464            YamlOwned::Value(ScalarOwned::FloatingPoint(value)) => Some(value.into_inner()),
3465            YamlOwned::Tagged(_, value) => yaml_float(value),
3466            _ => None,
3467        }
3468    }
3469
3470    fn yaml_sequence_len(value: &YamlOwned) -> Option<usize> {
3471        match value {
3472            YamlOwned::Sequence(value) => Some(value.len()),
3473            YamlOwned::Tagged(_, value) => yaml_sequence_len(value),
3474            _ => None,
3475        }
3476    }
3477
3478    fn yaml_mapping(value: &YamlOwned) -> Option<&saphyr::MappingOwned> {
3479        match value {
3480            YamlOwned::Mapping(value) => Some(value),
3481            YamlOwned::Tagged(_, value) => yaml_mapping(value),
3482            _ => None,
3483        }
3484    }
3485
3486    fn assert_string_entry(document: &YamlOwned, key: &str, expected: &str) {
3487        let mapping = yaml_mapping(document).expect("expected YAML mapping");
3488        let value = mapping
3489            .iter()
3490            .find_map(|(entry_key, entry_value)| {
3491                (yaml_scalar_text(entry_key) == Some(key)).then_some(entry_value)
3492            })
3493            .expect("expected YAML mapping entry");
3494        assert_eq!(yaml_scalar_text(value), Some(expected));
3495    }
3496
3497    fn assert_integer_entry(document: &YamlOwned, key: &str, expected: i64) {
3498        let mapping = yaml_mapping(document).expect("expected YAML mapping");
3499        let value = mapping
3500            .iter()
3501            .find_map(|(entry_key, entry_value)| {
3502                (yaml_scalar_text(entry_key) == Some(key)).then_some(entry_value)
3503            })
3504            .expect("expected YAML mapping entry");
3505        assert_eq!(yaml_integer(value), Some(expected));
3506    }
3507
3508    fn yaml_mapping_entry<'a>(document: &'a YamlOwned, key: &str) -> Option<&'a YamlOwned> {
3509        yaml_mapping(document).and_then(|mapping| {
3510            mapping.iter().find_map(|(entry_key, entry_value)| {
3511                (yaml_scalar_text(entry_key) == Some(key)).then_some(entry_value)
3512            })
3513        })
3514    }
3515
3516    #[test]
3517    fn parses_yaml_flow_and_scalar_nodes() {
3518        Python::with_gil(|py| {
3519            let module = PyModule::from_code(
3520                py,
3521                pyo3::ffi::c_str!(
3522                    "user='Alice'\ntemplate=t'name: \"hi-{user}\"\\nitems: [1, {user}]'\n"
3523                ),
3524                pyo3::ffi::c_str!("test_yaml.py"),
3525                pyo3::ffi::c_str!("test_yaml"),
3526            )
3527            .unwrap();
3528            let template = module.getattr("template").unwrap();
3529            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3530            let stream = parse_template(&template).unwrap();
3531            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3532                panic!("expected mapping");
3533            };
3534            assert_eq!(mapping.entries.len(), 2);
3535        });
3536    }
3537
3538    #[test]
3539    fn parses_tags_and_anchors_on_scalars() {
3540        Python::with_gil(|py| {
3541            let module = PyModule::from_code(
3542                py,
3543                pyo3::ffi::c_str!(
3544                    "tag='str'\nanchor='user'\ntemplate=t'value: !{tag} &{anchor} \"hi\"\\n'\n"
3545                ),
3546                pyo3::ffi::c_str!("test_yaml_decorators.py"),
3547                pyo3::ffi::c_str!("test_yaml_decorators"),
3548            )
3549            .unwrap();
3550            let template = module.getattr("template").unwrap();
3551            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3552            let stream = parse_template(&template).unwrap();
3553            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3554                panic!("expected mapping");
3555            };
3556            let YamlValueNode::Decorated(node) = &mapping.entries[0].value else {
3557                panic!("expected decorated scalar value");
3558            };
3559            assert!(node.tag.is_some());
3560            assert!(node.anchor.is_some());
3561        });
3562    }
3563
3564    #[test]
3565    fn renders_nested_yaml_values_and_validates() {
3566        Python::with_gil(|py| {
3567            let module = PyModule::from_code(
3568                py,
3569                pyo3::ffi::c_str!(
3570                    "name='Ada'\nmeta={'active': True, 'count': 2}\ntemplate=t'name: {name}\\nmeta: {meta}\\n'\n"
3571                ),
3572                pyo3::ffi::c_str!("test_yaml_render.py"),
3573                pyo3::ffi::c_str!("test_yaml_render"),
3574            )
3575            .unwrap();
3576            let template = module.getattr("template").unwrap();
3577            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3578            let stream = parse_template(&template).unwrap();
3579            let rendered = render_document(py, &stream).unwrap();
3580            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3581
3582            assert!(rendered.text.contains("name: \"Ada\""));
3583            assert_string_entry(&documents[0], "name", "Ada");
3584        });
3585    }
3586
3587    #[test]
3588    fn rejects_metadata_with_whitespace() {
3589        Python::with_gil(|py| {
3590            let module = PyModule::from_code(
3591                py,
3592                pyo3::ffi::c_str!("anchor='bad anchor'\ntemplate=t'value: &{anchor} \"hi\"\\n'\n"),
3593                pyo3::ffi::c_str!("test_yaml_error.py"),
3594                pyo3::ffi::c_str!("test_yaml_error"),
3595            )
3596            .unwrap();
3597            let template = module.getattr("template").unwrap();
3598            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3599            let stream = parse_template(&template).unwrap();
3600            let err = match render_document(py, &stream) {
3601                Ok(_) => panic!("expected YAML render failure"),
3602                Err(err) => err,
3603            };
3604
3605            assert_eq!(err.kind, ErrorKind::Unrepresentable);
3606            assert!(err.message.contains("YAML metadata"));
3607        });
3608    }
3609
3610    #[test]
3611    fn rejects_flow_mappings_missing_commas_during_parse() {
3612        Python::with_gil(|py| {
3613            let module = PyModule::from_code(
3614                py,
3615                pyo3::ffi::c_str!(
3616                    "from string.templatelib import Template\nmapping=Template('{a: 1 b: 2}\\n')\nmissing_colon=Template('value: {a b}\\n')\nleading_comma_mapping=Template('value: {, a: 1}\\n')\nsequence=Template('[1,,2]\\n')\ntrailing_sequence=Template('value: [1, 2,,]\\n')\nempty_sequence=Template('[,]\\n')\nempty_mapping=Template('value: {,}\\n')\n"
3617                ),
3618                pyo3::ffi::c_str!("test_yaml_flow_error.py"),
3619                pyo3::ffi::c_str!("test_yaml_flow_error"),
3620            )
3621            .unwrap();
3622            for name in [
3623                "mapping",
3624                "missing_colon",
3625                "leading_comma_mapping",
3626                "sequence",
3627                "trailing_sequence",
3628                "empty_sequence",
3629                "empty_mapping",
3630            ] {
3631                let template = module.getattr(name).unwrap();
3632                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3633                let err = parse_template(&template).expect_err("expected YAML parse failure");
3634                assert_eq!(err.kind, ErrorKind::Parse);
3635                assert!(err.message.contains("Expected"));
3636            }
3637        });
3638    }
3639
3640    #[test]
3641    fn rejects_tabs_as_mapping_separation_whitespace() {
3642        Python::with_gil(|py| {
3643            let module = PyModule::from_code(
3644                py,
3645                pyo3::ffi::c_str!(
3646                    "from string.templatelib import Template\nmapping=Template('a:\\t1\\n')\nplain=Template('url: a:b\\t\\n')\nindent=Template('a:\\n\\t- 1\\n')\n"
3647                ),
3648                pyo3::ffi::c_str!("test_yaml_tab_error.py"),
3649                pyo3::ffi::c_str!("test_yaml_tab_error"),
3650            )
3651            .unwrap();
3652            for name in ["mapping", "plain", "indent"] {
3653                let template = module.getattr(name).unwrap();
3654                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3655                let err = parse_template(&template).expect_err("expected YAML parse failure");
3656                assert_eq!(err.kind, ErrorKind::Parse);
3657                assert!(
3658                    err.message.contains("Tabs are not allowed"),
3659                    "{name}: {}",
3660                    err.message
3661                );
3662            }
3663        });
3664    }
3665
3666    #[test]
3667    fn splits_explicit_document_streams() {
3668        Python::with_gil(|py| {
3669            let module = PyModule::from_code(
3670                py,
3671                pyo3::ffi::c_str!("template=t'---\\nname: Alice\\n---\\nname: Bob\\n'\n"),
3672                pyo3::ffi::c_str!("test_yaml_stream.py"),
3673                pyo3::ffi::c_str!("test_yaml_stream"),
3674            )
3675            .unwrap();
3676            let template = module.getattr("template").unwrap();
3677            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3678            let stream = parse_template(&template).unwrap();
3679            assert_eq!(stream.documents.len(), 2);
3680        });
3681    }
3682
3683    #[test]
3684    fn parses_and_renders_flow_complex_keys_with_interpolation() {
3685        Python::with_gil(|py| {
3686            let module = PyModule::from_code(
3687                py,
3688                pyo3::ffi::c_str!(
3689                    "left='Alice'\nright='Bob'\ntemplate=t'{{ {{name: [{left}, {right}]}}: 1, [{left}, {right}]: 2 }}'\n"
3690                ),
3691                pyo3::ffi::c_str!("test_yaml_flow_complex_keys.py"),
3692                pyo3::ffi::c_str!("test_yaml_flow_complex_keys"),
3693            )
3694            .unwrap();
3695            let template = module.getattr("template").unwrap();
3696            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3697            let stream = parse_template(&template).unwrap();
3698            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3699                panic!("expected mapping");
3700            };
3701            assert!(matches!(
3702                mapping.entries[0].key.value,
3703                super::YamlKeyValue::Complex(_)
3704            ));
3705            assert!(matches!(
3706                mapping.entries[1].key.value,
3707                super::YamlKeyValue::Complex(_)
3708            ));
3709
3710            let rendered = render_document(py, &stream).unwrap();
3711            assert_eq!(
3712                rendered.text,
3713                "{ { name: [ \"Alice\", \"Bob\" ] }: 1, [ \"Alice\", \"Bob\" ]: 2 }"
3714            );
3715            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3716            assert_eq!(
3717                documents[0]
3718                    .as_mapping()
3719                    .expect("expected YAML mapping")
3720                    .len(),
3721                2
3722            );
3723        });
3724    }
3725
3726    #[test]
3727    fn parses_explicit_mapping_keys_with_nested_collections() {
3728        Python::with_gil(|py| {
3729            let module = PyModule::from_code(
3730                py,
3731                pyo3::ffi::c_str!(
3732                    "left='Alice'\nright='Bob'\ntemplate=t'? {{name: [{left}, {right}]}}\\n: 1\\n'\n"
3733                ),
3734                pyo3::ffi::c_str!("test_yaml_explicit_complex_key.py"),
3735                pyo3::ffi::c_str!("test_yaml_explicit_complex_key"),
3736            )
3737            .unwrap();
3738            let template = module.getattr("template").unwrap();
3739            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3740            let stream = parse_template(&template).unwrap();
3741            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3742                panic!("expected mapping");
3743            };
3744            assert!(matches!(
3745                mapping.entries[0].key.value,
3746                super::YamlKeyValue::Complex(_)
3747            ));
3748            let rendered = render_document(py, &stream).unwrap();
3749            assert!(rendered.text.contains("? { name: [ \"Alice\", \"Bob\" ] }"));
3750        });
3751    }
3752
3753    #[test]
3754    fn renders_explicit_complex_keys_text_and_validated_shape() {
3755        Python::with_gil(|py| {
3756            let module = PyModule::from_code(
3757                py,
3758                pyo3::ffi::c_str!(
3759                    "left='Alice'\nright='Bob'\ncomplex_key=t'? {{name: [{left}, {right}]}}\\n: 1\\n'\nempty_key=t'?\\n: 1\\n'\n"
3760                ),
3761                pyo3::ffi::c_str!("test_yaml_explicit_key_render.py"),
3762                pyo3::ffi::c_str!("test_yaml_explicit_key_render"),
3763            )
3764            .unwrap();
3765
3766            let complex_key = module.getattr("complex_key").unwrap();
3767            let complex_key = extract_template(py, &complex_key, "yaml_t/yaml_t_str").unwrap();
3768            let rendered = render_document(py, &parse_template(&complex_key).unwrap()).unwrap();
3769            assert!(
3770                rendered
3771                    .text
3772                    .contains("? { name: [ \"Alice\", \"Bob\" ] }\n: 1")
3773            );
3774            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3775            assert_eq!(
3776                documents[0]
3777                    .as_mapping()
3778                    .expect("expected YAML mapping")
3779                    .len(),
3780                1
3781            );
3782
3783            let empty_key = module.getattr("empty_key").unwrap();
3784            let empty_key = extract_template(py, &empty_key, "yaml_t/yaml_t_str").unwrap();
3785            let rendered = render_document(py, &parse_template(&empty_key).unwrap()).unwrap();
3786            assert_eq!(rendered.text, "? null\n: 1");
3787            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3788            assert_eq!(
3789                documents[0]
3790                    .as_mapping()
3791                    .expect("expected YAML mapping")
3792                    .len(),
3793                1
3794            );
3795        });
3796    }
3797
3798    #[test]
3799    fn parses_yaml_quoted_scalar_escapes() {
3800        Python::with_gil(|py| {
3801            let module = PyModule::from_code(
3802                py,
3803                pyo3::ffi::c_str!(
3804                    "from string.templatelib import Template\ntemplate=t'value: \"line\\\\nnext \\\\u03B1 \\\\x41\"\\nquote: \\'it\\'\\'s ok\\''\nunicode=t'value: \"\\\\U0001D11E\"\\n'\ncrlf_join=Template('value: \"a\\\\\\r\\n  b\"\\n')\nnel=t'value: \"\\\\N\"\\n'\nnbsp=t'value: \"\\\\_\"\\n'\nempty_single=Template(\"value: ''\\n\")\nempty_double=t'value: \"\"\\n'\nsingle_blank=Template(\"value: 'a\\n\\n  b\\n\\n  c'\\n\")\n"
3805                ),
3806                pyo3::ffi::c_str!("test_yaml_quoted_scalars.py"),
3807                pyo3::ffi::c_str!("test_yaml_quoted_scalars"),
3808            )
3809            .unwrap();
3810            let template = module.getattr("template").unwrap();
3811            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3812            let stream = parse_template(&template).unwrap();
3813            let rendered = render_document(py, &stream).unwrap();
3814            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3815            assert_string_entry(&documents[0], "value", "line\nnext α A");
3816            assert_string_entry(&documents[0], "quote", "it's ok");
3817
3818            for (name, expected) in [
3819                ("unicode", "𝄞"),
3820                ("crlf_join", "ab"),
3821                ("nel", "\u{0085}"),
3822                ("nbsp", "\u{00a0}"),
3823                ("empty_single", ""),
3824                ("empty_double", ""),
3825                ("single_blank", "a\nb\nc"),
3826            ] {
3827                let template = module.getattr(name).unwrap();
3828                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3829                let stream = parse_template(&template).unwrap();
3830                let rendered = render_document(py, &stream).unwrap();
3831                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3832                assert_string_entry(&documents[0], "value", expected);
3833            }
3834        });
3835    }
3836
3837    #[test]
3838    fn renders_quoted_scalar_escape_and_folding_families() {
3839        Python::with_gil(|py| {
3840            let module = PyModule::from_code(
3841                py,
3842                pyo3::ffi::c_str!(
3843                    "from string.templatelib import Template\nbase=t'value: \"line\\\\nnext \\\\u03B1 \\\\x41\"\\nquote: \\'it\\'\\'s ok\\''\nmultiline_double=t'value: \"a\\n  b\"\\n'\nmultiline_double_blank=t'value: \"a\\n\\n  b\"\\n'\nmultiline_double_more_blank=t'value: \"a\\n\\n\\n  b\"\\n'\nunicode=t'value: \"\\\\U0001D11E\"\\n'\ncrlf_join=Template('value: \"a\\\\\\r\\n  b\"\\n')\nnel=t'value: \"\\\\N\"\\n'\nnbsp=t'value: \"\\\\_\"\\n'\nspace=t'value: \"\\\\ \"\\n'\nslash=t'value: \"\\\\/\"\\n'\ntab=t'value: \"\\\\t\"\\n'\nempty_single=Template(\"value: ''\\n\")\nempty_double=t'value: \"\"\\n'\nsingle_blank=Template(\"value: 'a\\n\\n  b\\n\\n  c'\\n\")\n"
3844                ),
3845                pyo3::ffi::c_str!("test_yaml_quoted_scalar_families.py"),
3846                pyo3::ffi::c_str!("test_yaml_quoted_scalar_families"),
3847            )
3848            .unwrap();
3849
3850            let base = module.getattr("base").unwrap();
3851            let base = extract_template(py, &base, "yaml_t/yaml_t_str").unwrap();
3852            let rendered = render_document(py, &parse_template(&base).unwrap()).unwrap();
3853            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3854            assert_eq!(
3855                rendered.text,
3856                "value: \"line\\nnext α A\"\nquote: 'it''s ok'"
3857            );
3858            assert_string_entry(&documents[0], "value", "line\nnext α A");
3859            assert_string_entry(&documents[0], "quote", "it's ok");
3860
3861            for (name, expected_text, expected) in [
3862                ("multiline_double", "value: \"a b\"", "a b"),
3863                ("multiline_double_blank", "value: \"a\\nb\"", "a\nb"),
3864                (
3865                    "multiline_double_more_blank",
3866                    "value: \"a\\n\\nb\"",
3867                    "a\n\nb",
3868                ),
3869                ("unicode", "value: \"𝄞\"", "𝄞"),
3870                ("crlf_join", "value: \"ab\"", "ab"),
3871                ("nel", "value: \"\u{0085}\"", "\u{0085}"),
3872                ("nbsp", "value: \"\u{00a0}\"", "\u{00a0}"),
3873                ("space", "value: \" \"", " "),
3874                ("slash", "value: \"/\"", "/"),
3875                ("tab", "value: \"\\t\"", "\t"),
3876                ("empty_single", "value: ''", ""),
3877                ("empty_double", "value: \"\"", ""),
3878                ("single_blank", "value: 'a\n\n  b\n\n  c'", "a\nb\nc"),
3879            ] {
3880                let template = module.getattr(name).unwrap();
3881                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3882                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
3883                assert_eq!(rendered.text, expected_text, "{name}");
3884                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3885                assert_string_entry(&documents[0], "value", expected);
3886            }
3887        });
3888    }
3889
3890    #[test]
3891    fn renders_spec_quoted_scalar_examples_round_trip() {
3892        Python::with_gil(|py| {
3893            let module = PyModule::from_code(
3894                py,
3895                pyo3::ffi::c_str!(
3896                    "unicode=t'unicode: \"Sosa did fine.\\\\u263A\"'\ncontrol=t'control: \"\\\\b1998\\\\t1999\\\\t2000\\\\n\"'\nsingle=t'''single: '\"Howdy!\" he cried.' '''\nquoted=t'''quoted: ' # Not a ''comment''.' '''\ntie=t'''tie: '|\\\\-*-/|' '''\n"
3897                ),
3898                pyo3::ffi::c_str!("test_yaml_spec_quoted_examples.py"),
3899                pyo3::ffi::c_str!("test_yaml_spec_quoted_examples"),
3900            )
3901            .unwrap();
3902
3903            for (name, key, expected_text, expected_value) in [
3904                (
3905                    "unicode",
3906                    "unicode",
3907                    "unicode: \"Sosa did fine.☺\"",
3908                    "Sosa did fine.\u{263a}",
3909                ),
3910                (
3911                    "control",
3912                    "control",
3913                    "control: \"\\b1998\\t1999\\t2000\\n\"",
3914                    "\u{0008}1998\t1999\t2000\n",
3915                ),
3916                (
3917                    "single",
3918                    "single",
3919                    "single: '\"Howdy!\" he cried.'",
3920                    "\"Howdy!\" he cried.",
3921                ),
3922                (
3923                    "quoted",
3924                    "quoted",
3925                    "quoted: ' # Not a ''comment''.'",
3926                    " # Not a 'comment'.",
3927                ),
3928                ("tie", "tie", "tie: '|\\-*-/|'", "|\\-*-/|"),
3929            ] {
3930                let template = module.getattr(name).unwrap();
3931                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3932                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
3933                assert_eq!(rendered.text, expected_text, "{name}");
3934                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3935                assert_string_entry(&documents[0], key, expected_value);
3936            }
3937        });
3938    }
3939
3940    #[test]
3941    fn folds_multiline_double_quoted_scalars() {
3942        Python::with_gil(|py| {
3943            let module = PyModule::from_code(
3944                py,
3945                pyo3::ffi::c_str!(
3946                    "single=t'value: \"a\\n  b\"\\n'\nblank=t'value: \"a\\n\\n  b\"\\n'\n"
3947                ),
3948                pyo3::ffi::c_str!("test_yaml_multiline_double_quoted.py"),
3949                pyo3::ffi::c_str!("test_yaml_multiline_double_quoted"),
3950            )
3951            .unwrap();
3952
3953            for (name, expected) in [("single", "a b"), ("blank", "a\nb")] {
3954                let template = module.getattr(name).unwrap();
3955                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3956                let stream = parse_template(&template).unwrap();
3957                let rendered = render_document(py, &stream).unwrap();
3958                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3959                assert_string_entry(&documents[0], "value", expected);
3960            }
3961        });
3962    }
3963
3964    #[test]
3965    fn renders_block_chomping_and_indent_indicator_families() {
3966        Python::with_gil(|py| {
3967            let module = PyModule::from_code(
3968                py,
3969                pyo3::ffi::c_str!(
3970                    "literal_strip=t'value: |-\\n  a\\n  b\\n'\nliteral_keep=t'value: |+\\n  a\\n  b\\n'\nliteral_keep_leading_blank=t'value: |+\\n\\n  a\\n'\nfolded_strip=t'value: >-\\n  a\\n  b\\n'\nfolded_keep=t'value: >+\\n  a\\n  b\\n'\nfolded_more=t'value: >\\n  a\\n    b\\n  c\\n'\nindent_indicator=t'value: |2\\n  a\\n  b\\n'\nliteral_blank_keep=t'value: |+\\n  a\\n\\n  b\\n'\nfolded_blank_keep=t'value: >+\\n  a\\n\\n  b\\n'\n"
3971                ),
3972                pyo3::ffi::c_str!("test_yaml_block_chomping_families.py"),
3973                pyo3::ffi::c_str!("test_yaml_block_chomping_families"),
3974            )
3975            .unwrap();
3976
3977            for (name, expected_text, key, expected_value) in [
3978                ("literal_strip", "value: |-\n  a\n  b", "value", "a\nb"),
3979                ("literal_keep", "value: |+\n  a\n  b\n", "value", "a\nb\n"),
3980                (
3981                    "literal_keep_leading_blank",
3982                    "value: |+\n  \n  a\n",
3983                    "value",
3984                    "\na\n",
3985                ),
3986                ("folded_strip", "value: >-\n  a\n  b", "value", "a b"),
3987                ("folded_keep", "value: >+\n  a\n  b\n", "value", "a b\n"),
3988                (
3989                    "folded_more",
3990                    "value: >\n  a\n    b\n  c\n",
3991                    "value",
3992                    "a\n  b\nc\n",
3993                ),
3994                (
3995                    "indent_indicator",
3996                    "value: |2\n  a\n  b\n",
3997                    "value",
3998                    "a\nb\n",
3999                ),
4000                (
4001                    "literal_blank_keep",
4002                    "value: |+\n  a\n  \n  b\n",
4003                    "value",
4004                    "a\n\nb\n",
4005                ),
4006                (
4007                    "folded_blank_keep",
4008                    "value: >+\n  a\n  \n  b\n",
4009                    "value",
4010                    "a\nb\n",
4011                ),
4012            ] {
4013                let template = module.getattr(name).unwrap();
4014                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4015                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
4016                assert_eq!(rendered.text, expected_text, "{name}");
4017                let documents = parse_rendered_yaml(&rendered.text).unwrap();
4018                assert_string_entry(&documents[0], key, expected_value);
4019            }
4020        });
4021    }
4022
4023    #[test]
4024    fn folds_multiline_plain_scalars() {
4025        Python::with_gil(|py| {
4026            let module = PyModule::from_code(
4027                py,
4028                pyo3::ffi::c_str!("template=t'value: a\\n  b\\n\\n  c\\n'\n"),
4029                pyo3::ffi::c_str!("test_yaml_multiline_plain.py"),
4030                pyo3::ffi::c_str!("test_yaml_multiline_plain"),
4031            )
4032            .unwrap();
4033            let template = module.getattr("template").unwrap();
4034            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4035            let stream = parse_template(&template).unwrap();
4036            let rendered = render_document(py, &stream).unwrap();
4037            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4038            assert_string_entry(&documents[0], "value", "a b\nc");
4039            assert!(rendered.text.contains("\"a b\\nc\""));
4040        });
4041    }
4042
4043    #[test]
4044    fn accepts_top_level_flow_collections_with_trailing_newlines() {
4045        Python::with_gil(|py| {
4046            let module = PyModule::from_code(
4047                py,
4048                pyo3::ffi::c_str!(
4049                    "from string.templatelib import Template\ntemplate=Template('{a: 1, b: [2, 3]}\\n')\n"
4050                ),
4051                pyo3::ffi::c_str!("test_yaml_flow_trailing_newline.py"),
4052                pyo3::ffi::c_str!("test_yaml_flow_trailing_newline"),
4053            )
4054            .unwrap();
4055            let template = module.getattr("template").unwrap();
4056            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4057            let stream = parse_template(&template).unwrap();
4058            let rendered = render_document(py, &stream).unwrap();
4059            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4060            assert_eq!(
4061                documents[0]
4062                    .as_mapping()
4063                    .expect("expected YAML mapping")
4064                    .len(),
4065                2
4066            );
4067        });
4068    }
4069
4070    #[test]
4071    fn accepts_line_wrapped_flow_collections() {
4072        Python::with_gil(|py| {
4073            let module = PyModule::from_code(
4074                py,
4075                pyo3::ffi::c_str!(
4076                    "from string.templatelib import Template\nsequence=Template('key: [a,\\n  b]\\n')\nmapping=Template('key: {a: 1,\\n  b: 2}\\n')\n"
4077                ),
4078                pyo3::ffi::c_str!("test_yaml_wrapped_flow.py"),
4079                pyo3::ffi::c_str!("test_yaml_wrapped_flow"),
4080            )
4081            .unwrap();
4082
4083            for name in ["sequence", "mapping"] {
4084                let template = module.getattr(name).unwrap();
4085                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4086                let stream = parse_template(&template).unwrap();
4087                let rendered = render_document(py, &stream).unwrap();
4088                let documents = parse_rendered_yaml(&rendered.text).unwrap();
4089                assert_eq!(
4090                    documents[0]
4091                        .as_mapping()
4092                        .expect("expected YAML mapping")
4093                        .len(),
4094                    1
4095                );
4096            }
4097        });
4098    }
4099
4100    #[test]
4101    fn accepts_flow_collections_with_comments() {
4102        Python::with_gil(|py| {
4103            let module = PyModule::from_code(
4104                py,
4105                pyo3::ffi::c_str!(
4106                    "from string.templatelib import Template\nsequence=Template('key: [a, # first\\n  b]\\n')\nmapping=Template('key: {a: 1, # first\\n  b: 2}\\n')\n"
4107                ),
4108                pyo3::ffi::c_str!("test_yaml_flow_comments.py"),
4109                pyo3::ffi::c_str!("test_yaml_flow_comments"),
4110            )
4111            .unwrap();
4112
4113            for name in ["sequence", "mapping"] {
4114                let template = module.getattr(name).unwrap();
4115                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4116                let stream = parse_template(&template).unwrap();
4117                let rendered = render_document(py, &stream).unwrap();
4118                let documents = parse_rendered_yaml(&rendered.text).unwrap();
4119                assert_eq!(
4120                    documents[0]
4121                        .as_mapping()
4122                        .expect("expected YAML mapping")
4123                        .len(),
4124                    1
4125                );
4126            }
4127        });
4128    }
4129
4130    #[test]
4131    fn treats_empty_documents_as_null() {
4132        Python::with_gil(|py| {
4133            let module = PyModule::from_code(
4134                py,
4135                pyo3::ffi::c_str!(
4136                    "from string.templatelib import Template\nempty=Template('---\\n...\\n')\nstream=Template('---\\n\\n---\\na: 1\\n')\n"
4137                ),
4138                pyo3::ffi::c_str!("test_yaml_empty_docs.py"),
4139                pyo3::ffi::c_str!("test_yaml_empty_docs"),
4140            )
4141            .unwrap();
4142
4143            let empty = module.getattr("empty").unwrap();
4144            let empty = extract_template(py, &empty, "yaml_t/yaml_t_str").unwrap();
4145            let rendered = render_document(py, &parse_template(&empty).unwrap()).unwrap();
4146            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4147            assert!(documents.as_slice()[0].is_null());
4148
4149            let stream = module.getattr("stream").unwrap();
4150            let stream = extract_template(py, &stream, "yaml_t/yaml_t_str").unwrap();
4151            let rendered = render_document(py, &parse_template(&stream).unwrap()).unwrap();
4152            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4153            assert!(documents[0].is_null());
4154            assert!(documents[1].as_mapping().is_some());
4155        });
4156    }
4157
4158    #[test]
4159    fn supports_indentless_sequence_values_and_empty_explicit_keys() {
4160        Python::with_gil(|py| {
4161            let module = PyModule::from_code(
4162                py,
4163                pyo3::ffi::c_str!(
4164                    "from string.templatelib import Template\nindentless=Template('a:\\n- 1\\n- 2\\n')\nempty_key=Template('?\\n: 1\\n')\n"
4165                ),
4166                pyo3::ffi::c_str!("test_yaml_indentless_and_empty_key.py"),
4167                pyo3::ffi::c_str!("test_yaml_indentless_and_empty_key"),
4168            )
4169            .unwrap();
4170
4171            let indentless = module.getattr("indentless").unwrap();
4172            let indentless = extract_template(py, &indentless, "yaml_t/yaml_t_str").unwrap();
4173            let rendered = render_document(py, &parse_template(&indentless).unwrap()).unwrap();
4174            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4175            let mapping = documents[0].as_mapping().expect("expected YAML mapping");
4176            let value = mapping
4177                .iter()
4178                .find_map(|(key, value)| (key.as_str() == Some("a")).then_some(value))
4179                .expect("expected key a");
4180            assert_eq!(value.as_vec().expect("expected YAML sequence").len(), 2);
4181
4182            let empty_key = module.getattr("empty_key").unwrap();
4183            let empty_key = extract_template(py, &empty_key, "yaml_t/yaml_t_str").unwrap();
4184            let rendered = render_document(py, &parse_template(&empty_key).unwrap()).unwrap();
4185            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4186            let mapping = documents[0].as_mapping().expect("expected YAML mapping");
4187            let value = mapping
4188                .iter()
4189                .find_map(|(key, value)| key.is_null().then_some(value))
4190                .expect("expected null key");
4191            assert_eq!(value.as_integer(), Some(1));
4192        });
4193    }
4194
4195    #[test]
4196    fn supports_compact_mappings_in_sequences_and_plain_hash_chars() {
4197        Python::with_gil(|py| {
4198            let module = PyModule::from_code(
4199                py,
4200                pyo3::ffi::c_str!(
4201                    "from string.templatelib import Template\nseq=Template('- a: 1\\n  b: 2\\n- c: 3\\n')\nmapped=Template('items:\\n- a: 1\\n  b: 2\\n- c: 3\\n')\nseqs=Template('- - 1\\n  - 2\\n- - 3\\n')\nhash_value=Template('value: a#b\\n')\n"
4202                ),
4203                pyo3::ffi::c_str!("test_yaml_compact_sequence_maps.py"),
4204                pyo3::ffi::c_str!("test_yaml_compact_sequence_maps"),
4205            )
4206            .unwrap();
4207
4208            for name in ["seq", "mapped", "seqs", "hash_value"] {
4209                let template = module.getattr(name).unwrap();
4210                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4211                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
4212                let documents = parse_rendered_yaml(&rendered.text).unwrap();
4213                assert_eq!(documents.len(), 1);
4214            }
4215        });
4216    }
4217
4218    #[test]
4219    fn preserves_explicit_document_end_markers_in_streams() {
4220        Python::with_gil(|py| {
4221            let module = PyModule::from_code(
4222                py,
4223                pyo3::ffi::c_str!(
4224                    "from string.templatelib import Template\ntemplate=Template('---\\na: 1\\n...\\n---\\nb: 2\\n')\ncommented=Template('---\\na: 1\\n... # end\\n---\\nb: 2\\n')\n"
4225                ),
4226                pyo3::ffi::c_str!("test_yaml_explicit_end.py"),
4227                pyo3::ffi::c_str!("test_yaml_explicit_end"),
4228            )
4229            .unwrap();
4230            let template = module.getattr("template").unwrap();
4231            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4232            let stream = parse_template(&template).unwrap();
4233            let rendered = render_document(py, &stream).unwrap();
4234
4235            assert!(rendered.text.contains("...\n---"));
4236
4237            let commented = module.getattr("commented").unwrap();
4238            let commented = extract_template(py, &commented, "yaml_t/yaml_t_str").unwrap();
4239            let rendered = render_document(py, &parse_template(&commented).unwrap()).unwrap();
4240            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4241            assert_eq!(documents.len(), 2);
4242        });
4243    }
4244
4245    #[test]
4246    fn parses_verbatim_tags() {
4247        Python::with_gil(|py| {
4248            let module = PyModule::from_code(
4249                py,
4250                pyo3::ffi::c_str!(
4251                    "from string.templatelib import Template\ntemplate=Template('value: !<tag:yaml.org,2002:str> hello\\n')\n"
4252                ),
4253                pyo3::ffi::c_str!("test_yaml_verbatim_tag.py"),
4254                pyo3::ffi::c_str!("test_yaml_verbatim_tag"),
4255            )
4256            .unwrap();
4257            let template = module.getattr("template").unwrap();
4258            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4259            let stream = parse_template(&template).unwrap();
4260            let rendered = render_document(py, &stream).unwrap();
4261            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4262
4263            assert!(rendered.text.contains("!<tag:yaml.org,2002:str>"));
4264            assert_string_entry(&documents[0], "value", "hello");
4265        });
4266    }
4267
4268    #[test]
4269    fn validates_user_defined_tags_via_saphyr() {
4270        Python::with_gil(|py| {
4271            let module = PyModule::from_code(
4272                py,
4273                pyo3::ffi::c_str!(
4274                    "from string.templatelib import Template\nscalar=Template('!custom 3\\n')\ncustom_tag_scalar=Template('value: !custom 3\\n')\ncustom_tag_sequence=Template('value: !custom [1, 2]\\n')\ncommented_root=Template('--- # comment\\n!custom [1, 2]\\n')\n"
4275                ),
4276                pyo3::ffi::c_str!("test_yaml_custom_tags.py"),
4277                pyo3::ffi::c_str!("test_yaml_custom_tags"),
4278            )
4279            .unwrap();
4280
4281            let scalar = module.getattr("scalar").unwrap();
4282            let scalar = extract_template(py, &scalar, "yaml_t/yaml_t_str").unwrap();
4283            let rendered = render_document(py, &parse_template(&scalar).unwrap()).unwrap();
4284            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4285            assert_eq!(rendered.text.trim_end(), "!custom 3");
4286            assert!(rendered.text.contains("!custom 3"));
4287            assert_eq!(yaml_integer(&documents[0]), Some(3));
4288
4289            let custom_tag_scalar = module.getattr("custom_tag_scalar").unwrap();
4290            let custom_tag_scalar =
4291                extract_template(py, &custom_tag_scalar, "yaml_t/yaml_t_str").unwrap();
4292            let rendered =
4293                render_document(py, &parse_template(&custom_tag_scalar).unwrap()).unwrap();
4294            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4295            assert_eq!(rendered.text.trim_end(), "value: !custom 3");
4296            assert!(rendered.text.contains("value: !custom 3"));
4297            assert_integer_entry(&documents[0], "value", 3);
4298
4299            let custom_tag_sequence = module.getattr("custom_tag_sequence").unwrap();
4300            let custom_tag_sequence =
4301                extract_template(py, &custom_tag_sequence, "yaml_t/yaml_t_str").unwrap();
4302            let rendered =
4303                render_document(py, &parse_template(&custom_tag_sequence).unwrap()).unwrap();
4304            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4305            assert!(rendered.text.contains("value: !custom [ 1, 2 ]"));
4306            let value = documents[0]
4307                .as_mapping()
4308                .expect("expected YAML mapping")
4309                .iter()
4310                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("value")).then_some(value))
4311                .expect("expected value key");
4312            assert_eq!(yaml_sequence_len(value), Some(2));
4313
4314            let commented_root = module.getattr("commented_root").unwrap();
4315            let commented_root =
4316                extract_template(py, &commented_root, "yaml_t/yaml_t_str").unwrap();
4317            let rendered = render_document(py, &parse_template(&commented_root).unwrap()).unwrap();
4318            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4319            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
4320        });
4321    }
4322
4323    #[test]
4324    fn rejects_aliases_that_cross_document_boundaries() {
4325        Python::with_gil(|py| {
4326            let module = PyModule::from_code(
4327                py,
4328                pyo3::ffi::c_str!(
4329                    "from string.templatelib import Template\nstream=Template('--- &a\\n- 1\\n- 2\\n---\\n*a\\n')\n"
4330                ),
4331                pyo3::ffi::c_str!("test_yaml_cross_doc_alias.py"),
4332                pyo3::ffi::c_str!("test_yaml_cross_doc_alias"),
4333            )
4334            .unwrap();
4335
4336            let stream = module.getattr("stream").unwrap();
4337            let stream = extract_template(py, &stream, "yaml_t/yaml_t_str").unwrap();
4338            let rendered = render_document(py, &parse_template(&stream).unwrap()).unwrap();
4339            let err = parse_rendered_yaml(&rendered.text).expect_err("expected YAML parse failure");
4340            assert_eq!(err.kind, ErrorKind::Parse);
4341            assert!(err.message.contains("unknown anchor"));
4342        });
4343    }
4344
4345    #[test]
4346    fn preserves_tag_directives_and_handle_tags() {
4347        Python::with_gil(|py| {
4348            let module = PyModule::from_code(
4349                py,
4350                pyo3::ffi::c_str!(
4351                    "from string.templatelib import Template\nscalar=Template('%TAG !e! tag:example.com,2020:\\n---\\nvalue: !e!foo 1\\n')\ndoc_start_comment=Template('--- # comment\\nvalue: 1\\n')\ndoc_start_tag_comment=Template('--- !!str true # comment\\n')\nroot=Template('%YAML 1.2\\n%TAG !e! tag:example.com,2020:\\n---\\n!e!root {value: !e!leaf 1}\\n')\nblock_map=Template('--- !!map\\na: 1\\n')\nblock_seq=Template('--- !!seq\\n- 1\\n- 2\\n')\nanchor_map=Template('--- &root\\n  a: 1\\n')\nanchor_seq=Template('--- !custom &root\\n  - 1\\n  - 2\\n')\n"
4352                ),
4353                pyo3::ffi::c_str!("test_yaml_tag_directives.py"),
4354                pyo3::ffi::c_str!("test_yaml_tag_directives"),
4355            )
4356            .unwrap();
4357
4358            let scalar = module.getattr("scalar").unwrap();
4359            let scalar = extract_template(py, &scalar, "yaml_t/yaml_t_str").unwrap();
4360            let stream = parse_template(&scalar).unwrap();
4361            let rendered = render_document(py, &stream).unwrap();
4362            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4363            assert_eq!(
4364                rendered.text,
4365                "%TAG !e! tag:example.com,2020:\n---\nvalue: !e!foo 1"
4366            );
4367            assert_integer_entry(&documents[0], "value", 1);
4368
4369            let doc_start_comment = module.getattr("doc_start_comment").unwrap();
4370            let doc_start_comment =
4371                extract_template(py, &doc_start_comment, "yaml_t/yaml_t_str").unwrap();
4372            let stream = parse_template(&doc_start_comment).unwrap();
4373            let rendered = render_document(py, &stream).unwrap();
4374            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4375            assert_eq!(rendered.text, "---\nvalue: 1");
4376            assert_integer_entry(&documents[0], "value", 1);
4377
4378            let doc_start_tag_comment = module.getattr("doc_start_tag_comment").unwrap();
4379            let doc_start_tag_comment =
4380                extract_template(py, &doc_start_tag_comment, "yaml_t/yaml_t_str").unwrap();
4381            let stream = parse_template(&doc_start_tag_comment).unwrap();
4382            let rendered = render_document(py, &stream).unwrap();
4383            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4384            assert_eq!(rendered.text, "---\n!!str true");
4385            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
4386
4387            let root = module.getattr("root").unwrap();
4388            let root = extract_template(py, &root, "yaml_t/yaml_t_str").unwrap();
4389            let stream = parse_template(&root).unwrap();
4390            let rendered = render_document(py, &stream).unwrap();
4391            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4392            assert_eq!(
4393                rendered.text,
4394                "%YAML 1.2\n%TAG !e! tag:example.com,2020:\n---\n!e!root { value: !e!leaf 1 }"
4395            );
4396            assert_integer_entry(&documents[0], "value", 1);
4397
4398            let block_map = module.getattr("block_map").unwrap();
4399            let block_map = extract_template(py, &block_map, "yaml_t/yaml_t_str").unwrap();
4400            let stream = parse_template(&block_map).unwrap();
4401            let rendered = render_document(py, &stream).unwrap();
4402            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4403            assert_eq!(rendered.text, "---\n!!map\na: 1");
4404            assert_integer_entry(&documents[0], "a", 1);
4405
4406            let block_seq = module.getattr("block_seq").unwrap();
4407            let block_seq = extract_template(py, &block_seq, "yaml_t/yaml_t_str").unwrap();
4408            let stream = parse_template(&block_seq).unwrap();
4409            let rendered = render_document(py, &stream).unwrap();
4410            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4411            assert_eq!(rendered.text, "---\n!!seq\n- 1\n- 2");
4412            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
4413
4414            let anchor_map = module.getattr("anchor_map").unwrap();
4415            let anchor_map = extract_template(py, &anchor_map, "yaml_t/yaml_t_str").unwrap();
4416            let stream = parse_template(&anchor_map).unwrap();
4417            let rendered = render_document(py, &stream).unwrap();
4418            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4419            assert_eq!(rendered.text, "---\n&root\na: 1");
4420            assert_integer_entry(&documents[0], "a", 1);
4421
4422            let anchor_seq = module.getattr("anchor_seq").unwrap();
4423            let anchor_seq = extract_template(py, &anchor_seq, "yaml_t/yaml_t_str").unwrap();
4424            let stream = parse_template(&anchor_seq).unwrap();
4425            let rendered = render_document(py, &stream).unwrap();
4426            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4427            assert_eq!(rendered.text, "---\n!custom &root\n- 1\n- 2");
4428            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
4429        });
4430    }
4431
4432    #[test]
4433    fn preserves_explicit_core_schema_tags() {
4434        Python::with_gil(|py| {
4435            let module = PyModule::from_code(
4436                py,
4437                pyo3::ffi::c_str!(
4438                    "from string.templatelib import Template\nmapping=Template('value_bool: !!bool true\\nvalue_str: !!str true\\nvalue_float: !!float 1\\nvalue_null: !!null null\\n')\nroot_int=Template('--- !!int 3\\n')\nroot_str=Template('--- !!str true\\n')\nroot_bool=Template('--- !!bool true\\n')\n"
4439                ),
4440                pyo3::ffi::c_str!("test_yaml_core_tags.py"),
4441                pyo3::ffi::c_str!("test_yaml_core_tags"),
4442            )
4443            .unwrap();
4444
4445            let mapping = module.getattr("mapping").unwrap();
4446            let mapping = extract_template(py, &mapping, "yaml_t/yaml_t_str").unwrap();
4447            let rendered = render_document(py, &parse_template(&mapping).unwrap()).unwrap();
4448            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4449            let mapping = yaml_mapping(&documents[0]).expect("expected YAML mapping");
4450            let float_value = mapping
4451                .iter()
4452                .find_map(|(key, value)| {
4453                    (yaml_scalar_text(key) == Some("value_float")).then_some(value)
4454                })
4455                .expect("expected value_float key");
4456            assert_eq!(yaml_float(float_value), Some(1.0));
4457            assert_string_entry(&documents[0], "value_str", "true");
4458
4459            let root_int = module.getattr("root_int").unwrap();
4460            let root_int = extract_template(py, &root_int, "yaml_t/yaml_t_str").unwrap();
4461            let rendered = render_document(py, &parse_template(&root_int).unwrap()).unwrap();
4462            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4463            assert_eq!(yaml_integer(&documents[0]), Some(3));
4464
4465            let root_str = module.getattr("root_str").unwrap();
4466            let root_str = extract_template(py, &root_str, "yaml_t/yaml_t_str").unwrap();
4467            let rendered = render_document(py, &parse_template(&root_str).unwrap()).unwrap();
4468            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4469            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
4470
4471            let root_bool = module.getattr("root_bool").unwrap();
4472            let root_bool = extract_template(py, &root_bool, "yaml_t/yaml_t_str").unwrap();
4473            let rendered = render_document(py, &parse_template(&root_bool).unwrap()).unwrap();
4474            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4475            assert!(documents[0].is_boolean());
4476        });
4477    }
4478
4479    #[test]
4480    fn supports_flow_trailing_commas_sequence_values_and_indent_indicators() {
4481        Python::with_gil(|py| {
4482            let module = PyModule::from_code(
4483                py,
4484                pyo3::ffi::c_str!(
4485                    "from string.templatelib import Template\ncomment_only=Template('# comment\\n')\ncomment_only_explicit=Template('--- # comment\\n')\ncomment_only_explicit_end=Template('--- # comment\\n...\\n')\ncomment_only_explicit_end_stream=Template('--- # comment\\n...\\n---\\na: 1\\n')\ncomment_only_mid_stream=Template('---\\na: 1\\n--- # comment\\n...\\n---\\nb: 2\\n')\ncomment_only_tail_stream=Template('---\\na: 1\\n--- # comment\\n...\\n')\nflow_seq=Template('[1, 2,]\\n')\nempty_flow_seq=Template('value: []\\n')\nflow_map=Template('{a: 1,}\\n')\nempty_flow_map=Template('value: {}\\n')\nflow_scalar_mix=Template('value: [\"\", \\'\\', plain]\\n')\nflow_plain_scalar=Template('value: [1 2]\\n')\nflow_hash_plain_mapping_value=Template('value: {a: b#c}\\n')\nflow_hash_plain_mapping_values=Template('value: {a: b#c, d: e#f}\\n')\nflow_hash_plain_scalars=Template('value: [a#b, c#d]\\n')\nflow_hash_value_sequence=Template('value: [a#b, c#d, e#f]\\n')\nflow_hash_long_sequence=Template('value: [a#b, c#d, e#f, g#h]\\n')\nflow_hash_five_sequence=Template('value: [a#b, c#d, e#f, g#h, i#j]\\n')\nflow_hash_seq_six=Template('value: [a#b, c#d, e#f, g#h, i#j, k#l]\\n')\nflow_hash_seq_seven=Template('value: [a#b, c#d, e#f, g#h, i#j, k#l, m#n]\\n')\nflow_mapping_hash_key=Template('value: {a#b: 1}\\n')\nflow_sequence_comments_value=Template('value: [1, # c\\n 2]\\n')\nflow_mapping_comments_value=Template('value: {a: 1, # c\\n b: 2}\\n')\ncomment_after_value=Template('value: a # c\\n')\nplain_colon_hash=Template('value: a:b#c\\n')\nplain_colon_hash_deeper=Template('value: a:b:c#d\\n')\nplain_hash_chain=Template('value: a#b#c\\n')\nplain_hash_chain_deeper=Template('value: a#b#c#d\\n')\nplain_hash_chain_deeper_comment=Template('value: a#b#c#d # comment\\n')\nflow_hash_mapping_long=Template('value: {a: b#c, d: e#f, g: h#i}\\n')\nflow_hash_mapping_four=Template('value: {a: b#c, d: e#f, g: h#i, j: k#l}\\n')\nflow_hash_mapping_five=Template('value: {a: b#c, d: e#f, g: h#i, j: k#l, m: n#o}\\n')\nflow_hash_mapping_six=Template('value: {a: b#c, d: e#f, g: h#i, j: k#l, m: n#o, p: q#r}\\n')\ncomment_after_plain_colon=Template('value: a:b # c\\n')\ncomment_after_flow_plain_colon=Template('value: [a:b # c\\n]\\n')\nflow_plain_hash_chain=Template('value: [a#b#c, d#e#f]\\n')\nflow_plain_hash_chain_single_deeper=Template('value: [a#b#c#d]\\n')\nflow_plain_hash_chain_single_deeper_comment=Template('value: [a#b#c#d # comment\\n]\\n')\nflow_plain_hash_chain_long=Template('value: [a#b#c, d#e#f, g#h#i]\\n')\nflow_plain_hash_chain_four=Template('value: [a#b#c, d#e#f, g#h#i, j#k#l]\\n')\nblock_plain_comment_after_colon_long=Template('value: a:b:c # comment\\n')\nblock_plain_comment_after_colon_deeper=Template('value: a:b:c:d # comment\\n')\nflow_plain_comment_after_colon_long=Template('value: [a:b:c # comment\\n]\\n')\nflow_plain_comment_after_colon_deeper=Template('value: [a:b:c:d # comment\\n]\\n')\nflow_plain_colon_hash_deeper=Template('value: [a:b:c#d]\\n')\nflow_mapping_plain_key_question=Template('value: {?x: 1}\\n')\nflow_mapping_plain_key_questions=Template('value: {?x: 1, ?y: 2}\\n')\nmapping_empty_flow_values=Template('value: {a: [], b: {}}\\n')\nflow_mapping_empty_key=Template('{\"\": 1}\\n')\nflow_mapping_empty_key_and_values=Template('{\"\": [], foo: {}}\\n')\nflow_mapping_nested_empty=Template('{a: {}, b: []}\\n')\nflow_null_key=Template('{null: 1, \"\": 2}\\n')\nflow_sequence_nested_empty=Template('[[], {}]\\n')\nplain_scalar_colon_no_space=Template('value: a:b\\n')\nplain_question_mark_scalar=Template('value: ?x\\n')\nplain_colon_scalar_flow=Template('value: [a:b, c:d]\\n')\nflow_mapping_colon_plain_key=Template('value: {a:b: c}\\n')\nflow_mapping_colon_and_hash=Template('value: {a:b: c#d}\\n')\nblock_plain_colon_no_space=Template('value: a:b:c\\n')\nblock_null_key=Template('? null\\n: 1\\n')\nquoted_null_key=Template('? \"\"\\n: 1\\n')\nalias_in_flow_mapping_value=Template('base: &a {x: 1}\\nvalue: {ref: *a}\\n')\nflow_null_and_alias=Template('base: &a {x: 1}\\nvalue: {null: *a}\\n')\nalias_seq_value=Template('a: &x [1, 2]\\nb: *x\\n')\nflow_mapping_missing_value=Template('value: {a: }\\n')\nflow_seq_missing_value_before_end=Template('value: [1, 2, ]\\n')\nflow_alias_map=Template('value: {left: &a 1, right: *a}\\n')\nflow_alias_seq=Template('value: [&a 1, *a]\\n')\nflow_merge=Template('value: {<<: &base {a: 1}, b: 2}\\n')\nnested_flow_alias_merge=Template('value: [{<<: &base {a: 1}, b: 2}, *base]\\n')\nexplicit_seq=Template('? a\\n: - 1\\n  - 2\\n')\nindented_block=Template('value: |1\\n a\\n b\\n')\n"
4486                ),
4487                pyo3::ffi::c_str!("test_yaml_edge_cases.py"),
4488                pyo3::ffi::c_str!("test_yaml_edge_cases"),
4489            )
4490            .unwrap();
4491
4492            let flow_seq = module.getattr("flow_seq").unwrap();
4493            let flow_seq = extract_template(py, &flow_seq, "yaml_t/yaml_t_str").unwrap();
4494            let rendered = render_document(py, &parse_template(&flow_seq).unwrap()).unwrap();
4495            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4496            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
4497
4498            let comment_only = module.getattr("comment_only").unwrap();
4499            let comment_only = extract_template(py, &comment_only, "yaml_t/yaml_t_str").unwrap();
4500            let rendered = render_document(py, &parse_template(&comment_only).unwrap()).unwrap();
4501            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4502            assert!(documents[0].is_null());
4503
4504            let comment_only_explicit = module.getattr("comment_only_explicit").unwrap();
4505            let comment_only_explicit =
4506                extract_template(py, &comment_only_explicit, "yaml_t/yaml_t_str").unwrap();
4507            let rendered =
4508                render_document(py, &parse_template(&comment_only_explicit).unwrap()).unwrap();
4509            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4510            assert!(documents[0].is_null());
4511
4512            let comment_only_explicit_end = module.getattr("comment_only_explicit_end").unwrap();
4513            let comment_only_explicit_end =
4514                extract_template(py, &comment_only_explicit_end, "yaml_t/yaml_t_str").unwrap();
4515            let rendered =
4516                render_document(py, &parse_template(&comment_only_explicit_end).unwrap()).unwrap();
4517            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4518            assert!(documents[0].is_null());
4519
4520            let comment_only_explicit_end_stream =
4521                module.getattr("comment_only_explicit_end_stream").unwrap();
4522            let comment_only_explicit_end_stream =
4523                extract_template(py, &comment_only_explicit_end_stream, "yaml_t/yaml_t_str")
4524                    .unwrap();
4525            let rendered = render_document(
4526                py,
4527                &parse_template(&comment_only_explicit_end_stream).unwrap(),
4528            )
4529            .unwrap();
4530            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4531            assert_eq!(documents.len(), 2);
4532            assert!(documents[0].is_null());
4533            assert_integer_entry(&documents[1], "a", 1);
4534
4535            let comment_only_mid_stream = module.getattr("comment_only_mid_stream").unwrap();
4536            let comment_only_mid_stream =
4537                extract_template(py, &comment_only_mid_stream, "yaml_t/yaml_t_str").unwrap();
4538            let rendered =
4539                render_document(py, &parse_template(&comment_only_mid_stream).unwrap()).unwrap();
4540            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4541            assert_eq!(documents.len(), 3);
4542            assert_integer_entry(&documents[0], "a", 1);
4543            assert!(documents[1].is_null());
4544            assert_integer_entry(&documents[2], "b", 2);
4545
4546            let comment_only_tail_stream = module.getattr("comment_only_tail_stream").unwrap();
4547            let comment_only_tail_stream =
4548                extract_template(py, &comment_only_tail_stream, "yaml_t/yaml_t_str").unwrap();
4549            let rendered =
4550                render_document(py, &parse_template(&comment_only_tail_stream).unwrap()).unwrap();
4551            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4552            assert_eq!(documents.len(), 2);
4553            assert_integer_entry(&documents[0], "a", 1);
4554            assert!(documents[1].is_null());
4555
4556            let flow_map = module.getattr("flow_map").unwrap();
4557            let flow_map = extract_template(py, &flow_map, "yaml_t/yaml_t_str").unwrap();
4558            let rendered = render_document(py, &parse_template(&flow_map).unwrap()).unwrap();
4559            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4560            assert_integer_entry(&documents[0], "a", 1);
4561
4562            let empty_flow_seq = module.getattr("empty_flow_seq").unwrap();
4563            let empty_flow_seq =
4564                extract_template(py, &empty_flow_seq, "yaml_t/yaml_t_str").unwrap();
4565            let rendered = render_document(py, &parse_template(&empty_flow_seq).unwrap()).unwrap();
4566            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4567            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4568            assert_eq!(yaml_sequence_len(value), Some(0));
4569
4570            let empty_flow_map = module.getattr("empty_flow_map").unwrap();
4571            let empty_flow_map =
4572                extract_template(py, &empty_flow_map, "yaml_t/yaml_t_str").unwrap();
4573            let rendered = render_document(py, &parse_template(&empty_flow_map).unwrap()).unwrap();
4574            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4575            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4576            assert!(yaml_mapping(value).is_some());
4577
4578            let flow_scalar_mix = module.getattr("flow_scalar_mix").unwrap();
4579            let flow_scalar_mix =
4580                extract_template(py, &flow_scalar_mix, "yaml_t/yaml_t_str").unwrap();
4581            let rendered = render_document(py, &parse_template(&flow_scalar_mix).unwrap()).unwrap();
4582            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4583            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4584            match value {
4585                YamlOwned::Sequence(sequence) => {
4586                    assert_eq!(sequence.len(), 3);
4587                    assert_eq!(yaml_scalar_text(&sequence[0]), Some(""));
4588                    assert_eq!(yaml_scalar_text(&sequence[1]), Some(""));
4589                    assert_eq!(yaml_scalar_text(&sequence[2]), Some("plain"));
4590                }
4591                _ => panic!("expected YAML sequence"),
4592            }
4593
4594            let flow_plain_scalar = module.getattr("flow_plain_scalar").unwrap();
4595            let flow_plain_scalar =
4596                extract_template(py, &flow_plain_scalar, "yaml_t/yaml_t_str").unwrap();
4597            let rendered =
4598                render_document(py, &parse_template(&flow_plain_scalar).unwrap()).unwrap();
4599            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4600            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4601            match value {
4602                YamlOwned::Sequence(sequence) => {
4603                    assert_eq!(sequence.len(), 1);
4604                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("1 2"));
4605                }
4606                _ => panic!("expected YAML sequence"),
4607            }
4608
4609            let flow_hash_plain_mapping_value =
4610                module.getattr("flow_hash_plain_mapping_value").unwrap();
4611            let flow_hash_plain_mapping_value =
4612                extract_template(py, &flow_hash_plain_mapping_value, "yaml_t/yaml_t_str").unwrap();
4613            let rendered =
4614                render_document(py, &parse_template(&flow_hash_plain_mapping_value).unwrap())
4615                    .unwrap();
4616            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4617            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4618            assert_string_entry(value, "a", "b#c");
4619
4620            let flow_hash_plain_mapping_values =
4621                module.getattr("flow_hash_plain_mapping_values").unwrap();
4622            let flow_hash_plain_mapping_values =
4623                extract_template(py, &flow_hash_plain_mapping_values, "yaml_t/yaml_t_str").unwrap();
4624            let rendered = render_document(
4625                py,
4626                &parse_template(&flow_hash_plain_mapping_values).unwrap(),
4627            )
4628            .unwrap();
4629            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4630            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4631            assert_string_entry(value, "a", "b#c");
4632            assert_string_entry(value, "d", "e#f");
4633
4634            let flow_hash_plain_scalars = module.getattr("flow_hash_plain_scalars").unwrap();
4635            let flow_hash_plain_scalars =
4636                extract_template(py, &flow_hash_plain_scalars, "yaml_t/yaml_t_str").unwrap();
4637            let rendered =
4638                render_document(py, &parse_template(&flow_hash_plain_scalars).unwrap()).unwrap();
4639            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4640            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4641            match value {
4642                YamlOwned::Sequence(sequence) => {
4643                    assert_eq!(sequence.len(), 2);
4644                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b"));
4645                    assert_eq!(yaml_scalar_text(&sequence[1]), Some("c#d"));
4646                }
4647                _ => panic!("expected YAML sequence"),
4648            }
4649
4650            let flow_hash_value_sequence = module.getattr("flow_hash_value_sequence").unwrap();
4651            let flow_hash_value_sequence =
4652                extract_template(py, &flow_hash_value_sequence, "yaml_t/yaml_t_str").unwrap();
4653            let rendered =
4654                render_document(py, &parse_template(&flow_hash_value_sequence).unwrap()).unwrap();
4655            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4656            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4657            match value {
4658                YamlOwned::Sequence(sequence) => {
4659                    assert_eq!(sequence.len(), 3);
4660                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b"));
4661                    assert_eq!(yaml_scalar_text(&sequence[1]), Some("c#d"));
4662                    assert_eq!(yaml_scalar_text(&sequence[2]), Some("e#f"));
4663                }
4664                _ => panic!("expected YAML sequence"),
4665            }
4666
4667            let flow_hash_long_sequence = module.getattr("flow_hash_long_sequence").unwrap();
4668            let flow_hash_long_sequence =
4669                extract_template(py, &flow_hash_long_sequence, "yaml_t/yaml_t_str").unwrap();
4670            let rendered =
4671                render_document(py, &parse_template(&flow_hash_long_sequence).unwrap()).unwrap();
4672            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4673            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4674            match value {
4675                YamlOwned::Sequence(sequence) => {
4676                    assert_eq!(sequence.len(), 4);
4677                    assert_eq!(yaml_scalar_text(&sequence[3]), Some("g#h"));
4678                }
4679                _ => panic!("expected YAML sequence"),
4680            }
4681
4682            let flow_hash_five_sequence = module.getattr("flow_hash_five_sequence").unwrap();
4683            let flow_hash_five_sequence =
4684                extract_template(py, &flow_hash_five_sequence, "yaml_t/yaml_t_str").unwrap();
4685            let rendered =
4686                render_document(py, &parse_template(&flow_hash_five_sequence).unwrap()).unwrap();
4687            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4688            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4689            match value {
4690                YamlOwned::Sequence(sequence) => {
4691                    assert_eq!(sequence.len(), 5);
4692                    assert_eq!(yaml_scalar_text(&sequence[4]), Some("i#j"));
4693                }
4694                _ => panic!("expected YAML sequence"),
4695            }
4696
4697            let flow_mapping_hash_key = module.getattr("flow_mapping_hash_key").unwrap();
4698            let flow_mapping_hash_key =
4699                extract_template(py, &flow_mapping_hash_key, "yaml_t/yaml_t_str").unwrap();
4700            let rendered =
4701                render_document(py, &parse_template(&flow_mapping_hash_key).unwrap()).unwrap();
4702            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4703            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4704            assert_integer_entry(value, "a#b", 1);
4705
4706            let flow_sequence_comments_value =
4707                module.getattr("flow_sequence_comments_value").unwrap();
4708            let flow_sequence_comments_value =
4709                extract_template(py, &flow_sequence_comments_value, "yaml_t/yaml_t_str").unwrap();
4710            let rendered =
4711                render_document(py, &parse_template(&flow_sequence_comments_value).unwrap())
4712                    .unwrap();
4713            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4714            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4715            match value {
4716                YamlOwned::Sequence(sequence) => {
4717                    assert_eq!(sequence.len(), 2);
4718                    assert_eq!(yaml_integer(&sequence[0]), Some(1));
4719                    assert_eq!(yaml_integer(&sequence[1]), Some(2));
4720                }
4721                _ => panic!("expected YAML sequence"),
4722            }
4723
4724            let flow_mapping_comments_value =
4725                module.getattr("flow_mapping_comments_value").unwrap();
4726            let flow_mapping_comments_value =
4727                extract_template(py, &flow_mapping_comments_value, "yaml_t/yaml_t_str").unwrap();
4728            let rendered =
4729                render_document(py, &parse_template(&flow_mapping_comments_value).unwrap())
4730                    .unwrap();
4731            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4732            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4733            assert_integer_entry(value, "a", 1);
4734            assert_integer_entry(value, "b", 2);
4735
4736            let comment_after_value = module.getattr("comment_after_value").unwrap();
4737            let comment_after_value =
4738                extract_template(py, &comment_after_value, "yaml_t/yaml_t_str").unwrap();
4739            let rendered =
4740                render_document(py, &parse_template(&comment_after_value).unwrap()).unwrap();
4741            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4742            assert_string_entry(&documents[0], "value", "a");
4743
4744            let plain_colon_hash = module.getattr("plain_colon_hash").unwrap();
4745            let plain_colon_hash =
4746                extract_template(py, &plain_colon_hash, "yaml_t/yaml_t_str").unwrap();
4747            let rendered =
4748                render_document(py, &parse_template(&plain_colon_hash).unwrap()).unwrap();
4749            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4750            assert_string_entry(&documents[0], "value", "a:b#c");
4751
4752            let plain_colon_hash_deeper = module.getattr("plain_colon_hash_deeper").unwrap();
4753            let plain_colon_hash_deeper =
4754                extract_template(py, &plain_colon_hash_deeper, "yaml_t/yaml_t_str").unwrap();
4755            let rendered =
4756                render_document(py, &parse_template(&plain_colon_hash_deeper).unwrap()).unwrap();
4757            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4758            assert_string_entry(&documents[0], "value", "a:b:c#d");
4759
4760            let plain_hash_chain = module.getattr("plain_hash_chain").unwrap();
4761            let plain_hash_chain =
4762                extract_template(py, &plain_hash_chain, "yaml_t/yaml_t_str").unwrap();
4763            let rendered =
4764                render_document(py, &parse_template(&plain_hash_chain).unwrap()).unwrap();
4765            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4766            assert_string_entry(&documents[0], "value", "a#b#c");
4767
4768            let plain_hash_chain_deeper = module.getattr("plain_hash_chain_deeper").unwrap();
4769            let plain_hash_chain_deeper =
4770                extract_template(py, &plain_hash_chain_deeper, "yaml_t/yaml_t_str").unwrap();
4771            let rendered =
4772                render_document(py, &parse_template(&plain_hash_chain_deeper).unwrap()).unwrap();
4773            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4774            assert_string_entry(&documents[0], "value", "a#b#c#d");
4775
4776            let plain_hash_chain_deeper_comment =
4777                module.getattr("plain_hash_chain_deeper_comment").unwrap();
4778            let plain_hash_chain_deeper_comment =
4779                extract_template(py, &plain_hash_chain_deeper_comment, "yaml_t/yaml_t_str")
4780                    .unwrap();
4781            let rendered = render_document(
4782                py,
4783                &parse_template(&plain_hash_chain_deeper_comment).unwrap(),
4784            )
4785            .unwrap();
4786            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4787            assert_string_entry(&documents[0], "value", "a#b#c#d");
4788
4789            let flow_hash_mapping_long = module.getattr("flow_hash_mapping_long").unwrap();
4790            let flow_hash_mapping_long =
4791                extract_template(py, &flow_hash_mapping_long, "yaml_t/yaml_t_str").unwrap();
4792            let rendered =
4793                render_document(py, &parse_template(&flow_hash_mapping_long).unwrap()).unwrap();
4794            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4795            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4796            assert_string_entry(value, "a", "b#c");
4797            assert_string_entry(value, "d", "e#f");
4798            assert_string_entry(value, "g", "h#i");
4799
4800            let flow_hash_mapping_four = module.getattr("flow_hash_mapping_four").unwrap();
4801            let flow_hash_mapping_four =
4802                extract_template(py, &flow_hash_mapping_four, "yaml_t/yaml_t_str").unwrap();
4803            let rendered =
4804                render_document(py, &parse_template(&flow_hash_mapping_four).unwrap()).unwrap();
4805            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4806            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4807            assert_string_entry(value, "a", "b#c");
4808            assert_string_entry(value, "d", "e#f");
4809            assert_string_entry(value, "g", "h#i");
4810            assert_string_entry(value, "j", "k#l");
4811
4812            let flow_hash_mapping_five = module.getattr("flow_hash_mapping_five").unwrap();
4813            let flow_hash_mapping_five =
4814                extract_template(py, &flow_hash_mapping_five, "yaml_t/yaml_t_str").unwrap();
4815            let rendered =
4816                render_document(py, &parse_template(&flow_hash_mapping_five).unwrap()).unwrap();
4817            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4818            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4819            assert_string_entry(value, "m", "n#o");
4820
4821            let flow_hash_mapping_six = module.getattr("flow_hash_mapping_six").unwrap();
4822            let flow_hash_mapping_six =
4823                extract_template(py, &flow_hash_mapping_six, "yaml_t/yaml_t_str").unwrap();
4824            let rendered =
4825                render_document(py, &parse_template(&flow_hash_mapping_six).unwrap()).unwrap();
4826            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4827            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4828            assert_string_entry(value, "p", "q#r");
4829
4830            let comment_after_plain_colon = module.getattr("comment_after_plain_colon").unwrap();
4831            let comment_after_plain_colon =
4832                extract_template(py, &comment_after_plain_colon, "yaml_t/yaml_t_str").unwrap();
4833            let rendered =
4834                render_document(py, &parse_template(&comment_after_plain_colon).unwrap()).unwrap();
4835            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4836            assert_string_entry(&documents[0], "value", "a:b");
4837
4838            let comment_after_flow_plain_colon =
4839                module.getattr("comment_after_flow_plain_colon").unwrap();
4840            let comment_after_flow_plain_colon =
4841                extract_template(py, &comment_after_flow_plain_colon, "yaml_t/yaml_t_str").unwrap();
4842            let rendered = render_document(
4843                py,
4844                &parse_template(&comment_after_flow_plain_colon).unwrap(),
4845            )
4846            .unwrap();
4847            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4848            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4849            match value {
4850                YamlOwned::Sequence(sequence) => {
4851                    assert_eq!(sequence.len(), 1);
4852                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a:b"));
4853                }
4854                _ => panic!("expected YAML sequence"),
4855            }
4856
4857            let flow_plain_hash_chain = module.getattr("flow_plain_hash_chain").unwrap();
4858            let flow_plain_hash_chain =
4859                extract_template(py, &flow_plain_hash_chain, "yaml_t/yaml_t_str").unwrap();
4860            let rendered =
4861                render_document(py, &parse_template(&flow_plain_hash_chain).unwrap()).unwrap();
4862            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4863            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4864            match value {
4865                YamlOwned::Sequence(sequence) => {
4866                    assert_eq!(sequence.len(), 2);
4867                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b#c"));
4868                    assert_eq!(yaml_scalar_text(&sequence[1]), Some("d#e#f"));
4869                }
4870                _ => panic!("expected YAML sequence"),
4871            }
4872
4873            let flow_plain_hash_chain_single_deeper = module
4874                .getattr("flow_plain_hash_chain_single_deeper")
4875                .unwrap();
4876            let flow_plain_hash_chain_single_deeper = extract_template(
4877                py,
4878                &flow_plain_hash_chain_single_deeper,
4879                "yaml_t/yaml_t_str",
4880            )
4881            .unwrap();
4882            let rendered = render_document(
4883                py,
4884                &parse_template(&flow_plain_hash_chain_single_deeper).unwrap(),
4885            )
4886            .unwrap();
4887            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4888            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4889            match value {
4890                YamlOwned::Sequence(sequence) => {
4891                    assert_eq!(sequence.len(), 1);
4892                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b#c#d"));
4893                }
4894                _ => panic!("expected YAML sequence"),
4895            }
4896
4897            let flow_plain_hash_chain_single_deeper_comment = module
4898                .getattr("flow_plain_hash_chain_single_deeper_comment")
4899                .unwrap();
4900            let flow_plain_hash_chain_single_deeper_comment = extract_template(
4901                py,
4902                &flow_plain_hash_chain_single_deeper_comment,
4903                "yaml_t/yaml_t_str",
4904            )
4905            .unwrap();
4906            let rendered = render_document(
4907                py,
4908                &parse_template(&flow_plain_hash_chain_single_deeper_comment).unwrap(),
4909            )
4910            .unwrap();
4911            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4912            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4913            match value {
4914                YamlOwned::Sequence(sequence) => {
4915                    assert_eq!(sequence.len(), 1);
4916                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b#c#d"));
4917                }
4918                _ => panic!("expected YAML sequence"),
4919            }
4920
4921            let flow_hash_seq_six = module.getattr("flow_hash_seq_six").unwrap();
4922            let flow_hash_seq_six =
4923                extract_template(py, &flow_hash_seq_six, "yaml_t/yaml_t_str").unwrap();
4924            let rendered =
4925                render_document(py, &parse_template(&flow_hash_seq_six).unwrap()).unwrap();
4926            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4927            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4928            match value {
4929                YamlOwned::Sequence(sequence) => {
4930                    assert_eq!(sequence.len(), 6);
4931                    assert_eq!(yaml_scalar_text(&sequence[5]), Some("k#l"));
4932                }
4933                _ => panic!("expected YAML sequence"),
4934            }
4935
4936            let flow_hash_seq_seven = module.getattr("flow_hash_seq_seven").unwrap();
4937            let flow_hash_seq_seven =
4938                extract_template(py, &flow_hash_seq_seven, "yaml_t/yaml_t_str").unwrap();
4939            let rendered =
4940                render_document(py, &parse_template(&flow_hash_seq_seven).unwrap()).unwrap();
4941            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4942            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4943            match value {
4944                YamlOwned::Sequence(sequence) => {
4945                    assert_eq!(sequence.len(), 7);
4946                    assert_eq!(yaml_scalar_text(&sequence[6]), Some("m#n"));
4947                }
4948                _ => panic!("expected YAML sequence"),
4949            }
4950
4951            let flow_plain_hash_chain_four = module.getattr("flow_plain_hash_chain_four").unwrap();
4952            let flow_plain_hash_chain_four =
4953                extract_template(py, &flow_plain_hash_chain_four, "yaml_t/yaml_t_str").unwrap();
4954            let rendered =
4955                render_document(py, &parse_template(&flow_plain_hash_chain_four).unwrap()).unwrap();
4956            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4957            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4958            match value {
4959                YamlOwned::Sequence(sequence) => {
4960                    assert_eq!(sequence.len(), 4);
4961                    assert_eq!(yaml_scalar_text(&sequence[3]), Some("j#k#l"));
4962                }
4963                _ => panic!("expected YAML sequence"),
4964            }
4965
4966            let block_plain_comment_after_colon_deeper = module
4967                .getattr("block_plain_comment_after_colon_deeper")
4968                .unwrap();
4969            let block_plain_comment_after_colon_deeper = extract_template(
4970                py,
4971                &block_plain_comment_after_colon_deeper,
4972                "yaml_t/yaml_t_str",
4973            )
4974            .unwrap();
4975            let rendered = render_document(
4976                py,
4977                &parse_template(&block_plain_comment_after_colon_deeper).unwrap(),
4978            )
4979            .unwrap();
4980            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4981            assert_string_entry(&documents[0], "value", "a:b:c:d");
4982
4983            let flow_plain_comment_after_colon_deeper = module
4984                .getattr("flow_plain_comment_after_colon_deeper")
4985                .unwrap();
4986            let flow_plain_comment_after_colon_deeper = extract_template(
4987                py,
4988                &flow_plain_comment_after_colon_deeper,
4989                "yaml_t/yaml_t_str",
4990            )
4991            .unwrap();
4992            let rendered = render_document(
4993                py,
4994                &parse_template(&flow_plain_comment_after_colon_deeper).unwrap(),
4995            )
4996            .unwrap();
4997            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4998            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4999            match value {
5000                YamlOwned::Sequence(sequence) => {
5001                    assert_eq!(sequence.len(), 1);
5002                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a:b:c:d"));
5003                }
5004                _ => panic!("expected YAML sequence"),
5005            }
5006
5007            let flow_mapping_plain_key_question =
5008                module.getattr("flow_mapping_plain_key_question").unwrap();
5009            let flow_mapping_plain_key_question =
5010                extract_template(py, &flow_mapping_plain_key_question, "yaml_t/yaml_t_str")
5011                    .unwrap();
5012            let rendered = render_document(
5013                py,
5014                &parse_template(&flow_mapping_plain_key_question).unwrap(),
5015            )
5016            .unwrap();
5017            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5018            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5019            let mapping = yaml_mapping(value).expect("expected YAML mapping");
5020            assert_eq!(mapping.len(), 1);
5021            let (_, entry_value) = mapping.iter().next().expect("expected YAML mapping entry");
5022            assert_eq!(yaml_integer(entry_value), Some(1));
5023
5024            let flow_mapping_plain_key_questions =
5025                module.getattr("flow_mapping_plain_key_questions").unwrap();
5026            let flow_mapping_plain_key_questions =
5027                extract_template(py, &flow_mapping_plain_key_questions, "yaml_t/yaml_t_str")
5028                    .unwrap();
5029            let rendered = render_document(
5030                py,
5031                &parse_template(&flow_mapping_plain_key_questions).unwrap(),
5032            )
5033            .unwrap();
5034            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5035            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5036            let mapping = yaml_mapping(value).expect("expected YAML mapping");
5037            assert_eq!(mapping.len(), 2);
5038
5039            let mapping_empty_flow_values = module.getattr("mapping_empty_flow_values").unwrap();
5040            let mapping_empty_flow_values =
5041                extract_template(py, &mapping_empty_flow_values, "yaml_t/yaml_t_str").unwrap();
5042            let rendered =
5043                render_document(py, &parse_template(&mapping_empty_flow_values).unwrap()).unwrap();
5044            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5045            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5046            let mapping = yaml_mapping(value).expect("expected YAML mapping");
5047            let left = mapping
5048                .iter()
5049                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("a")).then_some(value))
5050                .expect("expected key a");
5051            let right = mapping
5052                .iter()
5053                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("b")).then_some(value))
5054                .expect("expected key b");
5055            assert_eq!(yaml_sequence_len(left), Some(0));
5056            assert!(yaml_mapping(right).is_some());
5057
5058            let flow_mapping_empty_key = module.getattr("flow_mapping_empty_key").unwrap();
5059            let flow_mapping_empty_key =
5060                extract_template(py, &flow_mapping_empty_key, "yaml_t/yaml_t_str").unwrap();
5061            let rendered =
5062                render_document(py, &parse_template(&flow_mapping_empty_key).unwrap()).unwrap();
5063            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5064            assert_integer_entry(&documents[0], "", 1);
5065
5066            let flow_mapping_empty_key_and_values =
5067                module.getattr("flow_mapping_empty_key_and_values").unwrap();
5068            let flow_mapping_empty_key_and_values =
5069                extract_template(py, &flow_mapping_empty_key_and_values, "yaml_t/yaml_t_str")
5070                    .unwrap();
5071            let rendered = render_document(
5072                py,
5073                &parse_template(&flow_mapping_empty_key_and_values).unwrap(),
5074            )
5075            .unwrap();
5076            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5077            assert!(yaml_mapping_entry(&documents[0], "").is_some());
5078            let foo = yaml_mapping_entry(&documents[0], "foo").expect("expected foo");
5079            assert!(yaml_mapping(foo).is_some());
5080
5081            let flow_mapping_nested_empty = module.getattr("flow_mapping_nested_empty").unwrap();
5082            let flow_mapping_nested_empty =
5083                extract_template(py, &flow_mapping_nested_empty, "yaml_t/yaml_t_str").unwrap();
5084            let rendered =
5085                render_document(py, &parse_template(&flow_mapping_nested_empty).unwrap()).unwrap();
5086            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5087            let a = yaml_mapping_entry(&documents[0], "a").expect("expected key a");
5088            let b = yaml_mapping_entry(&documents[0], "b").expect("expected key b");
5089            assert!(yaml_mapping(a).is_some());
5090            assert_eq!(yaml_sequence_len(b), Some(0));
5091
5092            let flow_null_key = module.getattr("flow_null_key").unwrap();
5093            let flow_null_key = extract_template(py, &flow_null_key, "yaml_t/yaml_t_str").unwrap();
5094            let rendered = render_document(py, &parse_template(&flow_null_key).unwrap()).unwrap();
5095            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5096            let mapping = yaml_mapping(&documents[0]).expect("expected YAML mapping");
5097            assert!(
5098                mapping
5099                    .iter()
5100                    .any(|(key, value)| key.is_null() && yaml_integer(value) == Some(1))
5101            );
5102            assert!(mapping.iter().any(|(key, value)| {
5103                yaml_scalar_text(key) == Some("") && yaml_integer(value) == Some(2)
5104            }));
5105
5106            let block_null_key = module.getattr("block_null_key").unwrap();
5107            let block_null_key =
5108                extract_template(py, &block_null_key, "yaml_t/yaml_t_str").unwrap();
5109            let rendered = render_document(py, &parse_template(&block_null_key).unwrap()).unwrap();
5110            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5111            let mapping = yaml_mapping(&documents[0]).expect("expected YAML mapping");
5112            assert!(
5113                mapping
5114                    .iter()
5115                    .any(|(key, value)| key.is_null() && yaml_integer(value) == Some(1))
5116            );
5117
5118            let quoted_null_key = module.getattr("quoted_null_key").unwrap();
5119            let quoted_null_key =
5120                extract_template(py, &quoted_null_key, "yaml_t/yaml_t_str").unwrap();
5121            let rendered = render_document(py, &parse_template(&quoted_null_key).unwrap()).unwrap();
5122            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5123            assert_integer_entry(&documents[0], "", 1);
5124
5125            let flow_sequence_nested_empty = module.getattr("flow_sequence_nested_empty").unwrap();
5126            let flow_sequence_nested_empty =
5127                extract_template(py, &flow_sequence_nested_empty, "yaml_t/yaml_t_str").unwrap();
5128            let rendered =
5129                render_document(py, &parse_template(&flow_sequence_nested_empty).unwrap()).unwrap();
5130            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5131            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5132
5133            let plain_scalar_colon_no_space =
5134                module.getattr("plain_scalar_colon_no_space").unwrap();
5135            let plain_scalar_colon_no_space =
5136                extract_template(py, &plain_scalar_colon_no_space, "yaml_t/yaml_t_str").unwrap();
5137            let rendered =
5138                render_document(py, &parse_template(&plain_scalar_colon_no_space).unwrap())
5139                    .unwrap();
5140            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5141            assert_string_entry(&documents[0], "value", "a:b");
5142
5143            let plain_question_mark_scalar = module.getattr("plain_question_mark_scalar").unwrap();
5144            let plain_question_mark_scalar =
5145                extract_template(py, &plain_question_mark_scalar, "yaml_t/yaml_t_str").unwrap();
5146            let rendered =
5147                render_document(py, &parse_template(&plain_question_mark_scalar).unwrap()).unwrap();
5148            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5149            assert_string_entry(&documents[0], "value", "?x");
5150
5151            let plain_colon_scalar_flow = module.getattr("plain_colon_scalar_flow").unwrap();
5152            let plain_colon_scalar_flow =
5153                extract_template(py, &plain_colon_scalar_flow, "yaml_t/yaml_t_str").unwrap();
5154            let rendered =
5155                render_document(py, &parse_template(&plain_colon_scalar_flow).unwrap()).unwrap();
5156            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5157            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5158            assert_eq!(yaml_sequence_len(value), Some(2));
5159
5160            let flow_mapping_colon_plain_key =
5161                module.getattr("flow_mapping_colon_plain_key").unwrap();
5162            let flow_mapping_colon_plain_key =
5163                extract_template(py, &flow_mapping_colon_plain_key, "yaml_t/yaml_t_str").unwrap();
5164            let rendered =
5165                render_document(py, &parse_template(&flow_mapping_colon_plain_key).unwrap())
5166                    .unwrap();
5167            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5168            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5169            assert_string_entry(value, "a:b", "c");
5170
5171            let flow_mapping_colon_and_hash =
5172                module.getattr("flow_mapping_colon_and_hash").unwrap();
5173            let flow_mapping_colon_and_hash =
5174                extract_template(py, &flow_mapping_colon_and_hash, "yaml_t/yaml_t_str").unwrap();
5175            let rendered =
5176                render_document(py, &parse_template(&flow_mapping_colon_and_hash).unwrap())
5177                    .unwrap();
5178            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5179            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5180            assert_string_entry(value, "a:b", "c#d");
5181
5182            let block_plain_colon_no_space = module.getattr("block_plain_colon_no_space").unwrap();
5183            let block_plain_colon_no_space =
5184                extract_template(py, &block_plain_colon_no_space, "yaml_t/yaml_t_str").unwrap();
5185            let rendered =
5186                render_document(py, &parse_template(&block_plain_colon_no_space).unwrap()).unwrap();
5187            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5188            assert_string_entry(&documents[0], "value", "a:b:c");
5189
5190            let flow_plain_colon_hash_deeper =
5191                module.getattr("flow_plain_colon_hash_deeper").unwrap();
5192            let flow_plain_colon_hash_deeper =
5193                extract_template(py, &flow_plain_colon_hash_deeper, "yaml_t/yaml_t_str").unwrap();
5194            let rendered =
5195                render_document(py, &parse_template(&flow_plain_colon_hash_deeper).unwrap())
5196                    .unwrap();
5197            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5198            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5199            match value {
5200                YamlOwned::Sequence(sequence) => {
5201                    assert_eq!(sequence.len(), 1);
5202                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a:b:c#d"));
5203                }
5204                _ => panic!("expected YAML sequence"),
5205            }
5206
5207            let alias_in_flow_mapping_value =
5208                module.getattr("alias_in_flow_mapping_value").unwrap();
5209            let alias_in_flow_mapping_value =
5210                extract_template(py, &alias_in_flow_mapping_value, "yaml_t/yaml_t_str").unwrap();
5211            let rendered =
5212                render_document(py, &parse_template(&alias_in_flow_mapping_value).unwrap())
5213                    .unwrap();
5214            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5215            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5216            let reference = yaml_mapping_entry(value, "ref").expect("expected ref");
5217            assert_integer_entry(reference, "x", 1);
5218
5219            let flow_null_and_alias = module.getattr("flow_null_and_alias").unwrap();
5220            let flow_null_and_alias =
5221                extract_template(py, &flow_null_and_alias, "yaml_t/yaml_t_str").unwrap();
5222            let rendered =
5223                render_document(py, &parse_template(&flow_null_and_alias).unwrap()).unwrap();
5224            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5225            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5226            let null_value = yaml_mapping(value)
5227                .and_then(|entries| {
5228                    entries
5229                        .iter()
5230                        .find_map(|(key, value)| key.is_null().then_some(value))
5231                })
5232                .expect("expected null key");
5233            assert_integer_entry(null_value, "x", 1);
5234
5235            let alias_seq_value = module.getattr("alias_seq_value").unwrap();
5236            let alias_seq_value =
5237                extract_template(py, &alias_seq_value, "yaml_t/yaml_t_str").unwrap();
5238            let rendered = render_document(py, &parse_template(&alias_seq_value).unwrap()).unwrap();
5239            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5240            assert_eq!(
5241                yaml_sequence_len(yaml_mapping_entry(&documents[0], "a").expect("expected key a")),
5242                Some(2)
5243            );
5244            assert_eq!(
5245                yaml_sequence_len(yaml_mapping_entry(&documents[0], "b").expect("expected key b")),
5246                Some(2)
5247            );
5248
5249            let flow_mapping_missing_value = module.getattr("flow_mapping_missing_value").unwrap();
5250            let flow_mapping_missing_value =
5251                extract_template(py, &flow_mapping_missing_value, "yaml_t/yaml_t_str").unwrap();
5252            let rendered =
5253                render_document(py, &parse_template(&flow_mapping_missing_value).unwrap()).unwrap();
5254            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5255            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5256            let a = yaml_mapping_entry(value, "a").expect("expected key a");
5257            assert!(a.is_null());
5258
5259            let flow_seq_missing_value_before_end =
5260                module.getattr("flow_seq_missing_value_before_end").unwrap();
5261            let flow_seq_missing_value_before_end =
5262                extract_template(py, &flow_seq_missing_value_before_end, "yaml_t/yaml_t_str")
5263                    .unwrap();
5264            let rendered = render_document(
5265                py,
5266                &parse_template(&flow_seq_missing_value_before_end).unwrap(),
5267            )
5268            .unwrap();
5269            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5270            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5271            assert_eq!(yaml_sequence_len(value), Some(2));
5272
5273            let flow_alias_map = module.getattr("flow_alias_map").unwrap();
5274            let flow_alias_map =
5275                extract_template(py, &flow_alias_map, "yaml_t/yaml_t_str").unwrap();
5276            let rendered = render_document(py, &parse_template(&flow_alias_map).unwrap()).unwrap();
5277            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5278            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5279            assert_integer_entry(value, "left", 1);
5280            assert_integer_entry(value, "right", 1);
5281
5282            let flow_alias_seq = module.getattr("flow_alias_seq").unwrap();
5283            let flow_alias_seq =
5284                extract_template(py, &flow_alias_seq, "yaml_t/yaml_t_str").unwrap();
5285            let rendered = render_document(py, &parse_template(&flow_alias_seq).unwrap()).unwrap();
5286            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5287            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5288            assert_eq!(yaml_sequence_len(value), Some(2));
5289
5290            let flow_merge = module.getattr("flow_merge").unwrap();
5291            let flow_merge = extract_template(py, &flow_merge, "yaml_t/yaml_t_str").unwrap();
5292            let rendered = render_document(py, &parse_template(&flow_merge).unwrap()).unwrap();
5293            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5294            assert!(rendered.text.contains("<<: &base"));
5295            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5296            assert_integer_entry(value, "b", 2);
5297
5298            let template = module.getattr("nested_flow_alias_merge").unwrap();
5299            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5300            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5301            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5302            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5303            let sequence = match value {
5304                YamlOwned::Sequence(sequence) => sequence,
5305                _ => panic!("expected YAML sequence"),
5306            };
5307            assert_eq!(sequence.len(), 2);
5308            assert_integer_entry(&sequence[0], "b", 2);
5309            assert_integer_entry(&sequence[1], "a", 1);
5310
5311            let explicit_seq = module.getattr("explicit_seq").unwrap();
5312            let explicit_seq = extract_template(py, &explicit_seq, "yaml_t/yaml_t_str").unwrap();
5313            let rendered = render_document(py, &parse_template(&explicit_seq).unwrap()).unwrap();
5314            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5315            let mapping = documents[0].as_mapping().expect("expected YAML mapping");
5316            let value = mapping
5317                .iter()
5318                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("a")).then_some(value))
5319                .expect("expected key a");
5320            assert_eq!(yaml_sequence_len(value), Some(2));
5321
5322            let indented_block = module.getattr("indented_block").unwrap();
5323            let indented_block =
5324                extract_template(py, &indented_block, "yaml_t/yaml_t_str").unwrap();
5325            let rendered = render_document(py, &parse_template(&indented_block).unwrap()).unwrap();
5326            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5327            assert_string_entry(&documents[0], "value", "a\nb\n");
5328            assert_eq!(rendered.text, "value: |1\n a\n b\n");
5329        });
5330    }
5331
5332    #[test]
5333    fn rejects_parse_render_and_validation_contracts() {
5334        Python::with_gil(|py| {
5335            let module = PyModule::from_code(
5336                py,
5337                pyo3::ffi::c_str!(
5338                    "from string.templatelib import Template\nclass BadStringValue:\n    def __str__(self):\n        raise ValueError('cannot stringify')\nbad=BadStringValue()\ntag='bad tag'\nparse_open=t'value: [1, 2'\nparse_tab=Template('a:\\t1\\n')\nparse_nested_tab=Template('a:\\n  b:\\n\\t- 1\\n')\nparse_trailing=Template('value: *not alias\\n')\nparse_empty_flow=Template('[1,,2]\\n')\nparse_trailing_entry=Template('value: [1, 2,,]\\n')\nparse_empty_mapping=Template('value: {,}\\n')\nparse_missing_colon=Template('value: {a b}\\n')\nparse_extra_comma=Template('value: {a: 1,, b: 2}\\n')\nunknown_anchor=Template('value: *not_alias\\n')\ncross_doc_anchor=Template('--- &a\\n- 1\\n- 2\\n---\\n*a\\n')\nfragment_template=t'label: \"hi-{bad}\"'\nmetadata_template=t'value: !{tag} ok'\nfloat_template=t'value: {float(\"inf\")}'\n"
5339                ),
5340                pyo3::ffi::c_str!("test_yaml_error_contracts.py"),
5341                pyo3::ffi::c_str!("test_yaml_error_contracts"),
5342            )
5343            .unwrap();
5344
5345            for (name, expected) in [
5346                ("parse_open", "Expected"),
5347                ("parse_tab", "Tabs are not allowed"),
5348                ("parse_nested_tab", "Tabs are not allowed"),
5349                ("parse_trailing", "Unexpected trailing YAML content"),
5350                ("parse_empty_flow", "Expected a YAML value"),
5351                ("parse_trailing_entry", "Expected"),
5352                ("parse_empty_mapping", "Expected ':' in YAML template"),
5353                ("parse_missing_colon", "Expected ':' in YAML template"),
5354                ("parse_extra_comma", "Expected ':' in YAML template"),
5355            ] {
5356                let template = module.getattr(name).unwrap();
5357                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5358                let err = parse_template(&template).expect_err("expected YAML parse failure");
5359                assert_eq!(err.kind, ErrorKind::Parse);
5360                assert!(err.message.contains(expected), "{name}: {}", err.message);
5361            }
5362
5363            let unknown_anchor = module.getattr("unknown_anchor").unwrap();
5364            let unknown_anchor =
5365                extract_template(py, &unknown_anchor, "yaml_t/yaml_t_str").unwrap();
5366            let rendered = render_document(py, &parse_template(&unknown_anchor).unwrap()).unwrap();
5367            let err = parse_rendered_yaml(&rendered.text)
5368                .expect_err("expected YAML unknown-anchor parse failure");
5369            assert_eq!(err.kind, ErrorKind::Parse);
5370            assert!(err.message.contains("unknown anchor"));
5371
5372            let cross_doc_anchor = module.getattr("cross_doc_anchor").unwrap();
5373            let cross_doc_anchor =
5374                extract_template(py, &cross_doc_anchor, "yaml_t/yaml_t_str").unwrap();
5375            let rendered =
5376                render_document(py, &parse_template(&cross_doc_anchor).unwrap()).unwrap();
5377            let err = parse_rendered_yaml(&rendered.text)
5378                .expect_err("expected YAML cross-document anchor parse failure");
5379            assert_eq!(err.kind, ErrorKind::Parse);
5380            assert!(err.message.contains("unknown anchor"));
5381
5382            for (name, expected) in [
5383                ("fragment_template", "fragment"),
5384                ("metadata_template", "metadata"),
5385                ("float_template", "non-finite float"),
5386            ] {
5387                let template = module.getattr(name).unwrap();
5388                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5389                let document = parse_template(&template).unwrap();
5390                let err = match render_document(py, &document) {
5391                    Ok(_) => panic!("expected YAML render failure"),
5392                    Err(err) => err,
5393                };
5394                assert_eq!(err.kind, ErrorKind::Unrepresentable);
5395                assert!(err.message.contains(expected), "{name}: {}", err.message);
5396            }
5397        });
5398    }
5399
5400    #[test]
5401    fn renders_custom_tag_stream_and_complex_key_text() {
5402        Python::with_gil(|py| {
5403            let module = PyModule::from_code(
5404                py,
5405                pyo3::ffi::c_str!(
5406                    "from string.templatelib import Template\ncomment_only_tail_stream=Template('---\\na: 1\\n--- # comment\\n...\\n')\ncustom_tag_sequence=Template('value: !custom [1, 2]\\n')\nflow_alias_map=Template('value: {left: &a 1, right: *a}\\n')\n"
5407                ),
5408                pyo3::ffi::c_str!("test_yaml_render_streams_and_keys.py"),
5409                pyo3::ffi::c_str!("test_yaml_render_streams_and_keys"),
5410            )
5411            .unwrap();
5412
5413            let comment_only_tail_stream = module.getattr("comment_only_tail_stream").unwrap();
5414            let comment_only_tail_stream =
5415                extract_template(py, &comment_only_tail_stream, "yaml_t/yaml_t_str").unwrap();
5416            let rendered =
5417                render_document(py, &parse_template(&comment_only_tail_stream).unwrap()).unwrap();
5418            assert_eq!(rendered.text, "---\na: 1\n---\nnull\n...");
5419            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5420            assert_eq!(documents.len(), 2);
5421            assert_integer_entry(&documents[0], "a", 1);
5422            assert!(documents[1].is_null());
5423
5424            let custom_tag_sequence = module.getattr("custom_tag_sequence").unwrap();
5425            let custom_tag_sequence =
5426                extract_template(py, &custom_tag_sequence, "yaml_t/yaml_t_str").unwrap();
5427            let rendered =
5428                render_document(py, &parse_template(&custom_tag_sequence).unwrap()).unwrap();
5429            assert_eq!(rendered.text, "value: !custom [ 1, 2 ]");
5430            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5431            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5432            assert_eq!(yaml_sequence_len(value), Some(2));
5433
5434            let flow_alias_map = module.getattr("flow_alias_map").unwrap();
5435            let flow_alias_map =
5436                extract_template(py, &flow_alias_map, "yaml_t/yaml_t_str").unwrap();
5437            let rendered = render_document(py, &parse_template(&flow_alias_map).unwrap()).unwrap();
5438            assert_eq!(rendered.text, "value: { left: &a 1, right: *a }");
5439            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5440            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
5441            assert_integer_entry(value, "left", 1);
5442            assert_integer_entry(value, "right", 1);
5443        });
5444    }
5445
5446    #[test]
5447    fn renders_custom_tag_scalar_mapping_and_root_sequence_shapes() {
5448        Python::with_gil(|py| {
5449            let module = PyModule::from_code(
5450                py,
5451                pyo3::ffi::c_str!(
5452                    "scalar_root=t'!custom 3\\n'\nmapping=t'value: !custom 3\\n'\nsequence=t'value: !custom [1, 2]\\n'\ncommented_root_sequence=t'--- # comment\\n!custom [1, 2]\\n'\n"
5453                ),
5454                pyo3::ffi::c_str!("test_yaml_custom_tag_shapes.py"),
5455                pyo3::ffi::c_str!("test_yaml_custom_tag_shapes"),
5456            )
5457            .unwrap();
5458
5459            let scalar_root = module.getattr("scalar_root").unwrap();
5460            let scalar_root = extract_template(py, &scalar_root, "yaml_t/yaml_t_str").unwrap();
5461            let rendered = render_document(py, &parse_template(&scalar_root).unwrap()).unwrap();
5462            assert_eq!(rendered.text, "!custom 3");
5463            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5464            assert_eq!(yaml_integer(&documents[0]), Some(3));
5465
5466            let mapping = module.getattr("mapping").unwrap();
5467            let mapping = extract_template(py, &mapping, "yaml_t/yaml_t_str").unwrap();
5468            let rendered = render_document(py, &parse_template(&mapping).unwrap()).unwrap();
5469            assert_eq!(rendered.text, "value: !custom 3");
5470            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5471            assert_integer_entry(&documents[0], "value", 3);
5472
5473            let sequence = module.getattr("sequence").unwrap();
5474            let sequence = extract_template(py, &sequence, "yaml_t/yaml_t_str").unwrap();
5475            let rendered = render_document(py, &parse_template(&sequence).unwrap()).unwrap();
5476            assert_eq!(rendered.text, "value: !custom [ 1, 2 ]");
5477            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5478            assert_eq!(
5479                yaml_sequence_len(yaml_mapping_entry(&documents[0], "value").expect("value key")),
5480                Some(2)
5481            );
5482
5483            let commented_root_sequence = module.getattr("commented_root_sequence").unwrap();
5484            let commented_root_sequence =
5485                extract_template(py, &commented_root_sequence, "yaml_t/yaml_t_str").unwrap();
5486            let rendered =
5487                render_document(py, &parse_template(&commented_root_sequence).unwrap()).unwrap();
5488            assert_eq!(rendered.text, "---\n!custom [ 1, 2 ]");
5489            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5490            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5491        });
5492    }
5493
5494    #[test]
5495    fn renders_core_schema_scalars_and_top_level_sequence() {
5496        Python::with_gil(|py| {
5497            let module = PyModule::from_code(
5498                py,
5499                pyo3::ffi::c_str!(
5500                    "from string.templatelib import Template\ncore_scalars=Template('value: true\\nnone: null\\nlegacy_bool: on\\nlegacy_yes: yes\\n')\ntop_level_sequence=Template('- 1\\n- true\\n- null\\n- on\\n')\n"
5501                ),
5502                pyo3::ffi::c_str!("test_yaml_core_scalars_and_sequence.py"),
5503                pyo3::ffi::c_str!("test_yaml_core_scalars_and_sequence"),
5504            )
5505            .unwrap();
5506
5507            let core_scalars = module.getattr("core_scalars").unwrap();
5508            let core_scalars = extract_template(py, &core_scalars, "yaml_t/yaml_t_str").unwrap();
5509            let rendered = render_document(py, &parse_template(&core_scalars).unwrap()).unwrap();
5510            assert_eq!(
5511                rendered.text,
5512                "value: true\nnone: null\nlegacy_bool: on\nlegacy_yes: yes"
5513            );
5514            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5515            assert_eq!(documents.len(), 1);
5516            assert_eq!(
5517                yaml_mapping_entry(&documents[0], "value").and_then(YamlOwned::as_bool),
5518                Some(true)
5519            );
5520            assert!(
5521                yaml_mapping_entry(&documents[0], "none")
5522                    .expect("none key")
5523                    .is_null()
5524            );
5525            assert_string_entry(&documents[0], "legacy_bool", "on");
5526            assert_string_entry(&documents[0], "legacy_yes", "yes");
5527
5528            let top_level_sequence = module.getattr("top_level_sequence").unwrap();
5529            let top_level_sequence =
5530                extract_template(py, &top_level_sequence, "yaml_t/yaml_t_str").unwrap();
5531            let rendered =
5532                render_document(py, &parse_template(&top_level_sequence).unwrap()).unwrap();
5533            assert_eq!(rendered.text, "- 1\n- true\n- null\n- on");
5534            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5535            assert_eq!(documents.len(), 1);
5536            let sequence = documents[0].as_vec().expect("expected top-level sequence");
5537            assert_eq!(sequence.len(), 4);
5538            assert_eq!(yaml_integer(&sequence[0]), Some(1));
5539            assert_eq!(sequence[1].as_bool(), Some(true));
5540            assert!(sequence[2].is_null());
5541            assert_eq!(yaml_scalar_text(&sequence[3]), Some("on"));
5542        });
5543    }
5544
5545    #[test]
5546    fn renders_end_to_end_supported_positions_text_and_data() {
5547        Python::with_gil(|py| {
5548            let module = PyModule::from_code(
5549                py,
5550                pyo3::ffi::c_str!(
5551                    "user='Alice'\nkey='owner'\nanchor='item'\ntag='str'\ntemplate=t'''\n{key}: {user}\nlabel: \"prefix-{user}\"\nplain: item-{user}\nitems:\n  - &{anchor} {user}\n  - *{anchor}\ntagged: !{tag} {user}\nflow: [{user}, {{label: {user}}}]\n'''\n"
5552                ),
5553                pyo3::ffi::c_str!("test_yaml_end_to_end_positions.py"),
5554                pyo3::ffi::c_str!("test_yaml_end_to_end_positions"),
5555            )
5556            .unwrap();
5557
5558            let template = module.getattr("template").unwrap();
5559            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5560            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5561            assert_eq!(
5562                rendered.text,
5563                "\"owner\": \"Alice\"\nlabel: \"prefix-Alice\"\nplain: item-Alice\nitems:\n  - &item \"Alice\"\n  - *item\ntagged: !str \"Alice\"\nflow: [ \"Alice\", { label: \"Alice\" } ]"
5564            );
5565            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5566            assert_eq!(documents.len(), 1);
5567            assert_string_entry(&documents[0], "owner", "Alice");
5568            assert_string_entry(&documents[0], "label", "prefix-Alice");
5569            assert_string_entry(&documents[0], "plain", "item-Alice");
5570            assert_string_entry(&documents[0], "tagged", "Alice");
5571            let items = yaml_mapping_entry(&documents[0], "items").expect("items key");
5572            let items = items.as_vec().expect("items sequence");
5573            assert_eq!(items.len(), 2);
5574            assert_eq!(yaml_scalar_text(&items[0]), Some("Alice"));
5575            assert_eq!(yaml_scalar_text(&items[1]), Some("Alice"));
5576            let flow = yaml_mapping_entry(&documents[0], "flow").expect("flow key");
5577            let flow = flow.as_vec().expect("flow sequence");
5578            assert_eq!(yaml_scalar_text(&flow[0]), Some("Alice"));
5579            assert_string_entry(&flow[1], "label", "Alice");
5580        });
5581    }
5582
5583    #[test]
5584    fn renders_block_scalars_and_sequence_item_text_and_data() {
5585        Python::with_gil(|py| {
5586            let module = PyModule::from_code(
5587                py,
5588                pyo3::ffi::c_str!(
5589                    "user='Alice'\ntemplate=t'''\nliteral: |\n  hello {user}\n  world\nfolded: >\n  hello {user}\n  world\nlines:\n  - |\n      item {user}\n'''\n"
5590                ),
5591                pyo3::ffi::c_str!("test_yaml_block_scalars_render.py"),
5592                pyo3::ffi::c_str!("test_yaml_block_scalars_render"),
5593            )
5594            .unwrap();
5595
5596            let template = module.getattr("template").unwrap();
5597            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5598            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5599            assert_eq!(
5600                rendered.text,
5601                "literal: |\n  hello Alice\n  world\nfolded: >\n  hello Alice\n  world\nlines:\n  - |\n    item Alice\n"
5602            );
5603            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5604            assert_eq!(documents.len(), 1);
5605            assert_string_entry(&documents[0], "literal", "hello Alice\nworld\n");
5606            assert_string_entry(&documents[0], "folded", "hello Alice world\n");
5607            let lines = yaml_mapping_entry(&documents[0], "lines").expect("lines key");
5608            let lines = lines.as_vec().expect("lines sequence");
5609            assert_eq!(yaml_scalar_text(&lines[0]), Some("item Alice\n"));
5610        });
5611    }
5612
5613    #[test]
5614    fn renders_comment_only_document_variants_and_mid_stream_shapes() {
5615        Python::with_gil(|py| {
5616            let module = PyModule::from_code(
5617                py,
5618                pyo3::ffi::c_str!(
5619                    "comment_only=t'# comment\\n'\ncomment_only_explicit=t'--- # comment\\n'\ncomment_only_explicit_end=t'--- # comment\\n...\\n'\ncomment_only_mid_stream=t'---\\na: 1\\n--- # comment\\n...\\n---\\nb: 2\\n'\n"
5620                ),
5621                pyo3::ffi::c_str!("test_yaml_comment_variants.py"),
5622                pyo3::ffi::c_str!("test_yaml_comment_variants"),
5623            )
5624            .unwrap();
5625
5626            for (name, expected_text) in [
5627                ("comment_only", "null"),
5628                ("comment_only_explicit", "---\nnull"),
5629                ("comment_only_explicit_end", "---\nnull\n..."),
5630            ] {
5631                let template = module.getattr(name).unwrap();
5632                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5633                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5634                assert_eq!(rendered.text, expected_text);
5635                let documents = parse_rendered_yaml(&rendered.text).unwrap();
5636                assert_eq!(documents.len(), 1);
5637                assert!(documents[0].is_null());
5638            }
5639
5640            let comment_only_mid_stream = module.getattr("comment_only_mid_stream").unwrap();
5641            let comment_only_mid_stream =
5642                extract_template(py, &comment_only_mid_stream, "yaml_t/yaml_t_str").unwrap();
5643            let rendered =
5644                render_document(py, &parse_template(&comment_only_mid_stream).unwrap()).unwrap();
5645            assert_eq!(rendered.text, "---\na: 1\n---\nnull\n...\n---\nb: 2");
5646            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5647            assert_eq!(documents.len(), 3);
5648            assert_integer_entry(&documents[0], "a", 1);
5649            assert!(documents[1].is_null());
5650            assert_integer_entry(&documents[2], "b", 2);
5651        });
5652    }
5653
5654    #[test]
5655    fn renders_tag_directives_and_handle_tag_roots() {
5656        Python::with_gil(|py| {
5657            let module = PyModule::from_code(
5658                py,
5659                pyo3::ffi::c_str!(
5660                    "tag_directive_scalar=t'%TAG !e! tag:example.com,2020:\\n---\\nvalue: !e!foo 1\\n'\ntag_directive_root=t'%YAML 1.2\\n%TAG !e! tag:example.com,2020:\\n---\\n!e!root {{value: !e!leaf 1}}\\n'\ntag_directive_root_comment=t'%YAML 1.2\\n%TAG !e! tag:example.com,2020:\\n--- # comment\\n!e!root {{value: !e!leaf 1}}\\n'\nverbatim_root_mapping=t'--- !<tag:yaml.org,2002:map>\\na: 1\\n'\nverbatim_root_sequence=t'--- !<tag:yaml.org,2002:seq>\\n- 1\\n- 2\\n'\n"
5661                ),
5662                pyo3::ffi::c_str!("test_yaml_tag_directives.py"),
5663                pyo3::ffi::c_str!("test_yaml_tag_directives"),
5664            )
5665            .unwrap();
5666
5667            let tag_directive_scalar = module.getattr("tag_directive_scalar").unwrap();
5668            let tag_directive_scalar =
5669                extract_template(py, &tag_directive_scalar, "yaml_t/yaml_t_str").unwrap();
5670            let rendered =
5671                render_document(py, &parse_template(&tag_directive_scalar).unwrap()).unwrap();
5672            assert_eq!(
5673                rendered.text,
5674                "%TAG !e! tag:example.com,2020:\n---\nvalue: !e!foo 1"
5675            );
5676            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5677            assert_integer_entry(&documents[0], "value", 1);
5678
5679            let tag_directive_root = module.getattr("tag_directive_root").unwrap();
5680            let tag_directive_root =
5681                extract_template(py, &tag_directive_root, "yaml_t/yaml_t_str").unwrap();
5682            let rendered =
5683                render_document(py, &parse_template(&tag_directive_root).unwrap()).unwrap();
5684            assert_eq!(
5685                rendered.text,
5686                "%YAML 1.2\n%TAG !e! tag:example.com,2020:\n---\n!e!root { value: !e!leaf 1 }"
5687            );
5688            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5689            assert_integer_entry(&documents[0], "value", 1);
5690
5691            let tag_directive_root_comment = module.getattr("tag_directive_root_comment").unwrap();
5692            let tag_directive_root_comment =
5693                extract_template(py, &tag_directive_root_comment, "yaml_t/yaml_t_str").unwrap();
5694            let rendered =
5695                render_document(py, &parse_template(&tag_directive_root_comment).unwrap()).unwrap();
5696            assert_eq!(
5697                rendered.text,
5698                "%YAML 1.2\n%TAG !e! tag:example.com,2020:\n---\n!e!root { value: !e!leaf 1 }"
5699            );
5700            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5701            assert_integer_entry(&documents[0], "value", 1);
5702
5703            let verbatim_root_mapping = module.getattr("verbatim_root_mapping").unwrap();
5704            let verbatim_root_mapping =
5705                extract_template(py, &verbatim_root_mapping, "yaml_t/yaml_t_str").unwrap();
5706            let rendered =
5707                render_document(py, &parse_template(&verbatim_root_mapping).unwrap()).unwrap();
5708            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:map>\na: 1");
5709            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5710            assert_integer_entry(&documents[0], "a", 1);
5711
5712            let verbatim_root_sequence = module.getattr("verbatim_root_sequence").unwrap();
5713            let verbatim_root_sequence =
5714                extract_template(py, &verbatim_root_sequence, "yaml_t/yaml_t_str").unwrap();
5715            let rendered =
5716                render_document(py, &parse_template(&verbatim_root_sequence).unwrap()).unwrap();
5717            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:seq>\n- 1\n- 2");
5718            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5719            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5720        });
5721    }
5722
5723    #[test]
5724    fn renders_explicit_core_tag_root_shapes() {
5725        Python::with_gil(|py| {
5726            let module = PyModule::from_code(
5727                py,
5728                pyo3::ffi::c_str!(
5729                    "root_bool=t'--- !!bool true\\n'\nroot_str=t'--- !!str true\\n'\nroot_int=t'--- !!int 3\\n'\n"
5730                ),
5731                pyo3::ffi::c_str!("test_yaml_core_tag_roots.py"),
5732                pyo3::ffi::c_str!("test_yaml_core_tag_roots"),
5733            )
5734            .unwrap();
5735
5736            for (name, expected_text) in [
5737                ("root_bool", "---\n!!bool true"),
5738                ("root_str", "---\n!!str true"),
5739                ("root_int", "---\n!!int 3"),
5740            ] {
5741                let template = module.getattr(name).unwrap();
5742                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5743                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5744                assert_eq!(rendered.text, expected_text);
5745            }
5746
5747            let root_bool = module.getattr("root_bool").unwrap();
5748            let root_bool = extract_template(py, &root_bool, "yaml_t/yaml_t_str").unwrap();
5749            let rendered = render_document(py, &parse_template(&root_bool).unwrap()).unwrap();
5750            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5751            assert_eq!(documents[0].as_bool(), Some(true));
5752
5753            let root_str = module.getattr("root_str").unwrap();
5754            let root_str = extract_template(py, &root_str, "yaml_t/yaml_t_str").unwrap();
5755            let rendered = render_document(py, &parse_template(&root_str).unwrap()).unwrap();
5756            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5757            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
5758
5759            let root_int = module.getattr("root_int").unwrap();
5760            let root_int = extract_template(py, &root_int, "yaml_t/yaml_t_str").unwrap();
5761            let rendered = render_document(py, &parse_template(&root_int).unwrap()).unwrap();
5762            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5763            assert_eq!(yaml_integer(&documents[0]), Some(3));
5764        });
5765    }
5766
5767    #[test]
5768    fn renders_explicit_core_tag_mapping_and_root_text_families() {
5769        Python::with_gil(|py| {
5770            let module = PyModule::from_code(
5771                py,
5772                pyo3::ffi::c_str!(
5773                    "mapping=t'value_bool: !!bool true\\nvalue_str: !!str true\\nvalue_float: !!float 1\\nvalue_null: !!null null\\n'\nroot_int=t'--- !!int 3\\n'\nroot_str=t'--- !!str true\\n'\nroot_bool=t'--- !!bool true\\n'\n"
5774                ),
5775                pyo3::ffi::c_str!("test_yaml_explicit_core_tag_families.py"),
5776                pyo3::ffi::c_str!("test_yaml_explicit_core_tag_families"),
5777            )
5778            .unwrap();
5779
5780            let mapping = module.getattr("mapping").unwrap();
5781            let mapping = extract_template(py, &mapping, "yaml_t/yaml_t_str").unwrap();
5782            let rendered = render_document(py, &parse_template(&mapping).unwrap()).unwrap();
5783            assert_eq!(
5784                rendered.text,
5785                "value_bool: !!bool true\nvalue_str: !!str true\nvalue_float: !!float 1\nvalue_null: !!null null"
5786            );
5787            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5788            assert_eq!(
5789                yaml_mapping_entry(&documents[0], "value_bool").and_then(YamlOwned::as_bool),
5790                Some(true)
5791            );
5792            assert_eq!(
5793                yaml_scalar_text(
5794                    yaml_mapping_entry(&documents[0], "value_str").expect("value_str")
5795                ),
5796                Some("true")
5797            );
5798            assert_eq!(
5799                yaml_mapping_entry(&documents[0], "value_float").and_then(yaml_float),
5800                Some(1.0)
5801            );
5802            assert!(
5803                yaml_mapping_entry(&documents[0], "value_null")
5804                    .expect("value_null")
5805                    .is_null()
5806            );
5807
5808            for (name, expected_text) in [
5809                ("root_int", "---\n!!int 3"),
5810                ("root_str", "---\n!!str true"),
5811                ("root_bool", "---\n!!bool true"),
5812            ] {
5813                let template = module.getattr(name).unwrap();
5814                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5815                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5816                assert_eq!(rendered.text, expected_text, "{name}");
5817            }
5818        });
5819    }
5820
5821    #[test]
5822    fn renders_flow_trailing_comma_explicit_key_and_indent_indicator_families() {
5823        Python::with_gil(|py| {
5824            let module = PyModule::from_code(
5825                py,
5826                pyo3::ffi::c_str!(
5827                    "from string.templatelib import Template\nflow_sequence=t'[1, 2,]\\n'\nflow_mapping=Template('{a: 1,}\\n')\nexplicit_key_sequence_value=t'? a\\n: - 1\\n  - 2\\n'\nindent_indicator=t'value: |1\\n a\\n b\\n'\n"
5828                ),
5829                pyo3::ffi::c_str!("test_yaml_flow_indent_families.py"),
5830                pyo3::ffi::c_str!("test_yaml_flow_indent_families"),
5831            )
5832            .unwrap();
5833
5834            let flow_sequence = module.getattr("flow_sequence").unwrap();
5835            let flow_sequence = extract_template(py, &flow_sequence, "yaml_t/yaml_t_str").unwrap();
5836            let rendered = render_document(py, &parse_template(&flow_sequence).unwrap()).unwrap();
5837            assert_eq!(rendered.text, "[ 1, 2 ]");
5838            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5839            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5840
5841            let flow_mapping = module.getattr("flow_mapping").unwrap();
5842            let flow_mapping = extract_template(py, &flow_mapping, "yaml_t/yaml_t_str").unwrap();
5843            let rendered = render_document(py, &parse_template(&flow_mapping).unwrap()).unwrap();
5844            assert_eq!(rendered.text, "{ a: 1 }");
5845            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5846            assert_integer_entry(&documents[0], "a", 1);
5847
5848            let explicit_key_sequence_value =
5849                module.getattr("explicit_key_sequence_value").unwrap();
5850            let explicit_key_sequence_value =
5851                extract_template(py, &explicit_key_sequence_value, "yaml_t/yaml_t_str").unwrap();
5852            let rendered =
5853                render_document(py, &parse_template(&explicit_key_sequence_value).unwrap())
5854                    .unwrap();
5855            assert_eq!(rendered.text, "? a\n:\n  - 1\n  - 2");
5856            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5857            let entry = documents[0].as_mapping().expect("mapping");
5858            assert_eq!(entry.len(), 1);
5859
5860            let indent_indicator = module.getattr("indent_indicator").unwrap();
5861            let indent_indicator =
5862                extract_template(py, &indent_indicator, "yaml_t/yaml_t_str").unwrap();
5863            let rendered =
5864                render_document(py, &parse_template(&indent_indicator).unwrap()).unwrap();
5865            assert_eq!(rendered.text, "value: |1\n a\n b\n");
5866            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5867            assert_string_entry(&documents[0], "value", "a\nb\n");
5868        });
5869    }
5870
5871    #[test]
5872    fn renders_flow_alias_and_merge_shapes() {
5873        Python::with_gil(|py| {
5874            let module = PyModule::from_code(
5875                py,
5876                pyo3::ffi::c_str!(
5877                    "from string.templatelib import Template\nflow_alias_map=Template('value: {left: &a 1, right: *a}\\n')\nflow_alias_seq=Template('value: [&a 1, *a]\\n')\nflow_merge=Template('value: {<<: &base {a: 1}, b: 2}\\n')\n"
5878                ),
5879                pyo3::ffi::c_str!("test_yaml_flow_alias_merge.py"),
5880                pyo3::ffi::c_str!("test_yaml_flow_alias_merge"),
5881            )
5882            .unwrap();
5883
5884            let flow_alias_map = module.getattr("flow_alias_map").unwrap();
5885            let flow_alias_map =
5886                extract_template(py, &flow_alias_map, "yaml_t/yaml_t_str").unwrap();
5887            let rendered = render_document(py, &parse_template(&flow_alias_map).unwrap()).unwrap();
5888            assert_eq!(rendered.text, "value: { left: &a 1, right: *a }");
5889            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5890            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5891            assert_integer_entry(value, "left", 1);
5892            assert_integer_entry(value, "right", 1);
5893
5894            let flow_alias_seq = module.getattr("flow_alias_seq").unwrap();
5895            let flow_alias_seq =
5896                extract_template(py, &flow_alias_seq, "yaml_t/yaml_t_str").unwrap();
5897            let rendered = render_document(py, &parse_template(&flow_alias_seq).unwrap()).unwrap();
5898            assert_eq!(rendered.text, "value: [ &a 1, *a ]");
5899            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5900            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5901            let value = value.as_vec().expect("value seq");
5902            assert_eq!(yaml_integer(&value[0]), Some(1));
5903            assert_eq!(yaml_integer(&value[1]), Some(1));
5904
5905            let flow_merge = module.getattr("flow_merge").unwrap();
5906            let flow_merge = extract_template(py, &flow_merge, "yaml_t/yaml_t_str").unwrap();
5907            let rendered = render_document(py, &parse_template(&flow_merge).unwrap()).unwrap();
5908            assert_eq!(rendered.text, "value: { <<: &base { a: 1 }, b: 2 }");
5909            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5910            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5911            assert_integer_entry(value, "b", 2);
5912            assert!(
5913                yaml_mapping_entry(value, "a").is_some()
5914                    || yaml_mapping_entry(value, "<<").is_some()
5915            );
5916        });
5917    }
5918
5919    #[test]
5920    fn renders_document_stream_and_root_decorator_shapes() {
5921        Python::with_gil(|py| {
5922            let module = PyModule::from_code(
5923                py,
5924                pyo3::ffi::c_str!(
5925                    "from string.templatelib import Template\ncomment_only_explicit_end_document=Template('--- # comment\\n...\\n')\ncomment_only_explicit_end_stream=Template('--- # comment\\n...\\n---\\na: 1\\n')\ncomment_only_mid_stream=Template('---\\na: 1\\n--- # comment\\n...\\n---\\nb: 2\\n')\nexplicit_end_comment_stream=Template('---\\na: 1\\n... # end\\n---\\nb: 2\\n')\ndoc_start_comment=Template('--- # comment\\nvalue: 1\\n')\ndoc_start_tag_comment=Template('--- !!str true # comment\\n')\ntagged_block_root_mapping=Template('--- !!map\\na: 1\\n')\ntagged_block_root_sequence=Template('--- !!seq\\n- 1\\n- 2\\n')\nroot_anchor_sequence=Template('--- &root\\n  - 1\\n  - 2\\n')\nroot_anchor_custom_mapping=Template('--- &root !custom\\n  a: 1\\n')\nroot_custom_anchor_sequence=Template('--- !custom &root\\n  - 1\\n  - 2\\n')\nflow_newline=Template('{a: 1, b: [2, 3]}\\n')\n"
5926                ),
5927                pyo3::ffi::c_str!("test_yaml_stream_root_shapes.py"),
5928                pyo3::ffi::c_str!("test_yaml_stream_root_shapes"),
5929            )
5930            .unwrap();
5931
5932            let comment_only_explicit_end_document = module
5933                .getattr("comment_only_explicit_end_document")
5934                .unwrap();
5935            let comment_only_explicit_end_document =
5936                extract_template(py, &comment_only_explicit_end_document, "yaml_t/yaml_t_str")
5937                    .unwrap();
5938            let rendered = render_document(
5939                py,
5940                &parse_template(&comment_only_explicit_end_document).unwrap(),
5941            )
5942            .unwrap();
5943            assert_eq!(rendered.text, "---\nnull\n...");
5944            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5945            assert!(matches!(documents[0], YamlOwned::Value(_)));
5946
5947            let comment_only_explicit_end_stream =
5948                module.getattr("comment_only_explicit_end_stream").unwrap();
5949            let comment_only_explicit_end_stream =
5950                extract_template(py, &comment_only_explicit_end_stream, "yaml_t/yaml_t_str")
5951                    .unwrap();
5952            let rendered = render_document(
5953                py,
5954                &parse_template(&comment_only_explicit_end_stream).unwrap(),
5955            )
5956            .unwrap();
5957            assert_eq!(rendered.text, "---\nnull\n...\n---\na: 1");
5958            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5959            assert!(matches!(documents[0], YamlOwned::Value(_)));
5960            assert_integer_entry(&documents[1], "a", 1);
5961
5962            let comment_only_mid_stream = module.getattr("comment_only_mid_stream").unwrap();
5963            let comment_only_mid_stream =
5964                extract_template(py, &comment_only_mid_stream, "yaml_t/yaml_t_str").unwrap();
5965            let rendered =
5966                render_document(py, &parse_template(&comment_only_mid_stream).unwrap()).unwrap();
5967            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5968            assert_integer_entry(&documents[0], "a", 1);
5969            assert!(matches!(documents[1], YamlOwned::Value(_)));
5970            assert_integer_entry(&documents[2], "b", 2);
5971
5972            let explicit_end_comment_stream =
5973                module.getattr("explicit_end_comment_stream").unwrap();
5974            let explicit_end_comment_stream =
5975                extract_template(py, &explicit_end_comment_stream, "yaml_t/yaml_t_str").unwrap();
5976            let rendered =
5977                render_document(py, &parse_template(&explicit_end_comment_stream).unwrap())
5978                    .unwrap();
5979            assert_eq!(rendered.text, "---\na: 1\n...\n---\nb: 2");
5980            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5981            assert_integer_entry(&documents[0], "a", 1);
5982            assert_integer_entry(&documents[1], "b", 2);
5983
5984            let doc_start_comment = module.getattr("doc_start_comment").unwrap();
5985            let doc_start_comment =
5986                extract_template(py, &doc_start_comment, "yaml_t/yaml_t_str").unwrap();
5987            let rendered =
5988                render_document(py, &parse_template(&doc_start_comment).unwrap()).unwrap();
5989            assert_eq!(rendered.text, "---\nvalue: 1");
5990            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5991            assert_integer_entry(&documents[0], "value", 1);
5992
5993            let doc_start_tag_comment = module.getattr("doc_start_tag_comment").unwrap();
5994            let doc_start_tag_comment =
5995                extract_template(py, &doc_start_tag_comment, "yaml_t/yaml_t_str").unwrap();
5996            let rendered =
5997                render_document(py, &parse_template(&doc_start_tag_comment).unwrap()).unwrap();
5998            assert_eq!(rendered.text, "---\n!!str true");
5999            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6000            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
6001
6002            let tagged_block_root_mapping = module.getattr("tagged_block_root_mapping").unwrap();
6003            let tagged_block_root_mapping =
6004                extract_template(py, &tagged_block_root_mapping, "yaml_t/yaml_t_str").unwrap();
6005            let rendered =
6006                render_document(py, &parse_template(&tagged_block_root_mapping).unwrap()).unwrap();
6007            assert_eq!(rendered.text, "---\n!!map\na: 1");
6008            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6009            assert_integer_entry(&documents[0], "a", 1);
6010
6011            let tagged_block_root_sequence = module.getattr("tagged_block_root_sequence").unwrap();
6012            let tagged_block_root_sequence =
6013                extract_template(py, &tagged_block_root_sequence, "yaml_t/yaml_t_str").unwrap();
6014            let rendered =
6015                render_document(py, &parse_template(&tagged_block_root_sequence).unwrap()).unwrap();
6016            assert_eq!(rendered.text, "---\n!!seq\n- 1\n- 2");
6017            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6018            assert_eq!(documents[0].as_vec().expect("root seq").len(), 2);
6019
6020            let root_anchor_sequence = module.getattr("root_anchor_sequence").unwrap();
6021            let root_anchor_sequence =
6022                extract_template(py, &root_anchor_sequence, "yaml_t/yaml_t_str").unwrap();
6023            let rendered =
6024                render_document(py, &parse_template(&root_anchor_sequence).unwrap()).unwrap();
6025            assert_eq!(rendered.text, "---\n&root\n- 1\n- 2");
6026            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6027            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
6028
6029            let root_anchor_custom_mapping = module.getattr("root_anchor_custom_mapping").unwrap();
6030            let root_anchor_custom_mapping =
6031                extract_template(py, &root_anchor_custom_mapping, "yaml_t/yaml_t_str").unwrap();
6032            let rendered =
6033                render_document(py, &parse_template(&root_anchor_custom_mapping).unwrap()).unwrap();
6034            assert_eq!(rendered.text, "---\n!custom &root\na: 1");
6035            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6036            assert_integer_entry(&documents[0], "a", 1);
6037
6038            let root_custom_anchor_sequence =
6039                module.getattr("root_custom_anchor_sequence").unwrap();
6040            let root_custom_anchor_sequence =
6041                extract_template(py, &root_custom_anchor_sequence, "yaml_t/yaml_t_str").unwrap();
6042            let rendered =
6043                render_document(py, &parse_template(&root_custom_anchor_sequence).unwrap())
6044                    .unwrap();
6045            assert_eq!(rendered.text, "---\n!custom &root\n- 1\n- 2");
6046            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6047            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
6048
6049            let flow_newline = module.getattr("flow_newline").unwrap();
6050            let flow_newline = extract_template(py, &flow_newline, "yaml_t/yaml_t_str").unwrap();
6051            let rendered = render_document(py, &parse_template(&flow_newline).unwrap()).unwrap();
6052            assert_eq!(rendered.text, "{ a: 1, b: [ 2, 3 ] }");
6053            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6054            let mapping = yaml_mapping(&documents[0]).expect("expected root flow mapping");
6055            assert_eq!(mapping.len(), 2);
6056        });
6057    }
6058
6059    #[test]
6060    fn renders_merge_and_collection_shape_families() {
6061        Python::with_gil(|py| {
6062            let module = PyModule::from_code(
6063                py,
6064                pyo3::ffi::c_str!(
6065                    "from string.templatelib import Template\nmerge=Template('base: &base\\n  a: 1\\n  b: 2\\nderived:\\n  <<: *base\\n  c: 3\\n')\nflow_nested_alias_merge=Template('value: [{<<: &base {a: 1}, b: 2}, *base]\\n')\nalias_seq_value=Template('a: &x [1, 2]\\nb: *x\\n')\nempty_flow_sequence=Template('value: []\\n')\nempty_flow_mapping=Template('value: {}\\n')\nflow_mapping_missing_value=Template('value: {a: }\\n')\nflow_seq_missing_value_before_end=Template('value: [1, 2, ]\\n')\nindentless_sequence_value=Template('a:\\n- 1\\n- 2\\n')\nsequence_of_mappings=Template('- a: 1\\n  b: 2\\n- c: 3\\n')\nmapping_of_sequence_of_mappings=Template('items:\\n- a: 1\\n  b: 2\\n- c: 3\\n')\nsequence_of_sequences=Template('- - 1\\n  - 2\\n- - 3\\n')\n"
6066                ),
6067                pyo3::ffi::c_str!("test_yaml_merge_collection_shapes.py"),
6068                pyo3::ffi::c_str!("test_yaml_merge_collection_shapes"),
6069            )
6070            .unwrap();
6071
6072            let merge = module.getattr("merge").unwrap();
6073            let merge = extract_template(py, &merge, "yaml_t/yaml_t_str").unwrap();
6074            let rendered = render_document(py, &parse_template(&merge).unwrap()).unwrap();
6075            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6076            let derived = yaml_mapping_entry(&documents[0], "derived").expect("derived");
6077            assert_integer_entry(derived, "c", 3);
6078            assert!(
6079                yaml_mapping_entry(derived, "a").is_some()
6080                    || yaml_mapping_entry(derived, "<<").is_some()
6081            );
6082            assert!(
6083                yaml_mapping_entry(derived, "b").is_some()
6084                    || yaml_mapping_entry(derived, "<<").is_some()
6085            );
6086
6087            let flow_nested_alias_merge = module.getattr("flow_nested_alias_merge").unwrap();
6088            let flow_nested_alias_merge =
6089                extract_template(py, &flow_nested_alias_merge, "yaml_t/yaml_t_str").unwrap();
6090            let rendered =
6091                render_document(py, &parse_template(&flow_nested_alias_merge).unwrap()).unwrap();
6092            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6093            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6094            let value = value.as_vec().expect("value seq");
6095            assert_eq!(value.len(), 2);
6096
6097            let alias_seq_value = module.getattr("alias_seq_value").unwrap();
6098            let alias_seq_value =
6099                extract_template(py, &alias_seq_value, "yaml_t/yaml_t_str").unwrap();
6100            let rendered = render_document(py, &parse_template(&alias_seq_value).unwrap()).unwrap();
6101            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6102            assert_eq!(
6103                yaml_sequence_len(yaml_mapping_entry(&documents[0], "a").expect("a")),
6104                Some(2)
6105            );
6106            assert_eq!(
6107                yaml_sequence_len(yaml_mapping_entry(&documents[0], "b").expect("b")),
6108                Some(2)
6109            );
6110
6111            for (name, expected_len) in [
6112                ("empty_flow_sequence", 0usize),
6113                ("flow_seq_missing_value_before_end", 2usize),
6114            ] {
6115                let template = module.getattr(name).unwrap();
6116                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6117                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6118                let documents = parse_rendered_yaml(&rendered.text).unwrap();
6119                let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6120                assert_eq!(yaml_sequence_len(value), Some(expected_len), "{name}");
6121            }
6122
6123            let empty_flow_mapping = module.getattr("empty_flow_mapping").unwrap();
6124            let empty_flow_mapping =
6125                extract_template(py, &empty_flow_mapping, "yaml_t/yaml_t_str").unwrap();
6126            let rendered =
6127                render_document(py, &parse_template(&empty_flow_mapping).unwrap()).unwrap();
6128            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6129            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6130            assert_eq!(yaml_mapping(value).expect("mapping").len(), 0);
6131
6132            let flow_mapping_missing_value = module.getattr("flow_mapping_missing_value").unwrap();
6133            let flow_mapping_missing_value =
6134                extract_template(py, &flow_mapping_missing_value, "yaml_t/yaml_t_str").unwrap();
6135            let rendered =
6136                render_document(py, &parse_template(&flow_mapping_missing_value).unwrap()).unwrap();
6137            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6138            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6139            assert!(yaml_mapping_entry(value, "a").is_some());
6140
6141            let indentless_sequence_value = module.getattr("indentless_sequence_value").unwrap();
6142            let indentless_sequence_value =
6143                extract_template(py, &indentless_sequence_value, "yaml_t/yaml_t_str").unwrap();
6144            let rendered =
6145                render_document(py, &parse_template(&indentless_sequence_value).unwrap()).unwrap();
6146            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6147            assert_eq!(
6148                yaml_sequence_len(yaml_mapping_entry(&documents[0], "a").expect("a")),
6149                Some(2)
6150            );
6151
6152            let sequence_of_mappings = module.getattr("sequence_of_mappings").unwrap();
6153            let sequence_of_mappings =
6154                extract_template(py, &sequence_of_mappings, "yaml_t/yaml_t_str").unwrap();
6155            let rendered =
6156                render_document(py, &parse_template(&sequence_of_mappings).unwrap()).unwrap();
6157            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6158            assert_eq!(documents[0].as_vec().expect("top-level seq").len(), 2);
6159
6160            let mapping_of_sequence_of_mappings =
6161                module.getattr("mapping_of_sequence_of_mappings").unwrap();
6162            let mapping_of_sequence_of_mappings =
6163                extract_template(py, &mapping_of_sequence_of_mappings, "yaml_t/yaml_t_str")
6164                    .unwrap();
6165            let rendered = render_document(
6166                py,
6167                &parse_template(&mapping_of_sequence_of_mappings).unwrap(),
6168            )
6169            .unwrap();
6170            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6171            assert_eq!(
6172                yaml_sequence_len(yaml_mapping_entry(&documents[0], "items").expect("items")),
6173                Some(2)
6174            );
6175
6176            let sequence_of_sequences = module.getattr("sequence_of_sequences").unwrap();
6177            let sequence_of_sequences =
6178                extract_template(py, &sequence_of_sequences, "yaml_t/yaml_t_str").unwrap();
6179            let rendered =
6180                render_document(py, &parse_template(&sequence_of_sequences).unwrap()).unwrap();
6181            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6182            let top = documents[0].as_vec().expect("top-level seq");
6183            assert_eq!(top.len(), 2);
6184            assert_eq!(yaml_sequence_len(&top[0]), Some(2));
6185            assert_eq!(yaml_sequence_len(&top[1]), Some(1));
6186        });
6187    }
6188
6189    #[test]
6190    fn renders_flow_scalar_key_and_comment_edge_families() {
6191        Python::with_gil(|py| {
6192            let module = PyModule::from_code(
6193                py,
6194                pyo3::ffi::c_str!(
6195                    "from string.templatelib import Template\nflow_plain_scalar_with_space=Template('value: [1 2]\\n')\nmapping_empty_flow_values=Template('value: {a: [], b: {}}\\n')\nflow_mapping_empty_key_and_values=Template('{\"\": [], foo: {}}\\n')\nflow_null_key=Template('{null: 1, \"\": 2}\\n')\nblock_null_key=Template('? null\\n: 1\\n')\nquoted_null_key=Template('? \"\"\\n: 1\\n')\nplain_question_mark_scalar=Template('value: ?x\\n')\nplain_colon_scalar_flow=Template('value: [a:b, c:d]\\n')\nflow_mapping_plain_key_questions=Template('value: {?x: 1, ?y: 2}\\n')\nflow_hash_mapping_four=Template('value: {a: b#c, d: e#f, g: h#i, j: k#l}\\n')\nflow_hash_seq_seven=Template('value: [a#b, c#d, e#f, g#h, i#j, k#l, m#n]\\n')\ncomment_after_flow_plain_colon=Template('value: [a:b # c\\n]\\n')\nflow_plain_comment_after_colon_deeper=Template('value: [a:b:c:d # comment\\n]\\n')\n"
6196                ),
6197                pyo3::ffi::c_str!("test_yaml_flow_scalar_edge_families.py"),
6198                pyo3::ffi::c_str!("test_yaml_flow_scalar_edge_families"),
6199            )
6200            .unwrap();
6201
6202            let flow_plain_scalar_with_space =
6203                module.getattr("flow_plain_scalar_with_space").unwrap();
6204            let flow_plain_scalar_with_space =
6205                extract_template(py, &flow_plain_scalar_with_space, "yaml_t/yaml_t_str").unwrap();
6206            let rendered =
6207                render_document(py, &parse_template(&flow_plain_scalar_with_space).unwrap())
6208                    .unwrap();
6209            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6210            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6211            let value = value.as_vec().expect("value seq");
6212            assert_eq!(yaml_scalar_text(&value[0]), Some("1 2"));
6213
6214            let mapping_empty_flow_values = module.getattr("mapping_empty_flow_values").unwrap();
6215            let mapping_empty_flow_values =
6216                extract_template(py, &mapping_empty_flow_values, "yaml_t/yaml_t_str").unwrap();
6217            let rendered =
6218                render_document(py, &parse_template(&mapping_empty_flow_values).unwrap()).unwrap();
6219            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6220            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6221            assert_eq!(
6222                yaml_sequence_len(yaml_mapping_entry(value, "a").expect("a")),
6223                Some(0)
6224            );
6225            assert_eq!(
6226                yaml_mapping(yaml_mapping_entry(value, "b").expect("b"))
6227                    .expect("b mapping")
6228                    .len(),
6229                0
6230            );
6231
6232            let flow_mapping_empty_key_and_values =
6233                module.getattr("flow_mapping_empty_key_and_values").unwrap();
6234            let flow_mapping_empty_key_and_values =
6235                extract_template(py, &flow_mapping_empty_key_and_values, "yaml_t/yaml_t_str")
6236                    .unwrap();
6237            let rendered = render_document(
6238                py,
6239                &parse_template(&flow_mapping_empty_key_and_values).unwrap(),
6240            )
6241            .unwrap();
6242            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6243            assert_eq!(
6244                yaml_sequence_len(yaml_mapping_entry(&documents[0], "").expect("empty key")),
6245                Some(0)
6246            );
6247            assert_eq!(
6248                yaml_mapping(yaml_mapping_entry(&documents[0], "foo").expect("foo"))
6249                    .expect("foo mapping")
6250                    .len(),
6251                0
6252            );
6253
6254            let flow_null_key = module.getattr("flow_null_key").unwrap();
6255            let flow_null_key = extract_template(py, &flow_null_key, "yaml_t/yaml_t_str").unwrap();
6256            let rendered = render_document(py, &parse_template(&flow_null_key).unwrap()).unwrap();
6257            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6258            assert_eq!(documents[0].as_mapping().expect("mapping").len(), 2);
6259
6260            let block_null_key = module.getattr("block_null_key").unwrap();
6261            let block_null_key =
6262                extract_template(py, &block_null_key, "yaml_t/yaml_t_str").unwrap();
6263            let rendered = render_document(py, &parse_template(&block_null_key).unwrap()).unwrap();
6264            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6265            assert_eq!(documents[0].as_mapping().expect("mapping").len(), 1);
6266
6267            let quoted_null_key = module.getattr("quoted_null_key").unwrap();
6268            let quoted_null_key =
6269                extract_template(py, &quoted_null_key, "yaml_t/yaml_t_str").unwrap();
6270            let rendered = render_document(py, &parse_template(&quoted_null_key).unwrap()).unwrap();
6271            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6272            assert_eq!(documents[0].as_mapping().expect("mapping").len(), 1);
6273
6274            let plain_question_mark_scalar = module.getattr("plain_question_mark_scalar").unwrap();
6275            let plain_question_mark_scalar =
6276                extract_template(py, &plain_question_mark_scalar, "yaml_t/yaml_t_str").unwrap();
6277            let rendered =
6278                render_document(py, &parse_template(&plain_question_mark_scalar).unwrap()).unwrap();
6279            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6280            assert_string_entry(&documents[0], "value", "?x");
6281
6282            let plain_colon_scalar_flow = module.getattr("plain_colon_scalar_flow").unwrap();
6283            let plain_colon_scalar_flow =
6284                extract_template(py, &plain_colon_scalar_flow, "yaml_t/yaml_t_str").unwrap();
6285            let rendered =
6286                render_document(py, &parse_template(&plain_colon_scalar_flow).unwrap()).unwrap();
6287            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6288            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6289            let value = value.as_vec().expect("value seq");
6290            assert_eq!(yaml_scalar_text(&value[0]), Some("a:b"));
6291            assert_eq!(yaml_scalar_text(&value[1]), Some("c:d"));
6292
6293            let flow_mapping_plain_key_questions =
6294                module.getattr("flow_mapping_plain_key_questions").unwrap();
6295            let flow_mapping_plain_key_questions =
6296                extract_template(py, &flow_mapping_plain_key_questions, "yaml_t/yaml_t_str")
6297                    .unwrap();
6298            let rendered = render_document(
6299                py,
6300                &parse_template(&flow_mapping_plain_key_questions).unwrap(),
6301            )
6302            .unwrap();
6303            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6304            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6305            assert_eq!(yaml_mapping(value).expect("mapping").len(), 2);
6306
6307            let flow_hash_mapping_four = module.getattr("flow_hash_mapping_four").unwrap();
6308            let flow_hash_mapping_four =
6309                extract_template(py, &flow_hash_mapping_four, "yaml_t/yaml_t_str").unwrap();
6310            let rendered =
6311                render_document(py, &parse_template(&flow_hash_mapping_four).unwrap()).unwrap();
6312            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6313            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6314            assert_eq!(yaml_mapping(value).expect("mapping").len(), 4);
6315
6316            let flow_hash_seq_seven = module.getattr("flow_hash_seq_seven").unwrap();
6317            let flow_hash_seq_seven =
6318                extract_template(py, &flow_hash_seq_seven, "yaml_t/yaml_t_str").unwrap();
6319            let rendered =
6320                render_document(py, &parse_template(&flow_hash_seq_seven).unwrap()).unwrap();
6321            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6322            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6323            assert_eq!(yaml_sequence_len(value), Some(7));
6324
6325            let comment_after_flow_plain_colon =
6326                module.getattr("comment_after_flow_plain_colon").unwrap();
6327            let comment_after_flow_plain_colon =
6328                extract_template(py, &comment_after_flow_plain_colon, "yaml_t/yaml_t_str").unwrap();
6329            let rendered = render_document(
6330                py,
6331                &parse_template(&comment_after_flow_plain_colon).unwrap(),
6332            )
6333            .unwrap();
6334            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6335            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6336            let value = value.as_vec().expect("value seq");
6337            assert_eq!(yaml_scalar_text(&value[0]), Some("a:b"));
6338
6339            let flow_plain_comment_after_colon_deeper = module
6340                .getattr("flow_plain_comment_after_colon_deeper")
6341                .unwrap();
6342            let flow_plain_comment_after_colon_deeper = extract_template(
6343                py,
6344                &flow_plain_comment_after_colon_deeper,
6345                "yaml_t/yaml_t_str",
6346            )
6347            .unwrap();
6348            let rendered = render_document(
6349                py,
6350                &parse_template(&flow_plain_comment_after_colon_deeper).unwrap(),
6351            )
6352            .unwrap();
6353            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6354            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6355            let value = value.as_vec().expect("value seq");
6356            assert_eq!(yaml_scalar_text(&value[0]), Some("a:b:c:d"));
6357        });
6358    }
6359
6360    #[test]
6361    fn renders_flow_collection_comment_and_verbatim_tag_families() {
6362        Python::with_gil(|py| {
6363            let module = PyModule::from_code(
6364                py,
6365                pyo3::ffi::c_str!(
6366                    "from string.templatelib import Template\nverbatim_tag=Template('value: !<tag:yaml.org,2002:str> hello\\n')\nflow_wrapped_sequence=Template('key: [a,\\n  b]\\n')\nflow_wrapped_mapping=Template('key: {a: 1,\\n  b: 2}\\n')\nflow_sequence_comment=Template('key: [a, # first\\n  b]\\n')\nflow_mapping_comment=Template('key: {a: 1, # first\\n  b: 2}\\n')\nalias_in_flow_mapping_value=Template('base: &a {x: 1}\\nvalue: {ref: *a}\\n')\nflow_null_and_alias=Template('base: &a {x: 1}\\nvalue: {null: *a}\\n')\n"
6367                ),
6368                pyo3::ffi::c_str!("test_yaml_flow_collection_comment_and_tag_families.py"),
6369                pyo3::ffi::c_str!("test_yaml_flow_collection_comment_and_tag_families"),
6370            )
6371            .unwrap();
6372
6373            let verbatim_tag = module.getattr("verbatim_tag").unwrap();
6374            let verbatim_tag = extract_template(py, &verbatim_tag, "yaml_t/yaml_t_str").unwrap();
6375            let rendered = render_document(py, &parse_template(&verbatim_tag).unwrap()).unwrap();
6376            assert_eq!(rendered.text, "value: !<tag:yaml.org,2002:str> hello");
6377            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6378            assert_string_entry(&documents[0], "value", "hello");
6379
6380            for (name, expected_text) in [
6381                ("flow_wrapped_sequence", "key: [ a, b ]"),
6382                ("flow_sequence_comment", "key: [ a, b ]"),
6383            ] {
6384                let template = module.getattr(name).unwrap();
6385                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6386                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6387                assert_eq!(rendered.text, expected_text, "{name}");
6388                let documents = parse_rendered_yaml(&rendered.text).unwrap();
6389                let value = yaml_mapping_entry(&documents[0], "key").expect("sequence key");
6390                assert_eq!(yaml_sequence_len(value), Some(2), "{name}");
6391            }
6392
6393            for (name, expected_text) in [
6394                ("flow_wrapped_mapping", "key: { a: 1, b: 2 }"),
6395                ("flow_mapping_comment", "key: { a: 1, b: 2 }"),
6396            ] {
6397                let template = module.getattr(name).unwrap();
6398                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6399                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6400                assert_eq!(rendered.text, expected_text, "{name}");
6401                let documents = parse_rendered_yaml(&rendered.text).unwrap();
6402                let value = yaml_mapping_entry(&documents[0], "key").expect("mapping key");
6403                assert_eq!(yaml_mapping(value).expect("mapping").len(), 2, "{name}");
6404            }
6405
6406            let alias_in_flow_mapping_value =
6407                module.getattr("alias_in_flow_mapping_value").unwrap();
6408            let alias_in_flow_mapping_value =
6409                extract_template(py, &alias_in_flow_mapping_value, "yaml_t/yaml_t_str").unwrap();
6410            let rendered =
6411                render_document(py, &parse_template(&alias_in_flow_mapping_value).unwrap())
6412                    .unwrap();
6413            assert_eq!(rendered.text, "base: &a { x: 1 }\nvalue: { ref: *a }");
6414            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6415            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6416            let referenced = yaml_mapping_entry(value, "ref").expect("ref key");
6417            assert_integer_entry(referenced, "x", 1);
6418
6419            let flow_null_and_alias = module.getattr("flow_null_and_alias").unwrap();
6420            let flow_null_and_alias =
6421                extract_template(py, &flow_null_and_alias, "yaml_t/yaml_t_str").unwrap();
6422            let rendered =
6423                render_document(py, &parse_template(&flow_null_and_alias).unwrap()).unwrap();
6424            assert_eq!(rendered.text, "base: &a { x: 1 }\nvalue: { null: *a }");
6425            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6426            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
6427            assert_eq!(yaml_mapping(value).expect("mapping").len(), 1);
6428        });
6429    }
6430
6431    #[test]
6432    fn renders_verbatim_root_scalar_text_and_data() {
6433        Python::with_gil(|py| {
6434            let module = PyModule::from_code(
6435                py,
6436                pyo3::ffi::c_str!("template=t'--- !<tag:yaml.org,2002:str> hello\\n'\n"),
6437                pyo3::ffi::c_str!("test_yaml_verbatim_root_scalar.py"),
6438                pyo3::ffi::c_str!("test_yaml_verbatim_root_scalar"),
6439            )
6440            .unwrap();
6441
6442            let template = module.getattr("template").unwrap();
6443            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6444            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6445            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:str> hello");
6446            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6447            assert_eq!(yaml_scalar_text(&documents[0]), Some("hello"));
6448        });
6449    }
6450
6451    #[test]
6452    fn renders_verbatim_root_anchor_scalar_text_and_data() {
6453        Python::with_gil(|py| {
6454            let module = PyModule::from_code(
6455                py,
6456                pyo3::ffi::c_str!("template=t'--- !<tag:yaml.org,2002:str> &root hello\\n'\n"),
6457                pyo3::ffi::c_str!("test_yaml_verbatim_root_anchor_scalar.py"),
6458                pyo3::ffi::c_str!("test_yaml_verbatim_root_anchor_scalar"),
6459            )
6460            .unwrap();
6461
6462            let template = module.getattr("template").unwrap();
6463            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6464            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6465            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:str> &root hello");
6466            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6467            assert_eq!(yaml_scalar_text(&documents[0]), Some("hello"));
6468        });
6469    }
6470
6471    #[test]
6472    fn renders_spec_chapter_2_examples_text_and_data() {
6473        Python::with_gil(|py| {
6474            let module = PyModule::from_code(
6475                py,
6476                pyo3::ffi::c_str!(
6477                    "players=t'- Mark McGwire\\n- Sammy Sosa\\n- Ken Griffey\\n'\nclubs=t'american:\\n- Boston Red Sox\\n- Detroit Tigers\\n- New York Yankees\\nnational:\\n- New York Mets\\n- Chicago Cubs\\n- Atlanta Braves\\n'\nstats_seq=t'-\\n  name: Mark McGwire\\n  hr:   65\\n  avg:  0.278\\n-\\n  name: Sammy Sosa\\n  hr:   63\\n  avg:  0.288\\n'\nmap_of_maps=t'Mark McGwire: {{hr: 65, avg: 0.278}}\\nSammy Sosa: {{\\n  hr: 63,\\n  avg: 0.288,\\n}}\\n'\ntwo_docs=t'# Ranking of 1998 home runs\\n---\\n- Mark McGwire\\n- Sammy Sosa\\n- Ken Griffey\\n\\n# Team ranking\\n---\\n- Chicago Cubs\\n- St Louis Cardinals\\n'\nplay_feed=t'---\\ntime: 20:03:20\\nplayer: Sammy Sosa\\naction: strike (miss)\\n...\\n---\\ntime: 20:03:47\\nplayer: Sammy Sosa\\naction: grand slam\\n...\\n'\n"
6478                ),
6479                pyo3::ffi::c_str!("test_yaml_spec_chapter_2_examples.py"),
6480                pyo3::ffi::c_str!("test_yaml_spec_chapter_2_examples"),
6481            )
6482            .unwrap();
6483
6484            for (name, expected_text) in [
6485                ("players", "- Mark McGwire\n- Sammy Sosa\n- Ken Griffey"),
6486                (
6487                    "clubs",
6488                    "american:\n  - Boston Red Sox\n  - Detroit Tigers\n  - New York Yankees\nnational:\n  - New York Mets\n  - Chicago Cubs\n  - Atlanta Braves",
6489                ),
6490                (
6491                    "stats_seq",
6492                    "-\n  name: Mark McGwire\n  hr: 65\n  avg: 0.278\n-\n  name: Sammy Sosa\n  hr: 63\n  avg: 0.288",
6493                ),
6494                (
6495                    "map_of_maps",
6496                    "Mark McGwire: { hr: 65, avg: 0.278 }\nSammy Sosa: { hr: 63, avg: 0.288 }",
6497                ),
6498                (
6499                    "two_docs",
6500                    "---\n- Mark McGwire\n- Sammy Sosa\n- Ken Griffey\n---\n- Chicago Cubs\n- St Louis Cardinals",
6501                ),
6502                (
6503                    "play_feed",
6504                    "---\ntime: 20:03:20\nplayer: Sammy Sosa\naction: strike (miss)\n...\n---\ntime: 20:03:47\nplayer: Sammy Sosa\naction: grand slam\n...",
6505                ),
6506            ] {
6507                let template = module.getattr(name).unwrap();
6508                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6509                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6510                assert_eq!(rendered.text, expected_text, "{name}");
6511                let documents = parse_rendered_yaml(&rendered.text).unwrap();
6512                match name {
6513                    "players" => assert_eq!(yaml_sequence_len(&documents[0]), Some(3)),
6514                    "clubs" => assert_eq!(yaml_mapping(&documents[0]).expect("clubs").len(), 2),
6515                    "stats_seq" => assert_eq!(yaml_sequence_len(&documents[0]), Some(2)),
6516                    "map_of_maps" => {
6517                        assert_eq!(yaml_mapping(&documents[0]).expect("map_of_maps").len(), 2)
6518                    }
6519                    "two_docs" => assert_eq!(documents.len(), 2),
6520                    "play_feed" => assert_eq!(documents.len(), 2),
6521                    _ => unreachable!(),
6522                }
6523            }
6524        });
6525    }
6526
6527    #[test]
6528    fn test_parse_rendered_yaml_surfaces_parse_failures() {
6529        let err =
6530            parse_rendered_yaml("value: *missing\n").expect_err("expected YAML parse failure");
6531        assert_eq!(err.kind, ErrorKind::Parse);
6532        assert!(err.message.contains("Rendered YAML could not be reparsed"));
6533    }
6534}