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 normalize_documents_with_profile(
2052    documents: &[YamlOwned],
2053    _profile: YamlProfile,
2054) -> BackendResult<NormalizedStream> {
2055    // YAML normalization does not vary by profile yet, but the signature does
2056    // so future profile-specific semantics can plug in without reshaping APIs.
2057    documents
2058        .iter()
2059        .map(normalize_document)
2060        .collect::<BackendResult<Vec<_>>>()
2061        .map(NormalizedStream::new)
2062}
2063
2064pub fn normalize_documents(documents: &[YamlOwned]) -> BackendResult<NormalizedStream> {
2065    normalize_documents_with_profile(documents, YamlProfile::default())
2066}
2067
2068pub fn align_normalized_stream_with_ast(
2069    stream: &YamlStreamNode,
2070    normalized: &mut NormalizedStream,
2071) {
2072    for (document_node, document) in stream.documents.iter().zip(normalized.documents.iter_mut()) {
2073        align_document_with_ast(document_node, document);
2074    }
2075}
2076
2077fn normalize_document(document: &YamlOwned) -> BackendResult<NormalizedDocument> {
2078    if matches!(document, YamlOwned::BadValue) {
2079        return Ok(NormalizedDocument::Empty);
2080    }
2081    Ok(NormalizedDocument::Value(normalize_value(document)?))
2082}
2083
2084fn align_document_with_ast(node: &YamlDocumentNode, document: &mut NormalizedDocument) {
2085    if let NormalizedDocument::Value(value) = document {
2086        align_value_with_ast(&node.value, value);
2087    }
2088}
2089
2090fn normalize_value(value: &YamlOwned) -> BackendResult<NormalizedValue> {
2091    match value {
2092        YamlOwned::Value(value) => normalize_scalar(value),
2093        YamlOwned::Representation(value, style, tag) => {
2094            normalize_representation_value(value, *style, tag.as_ref())
2095        }
2096        YamlOwned::Sequence(values) => values
2097            .iter()
2098            .map(normalize_value)
2099            .collect::<BackendResult<Vec<_>>>()
2100            .map(NormalizedValue::Sequence),
2101        YamlOwned::Mapping(values) => normalize_mapping(values),
2102        YamlOwned::Tagged(tag, value) => {
2103            normalize_tagged_value(tag.handle.as_str(), &tag.suffix, value)
2104        }
2105        YamlOwned::Alias(_) => Err(BackendError::semantic(
2106            "Rendered YAML still contains an unresolved alias after validation.",
2107        )),
2108        YamlOwned::BadValue => Ok(NormalizedValue::Null),
2109    }
2110}
2111
2112fn normalize_representation_value(
2113    value: &str,
2114    style: ScalarStyle,
2115    tag: Option<&Tag>,
2116) -> BackendResult<NormalizedValue> {
2117    let parsed = ScalarOwned::parse_from_cow_and_metadata(
2118        Cow::Borrowed(value),
2119        style,
2120        tag.map(Cow::Borrowed).as_ref(),
2121    )
2122    .ok_or_else(|| {
2123        BackendError::semantic(format!(
2124            "Rendered YAML representation {value:?} could not be normalized with the active tag/schema."
2125        ))
2126    })?;
2127    normalize_scalar(&parsed)
2128}
2129
2130fn normalize_scalar(value: &saphyr::ScalarOwned) -> BackendResult<NormalizedValue> {
2131    match value {
2132        saphyr::ScalarOwned::Null => Ok(NormalizedValue::Null),
2133        saphyr::ScalarOwned::Boolean(value) => Ok(NormalizedValue::Bool(*value)),
2134        saphyr::ScalarOwned::Integer(value) => Ok(NormalizedValue::Integer((*value).into())),
2135        saphyr::ScalarOwned::FloatingPoint(value) => {
2136            let value = value.into_inner();
2137            if !value.is_finite() {
2138                return Err(BackendError::semantic(
2139                    "Rendered YAML contained a non-finite float, which this backend does not normalize.",
2140                ));
2141            }
2142            Ok(NormalizedValue::Float(NormalizedFloat::finite(value)))
2143        }
2144        saphyr::ScalarOwned::String(value) => Ok(NormalizedValue::String(value.clone())),
2145    }
2146}
2147
2148fn normalize_mapping(values: &saphyr::MappingOwned) -> BackendResult<NormalizedValue> {
2149    let mut entries = Vec::new();
2150    for (key, value) in values {
2151        if is_merge_key(key) {
2152            apply_merge_entries(value, &mut entries)?;
2153            continue;
2154        }
2155        insert_mapping_entry(
2156            &mut entries,
2157            normalize_key(key)?,
2158            normalize_value(value)?,
2159            true,
2160        );
2161    }
2162    Ok(NormalizedValue::Mapping(entries))
2163}
2164
2165fn align_value_with_ast(node: &YamlValueNode, normalized: &mut NormalizedValue) {
2166    match node {
2167        YamlValueNode::Decorated(decorated) => {
2168            if decorated.tag.as_ref().is_some_and(is_set_tag_literal)
2169                && let NormalizedValue::Mapping(entries) = normalized
2170            {
2171                let keys = entries.iter().map(|entry| entry.key.clone()).collect();
2172                *normalized = NormalizedValue::Set(keys);
2173                return;
2174            }
2175            align_value_with_ast(&decorated.value, normalized);
2176        }
2177        YamlValueNode::Mapping(mapping) => {
2178            if let NormalizedValue::Mapping(entries) = normalized {
2179                for (entry_node, entry) in mapping.entries.iter().zip(entries.iter_mut()) {
2180                    align_key_with_ast(mapping.flow, &entry_node.key, &mut entry.key);
2181                    align_value_with_ast(&entry_node.value, &mut entry.value);
2182                }
2183            }
2184        }
2185        YamlValueNode::Sequence(sequence) => {
2186            if let NormalizedValue::Sequence(values) = normalized {
2187                for (item_node, value) in sequence.items.iter().zip(values.iter_mut()) {
2188                    align_value_with_ast(item_node, value);
2189                }
2190            }
2191        }
2192        YamlValueNode::Scalar(_) | YamlValueNode::Interpolation(_) => {}
2193    }
2194}
2195
2196fn align_key_with_ast(flow_mapping: bool, key: &YamlKeyNode, normalized: &mut NormalizedKey) {
2197    match &key.value {
2198        YamlKeyValue::Scalar(YamlScalarNode::Plain(node))
2199            if flow_mapping && plain_scalar_starts_with_question(node) =>
2200        {
2201            if let NormalizedKey::String(value) = normalized
2202                && let Some(stripped) = value.strip_prefix('?')
2203            {
2204                *value = stripped.to_owned();
2205            }
2206        }
2207        YamlKeyValue::Complex(value) => align_key_value_with_ast(value, normalized),
2208        YamlKeyValue::Scalar(_) | YamlKeyValue::Interpolation(_) => {}
2209    }
2210}
2211
2212fn align_key_value_with_ast(node: &YamlValueNode, normalized: &mut NormalizedKey) {
2213    match node {
2214        YamlValueNode::Decorated(decorated) => {
2215            align_key_value_with_ast(&decorated.value, normalized)
2216        }
2217        YamlValueNode::Sequence(sequence) => {
2218            if let NormalizedKey::Sequence(keys) = normalized {
2219                for (item_node, key) in sequence.items.iter().zip(keys.iter_mut()) {
2220                    align_key_value_with_ast(item_node, key);
2221                }
2222            }
2223        }
2224        YamlValueNode::Mapping(mapping) => {
2225            if let NormalizedKey::Mapping(entries) = normalized {
2226                for (entry_node, entry) in mapping.entries.iter().zip(entries.iter_mut()) {
2227                    align_key_with_ast(mapping.flow, &entry_node.key, &mut entry.key);
2228                    align_key_value_with_ast(&entry_node.value, &mut entry.value);
2229                }
2230            }
2231        }
2232        YamlValueNode::Scalar(_) | YamlValueNode::Interpolation(_) => {}
2233    }
2234}
2235
2236fn plain_scalar_starts_with_question(node: &YamlPlainScalarNode) -> bool {
2237    let Some(YamlChunk::Text(text)) = node.chunks.first() else {
2238        return false;
2239    };
2240    text.value.starts_with('?')
2241}
2242
2243fn is_set_tag_literal(tag: &YamlTagNode) -> bool {
2244    let mut literal = String::new();
2245    for chunk in &tag.chunks {
2246        let YamlChunk::Text(text) = chunk else {
2247            return false;
2248        };
2249        literal.push_str(&text.value);
2250    }
2251    matches!(
2252        literal.as_str(),
2253        "!set" | "!!set" | "<tag:yaml.org,2002:set>" | "tag:yaml.org,2002:set"
2254    )
2255}
2256
2257fn normalize_key(value: &YamlOwned) -> BackendResult<NormalizedKey> {
2258    match value {
2259        YamlOwned::Value(value) => match value {
2260            saphyr::ScalarOwned::Null => Ok(NormalizedKey::Null),
2261            saphyr::ScalarOwned::Boolean(value) => Ok(NormalizedKey::Bool(*value)),
2262            saphyr::ScalarOwned::Integer(value) => Ok(NormalizedKey::Integer((*value).into())),
2263            saphyr::ScalarOwned::FloatingPoint(value) => {
2264                let value = value.into_inner();
2265                if !value.is_finite() {
2266                    return Err(BackendError::semantic(
2267                        "Rendered YAML contained a non-finite float mapping key, which this backend does not normalize.",
2268                    ));
2269                }
2270                Ok(NormalizedKey::Float(NormalizedFloat::finite(value)))
2271            }
2272            saphyr::ScalarOwned::String(value) => Ok(NormalizedKey::String(value.clone())),
2273        },
2274        YamlOwned::Representation(value, style, tag) => {
2275            normalize_representation_key(value, *style, tag.as_ref())
2276        }
2277        YamlOwned::Sequence(values) => values
2278            .iter()
2279            .map(normalize_key)
2280            .collect::<BackendResult<Vec<_>>>()
2281            .map(NormalizedKey::Sequence),
2282        YamlOwned::Mapping(values) => values
2283            .iter()
2284            .map(|(key, value)| {
2285                Ok(NormalizedKeyEntry {
2286                    key: normalize_key(key)?,
2287                    value: normalize_key(value)?,
2288                })
2289            })
2290            .collect::<BackendResult<Vec<_>>>()
2291            .map(NormalizedKey::Mapping),
2292        YamlOwned::Tagged(tag, value) => {
2293            normalize_tagged_key(tag.handle.as_str(), &tag.suffix, value)
2294        }
2295        YamlOwned::Alias(_) => Err(BackendError::semantic(
2296            "Rendered YAML still contains an unresolved alias after validation.",
2297        )),
2298        YamlOwned::BadValue => Ok(NormalizedKey::Null),
2299    }
2300}
2301
2302fn normalize_representation_key(
2303    value: &str,
2304    style: ScalarStyle,
2305    tag: Option<&Tag>,
2306) -> BackendResult<NormalizedKey> {
2307    let parsed = ScalarOwned::parse_from_cow_and_metadata(
2308        Cow::Borrowed(value),
2309        style,
2310        tag.map(Cow::Borrowed).as_ref(),
2311    )
2312    .ok_or_else(|| {
2313        BackendError::semantic(format!(
2314            "Rendered YAML key representation {value:?} could not be normalized with the active tag/schema."
2315        ))
2316    })?;
2317    match parsed {
2318        ScalarOwned::Null => Ok(NormalizedKey::Null),
2319        ScalarOwned::Boolean(value) => Ok(NormalizedKey::Bool(value)),
2320        ScalarOwned::Integer(value) => Ok(NormalizedKey::Integer(value.into())),
2321        ScalarOwned::FloatingPoint(value) => {
2322            let value = value.into_inner();
2323            if !value.is_finite() {
2324                return Err(BackendError::semantic(
2325                    "Rendered YAML contained a non-finite float mapping key, which this backend does not normalize.",
2326                ));
2327            }
2328            Ok(NormalizedKey::Float(NormalizedFloat::finite(value)))
2329        }
2330        ScalarOwned::String(value) => Ok(NormalizedKey::String(value)),
2331    }
2332}
2333
2334fn normalize_tagged_value(
2335    handle: &str,
2336    suffix: &str,
2337    value: &YamlOwned,
2338) -> BackendResult<NormalizedValue> {
2339    if is_yaml_core_tag(handle, suffix, "set") {
2340        let YamlOwned::Mapping(values) = value else {
2341            return Err(BackendError::semantic(
2342                "YAML !!set nodes must normalize from a mapping value.",
2343            ));
2344        };
2345        return values
2346            .iter()
2347            .map(|(key, _)| normalize_key(key))
2348            .collect::<BackendResult<Vec<_>>>()
2349            .map(NormalizedValue::Set);
2350    }
2351    if let Some(normalized) = normalize_core_tagged_scalar_value(handle, suffix, value)? {
2352        return Ok(normalized);
2353    }
2354    normalize_value(value)
2355}
2356
2357fn normalize_tagged_key(
2358    handle: &str,
2359    suffix: &str,
2360    value: &YamlOwned,
2361) -> BackendResult<NormalizedKey> {
2362    if is_yaml_core_tag(handle, suffix, "set") {
2363        return Err(BackendError::semantic(
2364            "YAML set values cannot be used as mapping keys in normalized output.",
2365        ));
2366    }
2367    if let Some(normalized) = normalize_core_tagged_scalar_key(handle, suffix, value)? {
2368        return Ok(normalized);
2369    }
2370    normalize_key(value)
2371}
2372
2373fn normalize_core_tagged_scalar_value(
2374    handle: &str,
2375    suffix: &str,
2376    value: &YamlOwned,
2377) -> BackendResult<Option<NormalizedValue>> {
2378    if !matches!(suffix, "null" | "bool" | "int" | "float" | "str")
2379        || !matches!(handle, "" | "!!" | "tag:yaml.org,2002:")
2380    {
2381        return Ok(None);
2382    }
2383    if is_empty_tagged_scalar(value) {
2384        return Ok(match suffix {
2385            "null" => Some(NormalizedValue::Null),
2386            "str" => Some(NormalizedValue::String(String::new())),
2387            _ => None,
2388        });
2389    }
2390    let Some(text) = scalar_text_for_core_tagged_value(value) else {
2391        return Ok(None);
2392    };
2393    normalize_representation_value(
2394        text.as_ref(),
2395        ScalarStyle::Plain,
2396        Some(&Tag {
2397            handle: canonical_core_tag_handle(handle).to_owned(),
2398            suffix: suffix.to_owned(),
2399        }),
2400    )
2401    .map(Some)
2402}
2403
2404fn normalize_core_tagged_scalar_key(
2405    handle: &str,
2406    suffix: &str,
2407    value: &YamlOwned,
2408) -> BackendResult<Option<NormalizedKey>> {
2409    if !matches!(suffix, "null" | "bool" | "int" | "float" | "str")
2410        || !matches!(handle, "" | "!!" | "tag:yaml.org,2002:")
2411    {
2412        return Ok(None);
2413    }
2414    if is_empty_tagged_scalar(value) {
2415        return Ok(match suffix {
2416            "null" => Some(NormalizedKey::Null),
2417            "str" => Some(NormalizedKey::String(String::new())),
2418            _ => None,
2419        });
2420    }
2421    let Some(text) = scalar_text_for_core_tagged_value(value) else {
2422        return Ok(None);
2423    };
2424    normalize_representation_key(
2425        text.as_ref(),
2426        ScalarStyle::Plain,
2427        Some(&Tag {
2428            handle: canonical_core_tag_handle(handle).to_owned(),
2429            suffix: suffix.to_owned(),
2430        }),
2431    )
2432    .map(Some)
2433}
2434
2435fn canonical_core_tag_handle(handle: &str) -> &str {
2436    match handle {
2437        "" | "!!" | "tag:yaml.org,2002:" => "tag:yaml.org,2002:",
2438        other => other,
2439    }
2440}
2441
2442fn scalar_text_for_tagged_value(value: &YamlOwned) -> Option<Cow<'_, str>> {
2443    match value {
2444        YamlOwned::Representation(value, _, _) => Some(Cow::Borrowed(value.as_str())),
2445        YamlOwned::Value(ScalarOwned::Null) => Some(Cow::Borrowed("null")),
2446        YamlOwned::Value(ScalarOwned::Boolean(value)) => {
2447            Some(Cow::Borrowed(if *value { "true" } else { "false" }))
2448        }
2449        YamlOwned::Value(ScalarOwned::Integer(value)) => Some(Cow::Owned(value.to_string())),
2450        YamlOwned::Value(ScalarOwned::FloatingPoint(value)) => {
2451            Some(Cow::Owned(value.into_inner().to_string()))
2452        }
2453        YamlOwned::Value(ScalarOwned::String(value)) => Some(Cow::Borrowed(value.as_str())),
2454        YamlOwned::Tagged(_, value) => scalar_text_for_tagged_value(value),
2455        YamlOwned::Sequence(_)
2456        | YamlOwned::Mapping(_)
2457        | YamlOwned::Alias(_)
2458        | YamlOwned::BadValue => None,
2459    }
2460}
2461
2462fn scalar_text_for_core_tagged_value(value: &YamlOwned) -> Option<Cow<'_, str>> {
2463    match value {
2464        YamlOwned::Value(ScalarOwned::Null) => Some(Cow::Borrowed("")),
2465        YamlOwned::Tagged(_, value) => scalar_text_for_core_tagged_value(value),
2466        other => scalar_text_for_tagged_value(other),
2467    }
2468}
2469
2470fn is_empty_tagged_scalar(value: &YamlOwned) -> bool {
2471    match value {
2472        YamlOwned::Value(ScalarOwned::Null) => true,
2473        YamlOwned::Representation(value, _, _) => value.is_empty(),
2474        YamlOwned::Tagged(_, value) => is_empty_tagged_scalar(value),
2475        _ => false,
2476    }
2477}
2478
2479fn is_yaml_core_tag(handle: &str, suffix: &str, expected_suffix: &str) -> bool {
2480    suffix == expected_suffix && matches!(handle, "" | "!!" | "tag:yaml.org,2002:")
2481}
2482
2483fn is_merge_key(value: &YamlOwned) -> bool {
2484    match value {
2485        YamlOwned::Value(saphyr::ScalarOwned::String(value)) => value == "<<",
2486        YamlOwned::Representation(value, _, _) => value == "<<",
2487        YamlOwned::Tagged(_, value) => is_merge_key(value),
2488        _ => false,
2489    }
2490}
2491
2492fn apply_merge_entries(value: &YamlOwned, entries: &mut Vec<NormalizedEntry>) -> BackendResult<()> {
2493    match value {
2494        YamlOwned::Mapping(values) => merge_mapping_entries(values, entries),
2495        YamlOwned::Sequence(values) => {
2496            for value in values {
2497                let YamlOwned::Mapping(values) = value else {
2498                    return Err(BackendError::semantic(
2499                        "YAML merge sequences must contain only mappings.",
2500                    ));
2501                };
2502                merge_mapping_entries(values, entries)?;
2503            }
2504            Ok(())
2505        }
2506        YamlOwned::Tagged(_, value) => apply_merge_entries(value, entries),
2507        _ => Err(BackendError::semantic(
2508            "YAML merge values must be a mapping or sequence of mappings.",
2509        )),
2510    }
2511}
2512
2513fn merge_mapping_entries(
2514    values: &saphyr::MappingOwned,
2515    entries: &mut Vec<NormalizedEntry>,
2516) -> BackendResult<()> {
2517    for (key, value) in values {
2518        insert_mapping_entry(entries, normalize_key(key)?, normalize_value(value)?, false);
2519    }
2520    Ok(())
2521}
2522
2523fn insert_mapping_entry(
2524    entries: &mut Vec<NormalizedEntry>,
2525    key: NormalizedKey,
2526    value: NormalizedValue,
2527    override_existing: bool,
2528) {
2529    if let Some(existing) = entries.iter_mut().find(|entry| entry.key == key) {
2530        if override_existing {
2531            existing.value = value;
2532        }
2533        return;
2534    }
2535    entries.push(NormalizedEntry { key, value });
2536}
2537
2538fn wrap_decorators(
2539    decorated: Option<(Option<YamlTagNode>, Option<YamlAnchorNode>)>,
2540    value: YamlValueNode,
2541) -> BackendResult<YamlValueNode> {
2542    if let Some((tag, anchor)) = decorated {
2543        let decorator_span = tag
2544            .as_ref()
2545            .map(|tag| tag.span.clone())
2546            .or_else(|| anchor.as_ref().map(|anchor| anchor.span.clone()))
2547            .unwrap_or_else(|| value_span(&value).clone());
2548        let span = decorator_span.merge(value_span(&value));
2549
2550        match value {
2551            YamlValueNode::Decorated(inner) => {
2552                if anchor.is_some() && inner.anchor.is_some() {
2553                    return Err(BackendError::parse_at(
2554                        "yaml.parse",
2555                        "YAML nodes cannot define more than one anchor.",
2556                        Some(span),
2557                    ));
2558                }
2559                if tag.is_some() && inner.tag.is_some() {
2560                    return Err(BackendError::parse_at(
2561                        "yaml.parse",
2562                        "YAML nodes cannot define more than one tag.",
2563                        Some(span),
2564                    ));
2565                }
2566
2567                Ok(YamlValueNode::Decorated(YamlDecoratedNode {
2568                    span,
2569                    value: inner.value,
2570                    tag: tag.or(inner.tag),
2571                    anchor: anchor.or(inner.anchor),
2572                }))
2573            }
2574            YamlValueNode::Scalar(YamlScalarNode::Alias(_))
2575                if tag.is_some() || anchor.is_some() =>
2576            {
2577                Err(BackendError::parse_at(
2578                    "yaml.parse",
2579                    "YAML aliases cannot define tags or anchors.",
2580                    Some(span),
2581                ))
2582            }
2583            value => Ok(YamlValueNode::Decorated(YamlDecoratedNode {
2584                span,
2585                value: Box::new(value),
2586                tag,
2587                anchor,
2588            })),
2589        }
2590    } else {
2591        Ok(value)
2592    }
2593}
2594
2595fn null_plain_scalar() -> YamlPlainScalarNode {
2596    YamlPlainScalarNode {
2597        span: SourceSpan::point(0, 0),
2598        chunks: vec![YamlChunk::Text(YamlTextChunkNode {
2599            span: SourceSpan::point(0, 0),
2600            value: "null".to_owned(),
2601        })],
2602    }
2603}
2604
2605fn empty_plain_scalar() -> YamlPlainScalarNode {
2606    YamlPlainScalarNode {
2607        span: SourceSpan::point(0, 0),
2608        chunks: Vec::new(),
2609    }
2610}
2611
2612fn flow_implicit_plain_key_needs_separator(key: &YamlKeyNode) -> bool {
2613    matches!(
2614        &key.value,
2615        YamlKeyValue::Scalar(YamlScalarNode::Plain(node))
2616            if node.chunks.iter().any(|chunk| match chunk {
2617                YamlChunk::Text(text) => text.value.chars().any(char::is_whitespace),
2618                YamlChunk::Interpolation(_) => true,
2619            })
2620    )
2621}
2622
2623fn split_stream(template: &TemplateInput) -> BackendResult<Vec<YamlDocumentFragment>> {
2624    let items = template.flatten();
2625    let mut fragments = Vec::new();
2626    let mut directives = Vec::new();
2627    let mut current_start = None;
2628    let mut explicit_start = false;
2629    let mut explicit_end = false;
2630    let mut line_start = 0;
2631
2632    while line_start < items.len() {
2633        let (line_end, next_line) = line_bounds(&items, line_start);
2634        let line = collect_line(&items[line_start..line_end]);
2635        let trimmed = line.trim_end_matches(['\r', '\n']);
2636        let document_start_payload = document_start_payload_start(&items, line_start, line_end);
2637        let document_end = is_document_end_marker(trimmed);
2638        let malformed_document_end = trimmed.starts_with("...") && !document_end;
2639
2640        if malformed_document_end {
2641            let span = items
2642                .get(line_start)
2643                .map(|item| item.span().clone())
2644                .unwrap_or_else(|| SourceSpan::point(0, 0));
2645            return Err(BackendError::parse_at(
2646                "yaml.parse",
2647                "Unexpected trailing YAML content.",
2648                Some(span),
2649            ));
2650        }
2651
2652        if current_start.is_none() {
2653            if trimmed.is_empty() || trimmed.starts_with('#') {
2654                line_start = next_line;
2655                continue;
2656            }
2657            if trimmed.starts_with('%') {
2658                if !is_valid_directive(trimmed) {
2659                    let span = items
2660                        .get(line_start)
2661                        .map(|item| item.span().clone())
2662                        .unwrap_or_else(|| SourceSpan::point(0, 0));
2663                    return Err(BackendError::parse_at(
2664                        "yaml.parse",
2665                        "Unexpected trailing YAML content.",
2666                        Some(span),
2667                    ));
2668                }
2669                if duplicates_existing_directive(&directives, trimmed) {
2670                    let span = items
2671                        .get(line_start)
2672                        .map(|item| item.span().clone())
2673                        .unwrap_or_else(|| SourceSpan::point(0, 0));
2674                    return Err(BackendError::parse_at(
2675                        "yaml.parse",
2676                        "Unexpected trailing YAML content.",
2677                        Some(span),
2678                    ));
2679                }
2680                directives.push(trimmed.to_owned());
2681                line_start = next_line;
2682                continue;
2683            }
2684            if trimmed == "---" {
2685                current_start = Some(next_line);
2686                explicit_start = true;
2687                line_start = next_line;
2688                continue;
2689            }
2690            if let Some(payload_start) = document_start_payload {
2691                if explicit_start_payload_looks_like_block_mapping(trimmed) {
2692                    let span = items
2693                        .get(line_start)
2694                        .map(|item| item.span().clone())
2695                        .unwrap_or_else(|| SourceSpan::point(0, 0));
2696                    return Err(BackendError::parse_at(
2697                        "yaml.parse",
2698                        "Unexpected trailing YAML content.",
2699                        Some(span),
2700                    ));
2701                }
2702                current_start = Some(payload_start.unwrap_or(next_line));
2703                explicit_start = true;
2704                line_start = next_line;
2705                continue;
2706            }
2707            if document_end {
2708                line_start = next_line;
2709                continue;
2710            }
2711            current_start = Some(line_start);
2712        } else if trimmed == "---" {
2713            fragments.push(build_fragment(
2714                &items,
2715                current_start.unwrap_or(line_start),
2716                line_start,
2717                std::mem::take(&mut directives),
2718                explicit_start,
2719                explicit_end,
2720            ));
2721            current_start = Some(next_line);
2722            explicit_start = true;
2723            explicit_end = false;
2724            line_start = next_line;
2725            continue;
2726        } else if let Some(payload_start) = document_start_payload {
2727            fragments.push(build_fragment(
2728                &items,
2729                current_start.unwrap_or(line_start),
2730                line_start,
2731                std::mem::take(&mut directives),
2732                explicit_start,
2733                explicit_end,
2734            ));
2735            current_start = Some(payload_start.unwrap_or(next_line));
2736            explicit_start = true;
2737            explicit_end = false;
2738            line_start = next_line;
2739            continue;
2740        } else if document_end {
2741            fragments.push(build_fragment(
2742                &items,
2743                current_start.unwrap_or(line_start),
2744                line_start,
2745                std::mem::take(&mut directives),
2746                explicit_start,
2747                true,
2748            ));
2749            current_start = None;
2750            explicit_start = false;
2751            explicit_end = false;
2752            line_start = next_line;
2753            continue;
2754        }
2755
2756        line_start = next_line;
2757    }
2758
2759    if let Some(start) = current_start {
2760        fragments.push(build_fragment(
2761            &items,
2762            start,
2763            items.len().saturating_sub(1),
2764            std::mem::take(&mut directives),
2765            explicit_start,
2766            explicit_end,
2767        ));
2768    }
2769
2770    if current_start.is_none() && !directives.is_empty() {
2771        let span = items
2772            .iter()
2773            .rev()
2774            .find(|item| item.kind() != "eof")
2775            .map(|item| item.span().clone())
2776            .unwrap_or_else(|| SourceSpan::point(0, 0));
2777        return Err(BackendError::parse_at(
2778            "yaml.parse",
2779            "Unexpected trailing YAML content.",
2780            Some(span),
2781        ));
2782    }
2783
2784    if fragments.is_empty() {
2785        fragments.push(YamlDocumentFragment {
2786            directives: Vec::new(),
2787            explicit_start: false,
2788            explicit_end: false,
2789            items: vec![StreamItem::Eof {
2790                span: SourceSpan::point(0, 0),
2791            }],
2792        });
2793    }
2794
2795    Ok(fragments)
2796}
2797
2798fn build_fragment(
2799    items: &[StreamItem],
2800    start: usize,
2801    end: usize,
2802    directives: Vec<String>,
2803    explicit_start: bool,
2804    explicit_end: bool,
2805) -> YamlDocumentFragment {
2806    let mut fragment_items = items[start..end].to_vec();
2807    let eof_span = fragment_items
2808        .last()
2809        .map_or_else(|| SourceSpan::point(0, 0), |item| item.span().clone());
2810    fragment_items.push(StreamItem::Eof { span: eof_span });
2811    YamlDocumentFragment {
2812        directives,
2813        explicit_start,
2814        explicit_end,
2815        items: fragment_items,
2816    }
2817}
2818
2819fn line_bounds(items: &[StreamItem], start: usize) -> (usize, usize) {
2820    let mut probe = start;
2821    while probe < items.len() {
2822        if items[probe].char() == Some('\n') {
2823            return (probe, probe + 1);
2824        }
2825        if items[probe].kind() == "eof" {
2826            return (probe, probe + 1);
2827        }
2828        probe += 1;
2829    }
2830    (items.len(), items.len())
2831}
2832
2833fn document_start_payload_start(
2834    items: &[StreamItem],
2835    line_start: usize,
2836    line_end: usize,
2837) -> Option<Option<usize>> {
2838    let mut probe = line_start;
2839    for expected in ['-', '-', '-'] {
2840        if items.get(probe).and_then(StreamItem::char) != Some(expected) {
2841            return None;
2842        }
2843        probe += 1;
2844    }
2845    if probe >= line_end {
2846        return Some(None);
2847    }
2848    if !matches!(
2849        items.get(probe).and_then(StreamItem::char),
2850        Some(' ' | '\t')
2851    ) {
2852        return None;
2853    }
2854    while probe < line_end
2855        && matches!(
2856            items.get(probe).and_then(StreamItem::char),
2857            Some(' ' | '\t')
2858        )
2859    {
2860        probe += 1;
2861    }
2862    if probe >= line_end || items.get(probe).and_then(StreamItem::char) == Some('#') {
2863        return Some(None);
2864    }
2865    Some(Some(probe))
2866}
2867
2868fn is_document_end_marker(trimmed: &str) -> bool {
2869    if !trimmed.starts_with("...") {
2870        return false;
2871    }
2872    let Some(remainder) = trimmed.get(3..) else {
2873        return true;
2874    };
2875    let remainder = remainder.trim_start_matches([' ', '\t']);
2876    remainder.is_empty() || remainder.starts_with('#')
2877}
2878
2879fn explicit_start_payload_looks_like_block_mapping(trimmed: &str) -> bool {
2880    let Some(rest) = trimmed.strip_prefix("---") else {
2881        return false;
2882    };
2883    let rest = rest.trim_start_matches([' ', '\t']);
2884    if !(rest.starts_with('&') || rest.starts_with('!')) {
2885        return false;
2886    }
2887    if rest.starts_with('[') || rest.starts_with('{') {
2888        return false;
2889    }
2890    let Some((before_colon, after_colon)) = rest.split_once(':') else {
2891        return false;
2892    };
2893    !before_colon.contains('[')
2894        && !before_colon.contains('{')
2895        && !before_colon.ends_with(':')
2896        && matches!(after_colon.chars().next(), Some(' ' | '\t') | None)
2897}
2898
2899fn is_valid_directive(trimmed: &str) -> bool {
2900    let directive = trimmed
2901        .split_once('#')
2902        .map(|(directive, _)| directive)
2903        .unwrap_or(trimmed)
2904        .trim_end();
2905    let mut parts = directive.split_whitespace();
2906    match parts.next() {
2907        Some("%YAML") => matches!(
2908            (parts.next(), parts.next(), parts.next()),
2909            (Some(_version), None, None)
2910        ),
2911        Some("%TAG") => matches!(
2912            (parts.next(), parts.next(), parts.next(), parts.next()),
2913            (Some(_handle), Some(_prefix), None, None)
2914        ),
2915        Some(_) => true,
2916        None => false,
2917    }
2918}
2919
2920fn duplicates_existing_directive(existing: &[String], candidate: &str) -> bool {
2921    let Some((name, handle)) = directive_identity(candidate) else {
2922        return false;
2923    };
2924    existing.iter().any(|directive| {
2925        directive_identity(directive).is_some_and(|(existing_name, existing_handle)| {
2926            existing_name == name && existing_handle == handle
2927        })
2928    })
2929}
2930
2931fn directive_identity(trimmed: &str) -> Option<(&str, Option<&str>)> {
2932    let directive = trimmed
2933        .split_once('#')
2934        .map(|(directive, _)| directive)
2935        .unwrap_or(trimmed)
2936        .trim_end();
2937    let mut parts = directive.split_whitespace();
2938    match parts.next() {
2939        Some("%YAML") => Some(("%YAML", None)),
2940        Some("%TAG") => parts.next().map(|handle| ("%TAG", Some(handle))),
2941        _ => None,
2942    }
2943}
2944
2945fn collect_line(items: &[StreamItem]) -> String {
2946    let mut line = String::new();
2947    for item in items {
2948        if let Some(ch) = item.char() {
2949            line.push(ch);
2950        } else {
2951            line.push('\u{fffc}');
2952        }
2953    }
2954    line
2955}
2956
2957fn value_span(value: &YamlValueNode) -> &SourceSpan {
2958    match value {
2959        YamlValueNode::Scalar(YamlScalarNode::Plain(node)) => &node.span,
2960        YamlValueNode::Scalar(YamlScalarNode::DoubleQuoted(node)) => &node.span,
2961        YamlValueNode::Scalar(YamlScalarNode::SingleQuoted(node)) => &node.span,
2962        YamlValueNode::Scalar(YamlScalarNode::Block(node)) => &node.span,
2963        YamlValueNode::Scalar(YamlScalarNode::Alias(node)) => &node.span,
2964        YamlValueNode::Interpolation(node) => &node.span,
2965        YamlValueNode::Mapping(node) => &node.span,
2966        YamlValueNode::Sequence(node) => &node.span,
2967        YamlValueNode::Decorated(node) => &node.span,
2968    }
2969}
2970
2971#[cfg(test)]
2972mod tests {
2973    use super::{YamlValueNode, parse_template};
2974    use pyo3::prelude::*;
2975    use saphyr::{LoadableYamlNode, ScalarOwned, YamlOwned};
2976    use tstring_pyo3_bindings::{extract_template, yaml::render_document};
2977    use tstring_syntax::{BackendError, BackendResult, ErrorKind};
2978
2979    fn parse_rendered_yaml(text: &str) -> BackendResult<Vec<YamlOwned>> {
2980        YamlOwned::load_from_str(text).map_err(|err| {
2981            BackendError::parse(format!(
2982                "Rendered YAML could not be reparsed during test verification: {err}"
2983            ))
2984        })
2985    }
2986
2987    fn yaml_scalar_text(value: &YamlOwned) -> Option<&str> {
2988        match value {
2989            YamlOwned::Value(value) => value.as_str(),
2990            YamlOwned::Representation(value, _, _) => Some(value.as_str()),
2991            YamlOwned::Tagged(_, value) => yaml_scalar_text(value),
2992            _ => None,
2993        }
2994    }
2995
2996    fn yaml_integer(value: &YamlOwned) -> Option<i64> {
2997        match value {
2998            YamlOwned::Value(value) => value.as_integer(),
2999            YamlOwned::Tagged(_, value) => yaml_integer(value),
3000            _ => None,
3001        }
3002    }
3003
3004    fn yaml_float(value: &YamlOwned) -> Option<f64> {
3005        match value {
3006            YamlOwned::Value(ScalarOwned::FloatingPoint(value)) => Some(value.into_inner()),
3007            YamlOwned::Tagged(_, value) => yaml_float(value),
3008            _ => None,
3009        }
3010    }
3011
3012    fn yaml_sequence_len(value: &YamlOwned) -> Option<usize> {
3013        match value {
3014            YamlOwned::Sequence(value) => Some(value.len()),
3015            YamlOwned::Tagged(_, value) => yaml_sequence_len(value),
3016            _ => None,
3017        }
3018    }
3019
3020    fn yaml_mapping(value: &YamlOwned) -> Option<&saphyr::MappingOwned> {
3021        match value {
3022            YamlOwned::Mapping(value) => Some(value),
3023            YamlOwned::Tagged(_, value) => yaml_mapping(value),
3024            _ => None,
3025        }
3026    }
3027
3028    fn assert_string_entry(document: &YamlOwned, key: &str, expected: &str) {
3029        let mapping = yaml_mapping(document).expect("expected YAML mapping");
3030        let value = mapping
3031            .iter()
3032            .find_map(|(entry_key, entry_value)| {
3033                (yaml_scalar_text(entry_key) == Some(key)).then_some(entry_value)
3034            })
3035            .expect("expected YAML mapping entry");
3036        assert_eq!(yaml_scalar_text(value), Some(expected));
3037    }
3038
3039    fn assert_integer_entry(document: &YamlOwned, key: &str, expected: i64) {
3040        let mapping = yaml_mapping(document).expect("expected YAML mapping");
3041        let value = mapping
3042            .iter()
3043            .find_map(|(entry_key, entry_value)| {
3044                (yaml_scalar_text(entry_key) == Some(key)).then_some(entry_value)
3045            })
3046            .expect("expected YAML mapping entry");
3047        assert_eq!(yaml_integer(value), Some(expected));
3048    }
3049
3050    fn yaml_mapping_entry<'a>(document: &'a YamlOwned, key: &str) -> Option<&'a YamlOwned> {
3051        yaml_mapping(document).and_then(|mapping| {
3052            mapping.iter().find_map(|(entry_key, entry_value)| {
3053                (yaml_scalar_text(entry_key) == Some(key)).then_some(entry_value)
3054            })
3055        })
3056    }
3057
3058    #[test]
3059    fn parses_yaml_flow_and_scalar_nodes() {
3060        Python::with_gil(|py| {
3061            let module = PyModule::from_code(
3062                py,
3063                pyo3::ffi::c_str!(
3064                    "user='Alice'\ntemplate=t'name: \"hi-{user}\"\\nitems: [1, {user}]'\n"
3065                ),
3066                pyo3::ffi::c_str!("test_yaml.py"),
3067                pyo3::ffi::c_str!("test_yaml"),
3068            )
3069            .unwrap();
3070            let template = module.getattr("template").unwrap();
3071            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3072            let stream = parse_template(&template).unwrap();
3073            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3074                panic!("expected mapping");
3075            };
3076            assert_eq!(mapping.entries.len(), 2);
3077        });
3078    }
3079
3080    #[test]
3081    fn parses_tags_and_anchors_on_scalars() {
3082        Python::with_gil(|py| {
3083            let module = PyModule::from_code(
3084                py,
3085                pyo3::ffi::c_str!(
3086                    "tag='str'\nanchor='user'\ntemplate=t'value: !{tag} &{anchor} \"hi\"\\n'\n"
3087                ),
3088                pyo3::ffi::c_str!("test_yaml_decorators.py"),
3089                pyo3::ffi::c_str!("test_yaml_decorators"),
3090            )
3091            .unwrap();
3092            let template = module.getattr("template").unwrap();
3093            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3094            let stream = parse_template(&template).unwrap();
3095            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3096                panic!("expected mapping");
3097            };
3098            let YamlValueNode::Decorated(node) = &mapping.entries[0].value else {
3099                panic!("expected decorated scalar value");
3100            };
3101            assert!(node.tag.is_some());
3102            assert!(node.anchor.is_some());
3103        });
3104    }
3105
3106    #[test]
3107    fn renders_nested_yaml_values_and_validates() {
3108        Python::with_gil(|py| {
3109            let module = PyModule::from_code(
3110                py,
3111                pyo3::ffi::c_str!(
3112                    "name='Ada'\nmeta={'active': True, 'count': 2}\ntemplate=t'name: {name}\\nmeta: {meta}\\n'\n"
3113                ),
3114                pyo3::ffi::c_str!("test_yaml_render.py"),
3115                pyo3::ffi::c_str!("test_yaml_render"),
3116            )
3117            .unwrap();
3118            let template = module.getattr("template").unwrap();
3119            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3120            let stream = parse_template(&template).unwrap();
3121            let rendered = render_document(py, &stream).unwrap();
3122            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3123
3124            assert!(rendered.text.contains("name: \"Ada\""));
3125            assert_string_entry(&documents[0], "name", "Ada");
3126        });
3127    }
3128
3129    #[test]
3130    fn rejects_metadata_with_whitespace() {
3131        Python::with_gil(|py| {
3132            let module = PyModule::from_code(
3133                py,
3134                pyo3::ffi::c_str!("anchor='bad anchor'\ntemplate=t'value: &{anchor} \"hi\"\\n'\n"),
3135                pyo3::ffi::c_str!("test_yaml_error.py"),
3136                pyo3::ffi::c_str!("test_yaml_error"),
3137            )
3138            .unwrap();
3139            let template = module.getattr("template").unwrap();
3140            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3141            let stream = parse_template(&template).unwrap();
3142            let err = match render_document(py, &stream) {
3143                Ok(_) => panic!("expected YAML render failure"),
3144                Err(err) => err,
3145            };
3146
3147            assert_eq!(err.kind, ErrorKind::Unrepresentable);
3148            assert!(err.message.contains("YAML metadata"));
3149        });
3150    }
3151
3152    #[test]
3153    fn rejects_flow_mappings_missing_commas_during_parse() {
3154        Python::with_gil(|py| {
3155            let module = PyModule::from_code(
3156                py,
3157                pyo3::ffi::c_str!(
3158                    "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"
3159                ),
3160                pyo3::ffi::c_str!("test_yaml_flow_error.py"),
3161                pyo3::ffi::c_str!("test_yaml_flow_error"),
3162            )
3163            .unwrap();
3164            for name in [
3165                "mapping",
3166                "missing_colon",
3167                "leading_comma_mapping",
3168                "sequence",
3169                "trailing_sequence",
3170                "empty_sequence",
3171                "empty_mapping",
3172            ] {
3173                let template = module.getattr(name).unwrap();
3174                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3175                let err = parse_template(&template).expect_err("expected YAML parse failure");
3176                assert_eq!(err.kind, ErrorKind::Parse);
3177                assert!(err.message.contains("Expected"));
3178            }
3179        });
3180    }
3181
3182    #[test]
3183    fn rejects_tabs_as_mapping_separation_whitespace() {
3184        Python::with_gil(|py| {
3185            let module = PyModule::from_code(
3186                py,
3187                pyo3::ffi::c_str!(
3188                    "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"
3189                ),
3190                pyo3::ffi::c_str!("test_yaml_tab_error.py"),
3191                pyo3::ffi::c_str!("test_yaml_tab_error"),
3192            )
3193            .unwrap();
3194            for name in ["mapping", "plain", "indent"] {
3195                let template = module.getattr(name).unwrap();
3196                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3197                let err = parse_template(&template).expect_err("expected YAML parse failure");
3198                assert_eq!(err.kind, ErrorKind::Parse);
3199                assert!(
3200                    err.message.contains("Tabs are not allowed"),
3201                    "{name}: {}",
3202                    err.message
3203                );
3204            }
3205        });
3206    }
3207
3208    #[test]
3209    fn splits_explicit_document_streams() {
3210        Python::with_gil(|py| {
3211            let module = PyModule::from_code(
3212                py,
3213                pyo3::ffi::c_str!("template=t'---\\nname: Alice\\n---\\nname: Bob\\n'\n"),
3214                pyo3::ffi::c_str!("test_yaml_stream.py"),
3215                pyo3::ffi::c_str!("test_yaml_stream"),
3216            )
3217            .unwrap();
3218            let template = module.getattr("template").unwrap();
3219            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3220            let stream = parse_template(&template).unwrap();
3221            assert_eq!(stream.documents.len(), 2);
3222        });
3223    }
3224
3225    #[test]
3226    fn parses_and_renders_flow_complex_keys_with_interpolation() {
3227        Python::with_gil(|py| {
3228            let module = PyModule::from_code(
3229                py,
3230                pyo3::ffi::c_str!(
3231                    "left='Alice'\nright='Bob'\ntemplate=t'{{ {{name: [{left}, {right}]}}: 1, [{left}, {right}]: 2 }}'\n"
3232                ),
3233                pyo3::ffi::c_str!("test_yaml_flow_complex_keys.py"),
3234                pyo3::ffi::c_str!("test_yaml_flow_complex_keys"),
3235            )
3236            .unwrap();
3237            let template = module.getattr("template").unwrap();
3238            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3239            let stream = parse_template(&template).unwrap();
3240            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3241                panic!("expected mapping");
3242            };
3243            assert!(matches!(
3244                mapping.entries[0].key.value,
3245                super::YamlKeyValue::Complex(_)
3246            ));
3247            assert!(matches!(
3248                mapping.entries[1].key.value,
3249                super::YamlKeyValue::Complex(_)
3250            ));
3251
3252            let rendered = render_document(py, &stream).unwrap();
3253            assert_eq!(
3254                rendered.text,
3255                "{ { name: [ \"Alice\", \"Bob\" ] }: 1, [ \"Alice\", \"Bob\" ]: 2 }"
3256            );
3257            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3258            assert_eq!(
3259                documents[0]
3260                    .as_mapping()
3261                    .expect("expected YAML mapping")
3262                    .len(),
3263                2
3264            );
3265        });
3266    }
3267
3268    #[test]
3269    fn parses_explicit_mapping_keys_with_nested_collections() {
3270        Python::with_gil(|py| {
3271            let module = PyModule::from_code(
3272                py,
3273                pyo3::ffi::c_str!(
3274                    "left='Alice'\nright='Bob'\ntemplate=t'? {{name: [{left}, {right}]}}\\n: 1\\n'\n"
3275                ),
3276                pyo3::ffi::c_str!("test_yaml_explicit_complex_key.py"),
3277                pyo3::ffi::c_str!("test_yaml_explicit_complex_key"),
3278            )
3279            .unwrap();
3280            let template = module.getattr("template").unwrap();
3281            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3282            let stream = parse_template(&template).unwrap();
3283            let YamlValueNode::Mapping(mapping) = &stream.documents[0].value else {
3284                panic!("expected mapping");
3285            };
3286            assert!(matches!(
3287                mapping.entries[0].key.value,
3288                super::YamlKeyValue::Complex(_)
3289            ));
3290            let rendered = render_document(py, &stream).unwrap();
3291            assert!(rendered.text.contains("? { name: [ \"Alice\", \"Bob\" ] }"));
3292        });
3293    }
3294
3295    #[test]
3296    fn renders_explicit_complex_keys_text_and_validated_shape() {
3297        Python::with_gil(|py| {
3298            let module = PyModule::from_code(
3299                py,
3300                pyo3::ffi::c_str!(
3301                    "left='Alice'\nright='Bob'\ncomplex_key=t'? {{name: [{left}, {right}]}}\\n: 1\\n'\nempty_key=t'?\\n: 1\\n'\n"
3302                ),
3303                pyo3::ffi::c_str!("test_yaml_explicit_key_render.py"),
3304                pyo3::ffi::c_str!("test_yaml_explicit_key_render"),
3305            )
3306            .unwrap();
3307
3308            let complex_key = module.getattr("complex_key").unwrap();
3309            let complex_key = extract_template(py, &complex_key, "yaml_t/yaml_t_str").unwrap();
3310            let rendered = render_document(py, &parse_template(&complex_key).unwrap()).unwrap();
3311            assert!(
3312                rendered
3313                    .text
3314                    .contains("? { name: [ \"Alice\", \"Bob\" ] }\n: 1")
3315            );
3316            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3317            assert_eq!(
3318                documents[0]
3319                    .as_mapping()
3320                    .expect("expected YAML mapping")
3321                    .len(),
3322                1
3323            );
3324
3325            let empty_key = module.getattr("empty_key").unwrap();
3326            let empty_key = extract_template(py, &empty_key, "yaml_t/yaml_t_str").unwrap();
3327            let rendered = render_document(py, &parse_template(&empty_key).unwrap()).unwrap();
3328            assert_eq!(rendered.text, "? null\n: 1");
3329            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3330            assert_eq!(
3331                documents[0]
3332                    .as_mapping()
3333                    .expect("expected YAML mapping")
3334                    .len(),
3335                1
3336            );
3337        });
3338    }
3339
3340    #[test]
3341    fn parses_yaml_quoted_scalar_escapes() {
3342        Python::with_gil(|py| {
3343            let module = PyModule::from_code(
3344                py,
3345                pyo3::ffi::c_str!(
3346                    "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"
3347                ),
3348                pyo3::ffi::c_str!("test_yaml_quoted_scalars.py"),
3349                pyo3::ffi::c_str!("test_yaml_quoted_scalars"),
3350            )
3351            .unwrap();
3352            let template = module.getattr("template").unwrap();
3353            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3354            let stream = parse_template(&template).unwrap();
3355            let rendered = render_document(py, &stream).unwrap();
3356            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3357            assert_string_entry(&documents[0], "value", "line\nnext α A");
3358            assert_string_entry(&documents[0], "quote", "it's ok");
3359
3360            for (name, expected) in [
3361                ("unicode", "𝄞"),
3362                ("crlf_join", "ab"),
3363                ("nel", "\u{0085}"),
3364                ("nbsp", "\u{00a0}"),
3365                ("empty_single", ""),
3366                ("empty_double", ""),
3367                ("single_blank", "a\nb\nc"),
3368            ] {
3369                let template = module.getattr(name).unwrap();
3370                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3371                let stream = parse_template(&template).unwrap();
3372                let rendered = render_document(py, &stream).unwrap();
3373                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3374                assert_string_entry(&documents[0], "value", expected);
3375            }
3376        });
3377    }
3378
3379    #[test]
3380    fn renders_quoted_scalar_escape_and_folding_families() {
3381        Python::with_gil(|py| {
3382            let module = PyModule::from_code(
3383                py,
3384                pyo3::ffi::c_str!(
3385                    "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"
3386                ),
3387                pyo3::ffi::c_str!("test_yaml_quoted_scalar_families.py"),
3388                pyo3::ffi::c_str!("test_yaml_quoted_scalar_families"),
3389            )
3390            .unwrap();
3391
3392            let base = module.getattr("base").unwrap();
3393            let base = extract_template(py, &base, "yaml_t/yaml_t_str").unwrap();
3394            let rendered = render_document(py, &parse_template(&base).unwrap()).unwrap();
3395            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3396            assert_eq!(
3397                rendered.text,
3398                "value: \"line\\nnext α A\"\nquote: 'it''s ok'"
3399            );
3400            assert_string_entry(&documents[0], "value", "line\nnext α A");
3401            assert_string_entry(&documents[0], "quote", "it's ok");
3402
3403            for (name, expected_text, expected) in [
3404                ("multiline_double", "value: \"a b\"", "a b"),
3405                ("multiline_double_blank", "value: \"a\\nb\"", "a\nb"),
3406                (
3407                    "multiline_double_more_blank",
3408                    "value: \"a\\n\\nb\"",
3409                    "a\n\nb",
3410                ),
3411                ("unicode", "value: \"𝄞\"", "𝄞"),
3412                ("crlf_join", "value: \"ab\"", "ab"),
3413                ("nel", "value: \"\u{0085}\"", "\u{0085}"),
3414                ("nbsp", "value: \"\u{00a0}\"", "\u{00a0}"),
3415                ("space", "value: \" \"", " "),
3416                ("slash", "value: \"/\"", "/"),
3417                ("tab", "value: \"\\t\"", "\t"),
3418                ("empty_single", "value: ''", ""),
3419                ("empty_double", "value: \"\"", ""),
3420                ("single_blank", "value: 'a\n\n  b\n\n  c'", "a\nb\nc"),
3421            ] {
3422                let template = module.getattr(name).unwrap();
3423                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3424                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
3425                assert_eq!(rendered.text, expected_text, "{name}");
3426                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3427                assert_string_entry(&documents[0], "value", expected);
3428            }
3429        });
3430    }
3431
3432    #[test]
3433    fn renders_spec_quoted_scalar_examples_round_trip() {
3434        Python::with_gil(|py| {
3435            let module = PyModule::from_code(
3436                py,
3437                pyo3::ffi::c_str!(
3438                    "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"
3439                ),
3440                pyo3::ffi::c_str!("test_yaml_spec_quoted_examples.py"),
3441                pyo3::ffi::c_str!("test_yaml_spec_quoted_examples"),
3442            )
3443            .unwrap();
3444
3445            for (name, key, expected_text, expected_value) in [
3446                (
3447                    "unicode",
3448                    "unicode",
3449                    "unicode: \"Sosa did fine.☺\"",
3450                    "Sosa did fine.\u{263a}",
3451                ),
3452                (
3453                    "control",
3454                    "control",
3455                    "control: \"\\b1998\\t1999\\t2000\\n\"",
3456                    "\u{0008}1998\t1999\t2000\n",
3457                ),
3458                (
3459                    "single",
3460                    "single",
3461                    "single: '\"Howdy!\" he cried.'",
3462                    "\"Howdy!\" he cried.",
3463                ),
3464                (
3465                    "quoted",
3466                    "quoted",
3467                    "quoted: ' # Not a ''comment''.'",
3468                    " # Not a 'comment'.",
3469                ),
3470                ("tie", "tie", "tie: '|\\-*-/|'", "|\\-*-/|"),
3471            ] {
3472                let template = module.getattr(name).unwrap();
3473                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3474                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
3475                assert_eq!(rendered.text, expected_text, "{name}");
3476                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3477                assert_string_entry(&documents[0], key, expected_value);
3478            }
3479        });
3480    }
3481
3482    #[test]
3483    fn folds_multiline_double_quoted_scalars() {
3484        Python::with_gil(|py| {
3485            let module = PyModule::from_code(
3486                py,
3487                pyo3::ffi::c_str!(
3488                    "single=t'value: \"a\\n  b\"\\n'\nblank=t'value: \"a\\n\\n  b\"\\n'\n"
3489                ),
3490                pyo3::ffi::c_str!("test_yaml_multiline_double_quoted.py"),
3491                pyo3::ffi::c_str!("test_yaml_multiline_double_quoted"),
3492            )
3493            .unwrap();
3494
3495            for (name, expected) in [("single", "a b"), ("blank", "a\nb")] {
3496                let template = module.getattr(name).unwrap();
3497                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3498                let stream = parse_template(&template).unwrap();
3499                let rendered = render_document(py, &stream).unwrap();
3500                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3501                assert_string_entry(&documents[0], "value", expected);
3502            }
3503        });
3504    }
3505
3506    #[test]
3507    fn renders_block_chomping_and_indent_indicator_families() {
3508        Python::with_gil(|py| {
3509            let module = PyModule::from_code(
3510                py,
3511                pyo3::ffi::c_str!(
3512                    "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"
3513                ),
3514                pyo3::ffi::c_str!("test_yaml_block_chomping_families.py"),
3515                pyo3::ffi::c_str!("test_yaml_block_chomping_families"),
3516            )
3517            .unwrap();
3518
3519            for (name, expected_text, key, expected_value) in [
3520                ("literal_strip", "value: |-\n  a\n  b", "value", "a\nb"),
3521                ("literal_keep", "value: |+\n  a\n  b\n", "value", "a\nb\n"),
3522                (
3523                    "literal_keep_leading_blank",
3524                    "value: |+\n  \n  a\n",
3525                    "value",
3526                    "\na\n",
3527                ),
3528                ("folded_strip", "value: >-\n  a\n  b", "value", "a b"),
3529                ("folded_keep", "value: >+\n  a\n  b\n", "value", "a b\n"),
3530                (
3531                    "folded_more",
3532                    "value: >\n  a\n    b\n  c\n",
3533                    "value",
3534                    "a\n  b\nc\n",
3535                ),
3536                (
3537                    "indent_indicator",
3538                    "value: |2\n  a\n  b\n",
3539                    "value",
3540                    "a\nb\n",
3541                ),
3542                (
3543                    "literal_blank_keep",
3544                    "value: |+\n  a\n  \n  b\n",
3545                    "value",
3546                    "a\n\nb\n",
3547                ),
3548                (
3549                    "folded_blank_keep",
3550                    "value: >+\n  a\n  \n  b\n",
3551                    "value",
3552                    "a\nb\n",
3553                ),
3554            ] {
3555                let template = module.getattr(name).unwrap();
3556                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3557                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
3558                assert_eq!(rendered.text, expected_text, "{name}");
3559                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3560                assert_string_entry(&documents[0], key, expected_value);
3561            }
3562        });
3563    }
3564
3565    #[test]
3566    fn folds_multiline_plain_scalars() {
3567        Python::with_gil(|py| {
3568            let module = PyModule::from_code(
3569                py,
3570                pyo3::ffi::c_str!("template=t'value: a\\n  b\\n\\n  c\\n'\n"),
3571                pyo3::ffi::c_str!("test_yaml_multiline_plain.py"),
3572                pyo3::ffi::c_str!("test_yaml_multiline_plain"),
3573            )
3574            .unwrap();
3575            let template = module.getattr("template").unwrap();
3576            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3577            let stream = parse_template(&template).unwrap();
3578            let rendered = render_document(py, &stream).unwrap();
3579            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3580            assert_string_entry(&documents[0], "value", "a b\nc");
3581            assert!(rendered.text.contains("\"a b\\nc\""));
3582        });
3583    }
3584
3585    #[test]
3586    fn accepts_top_level_flow_collections_with_trailing_newlines() {
3587        Python::with_gil(|py| {
3588            let module = PyModule::from_code(
3589                py,
3590                pyo3::ffi::c_str!(
3591                    "from string.templatelib import Template\ntemplate=Template('{a: 1, b: [2, 3]}\\n')\n"
3592                ),
3593                pyo3::ffi::c_str!("test_yaml_flow_trailing_newline.py"),
3594                pyo3::ffi::c_str!("test_yaml_flow_trailing_newline"),
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 rendered = render_document(py, &stream).unwrap();
3601            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3602            assert_eq!(
3603                documents[0]
3604                    .as_mapping()
3605                    .expect("expected YAML mapping")
3606                    .len(),
3607                2
3608            );
3609        });
3610    }
3611
3612    #[test]
3613    fn accepts_line_wrapped_flow_collections() {
3614        Python::with_gil(|py| {
3615            let module = PyModule::from_code(
3616                py,
3617                pyo3::ffi::c_str!(
3618                    "from string.templatelib import Template\nsequence=Template('key: [a,\\n  b]\\n')\nmapping=Template('key: {a: 1,\\n  b: 2}\\n')\n"
3619                ),
3620                pyo3::ffi::c_str!("test_yaml_wrapped_flow.py"),
3621                pyo3::ffi::c_str!("test_yaml_wrapped_flow"),
3622            )
3623            .unwrap();
3624
3625            for name in ["sequence", "mapping"] {
3626                let template = module.getattr(name).unwrap();
3627                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3628                let stream = parse_template(&template).unwrap();
3629                let rendered = render_document(py, &stream).unwrap();
3630                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3631                assert_eq!(
3632                    documents[0]
3633                        .as_mapping()
3634                        .expect("expected YAML mapping")
3635                        .len(),
3636                    1
3637                );
3638            }
3639        });
3640    }
3641
3642    #[test]
3643    fn accepts_flow_collections_with_comments() {
3644        Python::with_gil(|py| {
3645            let module = PyModule::from_code(
3646                py,
3647                pyo3::ffi::c_str!(
3648                    "from string.templatelib import Template\nsequence=Template('key: [a, # first\\n  b]\\n')\nmapping=Template('key: {a: 1, # first\\n  b: 2}\\n')\n"
3649                ),
3650                pyo3::ffi::c_str!("test_yaml_flow_comments.py"),
3651                pyo3::ffi::c_str!("test_yaml_flow_comments"),
3652            )
3653            .unwrap();
3654
3655            for name in ["sequence", "mapping"] {
3656                let template = module.getattr(name).unwrap();
3657                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3658                let stream = parse_template(&template).unwrap();
3659                let rendered = render_document(py, &stream).unwrap();
3660                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3661                assert_eq!(
3662                    documents[0]
3663                        .as_mapping()
3664                        .expect("expected YAML mapping")
3665                        .len(),
3666                    1
3667                );
3668            }
3669        });
3670    }
3671
3672    #[test]
3673    fn treats_empty_documents_as_null() {
3674        Python::with_gil(|py| {
3675            let module = PyModule::from_code(
3676                py,
3677                pyo3::ffi::c_str!(
3678                    "from string.templatelib import Template\nempty=Template('---\\n...\\n')\nstream=Template('---\\n\\n---\\na: 1\\n')\n"
3679                ),
3680                pyo3::ffi::c_str!("test_yaml_empty_docs.py"),
3681                pyo3::ffi::c_str!("test_yaml_empty_docs"),
3682            )
3683            .unwrap();
3684
3685            let empty = module.getattr("empty").unwrap();
3686            let empty = extract_template(py, &empty, "yaml_t/yaml_t_str").unwrap();
3687            let rendered = render_document(py, &parse_template(&empty).unwrap()).unwrap();
3688            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3689            assert!(documents.as_slice()[0].is_null());
3690
3691            let stream = module.getattr("stream").unwrap();
3692            let stream = extract_template(py, &stream, "yaml_t/yaml_t_str").unwrap();
3693            let rendered = render_document(py, &parse_template(&stream).unwrap()).unwrap();
3694            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3695            assert!(documents[0].is_null());
3696            assert!(documents[1].as_mapping().is_some());
3697        });
3698    }
3699
3700    #[test]
3701    fn supports_indentless_sequence_values_and_empty_explicit_keys() {
3702        Python::with_gil(|py| {
3703            let module = PyModule::from_code(
3704                py,
3705                pyo3::ffi::c_str!(
3706                    "from string.templatelib import Template\nindentless=Template('a:\\n- 1\\n- 2\\n')\nempty_key=Template('?\\n: 1\\n')\n"
3707                ),
3708                pyo3::ffi::c_str!("test_yaml_indentless_and_empty_key.py"),
3709                pyo3::ffi::c_str!("test_yaml_indentless_and_empty_key"),
3710            )
3711            .unwrap();
3712
3713            let indentless = module.getattr("indentless").unwrap();
3714            let indentless = extract_template(py, &indentless, "yaml_t/yaml_t_str").unwrap();
3715            let rendered = render_document(py, &parse_template(&indentless).unwrap()).unwrap();
3716            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3717            let mapping = documents[0].as_mapping().expect("expected YAML mapping");
3718            let value = mapping
3719                .iter()
3720                .find_map(|(key, value)| (key.as_str() == Some("a")).then_some(value))
3721                .expect("expected key a");
3722            assert_eq!(value.as_vec().expect("expected YAML sequence").len(), 2);
3723
3724            let empty_key = module.getattr("empty_key").unwrap();
3725            let empty_key = extract_template(py, &empty_key, "yaml_t/yaml_t_str").unwrap();
3726            let rendered = render_document(py, &parse_template(&empty_key).unwrap()).unwrap();
3727            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3728            let mapping = documents[0].as_mapping().expect("expected YAML mapping");
3729            let value = mapping
3730                .iter()
3731                .find_map(|(key, value)| key.is_null().then_some(value))
3732                .expect("expected null key");
3733            assert_eq!(value.as_integer(), Some(1));
3734        });
3735    }
3736
3737    #[test]
3738    fn supports_compact_mappings_in_sequences_and_plain_hash_chars() {
3739        Python::with_gil(|py| {
3740            let module = PyModule::from_code(
3741                py,
3742                pyo3::ffi::c_str!(
3743                    "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"
3744                ),
3745                pyo3::ffi::c_str!("test_yaml_compact_sequence_maps.py"),
3746                pyo3::ffi::c_str!("test_yaml_compact_sequence_maps"),
3747            )
3748            .unwrap();
3749
3750            for name in ["seq", "mapped", "seqs", "hash_value"] {
3751                let template = module.getattr(name).unwrap();
3752                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3753                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
3754                let documents = parse_rendered_yaml(&rendered.text).unwrap();
3755                assert_eq!(documents.len(), 1);
3756            }
3757        });
3758    }
3759
3760    #[test]
3761    fn preserves_explicit_document_end_markers_in_streams() {
3762        Python::with_gil(|py| {
3763            let module = PyModule::from_code(
3764                py,
3765                pyo3::ffi::c_str!(
3766                    "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"
3767                ),
3768                pyo3::ffi::c_str!("test_yaml_explicit_end.py"),
3769                pyo3::ffi::c_str!("test_yaml_explicit_end"),
3770            )
3771            .unwrap();
3772            let template = module.getattr("template").unwrap();
3773            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3774            let stream = parse_template(&template).unwrap();
3775            let rendered = render_document(py, &stream).unwrap();
3776
3777            assert!(rendered.text.contains("...\n---"));
3778
3779            let commented = module.getattr("commented").unwrap();
3780            let commented = extract_template(py, &commented, "yaml_t/yaml_t_str").unwrap();
3781            let rendered = render_document(py, &parse_template(&commented).unwrap()).unwrap();
3782            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3783            assert_eq!(documents.len(), 2);
3784        });
3785    }
3786
3787    #[test]
3788    fn parses_verbatim_tags() {
3789        Python::with_gil(|py| {
3790            let module = PyModule::from_code(
3791                py,
3792                pyo3::ffi::c_str!(
3793                    "from string.templatelib import Template\ntemplate=Template('value: !<tag:yaml.org,2002:str> hello\\n')\n"
3794                ),
3795                pyo3::ffi::c_str!("test_yaml_verbatim_tag.py"),
3796                pyo3::ffi::c_str!("test_yaml_verbatim_tag"),
3797            )
3798            .unwrap();
3799            let template = module.getattr("template").unwrap();
3800            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
3801            let stream = parse_template(&template).unwrap();
3802            let rendered = render_document(py, &stream).unwrap();
3803            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3804
3805            assert!(rendered.text.contains("!<tag:yaml.org,2002:str>"));
3806            assert_string_entry(&documents[0], "value", "hello");
3807        });
3808    }
3809
3810    #[test]
3811    fn validates_user_defined_tags_via_saphyr() {
3812        Python::with_gil(|py| {
3813            let module = PyModule::from_code(
3814                py,
3815                pyo3::ffi::c_str!(
3816                    "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"
3817                ),
3818                pyo3::ffi::c_str!("test_yaml_custom_tags.py"),
3819                pyo3::ffi::c_str!("test_yaml_custom_tags"),
3820            )
3821            .unwrap();
3822
3823            let scalar = module.getattr("scalar").unwrap();
3824            let scalar = extract_template(py, &scalar, "yaml_t/yaml_t_str").unwrap();
3825            let rendered = render_document(py, &parse_template(&scalar).unwrap()).unwrap();
3826            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3827            assert_eq!(rendered.text.trim_end(), "!custom 3");
3828            assert!(rendered.text.contains("!custom 3"));
3829            assert_eq!(yaml_integer(&documents[0]), Some(3));
3830
3831            let custom_tag_scalar = module.getattr("custom_tag_scalar").unwrap();
3832            let custom_tag_scalar =
3833                extract_template(py, &custom_tag_scalar, "yaml_t/yaml_t_str").unwrap();
3834            let rendered =
3835                render_document(py, &parse_template(&custom_tag_scalar).unwrap()).unwrap();
3836            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3837            assert_eq!(rendered.text.trim_end(), "value: !custom 3");
3838            assert!(rendered.text.contains("value: !custom 3"));
3839            assert_integer_entry(&documents[0], "value", 3);
3840
3841            let custom_tag_sequence = module.getattr("custom_tag_sequence").unwrap();
3842            let custom_tag_sequence =
3843                extract_template(py, &custom_tag_sequence, "yaml_t/yaml_t_str").unwrap();
3844            let rendered =
3845                render_document(py, &parse_template(&custom_tag_sequence).unwrap()).unwrap();
3846            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3847            assert!(rendered.text.contains("value: !custom [ 1, 2 ]"));
3848            let value = documents[0]
3849                .as_mapping()
3850                .expect("expected YAML mapping")
3851                .iter()
3852                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("value")).then_some(value))
3853                .expect("expected value key");
3854            assert_eq!(yaml_sequence_len(value), Some(2));
3855
3856            let commented_root = module.getattr("commented_root").unwrap();
3857            let commented_root =
3858                extract_template(py, &commented_root, "yaml_t/yaml_t_str").unwrap();
3859            let rendered = render_document(py, &parse_template(&commented_root).unwrap()).unwrap();
3860            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3861            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
3862        });
3863    }
3864
3865    #[test]
3866    fn rejects_aliases_that_cross_document_boundaries() {
3867        Python::with_gil(|py| {
3868            let module = PyModule::from_code(
3869                py,
3870                pyo3::ffi::c_str!(
3871                    "from string.templatelib import Template\nstream=Template('--- &a\\n- 1\\n- 2\\n---\\n*a\\n')\n"
3872                ),
3873                pyo3::ffi::c_str!("test_yaml_cross_doc_alias.py"),
3874                pyo3::ffi::c_str!("test_yaml_cross_doc_alias"),
3875            )
3876            .unwrap();
3877
3878            let stream = module.getattr("stream").unwrap();
3879            let stream = extract_template(py, &stream, "yaml_t/yaml_t_str").unwrap();
3880            let rendered = render_document(py, &parse_template(&stream).unwrap()).unwrap();
3881            let err = parse_rendered_yaml(&rendered.text).expect_err("expected YAML parse failure");
3882            assert_eq!(err.kind, ErrorKind::Parse);
3883            assert!(err.message.contains("unknown anchor"));
3884        });
3885    }
3886
3887    #[test]
3888    fn preserves_tag_directives_and_handle_tags() {
3889        Python::with_gil(|py| {
3890            let module = PyModule::from_code(
3891                py,
3892                pyo3::ffi::c_str!(
3893                    "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"
3894                ),
3895                pyo3::ffi::c_str!("test_yaml_tag_directives.py"),
3896                pyo3::ffi::c_str!("test_yaml_tag_directives"),
3897            )
3898            .unwrap();
3899
3900            let scalar = module.getattr("scalar").unwrap();
3901            let scalar = extract_template(py, &scalar, "yaml_t/yaml_t_str").unwrap();
3902            let stream = parse_template(&scalar).unwrap();
3903            let rendered = render_document(py, &stream).unwrap();
3904            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3905            assert_eq!(
3906                rendered.text,
3907                "%TAG !e! tag:example.com,2020:\n---\nvalue: !e!foo 1"
3908            );
3909            assert_integer_entry(&documents[0], "value", 1);
3910
3911            let doc_start_comment = module.getattr("doc_start_comment").unwrap();
3912            let doc_start_comment =
3913                extract_template(py, &doc_start_comment, "yaml_t/yaml_t_str").unwrap();
3914            let stream = parse_template(&doc_start_comment).unwrap();
3915            let rendered = render_document(py, &stream).unwrap();
3916            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3917            assert_eq!(rendered.text, "---\nvalue: 1");
3918            assert_integer_entry(&documents[0], "value", 1);
3919
3920            let doc_start_tag_comment = module.getattr("doc_start_tag_comment").unwrap();
3921            let doc_start_tag_comment =
3922                extract_template(py, &doc_start_tag_comment, "yaml_t/yaml_t_str").unwrap();
3923            let stream = parse_template(&doc_start_tag_comment).unwrap();
3924            let rendered = render_document(py, &stream).unwrap();
3925            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3926            assert_eq!(rendered.text, "---\n!!str true");
3927            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
3928
3929            let root = module.getattr("root").unwrap();
3930            let root = extract_template(py, &root, "yaml_t/yaml_t_str").unwrap();
3931            let stream = parse_template(&root).unwrap();
3932            let rendered = render_document(py, &stream).unwrap();
3933            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3934            assert_eq!(
3935                rendered.text,
3936                "%YAML 1.2\n%TAG !e! tag:example.com,2020:\n---\n!e!root { value: !e!leaf 1 }"
3937            );
3938            assert_integer_entry(&documents[0], "value", 1);
3939
3940            let block_map = module.getattr("block_map").unwrap();
3941            let block_map = extract_template(py, &block_map, "yaml_t/yaml_t_str").unwrap();
3942            let stream = parse_template(&block_map).unwrap();
3943            let rendered = render_document(py, &stream).unwrap();
3944            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3945            assert_eq!(rendered.text, "---\n!!map\na: 1");
3946            assert_integer_entry(&documents[0], "a", 1);
3947
3948            let block_seq = module.getattr("block_seq").unwrap();
3949            let block_seq = extract_template(py, &block_seq, "yaml_t/yaml_t_str").unwrap();
3950            let stream = parse_template(&block_seq).unwrap();
3951            let rendered = render_document(py, &stream).unwrap();
3952            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3953            assert_eq!(rendered.text, "---\n!!seq\n- 1\n- 2");
3954            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
3955
3956            let anchor_map = module.getattr("anchor_map").unwrap();
3957            let anchor_map = extract_template(py, &anchor_map, "yaml_t/yaml_t_str").unwrap();
3958            let stream = parse_template(&anchor_map).unwrap();
3959            let rendered = render_document(py, &stream).unwrap();
3960            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3961            assert_eq!(rendered.text, "---\n&root\na: 1");
3962            assert_integer_entry(&documents[0], "a", 1);
3963
3964            let anchor_seq = module.getattr("anchor_seq").unwrap();
3965            let anchor_seq = extract_template(py, &anchor_seq, "yaml_t/yaml_t_str").unwrap();
3966            let stream = parse_template(&anchor_seq).unwrap();
3967            let rendered = render_document(py, &stream).unwrap();
3968            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3969            assert_eq!(rendered.text, "---\n!custom &root\n- 1\n- 2");
3970            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
3971        });
3972    }
3973
3974    #[test]
3975    fn preserves_explicit_core_schema_tags() {
3976        Python::with_gil(|py| {
3977            let module = PyModule::from_code(
3978                py,
3979                pyo3::ffi::c_str!(
3980                    "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"
3981                ),
3982                pyo3::ffi::c_str!("test_yaml_core_tags.py"),
3983                pyo3::ffi::c_str!("test_yaml_core_tags"),
3984            )
3985            .unwrap();
3986
3987            let mapping = module.getattr("mapping").unwrap();
3988            let mapping = extract_template(py, &mapping, "yaml_t/yaml_t_str").unwrap();
3989            let rendered = render_document(py, &parse_template(&mapping).unwrap()).unwrap();
3990            let documents = parse_rendered_yaml(&rendered.text).unwrap();
3991            let mapping = yaml_mapping(&documents[0]).expect("expected YAML mapping");
3992            let float_value = mapping
3993                .iter()
3994                .find_map(|(key, value)| {
3995                    (yaml_scalar_text(key) == Some("value_float")).then_some(value)
3996                })
3997                .expect("expected value_float key");
3998            assert_eq!(yaml_float(float_value), Some(1.0));
3999            assert_string_entry(&documents[0], "value_str", "true");
4000
4001            let root_int = module.getattr("root_int").unwrap();
4002            let root_int = extract_template(py, &root_int, "yaml_t/yaml_t_str").unwrap();
4003            let rendered = render_document(py, &parse_template(&root_int).unwrap()).unwrap();
4004            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4005            assert_eq!(yaml_integer(&documents[0]), Some(3));
4006
4007            let root_str = module.getattr("root_str").unwrap();
4008            let root_str = extract_template(py, &root_str, "yaml_t/yaml_t_str").unwrap();
4009            let rendered = render_document(py, &parse_template(&root_str).unwrap()).unwrap();
4010            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4011            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
4012
4013            let root_bool = module.getattr("root_bool").unwrap();
4014            let root_bool = extract_template(py, &root_bool, "yaml_t/yaml_t_str").unwrap();
4015            let rendered = render_document(py, &parse_template(&root_bool).unwrap()).unwrap();
4016            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4017            assert!(documents[0].is_boolean());
4018        });
4019    }
4020
4021    #[test]
4022    fn supports_flow_trailing_commas_sequence_values_and_indent_indicators() {
4023        Python::with_gil(|py| {
4024            let module = PyModule::from_code(
4025                py,
4026                pyo3::ffi::c_str!(
4027                    "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"
4028                ),
4029                pyo3::ffi::c_str!("test_yaml_edge_cases.py"),
4030                pyo3::ffi::c_str!("test_yaml_edge_cases"),
4031            )
4032            .unwrap();
4033
4034            let flow_seq = module.getattr("flow_seq").unwrap();
4035            let flow_seq = extract_template(py, &flow_seq, "yaml_t/yaml_t_str").unwrap();
4036            let rendered = render_document(py, &parse_template(&flow_seq).unwrap()).unwrap();
4037            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4038            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
4039
4040            let comment_only = module.getattr("comment_only").unwrap();
4041            let comment_only = extract_template(py, &comment_only, "yaml_t/yaml_t_str").unwrap();
4042            let rendered = render_document(py, &parse_template(&comment_only).unwrap()).unwrap();
4043            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4044            assert!(documents[0].is_null());
4045
4046            let comment_only_explicit = module.getattr("comment_only_explicit").unwrap();
4047            let comment_only_explicit =
4048                extract_template(py, &comment_only_explicit, "yaml_t/yaml_t_str").unwrap();
4049            let rendered =
4050                render_document(py, &parse_template(&comment_only_explicit).unwrap()).unwrap();
4051            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4052            assert!(documents[0].is_null());
4053
4054            let comment_only_explicit_end = module.getattr("comment_only_explicit_end").unwrap();
4055            let comment_only_explicit_end =
4056                extract_template(py, &comment_only_explicit_end, "yaml_t/yaml_t_str").unwrap();
4057            let rendered =
4058                render_document(py, &parse_template(&comment_only_explicit_end).unwrap()).unwrap();
4059            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4060            assert!(documents[0].is_null());
4061
4062            let comment_only_explicit_end_stream =
4063                module.getattr("comment_only_explicit_end_stream").unwrap();
4064            let comment_only_explicit_end_stream =
4065                extract_template(py, &comment_only_explicit_end_stream, "yaml_t/yaml_t_str")
4066                    .unwrap();
4067            let rendered = render_document(
4068                py,
4069                &parse_template(&comment_only_explicit_end_stream).unwrap(),
4070            )
4071            .unwrap();
4072            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4073            assert_eq!(documents.len(), 2);
4074            assert!(documents[0].is_null());
4075            assert_integer_entry(&documents[1], "a", 1);
4076
4077            let comment_only_mid_stream = module.getattr("comment_only_mid_stream").unwrap();
4078            let comment_only_mid_stream =
4079                extract_template(py, &comment_only_mid_stream, "yaml_t/yaml_t_str").unwrap();
4080            let rendered =
4081                render_document(py, &parse_template(&comment_only_mid_stream).unwrap()).unwrap();
4082            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4083            assert_eq!(documents.len(), 3);
4084            assert_integer_entry(&documents[0], "a", 1);
4085            assert!(documents[1].is_null());
4086            assert_integer_entry(&documents[2], "b", 2);
4087
4088            let comment_only_tail_stream = module.getattr("comment_only_tail_stream").unwrap();
4089            let comment_only_tail_stream =
4090                extract_template(py, &comment_only_tail_stream, "yaml_t/yaml_t_str").unwrap();
4091            let rendered =
4092                render_document(py, &parse_template(&comment_only_tail_stream).unwrap()).unwrap();
4093            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4094            assert_eq!(documents.len(), 2);
4095            assert_integer_entry(&documents[0], "a", 1);
4096            assert!(documents[1].is_null());
4097
4098            let flow_map = module.getattr("flow_map").unwrap();
4099            let flow_map = extract_template(py, &flow_map, "yaml_t/yaml_t_str").unwrap();
4100            let rendered = render_document(py, &parse_template(&flow_map).unwrap()).unwrap();
4101            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4102            assert_integer_entry(&documents[0], "a", 1);
4103
4104            let empty_flow_seq = module.getattr("empty_flow_seq").unwrap();
4105            let empty_flow_seq =
4106                extract_template(py, &empty_flow_seq, "yaml_t/yaml_t_str").unwrap();
4107            let rendered = render_document(py, &parse_template(&empty_flow_seq).unwrap()).unwrap();
4108            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4109            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4110            assert_eq!(yaml_sequence_len(value), Some(0));
4111
4112            let empty_flow_map = module.getattr("empty_flow_map").unwrap();
4113            let empty_flow_map =
4114                extract_template(py, &empty_flow_map, "yaml_t/yaml_t_str").unwrap();
4115            let rendered = render_document(py, &parse_template(&empty_flow_map).unwrap()).unwrap();
4116            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4117            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4118            assert!(yaml_mapping(value).is_some());
4119
4120            let flow_scalar_mix = module.getattr("flow_scalar_mix").unwrap();
4121            let flow_scalar_mix =
4122                extract_template(py, &flow_scalar_mix, "yaml_t/yaml_t_str").unwrap();
4123            let rendered = render_document(py, &parse_template(&flow_scalar_mix).unwrap()).unwrap();
4124            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4125            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4126            match value {
4127                YamlOwned::Sequence(sequence) => {
4128                    assert_eq!(sequence.len(), 3);
4129                    assert_eq!(yaml_scalar_text(&sequence[0]), Some(""));
4130                    assert_eq!(yaml_scalar_text(&sequence[1]), Some(""));
4131                    assert_eq!(yaml_scalar_text(&sequence[2]), Some("plain"));
4132                }
4133                _ => panic!("expected YAML sequence"),
4134            }
4135
4136            let flow_plain_scalar = module.getattr("flow_plain_scalar").unwrap();
4137            let flow_plain_scalar =
4138                extract_template(py, &flow_plain_scalar, "yaml_t/yaml_t_str").unwrap();
4139            let rendered =
4140                render_document(py, &parse_template(&flow_plain_scalar).unwrap()).unwrap();
4141            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4142            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4143            match value {
4144                YamlOwned::Sequence(sequence) => {
4145                    assert_eq!(sequence.len(), 1);
4146                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("1 2"));
4147                }
4148                _ => panic!("expected YAML sequence"),
4149            }
4150
4151            let flow_hash_plain_mapping_value =
4152                module.getattr("flow_hash_plain_mapping_value").unwrap();
4153            let flow_hash_plain_mapping_value =
4154                extract_template(py, &flow_hash_plain_mapping_value, "yaml_t/yaml_t_str").unwrap();
4155            let rendered =
4156                render_document(py, &parse_template(&flow_hash_plain_mapping_value).unwrap())
4157                    .unwrap();
4158            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4159            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4160            assert_string_entry(value, "a", "b#c");
4161
4162            let flow_hash_plain_mapping_values =
4163                module.getattr("flow_hash_plain_mapping_values").unwrap();
4164            let flow_hash_plain_mapping_values =
4165                extract_template(py, &flow_hash_plain_mapping_values, "yaml_t/yaml_t_str").unwrap();
4166            let rendered = render_document(
4167                py,
4168                &parse_template(&flow_hash_plain_mapping_values).unwrap(),
4169            )
4170            .unwrap();
4171            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4172            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4173            assert_string_entry(value, "a", "b#c");
4174            assert_string_entry(value, "d", "e#f");
4175
4176            let flow_hash_plain_scalars = module.getattr("flow_hash_plain_scalars").unwrap();
4177            let flow_hash_plain_scalars =
4178                extract_template(py, &flow_hash_plain_scalars, "yaml_t/yaml_t_str").unwrap();
4179            let rendered =
4180                render_document(py, &parse_template(&flow_hash_plain_scalars).unwrap()).unwrap();
4181            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4182            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4183            match value {
4184                YamlOwned::Sequence(sequence) => {
4185                    assert_eq!(sequence.len(), 2);
4186                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b"));
4187                    assert_eq!(yaml_scalar_text(&sequence[1]), Some("c#d"));
4188                }
4189                _ => panic!("expected YAML sequence"),
4190            }
4191
4192            let flow_hash_value_sequence = module.getattr("flow_hash_value_sequence").unwrap();
4193            let flow_hash_value_sequence =
4194                extract_template(py, &flow_hash_value_sequence, "yaml_t/yaml_t_str").unwrap();
4195            let rendered =
4196                render_document(py, &parse_template(&flow_hash_value_sequence).unwrap()).unwrap();
4197            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4198            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4199            match value {
4200                YamlOwned::Sequence(sequence) => {
4201                    assert_eq!(sequence.len(), 3);
4202                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b"));
4203                    assert_eq!(yaml_scalar_text(&sequence[1]), Some("c#d"));
4204                    assert_eq!(yaml_scalar_text(&sequence[2]), Some("e#f"));
4205                }
4206                _ => panic!("expected YAML sequence"),
4207            }
4208
4209            let flow_hash_long_sequence = module.getattr("flow_hash_long_sequence").unwrap();
4210            let flow_hash_long_sequence =
4211                extract_template(py, &flow_hash_long_sequence, "yaml_t/yaml_t_str").unwrap();
4212            let rendered =
4213                render_document(py, &parse_template(&flow_hash_long_sequence).unwrap()).unwrap();
4214            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4215            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4216            match value {
4217                YamlOwned::Sequence(sequence) => {
4218                    assert_eq!(sequence.len(), 4);
4219                    assert_eq!(yaml_scalar_text(&sequence[3]), Some("g#h"));
4220                }
4221                _ => panic!("expected YAML sequence"),
4222            }
4223
4224            let flow_hash_five_sequence = module.getattr("flow_hash_five_sequence").unwrap();
4225            let flow_hash_five_sequence =
4226                extract_template(py, &flow_hash_five_sequence, "yaml_t/yaml_t_str").unwrap();
4227            let rendered =
4228                render_document(py, &parse_template(&flow_hash_five_sequence).unwrap()).unwrap();
4229            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4230            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4231            match value {
4232                YamlOwned::Sequence(sequence) => {
4233                    assert_eq!(sequence.len(), 5);
4234                    assert_eq!(yaml_scalar_text(&sequence[4]), Some("i#j"));
4235                }
4236                _ => panic!("expected YAML sequence"),
4237            }
4238
4239            let flow_mapping_hash_key = module.getattr("flow_mapping_hash_key").unwrap();
4240            let flow_mapping_hash_key =
4241                extract_template(py, &flow_mapping_hash_key, "yaml_t/yaml_t_str").unwrap();
4242            let rendered =
4243                render_document(py, &parse_template(&flow_mapping_hash_key).unwrap()).unwrap();
4244            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4245            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4246            assert_integer_entry(value, "a#b", 1);
4247
4248            let flow_sequence_comments_value =
4249                module.getattr("flow_sequence_comments_value").unwrap();
4250            let flow_sequence_comments_value =
4251                extract_template(py, &flow_sequence_comments_value, "yaml_t/yaml_t_str").unwrap();
4252            let rendered =
4253                render_document(py, &parse_template(&flow_sequence_comments_value).unwrap())
4254                    .unwrap();
4255            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4256            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4257            match value {
4258                YamlOwned::Sequence(sequence) => {
4259                    assert_eq!(sequence.len(), 2);
4260                    assert_eq!(yaml_integer(&sequence[0]), Some(1));
4261                    assert_eq!(yaml_integer(&sequence[1]), Some(2));
4262                }
4263                _ => panic!("expected YAML sequence"),
4264            }
4265
4266            let flow_mapping_comments_value =
4267                module.getattr("flow_mapping_comments_value").unwrap();
4268            let flow_mapping_comments_value =
4269                extract_template(py, &flow_mapping_comments_value, "yaml_t/yaml_t_str").unwrap();
4270            let rendered =
4271                render_document(py, &parse_template(&flow_mapping_comments_value).unwrap())
4272                    .unwrap();
4273            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4274            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4275            assert_integer_entry(value, "a", 1);
4276            assert_integer_entry(value, "b", 2);
4277
4278            let comment_after_value = module.getattr("comment_after_value").unwrap();
4279            let comment_after_value =
4280                extract_template(py, &comment_after_value, "yaml_t/yaml_t_str").unwrap();
4281            let rendered =
4282                render_document(py, &parse_template(&comment_after_value).unwrap()).unwrap();
4283            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4284            assert_string_entry(&documents[0], "value", "a");
4285
4286            let plain_colon_hash = module.getattr("plain_colon_hash").unwrap();
4287            let plain_colon_hash =
4288                extract_template(py, &plain_colon_hash, "yaml_t/yaml_t_str").unwrap();
4289            let rendered =
4290                render_document(py, &parse_template(&plain_colon_hash).unwrap()).unwrap();
4291            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4292            assert_string_entry(&documents[0], "value", "a:b#c");
4293
4294            let plain_colon_hash_deeper = module.getattr("plain_colon_hash_deeper").unwrap();
4295            let plain_colon_hash_deeper =
4296                extract_template(py, &plain_colon_hash_deeper, "yaml_t/yaml_t_str").unwrap();
4297            let rendered =
4298                render_document(py, &parse_template(&plain_colon_hash_deeper).unwrap()).unwrap();
4299            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4300            assert_string_entry(&documents[0], "value", "a:b:c#d");
4301
4302            let plain_hash_chain = module.getattr("plain_hash_chain").unwrap();
4303            let plain_hash_chain =
4304                extract_template(py, &plain_hash_chain, "yaml_t/yaml_t_str").unwrap();
4305            let rendered =
4306                render_document(py, &parse_template(&plain_hash_chain).unwrap()).unwrap();
4307            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4308            assert_string_entry(&documents[0], "value", "a#b#c");
4309
4310            let plain_hash_chain_deeper = module.getattr("plain_hash_chain_deeper").unwrap();
4311            let plain_hash_chain_deeper =
4312                extract_template(py, &plain_hash_chain_deeper, "yaml_t/yaml_t_str").unwrap();
4313            let rendered =
4314                render_document(py, &parse_template(&plain_hash_chain_deeper).unwrap()).unwrap();
4315            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4316            assert_string_entry(&documents[0], "value", "a#b#c#d");
4317
4318            let plain_hash_chain_deeper_comment =
4319                module.getattr("plain_hash_chain_deeper_comment").unwrap();
4320            let plain_hash_chain_deeper_comment =
4321                extract_template(py, &plain_hash_chain_deeper_comment, "yaml_t/yaml_t_str")
4322                    .unwrap();
4323            let rendered = render_document(
4324                py,
4325                &parse_template(&plain_hash_chain_deeper_comment).unwrap(),
4326            )
4327            .unwrap();
4328            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4329            assert_string_entry(&documents[0], "value", "a#b#c#d");
4330
4331            let flow_hash_mapping_long = module.getattr("flow_hash_mapping_long").unwrap();
4332            let flow_hash_mapping_long =
4333                extract_template(py, &flow_hash_mapping_long, "yaml_t/yaml_t_str").unwrap();
4334            let rendered =
4335                render_document(py, &parse_template(&flow_hash_mapping_long).unwrap()).unwrap();
4336            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4337            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4338            assert_string_entry(value, "a", "b#c");
4339            assert_string_entry(value, "d", "e#f");
4340            assert_string_entry(value, "g", "h#i");
4341
4342            let flow_hash_mapping_four = module.getattr("flow_hash_mapping_four").unwrap();
4343            let flow_hash_mapping_four =
4344                extract_template(py, &flow_hash_mapping_four, "yaml_t/yaml_t_str").unwrap();
4345            let rendered =
4346                render_document(py, &parse_template(&flow_hash_mapping_four).unwrap()).unwrap();
4347            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4348            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4349            assert_string_entry(value, "a", "b#c");
4350            assert_string_entry(value, "d", "e#f");
4351            assert_string_entry(value, "g", "h#i");
4352            assert_string_entry(value, "j", "k#l");
4353
4354            let flow_hash_mapping_five = module.getattr("flow_hash_mapping_five").unwrap();
4355            let flow_hash_mapping_five =
4356                extract_template(py, &flow_hash_mapping_five, "yaml_t/yaml_t_str").unwrap();
4357            let rendered =
4358                render_document(py, &parse_template(&flow_hash_mapping_five).unwrap()).unwrap();
4359            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4360            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4361            assert_string_entry(value, "m", "n#o");
4362
4363            let flow_hash_mapping_six = module.getattr("flow_hash_mapping_six").unwrap();
4364            let flow_hash_mapping_six =
4365                extract_template(py, &flow_hash_mapping_six, "yaml_t/yaml_t_str").unwrap();
4366            let rendered =
4367                render_document(py, &parse_template(&flow_hash_mapping_six).unwrap()).unwrap();
4368            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4369            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4370            assert_string_entry(value, "p", "q#r");
4371
4372            let comment_after_plain_colon = module.getattr("comment_after_plain_colon").unwrap();
4373            let comment_after_plain_colon =
4374                extract_template(py, &comment_after_plain_colon, "yaml_t/yaml_t_str").unwrap();
4375            let rendered =
4376                render_document(py, &parse_template(&comment_after_plain_colon).unwrap()).unwrap();
4377            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4378            assert_string_entry(&documents[0], "value", "a:b");
4379
4380            let comment_after_flow_plain_colon =
4381                module.getattr("comment_after_flow_plain_colon").unwrap();
4382            let comment_after_flow_plain_colon =
4383                extract_template(py, &comment_after_flow_plain_colon, "yaml_t/yaml_t_str").unwrap();
4384            let rendered = render_document(
4385                py,
4386                &parse_template(&comment_after_flow_plain_colon).unwrap(),
4387            )
4388            .unwrap();
4389            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4390            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4391            match value {
4392                YamlOwned::Sequence(sequence) => {
4393                    assert_eq!(sequence.len(), 1);
4394                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a:b"));
4395                }
4396                _ => panic!("expected YAML sequence"),
4397            }
4398
4399            let flow_plain_hash_chain = module.getattr("flow_plain_hash_chain").unwrap();
4400            let flow_plain_hash_chain =
4401                extract_template(py, &flow_plain_hash_chain, "yaml_t/yaml_t_str").unwrap();
4402            let rendered =
4403                render_document(py, &parse_template(&flow_plain_hash_chain).unwrap()).unwrap();
4404            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4405            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4406            match value {
4407                YamlOwned::Sequence(sequence) => {
4408                    assert_eq!(sequence.len(), 2);
4409                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b#c"));
4410                    assert_eq!(yaml_scalar_text(&sequence[1]), Some("d#e#f"));
4411                }
4412                _ => panic!("expected YAML sequence"),
4413            }
4414
4415            let flow_plain_hash_chain_single_deeper = module
4416                .getattr("flow_plain_hash_chain_single_deeper")
4417                .unwrap();
4418            let flow_plain_hash_chain_single_deeper = extract_template(
4419                py,
4420                &flow_plain_hash_chain_single_deeper,
4421                "yaml_t/yaml_t_str",
4422            )
4423            .unwrap();
4424            let rendered = render_document(
4425                py,
4426                &parse_template(&flow_plain_hash_chain_single_deeper).unwrap(),
4427            )
4428            .unwrap();
4429            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4430            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4431            match value {
4432                YamlOwned::Sequence(sequence) => {
4433                    assert_eq!(sequence.len(), 1);
4434                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b#c#d"));
4435                }
4436                _ => panic!("expected YAML sequence"),
4437            }
4438
4439            let flow_plain_hash_chain_single_deeper_comment = module
4440                .getattr("flow_plain_hash_chain_single_deeper_comment")
4441                .unwrap();
4442            let flow_plain_hash_chain_single_deeper_comment = extract_template(
4443                py,
4444                &flow_plain_hash_chain_single_deeper_comment,
4445                "yaml_t/yaml_t_str",
4446            )
4447            .unwrap();
4448            let rendered = render_document(
4449                py,
4450                &parse_template(&flow_plain_hash_chain_single_deeper_comment).unwrap(),
4451            )
4452            .unwrap();
4453            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4454            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4455            match value {
4456                YamlOwned::Sequence(sequence) => {
4457                    assert_eq!(sequence.len(), 1);
4458                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a#b#c#d"));
4459                }
4460                _ => panic!("expected YAML sequence"),
4461            }
4462
4463            let flow_hash_seq_six = module.getattr("flow_hash_seq_six").unwrap();
4464            let flow_hash_seq_six =
4465                extract_template(py, &flow_hash_seq_six, "yaml_t/yaml_t_str").unwrap();
4466            let rendered =
4467                render_document(py, &parse_template(&flow_hash_seq_six).unwrap()).unwrap();
4468            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4469            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4470            match value {
4471                YamlOwned::Sequence(sequence) => {
4472                    assert_eq!(sequence.len(), 6);
4473                    assert_eq!(yaml_scalar_text(&sequence[5]), Some("k#l"));
4474                }
4475                _ => panic!("expected YAML sequence"),
4476            }
4477
4478            let flow_hash_seq_seven = module.getattr("flow_hash_seq_seven").unwrap();
4479            let flow_hash_seq_seven =
4480                extract_template(py, &flow_hash_seq_seven, "yaml_t/yaml_t_str").unwrap();
4481            let rendered =
4482                render_document(py, &parse_template(&flow_hash_seq_seven).unwrap()).unwrap();
4483            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4484            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4485            match value {
4486                YamlOwned::Sequence(sequence) => {
4487                    assert_eq!(sequence.len(), 7);
4488                    assert_eq!(yaml_scalar_text(&sequence[6]), Some("m#n"));
4489                }
4490                _ => panic!("expected YAML sequence"),
4491            }
4492
4493            let flow_plain_hash_chain_four = module.getattr("flow_plain_hash_chain_four").unwrap();
4494            let flow_plain_hash_chain_four =
4495                extract_template(py, &flow_plain_hash_chain_four, "yaml_t/yaml_t_str").unwrap();
4496            let rendered =
4497                render_document(py, &parse_template(&flow_plain_hash_chain_four).unwrap()).unwrap();
4498            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4499            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4500            match value {
4501                YamlOwned::Sequence(sequence) => {
4502                    assert_eq!(sequence.len(), 4);
4503                    assert_eq!(yaml_scalar_text(&sequence[3]), Some("j#k#l"));
4504                }
4505                _ => panic!("expected YAML sequence"),
4506            }
4507
4508            let block_plain_comment_after_colon_deeper = module
4509                .getattr("block_plain_comment_after_colon_deeper")
4510                .unwrap();
4511            let block_plain_comment_after_colon_deeper = extract_template(
4512                py,
4513                &block_plain_comment_after_colon_deeper,
4514                "yaml_t/yaml_t_str",
4515            )
4516            .unwrap();
4517            let rendered = render_document(
4518                py,
4519                &parse_template(&block_plain_comment_after_colon_deeper).unwrap(),
4520            )
4521            .unwrap();
4522            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4523            assert_string_entry(&documents[0], "value", "a:b:c:d");
4524
4525            let flow_plain_comment_after_colon_deeper = module
4526                .getattr("flow_plain_comment_after_colon_deeper")
4527                .unwrap();
4528            let flow_plain_comment_after_colon_deeper = extract_template(
4529                py,
4530                &flow_plain_comment_after_colon_deeper,
4531                "yaml_t/yaml_t_str",
4532            )
4533            .unwrap();
4534            let rendered = render_document(
4535                py,
4536                &parse_template(&flow_plain_comment_after_colon_deeper).unwrap(),
4537            )
4538            .unwrap();
4539            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4540            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4541            match value {
4542                YamlOwned::Sequence(sequence) => {
4543                    assert_eq!(sequence.len(), 1);
4544                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a:b:c:d"));
4545                }
4546                _ => panic!("expected YAML sequence"),
4547            }
4548
4549            let flow_mapping_plain_key_question =
4550                module.getattr("flow_mapping_plain_key_question").unwrap();
4551            let flow_mapping_plain_key_question =
4552                extract_template(py, &flow_mapping_plain_key_question, "yaml_t/yaml_t_str")
4553                    .unwrap();
4554            let rendered = render_document(
4555                py,
4556                &parse_template(&flow_mapping_plain_key_question).unwrap(),
4557            )
4558            .unwrap();
4559            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4560            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4561            let mapping = yaml_mapping(value).expect("expected YAML mapping");
4562            assert_eq!(mapping.len(), 1);
4563            let (_, entry_value) = mapping.iter().next().expect("expected YAML mapping entry");
4564            assert_eq!(yaml_integer(entry_value), Some(1));
4565
4566            let flow_mapping_plain_key_questions =
4567                module.getattr("flow_mapping_plain_key_questions").unwrap();
4568            let flow_mapping_plain_key_questions =
4569                extract_template(py, &flow_mapping_plain_key_questions, "yaml_t/yaml_t_str")
4570                    .unwrap();
4571            let rendered = render_document(
4572                py,
4573                &parse_template(&flow_mapping_plain_key_questions).unwrap(),
4574            )
4575            .unwrap();
4576            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4577            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4578            let mapping = yaml_mapping(value).expect("expected YAML mapping");
4579            assert_eq!(mapping.len(), 2);
4580
4581            let mapping_empty_flow_values = module.getattr("mapping_empty_flow_values").unwrap();
4582            let mapping_empty_flow_values =
4583                extract_template(py, &mapping_empty_flow_values, "yaml_t/yaml_t_str").unwrap();
4584            let rendered =
4585                render_document(py, &parse_template(&mapping_empty_flow_values).unwrap()).unwrap();
4586            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4587            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4588            let mapping = yaml_mapping(value).expect("expected YAML mapping");
4589            let left = mapping
4590                .iter()
4591                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("a")).then_some(value))
4592                .expect("expected key a");
4593            let right = mapping
4594                .iter()
4595                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("b")).then_some(value))
4596                .expect("expected key b");
4597            assert_eq!(yaml_sequence_len(left), Some(0));
4598            assert!(yaml_mapping(right).is_some());
4599
4600            let flow_mapping_empty_key = module.getattr("flow_mapping_empty_key").unwrap();
4601            let flow_mapping_empty_key =
4602                extract_template(py, &flow_mapping_empty_key, "yaml_t/yaml_t_str").unwrap();
4603            let rendered =
4604                render_document(py, &parse_template(&flow_mapping_empty_key).unwrap()).unwrap();
4605            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4606            assert_integer_entry(&documents[0], "", 1);
4607
4608            let flow_mapping_empty_key_and_values =
4609                module.getattr("flow_mapping_empty_key_and_values").unwrap();
4610            let flow_mapping_empty_key_and_values =
4611                extract_template(py, &flow_mapping_empty_key_and_values, "yaml_t/yaml_t_str")
4612                    .unwrap();
4613            let rendered = render_document(
4614                py,
4615                &parse_template(&flow_mapping_empty_key_and_values).unwrap(),
4616            )
4617            .unwrap();
4618            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4619            assert!(yaml_mapping_entry(&documents[0], "").is_some());
4620            let foo = yaml_mapping_entry(&documents[0], "foo").expect("expected foo");
4621            assert!(yaml_mapping(foo).is_some());
4622
4623            let flow_mapping_nested_empty = module.getattr("flow_mapping_nested_empty").unwrap();
4624            let flow_mapping_nested_empty =
4625                extract_template(py, &flow_mapping_nested_empty, "yaml_t/yaml_t_str").unwrap();
4626            let rendered =
4627                render_document(py, &parse_template(&flow_mapping_nested_empty).unwrap()).unwrap();
4628            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4629            let a = yaml_mapping_entry(&documents[0], "a").expect("expected key a");
4630            let b = yaml_mapping_entry(&documents[0], "b").expect("expected key b");
4631            assert!(yaml_mapping(a).is_some());
4632            assert_eq!(yaml_sequence_len(b), Some(0));
4633
4634            let flow_null_key = module.getattr("flow_null_key").unwrap();
4635            let flow_null_key = extract_template(py, &flow_null_key, "yaml_t/yaml_t_str").unwrap();
4636            let rendered = render_document(py, &parse_template(&flow_null_key).unwrap()).unwrap();
4637            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4638            let mapping = yaml_mapping(&documents[0]).expect("expected YAML mapping");
4639            assert!(
4640                mapping
4641                    .iter()
4642                    .any(|(key, value)| key.is_null() && yaml_integer(value) == Some(1))
4643            );
4644            assert!(mapping.iter().any(|(key, value)| {
4645                yaml_scalar_text(key) == Some("") && yaml_integer(value) == Some(2)
4646            }));
4647
4648            let block_null_key = module.getattr("block_null_key").unwrap();
4649            let block_null_key =
4650                extract_template(py, &block_null_key, "yaml_t/yaml_t_str").unwrap();
4651            let rendered = render_document(py, &parse_template(&block_null_key).unwrap()).unwrap();
4652            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4653            let mapping = yaml_mapping(&documents[0]).expect("expected YAML mapping");
4654            assert!(
4655                mapping
4656                    .iter()
4657                    .any(|(key, value)| key.is_null() && yaml_integer(value) == Some(1))
4658            );
4659
4660            let quoted_null_key = module.getattr("quoted_null_key").unwrap();
4661            let quoted_null_key =
4662                extract_template(py, &quoted_null_key, "yaml_t/yaml_t_str").unwrap();
4663            let rendered = render_document(py, &parse_template(&quoted_null_key).unwrap()).unwrap();
4664            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4665            assert_integer_entry(&documents[0], "", 1);
4666
4667            let flow_sequence_nested_empty = module.getattr("flow_sequence_nested_empty").unwrap();
4668            let flow_sequence_nested_empty =
4669                extract_template(py, &flow_sequence_nested_empty, "yaml_t/yaml_t_str").unwrap();
4670            let rendered =
4671                render_document(py, &parse_template(&flow_sequence_nested_empty).unwrap()).unwrap();
4672            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4673            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
4674
4675            let plain_scalar_colon_no_space =
4676                module.getattr("plain_scalar_colon_no_space").unwrap();
4677            let plain_scalar_colon_no_space =
4678                extract_template(py, &plain_scalar_colon_no_space, "yaml_t/yaml_t_str").unwrap();
4679            let rendered =
4680                render_document(py, &parse_template(&plain_scalar_colon_no_space).unwrap())
4681                    .unwrap();
4682            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4683            assert_string_entry(&documents[0], "value", "a:b");
4684
4685            let plain_question_mark_scalar = module.getattr("plain_question_mark_scalar").unwrap();
4686            let plain_question_mark_scalar =
4687                extract_template(py, &plain_question_mark_scalar, "yaml_t/yaml_t_str").unwrap();
4688            let rendered =
4689                render_document(py, &parse_template(&plain_question_mark_scalar).unwrap()).unwrap();
4690            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4691            assert_string_entry(&documents[0], "value", "?x");
4692
4693            let plain_colon_scalar_flow = module.getattr("plain_colon_scalar_flow").unwrap();
4694            let plain_colon_scalar_flow =
4695                extract_template(py, &plain_colon_scalar_flow, "yaml_t/yaml_t_str").unwrap();
4696            let rendered =
4697                render_document(py, &parse_template(&plain_colon_scalar_flow).unwrap()).unwrap();
4698            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4699            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4700            assert_eq!(yaml_sequence_len(value), Some(2));
4701
4702            let flow_mapping_colon_plain_key =
4703                module.getattr("flow_mapping_colon_plain_key").unwrap();
4704            let flow_mapping_colon_plain_key =
4705                extract_template(py, &flow_mapping_colon_plain_key, "yaml_t/yaml_t_str").unwrap();
4706            let rendered =
4707                render_document(py, &parse_template(&flow_mapping_colon_plain_key).unwrap())
4708                    .unwrap();
4709            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4710            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4711            assert_string_entry(value, "a:b", "c");
4712
4713            let flow_mapping_colon_and_hash =
4714                module.getattr("flow_mapping_colon_and_hash").unwrap();
4715            let flow_mapping_colon_and_hash =
4716                extract_template(py, &flow_mapping_colon_and_hash, "yaml_t/yaml_t_str").unwrap();
4717            let rendered =
4718                render_document(py, &parse_template(&flow_mapping_colon_and_hash).unwrap())
4719                    .unwrap();
4720            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4721            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4722            assert_string_entry(value, "a:b", "c#d");
4723
4724            let block_plain_colon_no_space = module.getattr("block_plain_colon_no_space").unwrap();
4725            let block_plain_colon_no_space =
4726                extract_template(py, &block_plain_colon_no_space, "yaml_t/yaml_t_str").unwrap();
4727            let rendered =
4728                render_document(py, &parse_template(&block_plain_colon_no_space).unwrap()).unwrap();
4729            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4730            assert_string_entry(&documents[0], "value", "a:b:c");
4731
4732            let flow_plain_colon_hash_deeper =
4733                module.getattr("flow_plain_colon_hash_deeper").unwrap();
4734            let flow_plain_colon_hash_deeper =
4735                extract_template(py, &flow_plain_colon_hash_deeper, "yaml_t/yaml_t_str").unwrap();
4736            let rendered =
4737                render_document(py, &parse_template(&flow_plain_colon_hash_deeper).unwrap())
4738                    .unwrap();
4739            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4740            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4741            match value {
4742                YamlOwned::Sequence(sequence) => {
4743                    assert_eq!(sequence.len(), 1);
4744                    assert_eq!(yaml_scalar_text(&sequence[0]), Some("a:b:c#d"));
4745                }
4746                _ => panic!("expected YAML sequence"),
4747            }
4748
4749            let alias_in_flow_mapping_value =
4750                module.getattr("alias_in_flow_mapping_value").unwrap();
4751            let alias_in_flow_mapping_value =
4752                extract_template(py, &alias_in_flow_mapping_value, "yaml_t/yaml_t_str").unwrap();
4753            let rendered =
4754                render_document(py, &parse_template(&alias_in_flow_mapping_value).unwrap())
4755                    .unwrap();
4756            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4757            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4758            let reference = yaml_mapping_entry(value, "ref").expect("expected ref");
4759            assert_integer_entry(reference, "x", 1);
4760
4761            let flow_null_and_alias = module.getattr("flow_null_and_alias").unwrap();
4762            let flow_null_and_alias =
4763                extract_template(py, &flow_null_and_alias, "yaml_t/yaml_t_str").unwrap();
4764            let rendered =
4765                render_document(py, &parse_template(&flow_null_and_alias).unwrap()).unwrap();
4766            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4767            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4768            let null_value = yaml_mapping(value)
4769                .and_then(|entries| {
4770                    entries
4771                        .iter()
4772                        .find_map(|(key, value)| key.is_null().then_some(value))
4773                })
4774                .expect("expected null key");
4775            assert_integer_entry(null_value, "x", 1);
4776
4777            let alias_seq_value = module.getattr("alias_seq_value").unwrap();
4778            let alias_seq_value =
4779                extract_template(py, &alias_seq_value, "yaml_t/yaml_t_str").unwrap();
4780            let rendered = render_document(py, &parse_template(&alias_seq_value).unwrap()).unwrap();
4781            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4782            assert_eq!(
4783                yaml_sequence_len(yaml_mapping_entry(&documents[0], "a").expect("expected key a")),
4784                Some(2)
4785            );
4786            assert_eq!(
4787                yaml_sequence_len(yaml_mapping_entry(&documents[0], "b").expect("expected key b")),
4788                Some(2)
4789            );
4790
4791            let flow_mapping_missing_value = module.getattr("flow_mapping_missing_value").unwrap();
4792            let flow_mapping_missing_value =
4793                extract_template(py, &flow_mapping_missing_value, "yaml_t/yaml_t_str").unwrap();
4794            let rendered =
4795                render_document(py, &parse_template(&flow_mapping_missing_value).unwrap()).unwrap();
4796            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4797            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4798            let a = yaml_mapping_entry(value, "a").expect("expected key a");
4799            assert!(a.is_null());
4800
4801            let flow_seq_missing_value_before_end =
4802                module.getattr("flow_seq_missing_value_before_end").unwrap();
4803            let flow_seq_missing_value_before_end =
4804                extract_template(py, &flow_seq_missing_value_before_end, "yaml_t/yaml_t_str")
4805                    .unwrap();
4806            let rendered = render_document(
4807                py,
4808                &parse_template(&flow_seq_missing_value_before_end).unwrap(),
4809            )
4810            .unwrap();
4811            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4812            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4813            assert_eq!(yaml_sequence_len(value), Some(2));
4814
4815            let flow_alias_map = module.getattr("flow_alias_map").unwrap();
4816            let flow_alias_map =
4817                extract_template(py, &flow_alias_map, "yaml_t/yaml_t_str").unwrap();
4818            let rendered = render_document(py, &parse_template(&flow_alias_map).unwrap()).unwrap();
4819            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4820            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4821            assert_integer_entry(value, "left", 1);
4822            assert_integer_entry(value, "right", 1);
4823
4824            let flow_alias_seq = module.getattr("flow_alias_seq").unwrap();
4825            let flow_alias_seq =
4826                extract_template(py, &flow_alias_seq, "yaml_t/yaml_t_str").unwrap();
4827            let rendered = render_document(py, &parse_template(&flow_alias_seq).unwrap()).unwrap();
4828            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4829            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4830            assert_eq!(yaml_sequence_len(value), Some(2));
4831
4832            let flow_merge = module.getattr("flow_merge").unwrap();
4833            let flow_merge = extract_template(py, &flow_merge, "yaml_t/yaml_t_str").unwrap();
4834            let rendered = render_document(py, &parse_template(&flow_merge).unwrap()).unwrap();
4835            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4836            assert!(rendered.text.contains("<<: &base"));
4837            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4838            assert_integer_entry(value, "b", 2);
4839
4840            let template = module.getattr("nested_flow_alias_merge").unwrap();
4841            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4842            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
4843            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4844            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4845            let sequence = match value {
4846                YamlOwned::Sequence(sequence) => sequence,
4847                _ => panic!("expected YAML sequence"),
4848            };
4849            assert_eq!(sequence.len(), 2);
4850            assert_integer_entry(&sequence[0], "b", 2);
4851            assert_integer_entry(&sequence[1], "a", 1);
4852
4853            let explicit_seq = module.getattr("explicit_seq").unwrap();
4854            let explicit_seq = extract_template(py, &explicit_seq, "yaml_t/yaml_t_str").unwrap();
4855            let rendered = render_document(py, &parse_template(&explicit_seq).unwrap()).unwrap();
4856            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4857            let mapping = documents[0].as_mapping().expect("expected YAML mapping");
4858            let value = mapping
4859                .iter()
4860                .find_map(|(key, value)| (yaml_scalar_text(key) == Some("a")).then_some(value))
4861                .expect("expected key a");
4862            assert_eq!(yaml_sequence_len(value), Some(2));
4863
4864            let indented_block = module.getattr("indented_block").unwrap();
4865            let indented_block =
4866                extract_template(py, &indented_block, "yaml_t/yaml_t_str").unwrap();
4867            let rendered = render_document(py, &parse_template(&indented_block).unwrap()).unwrap();
4868            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4869            assert_string_entry(&documents[0], "value", "a\nb\n");
4870            assert_eq!(rendered.text, "value: |1\n a\n b\n");
4871        });
4872    }
4873
4874    #[test]
4875    fn rejects_parse_render_and_validation_contracts() {
4876        Python::with_gil(|py| {
4877            let module = PyModule::from_code(
4878                py,
4879                pyo3::ffi::c_str!(
4880                    "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"
4881                ),
4882                pyo3::ffi::c_str!("test_yaml_error_contracts.py"),
4883                pyo3::ffi::c_str!("test_yaml_error_contracts"),
4884            )
4885            .unwrap();
4886
4887            for (name, expected) in [
4888                ("parse_open", "Expected"),
4889                ("parse_tab", "Tabs are not allowed"),
4890                ("parse_nested_tab", "Tabs are not allowed"),
4891                ("parse_trailing", "Unexpected trailing YAML content"),
4892                ("parse_empty_flow", "Expected a YAML value"),
4893                ("parse_trailing_entry", "Expected"),
4894                ("parse_empty_mapping", "Expected ':' in YAML template"),
4895                ("parse_missing_colon", "Expected ':' in YAML template"),
4896                ("parse_extra_comma", "Expected ':' in YAML template"),
4897            ] {
4898                let template = module.getattr(name).unwrap();
4899                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4900                let err = parse_template(&template).expect_err("expected YAML parse failure");
4901                assert_eq!(err.kind, ErrorKind::Parse);
4902                assert!(err.message.contains(expected), "{name}: {}", err.message);
4903            }
4904
4905            let unknown_anchor = module.getattr("unknown_anchor").unwrap();
4906            let unknown_anchor =
4907                extract_template(py, &unknown_anchor, "yaml_t/yaml_t_str").unwrap();
4908            let rendered = render_document(py, &parse_template(&unknown_anchor).unwrap()).unwrap();
4909            let err = parse_rendered_yaml(&rendered.text)
4910                .expect_err("expected YAML unknown-anchor parse failure");
4911            assert_eq!(err.kind, ErrorKind::Parse);
4912            assert!(err.message.contains("unknown anchor"));
4913
4914            let cross_doc_anchor = module.getattr("cross_doc_anchor").unwrap();
4915            let cross_doc_anchor =
4916                extract_template(py, &cross_doc_anchor, "yaml_t/yaml_t_str").unwrap();
4917            let rendered =
4918                render_document(py, &parse_template(&cross_doc_anchor).unwrap()).unwrap();
4919            let err = parse_rendered_yaml(&rendered.text)
4920                .expect_err("expected YAML cross-document anchor parse failure");
4921            assert_eq!(err.kind, ErrorKind::Parse);
4922            assert!(err.message.contains("unknown anchor"));
4923
4924            for (name, expected) in [
4925                ("fragment_template", "fragment"),
4926                ("metadata_template", "metadata"),
4927                ("float_template", "non-finite float"),
4928            ] {
4929                let template = module.getattr(name).unwrap();
4930                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
4931                let document = parse_template(&template).unwrap();
4932                let err = match render_document(py, &document) {
4933                    Ok(_) => panic!("expected YAML render failure"),
4934                    Err(err) => err,
4935                };
4936                assert_eq!(err.kind, ErrorKind::Unrepresentable);
4937                assert!(err.message.contains(expected), "{name}: {}", err.message);
4938            }
4939        });
4940    }
4941
4942    #[test]
4943    fn renders_custom_tag_stream_and_complex_key_text() {
4944        Python::with_gil(|py| {
4945            let module = PyModule::from_code(
4946                py,
4947                pyo3::ffi::c_str!(
4948                    "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"
4949                ),
4950                pyo3::ffi::c_str!("test_yaml_render_streams_and_keys.py"),
4951                pyo3::ffi::c_str!("test_yaml_render_streams_and_keys"),
4952            )
4953            .unwrap();
4954
4955            let comment_only_tail_stream = module.getattr("comment_only_tail_stream").unwrap();
4956            let comment_only_tail_stream =
4957                extract_template(py, &comment_only_tail_stream, "yaml_t/yaml_t_str").unwrap();
4958            let rendered =
4959                render_document(py, &parse_template(&comment_only_tail_stream).unwrap()).unwrap();
4960            assert_eq!(rendered.text, "---\na: 1\n---\nnull\n...");
4961            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4962            assert_eq!(documents.len(), 2);
4963            assert_integer_entry(&documents[0], "a", 1);
4964            assert!(documents[1].is_null());
4965
4966            let custom_tag_sequence = module.getattr("custom_tag_sequence").unwrap();
4967            let custom_tag_sequence =
4968                extract_template(py, &custom_tag_sequence, "yaml_t/yaml_t_str").unwrap();
4969            let rendered =
4970                render_document(py, &parse_template(&custom_tag_sequence).unwrap()).unwrap();
4971            assert_eq!(rendered.text, "value: !custom [ 1, 2 ]");
4972            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4973            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4974            assert_eq!(yaml_sequence_len(value), Some(2));
4975
4976            let flow_alias_map = module.getattr("flow_alias_map").unwrap();
4977            let flow_alias_map =
4978                extract_template(py, &flow_alias_map, "yaml_t/yaml_t_str").unwrap();
4979            let rendered = render_document(py, &parse_template(&flow_alias_map).unwrap()).unwrap();
4980            assert_eq!(rendered.text, "value: { left: &a 1, right: *a }");
4981            let documents = parse_rendered_yaml(&rendered.text).unwrap();
4982            let value = yaml_mapping_entry(&documents[0], "value").expect("expected value key");
4983            assert_integer_entry(value, "left", 1);
4984            assert_integer_entry(value, "right", 1);
4985        });
4986    }
4987
4988    #[test]
4989    fn renders_custom_tag_scalar_mapping_and_root_sequence_shapes() {
4990        Python::with_gil(|py| {
4991            let module = PyModule::from_code(
4992                py,
4993                pyo3::ffi::c_str!(
4994                    "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"
4995                ),
4996                pyo3::ffi::c_str!("test_yaml_custom_tag_shapes.py"),
4997                pyo3::ffi::c_str!("test_yaml_custom_tag_shapes"),
4998            )
4999            .unwrap();
5000
5001            let scalar_root = module.getattr("scalar_root").unwrap();
5002            let scalar_root = extract_template(py, &scalar_root, "yaml_t/yaml_t_str").unwrap();
5003            let rendered = render_document(py, &parse_template(&scalar_root).unwrap()).unwrap();
5004            assert_eq!(rendered.text, "!custom 3");
5005            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5006            assert_eq!(yaml_integer(&documents[0]), Some(3));
5007
5008            let mapping = module.getattr("mapping").unwrap();
5009            let mapping = extract_template(py, &mapping, "yaml_t/yaml_t_str").unwrap();
5010            let rendered = render_document(py, &parse_template(&mapping).unwrap()).unwrap();
5011            assert_eq!(rendered.text, "value: !custom 3");
5012            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5013            assert_integer_entry(&documents[0], "value", 3);
5014
5015            let sequence = module.getattr("sequence").unwrap();
5016            let sequence = extract_template(py, &sequence, "yaml_t/yaml_t_str").unwrap();
5017            let rendered = render_document(py, &parse_template(&sequence).unwrap()).unwrap();
5018            assert_eq!(rendered.text, "value: !custom [ 1, 2 ]");
5019            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5020            assert_eq!(
5021                yaml_sequence_len(yaml_mapping_entry(&documents[0], "value").expect("value key")),
5022                Some(2)
5023            );
5024
5025            let commented_root_sequence = module.getattr("commented_root_sequence").unwrap();
5026            let commented_root_sequence =
5027                extract_template(py, &commented_root_sequence, "yaml_t/yaml_t_str").unwrap();
5028            let rendered =
5029                render_document(py, &parse_template(&commented_root_sequence).unwrap()).unwrap();
5030            assert_eq!(rendered.text, "---\n!custom [ 1, 2 ]");
5031            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5032            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5033        });
5034    }
5035
5036    #[test]
5037    fn renders_core_schema_scalars_and_top_level_sequence() {
5038        Python::with_gil(|py| {
5039            let module = PyModule::from_code(
5040                py,
5041                pyo3::ffi::c_str!(
5042                    "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"
5043                ),
5044                pyo3::ffi::c_str!("test_yaml_core_scalars_and_sequence.py"),
5045                pyo3::ffi::c_str!("test_yaml_core_scalars_and_sequence"),
5046            )
5047            .unwrap();
5048
5049            let core_scalars = module.getattr("core_scalars").unwrap();
5050            let core_scalars = extract_template(py, &core_scalars, "yaml_t/yaml_t_str").unwrap();
5051            let rendered = render_document(py, &parse_template(&core_scalars).unwrap()).unwrap();
5052            assert_eq!(
5053                rendered.text,
5054                "value: true\nnone: null\nlegacy_bool: on\nlegacy_yes: yes"
5055            );
5056            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5057            assert_eq!(documents.len(), 1);
5058            assert_eq!(
5059                yaml_mapping_entry(&documents[0], "value").and_then(YamlOwned::as_bool),
5060                Some(true)
5061            );
5062            assert!(
5063                yaml_mapping_entry(&documents[0], "none")
5064                    .expect("none key")
5065                    .is_null()
5066            );
5067            assert_string_entry(&documents[0], "legacy_bool", "on");
5068            assert_string_entry(&documents[0], "legacy_yes", "yes");
5069
5070            let top_level_sequence = module.getattr("top_level_sequence").unwrap();
5071            let top_level_sequence =
5072                extract_template(py, &top_level_sequence, "yaml_t/yaml_t_str").unwrap();
5073            let rendered =
5074                render_document(py, &parse_template(&top_level_sequence).unwrap()).unwrap();
5075            assert_eq!(rendered.text, "- 1\n- true\n- null\n- on");
5076            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5077            assert_eq!(documents.len(), 1);
5078            let sequence = documents[0].as_vec().expect("expected top-level sequence");
5079            assert_eq!(sequence.len(), 4);
5080            assert_eq!(yaml_integer(&sequence[0]), Some(1));
5081            assert_eq!(sequence[1].as_bool(), Some(true));
5082            assert!(sequence[2].is_null());
5083            assert_eq!(yaml_scalar_text(&sequence[3]), Some("on"));
5084        });
5085    }
5086
5087    #[test]
5088    fn renders_end_to_end_supported_positions_text_and_data() {
5089        Python::with_gil(|py| {
5090            let module = PyModule::from_code(
5091                py,
5092                pyo3::ffi::c_str!(
5093                    "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"
5094                ),
5095                pyo3::ffi::c_str!("test_yaml_end_to_end_positions.py"),
5096                pyo3::ffi::c_str!("test_yaml_end_to_end_positions"),
5097            )
5098            .unwrap();
5099
5100            let template = module.getattr("template").unwrap();
5101            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5102            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5103            assert_eq!(
5104                rendered.text,
5105                "\"owner\": \"Alice\"\nlabel: \"prefix-Alice\"\nplain: item-Alice\nitems:\n  - &item \"Alice\"\n  - *item\ntagged: !str \"Alice\"\nflow: [ \"Alice\", { label: \"Alice\" } ]"
5106            );
5107            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5108            assert_eq!(documents.len(), 1);
5109            assert_string_entry(&documents[0], "owner", "Alice");
5110            assert_string_entry(&documents[0], "label", "prefix-Alice");
5111            assert_string_entry(&documents[0], "plain", "item-Alice");
5112            assert_string_entry(&documents[0], "tagged", "Alice");
5113            let items = yaml_mapping_entry(&documents[0], "items").expect("items key");
5114            let items = items.as_vec().expect("items sequence");
5115            assert_eq!(items.len(), 2);
5116            assert_eq!(yaml_scalar_text(&items[0]), Some("Alice"));
5117            assert_eq!(yaml_scalar_text(&items[1]), Some("Alice"));
5118            let flow = yaml_mapping_entry(&documents[0], "flow").expect("flow key");
5119            let flow = flow.as_vec().expect("flow sequence");
5120            assert_eq!(yaml_scalar_text(&flow[0]), Some("Alice"));
5121            assert_string_entry(&flow[1], "label", "Alice");
5122        });
5123    }
5124
5125    #[test]
5126    fn renders_block_scalars_and_sequence_item_text_and_data() {
5127        Python::with_gil(|py| {
5128            let module = PyModule::from_code(
5129                py,
5130                pyo3::ffi::c_str!(
5131                    "user='Alice'\ntemplate=t'''\nliteral: |\n  hello {user}\n  world\nfolded: >\n  hello {user}\n  world\nlines:\n  - |\n      item {user}\n'''\n"
5132                ),
5133                pyo3::ffi::c_str!("test_yaml_block_scalars_render.py"),
5134                pyo3::ffi::c_str!("test_yaml_block_scalars_render"),
5135            )
5136            .unwrap();
5137
5138            let template = module.getattr("template").unwrap();
5139            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5140            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5141            assert_eq!(
5142                rendered.text,
5143                "literal: |\n  hello Alice\n  world\nfolded: >\n  hello Alice\n  world\nlines:\n  - |\n    item Alice\n"
5144            );
5145            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5146            assert_eq!(documents.len(), 1);
5147            assert_string_entry(&documents[0], "literal", "hello Alice\nworld\n");
5148            assert_string_entry(&documents[0], "folded", "hello Alice world\n");
5149            let lines = yaml_mapping_entry(&documents[0], "lines").expect("lines key");
5150            let lines = lines.as_vec().expect("lines sequence");
5151            assert_eq!(yaml_scalar_text(&lines[0]), Some("item Alice\n"));
5152        });
5153    }
5154
5155    #[test]
5156    fn renders_comment_only_document_variants_and_mid_stream_shapes() {
5157        Python::with_gil(|py| {
5158            let module = PyModule::from_code(
5159                py,
5160                pyo3::ffi::c_str!(
5161                    "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"
5162                ),
5163                pyo3::ffi::c_str!("test_yaml_comment_variants.py"),
5164                pyo3::ffi::c_str!("test_yaml_comment_variants"),
5165            )
5166            .unwrap();
5167
5168            for (name, expected_text) in [
5169                ("comment_only", "null"),
5170                ("comment_only_explicit", "---\nnull"),
5171                ("comment_only_explicit_end", "---\nnull\n..."),
5172            ] {
5173                let template = module.getattr(name).unwrap();
5174                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5175                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5176                assert_eq!(rendered.text, expected_text);
5177                let documents = parse_rendered_yaml(&rendered.text).unwrap();
5178                assert_eq!(documents.len(), 1);
5179                assert!(documents[0].is_null());
5180            }
5181
5182            let comment_only_mid_stream = module.getattr("comment_only_mid_stream").unwrap();
5183            let comment_only_mid_stream =
5184                extract_template(py, &comment_only_mid_stream, "yaml_t/yaml_t_str").unwrap();
5185            let rendered =
5186                render_document(py, &parse_template(&comment_only_mid_stream).unwrap()).unwrap();
5187            assert_eq!(rendered.text, "---\na: 1\n---\nnull\n...\n---\nb: 2");
5188            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5189            assert_eq!(documents.len(), 3);
5190            assert_integer_entry(&documents[0], "a", 1);
5191            assert!(documents[1].is_null());
5192            assert_integer_entry(&documents[2], "b", 2);
5193        });
5194    }
5195
5196    #[test]
5197    fn renders_tag_directives_and_handle_tag_roots() {
5198        Python::with_gil(|py| {
5199            let module = PyModule::from_code(
5200                py,
5201                pyo3::ffi::c_str!(
5202                    "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"
5203                ),
5204                pyo3::ffi::c_str!("test_yaml_tag_directives.py"),
5205                pyo3::ffi::c_str!("test_yaml_tag_directives"),
5206            )
5207            .unwrap();
5208
5209            let tag_directive_scalar = module.getattr("tag_directive_scalar").unwrap();
5210            let tag_directive_scalar =
5211                extract_template(py, &tag_directive_scalar, "yaml_t/yaml_t_str").unwrap();
5212            let rendered =
5213                render_document(py, &parse_template(&tag_directive_scalar).unwrap()).unwrap();
5214            assert_eq!(
5215                rendered.text,
5216                "%TAG !e! tag:example.com,2020:\n---\nvalue: !e!foo 1"
5217            );
5218            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5219            assert_integer_entry(&documents[0], "value", 1);
5220
5221            let tag_directive_root = module.getattr("tag_directive_root").unwrap();
5222            let tag_directive_root =
5223                extract_template(py, &tag_directive_root, "yaml_t/yaml_t_str").unwrap();
5224            let rendered =
5225                render_document(py, &parse_template(&tag_directive_root).unwrap()).unwrap();
5226            assert_eq!(
5227                rendered.text,
5228                "%YAML 1.2\n%TAG !e! tag:example.com,2020:\n---\n!e!root { value: !e!leaf 1 }"
5229            );
5230            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5231            assert_integer_entry(&documents[0], "value", 1);
5232
5233            let tag_directive_root_comment = module.getattr("tag_directive_root_comment").unwrap();
5234            let tag_directive_root_comment =
5235                extract_template(py, &tag_directive_root_comment, "yaml_t/yaml_t_str").unwrap();
5236            let rendered =
5237                render_document(py, &parse_template(&tag_directive_root_comment).unwrap()).unwrap();
5238            assert_eq!(
5239                rendered.text,
5240                "%YAML 1.2\n%TAG !e! tag:example.com,2020:\n---\n!e!root { value: !e!leaf 1 }"
5241            );
5242            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5243            assert_integer_entry(&documents[0], "value", 1);
5244
5245            let verbatim_root_mapping = module.getattr("verbatim_root_mapping").unwrap();
5246            let verbatim_root_mapping =
5247                extract_template(py, &verbatim_root_mapping, "yaml_t/yaml_t_str").unwrap();
5248            let rendered =
5249                render_document(py, &parse_template(&verbatim_root_mapping).unwrap()).unwrap();
5250            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:map>\na: 1");
5251            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5252            assert_integer_entry(&documents[0], "a", 1);
5253
5254            let verbatim_root_sequence = module.getattr("verbatim_root_sequence").unwrap();
5255            let verbatim_root_sequence =
5256                extract_template(py, &verbatim_root_sequence, "yaml_t/yaml_t_str").unwrap();
5257            let rendered =
5258                render_document(py, &parse_template(&verbatim_root_sequence).unwrap()).unwrap();
5259            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:seq>\n- 1\n- 2");
5260            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5261            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5262        });
5263    }
5264
5265    #[test]
5266    fn renders_explicit_core_tag_root_shapes() {
5267        Python::with_gil(|py| {
5268            let module = PyModule::from_code(
5269                py,
5270                pyo3::ffi::c_str!(
5271                    "root_bool=t'--- !!bool true\\n'\nroot_str=t'--- !!str true\\n'\nroot_int=t'--- !!int 3\\n'\n"
5272                ),
5273                pyo3::ffi::c_str!("test_yaml_core_tag_roots.py"),
5274                pyo3::ffi::c_str!("test_yaml_core_tag_roots"),
5275            )
5276            .unwrap();
5277
5278            for (name, expected_text) in [
5279                ("root_bool", "---\n!!bool true"),
5280                ("root_str", "---\n!!str true"),
5281                ("root_int", "---\n!!int 3"),
5282            ] {
5283                let template = module.getattr(name).unwrap();
5284                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5285                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5286                assert_eq!(rendered.text, expected_text);
5287            }
5288
5289            let root_bool = module.getattr("root_bool").unwrap();
5290            let root_bool = extract_template(py, &root_bool, "yaml_t/yaml_t_str").unwrap();
5291            let rendered = render_document(py, &parse_template(&root_bool).unwrap()).unwrap();
5292            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5293            assert_eq!(documents[0].as_bool(), Some(true));
5294
5295            let root_str = module.getattr("root_str").unwrap();
5296            let root_str = extract_template(py, &root_str, "yaml_t/yaml_t_str").unwrap();
5297            let rendered = render_document(py, &parse_template(&root_str).unwrap()).unwrap();
5298            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5299            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
5300
5301            let root_int = module.getattr("root_int").unwrap();
5302            let root_int = extract_template(py, &root_int, "yaml_t/yaml_t_str").unwrap();
5303            let rendered = render_document(py, &parse_template(&root_int).unwrap()).unwrap();
5304            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5305            assert_eq!(yaml_integer(&documents[0]), Some(3));
5306        });
5307    }
5308
5309    #[test]
5310    fn renders_explicit_core_tag_mapping_and_root_text_families() {
5311        Python::with_gil(|py| {
5312            let module = PyModule::from_code(
5313                py,
5314                pyo3::ffi::c_str!(
5315                    "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"
5316                ),
5317                pyo3::ffi::c_str!("test_yaml_explicit_core_tag_families.py"),
5318                pyo3::ffi::c_str!("test_yaml_explicit_core_tag_families"),
5319            )
5320            .unwrap();
5321
5322            let mapping = module.getattr("mapping").unwrap();
5323            let mapping = extract_template(py, &mapping, "yaml_t/yaml_t_str").unwrap();
5324            let rendered = render_document(py, &parse_template(&mapping).unwrap()).unwrap();
5325            assert_eq!(
5326                rendered.text,
5327                "value_bool: !!bool true\nvalue_str: !!str true\nvalue_float: !!float 1\nvalue_null: !!null null"
5328            );
5329            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5330            assert_eq!(
5331                yaml_mapping_entry(&documents[0], "value_bool").and_then(YamlOwned::as_bool),
5332                Some(true)
5333            );
5334            assert_eq!(
5335                yaml_scalar_text(
5336                    yaml_mapping_entry(&documents[0], "value_str").expect("value_str")
5337                ),
5338                Some("true")
5339            );
5340            assert_eq!(
5341                yaml_mapping_entry(&documents[0], "value_float").and_then(yaml_float),
5342                Some(1.0)
5343            );
5344            assert!(
5345                yaml_mapping_entry(&documents[0], "value_null")
5346                    .expect("value_null")
5347                    .is_null()
5348            );
5349
5350            for (name, expected_text) in [
5351                ("root_int", "---\n!!int 3"),
5352                ("root_str", "---\n!!str true"),
5353                ("root_bool", "---\n!!bool true"),
5354            ] {
5355                let template = module.getattr(name).unwrap();
5356                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5357                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5358                assert_eq!(rendered.text, expected_text, "{name}");
5359            }
5360        });
5361    }
5362
5363    #[test]
5364    fn renders_flow_trailing_comma_explicit_key_and_indent_indicator_families() {
5365        Python::with_gil(|py| {
5366            let module = PyModule::from_code(
5367                py,
5368                pyo3::ffi::c_str!(
5369                    "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"
5370                ),
5371                pyo3::ffi::c_str!("test_yaml_flow_indent_families.py"),
5372                pyo3::ffi::c_str!("test_yaml_flow_indent_families"),
5373            )
5374            .unwrap();
5375
5376            let flow_sequence = module.getattr("flow_sequence").unwrap();
5377            let flow_sequence = extract_template(py, &flow_sequence, "yaml_t/yaml_t_str").unwrap();
5378            let rendered = render_document(py, &parse_template(&flow_sequence).unwrap()).unwrap();
5379            assert_eq!(rendered.text, "[ 1, 2 ]");
5380            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5381            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5382
5383            let flow_mapping = module.getattr("flow_mapping").unwrap();
5384            let flow_mapping = extract_template(py, &flow_mapping, "yaml_t/yaml_t_str").unwrap();
5385            let rendered = render_document(py, &parse_template(&flow_mapping).unwrap()).unwrap();
5386            assert_eq!(rendered.text, "{ a: 1 }");
5387            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5388            assert_integer_entry(&documents[0], "a", 1);
5389
5390            let explicit_key_sequence_value =
5391                module.getattr("explicit_key_sequence_value").unwrap();
5392            let explicit_key_sequence_value =
5393                extract_template(py, &explicit_key_sequence_value, "yaml_t/yaml_t_str").unwrap();
5394            let rendered =
5395                render_document(py, &parse_template(&explicit_key_sequence_value).unwrap())
5396                    .unwrap();
5397            assert_eq!(rendered.text, "? a\n:\n  - 1\n  - 2");
5398            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5399            let entry = documents[0].as_mapping().expect("mapping");
5400            assert_eq!(entry.len(), 1);
5401
5402            let indent_indicator = module.getattr("indent_indicator").unwrap();
5403            let indent_indicator =
5404                extract_template(py, &indent_indicator, "yaml_t/yaml_t_str").unwrap();
5405            let rendered =
5406                render_document(py, &parse_template(&indent_indicator).unwrap()).unwrap();
5407            assert_eq!(rendered.text, "value: |1\n a\n b\n");
5408            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5409            assert_string_entry(&documents[0], "value", "a\nb\n");
5410        });
5411    }
5412
5413    #[test]
5414    fn renders_flow_alias_and_merge_shapes() {
5415        Python::with_gil(|py| {
5416            let module = PyModule::from_code(
5417                py,
5418                pyo3::ffi::c_str!(
5419                    "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"
5420                ),
5421                pyo3::ffi::c_str!("test_yaml_flow_alias_merge.py"),
5422                pyo3::ffi::c_str!("test_yaml_flow_alias_merge"),
5423            )
5424            .unwrap();
5425
5426            let flow_alias_map = module.getattr("flow_alias_map").unwrap();
5427            let flow_alias_map =
5428                extract_template(py, &flow_alias_map, "yaml_t/yaml_t_str").unwrap();
5429            let rendered = render_document(py, &parse_template(&flow_alias_map).unwrap()).unwrap();
5430            assert_eq!(rendered.text, "value: { left: &a 1, right: *a }");
5431            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5432            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5433            assert_integer_entry(value, "left", 1);
5434            assert_integer_entry(value, "right", 1);
5435
5436            let flow_alias_seq = module.getattr("flow_alias_seq").unwrap();
5437            let flow_alias_seq =
5438                extract_template(py, &flow_alias_seq, "yaml_t/yaml_t_str").unwrap();
5439            let rendered = render_document(py, &parse_template(&flow_alias_seq).unwrap()).unwrap();
5440            assert_eq!(rendered.text, "value: [ &a 1, *a ]");
5441            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5442            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5443            let value = value.as_vec().expect("value seq");
5444            assert_eq!(yaml_integer(&value[0]), Some(1));
5445            assert_eq!(yaml_integer(&value[1]), Some(1));
5446
5447            let flow_merge = module.getattr("flow_merge").unwrap();
5448            let flow_merge = extract_template(py, &flow_merge, "yaml_t/yaml_t_str").unwrap();
5449            let rendered = render_document(py, &parse_template(&flow_merge).unwrap()).unwrap();
5450            assert_eq!(rendered.text, "value: { <<: &base { a: 1 }, b: 2 }");
5451            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5452            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5453            assert_integer_entry(value, "b", 2);
5454            assert!(
5455                yaml_mapping_entry(value, "a").is_some()
5456                    || yaml_mapping_entry(value, "<<").is_some()
5457            );
5458        });
5459    }
5460
5461    #[test]
5462    fn renders_document_stream_and_root_decorator_shapes() {
5463        Python::with_gil(|py| {
5464            let module = PyModule::from_code(
5465                py,
5466                pyo3::ffi::c_str!(
5467                    "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"
5468                ),
5469                pyo3::ffi::c_str!("test_yaml_stream_root_shapes.py"),
5470                pyo3::ffi::c_str!("test_yaml_stream_root_shapes"),
5471            )
5472            .unwrap();
5473
5474            let comment_only_explicit_end_document = module
5475                .getattr("comment_only_explicit_end_document")
5476                .unwrap();
5477            let comment_only_explicit_end_document =
5478                extract_template(py, &comment_only_explicit_end_document, "yaml_t/yaml_t_str")
5479                    .unwrap();
5480            let rendered = render_document(
5481                py,
5482                &parse_template(&comment_only_explicit_end_document).unwrap(),
5483            )
5484            .unwrap();
5485            assert_eq!(rendered.text, "---\nnull\n...");
5486            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5487            assert!(matches!(documents[0], YamlOwned::Value(_)));
5488
5489            let comment_only_explicit_end_stream =
5490                module.getattr("comment_only_explicit_end_stream").unwrap();
5491            let comment_only_explicit_end_stream =
5492                extract_template(py, &comment_only_explicit_end_stream, "yaml_t/yaml_t_str")
5493                    .unwrap();
5494            let rendered = render_document(
5495                py,
5496                &parse_template(&comment_only_explicit_end_stream).unwrap(),
5497            )
5498            .unwrap();
5499            assert_eq!(rendered.text, "---\nnull\n...\n---\na: 1");
5500            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5501            assert!(matches!(documents[0], YamlOwned::Value(_)));
5502            assert_integer_entry(&documents[1], "a", 1);
5503
5504            let comment_only_mid_stream = module.getattr("comment_only_mid_stream").unwrap();
5505            let comment_only_mid_stream =
5506                extract_template(py, &comment_only_mid_stream, "yaml_t/yaml_t_str").unwrap();
5507            let rendered =
5508                render_document(py, &parse_template(&comment_only_mid_stream).unwrap()).unwrap();
5509            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5510            assert_integer_entry(&documents[0], "a", 1);
5511            assert!(matches!(documents[1], YamlOwned::Value(_)));
5512            assert_integer_entry(&documents[2], "b", 2);
5513
5514            let explicit_end_comment_stream =
5515                module.getattr("explicit_end_comment_stream").unwrap();
5516            let explicit_end_comment_stream =
5517                extract_template(py, &explicit_end_comment_stream, "yaml_t/yaml_t_str").unwrap();
5518            let rendered =
5519                render_document(py, &parse_template(&explicit_end_comment_stream).unwrap())
5520                    .unwrap();
5521            assert_eq!(rendered.text, "---\na: 1\n...\n---\nb: 2");
5522            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5523            assert_integer_entry(&documents[0], "a", 1);
5524            assert_integer_entry(&documents[1], "b", 2);
5525
5526            let doc_start_comment = module.getattr("doc_start_comment").unwrap();
5527            let doc_start_comment =
5528                extract_template(py, &doc_start_comment, "yaml_t/yaml_t_str").unwrap();
5529            let rendered =
5530                render_document(py, &parse_template(&doc_start_comment).unwrap()).unwrap();
5531            assert_eq!(rendered.text, "---\nvalue: 1");
5532            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5533            assert_integer_entry(&documents[0], "value", 1);
5534
5535            let doc_start_tag_comment = module.getattr("doc_start_tag_comment").unwrap();
5536            let doc_start_tag_comment =
5537                extract_template(py, &doc_start_tag_comment, "yaml_t/yaml_t_str").unwrap();
5538            let rendered =
5539                render_document(py, &parse_template(&doc_start_tag_comment).unwrap()).unwrap();
5540            assert_eq!(rendered.text, "---\n!!str true");
5541            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5542            assert_eq!(yaml_scalar_text(&documents[0]), Some("true"));
5543
5544            let tagged_block_root_mapping = module.getattr("tagged_block_root_mapping").unwrap();
5545            let tagged_block_root_mapping =
5546                extract_template(py, &tagged_block_root_mapping, "yaml_t/yaml_t_str").unwrap();
5547            let rendered =
5548                render_document(py, &parse_template(&tagged_block_root_mapping).unwrap()).unwrap();
5549            assert_eq!(rendered.text, "---\n!!map\na: 1");
5550            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5551            assert_integer_entry(&documents[0], "a", 1);
5552
5553            let tagged_block_root_sequence = module.getattr("tagged_block_root_sequence").unwrap();
5554            let tagged_block_root_sequence =
5555                extract_template(py, &tagged_block_root_sequence, "yaml_t/yaml_t_str").unwrap();
5556            let rendered =
5557                render_document(py, &parse_template(&tagged_block_root_sequence).unwrap()).unwrap();
5558            assert_eq!(rendered.text, "---\n!!seq\n- 1\n- 2");
5559            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5560            assert_eq!(documents[0].as_vec().expect("root seq").len(), 2);
5561
5562            let root_anchor_sequence = module.getattr("root_anchor_sequence").unwrap();
5563            let root_anchor_sequence =
5564                extract_template(py, &root_anchor_sequence, "yaml_t/yaml_t_str").unwrap();
5565            let rendered =
5566                render_document(py, &parse_template(&root_anchor_sequence).unwrap()).unwrap();
5567            assert_eq!(rendered.text, "---\n&root\n- 1\n- 2");
5568            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5569            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5570
5571            let root_anchor_custom_mapping = module.getattr("root_anchor_custom_mapping").unwrap();
5572            let root_anchor_custom_mapping =
5573                extract_template(py, &root_anchor_custom_mapping, "yaml_t/yaml_t_str").unwrap();
5574            let rendered =
5575                render_document(py, &parse_template(&root_anchor_custom_mapping).unwrap()).unwrap();
5576            assert_eq!(rendered.text, "---\n!custom &root\na: 1");
5577            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5578            assert_integer_entry(&documents[0], "a", 1);
5579
5580            let root_custom_anchor_sequence =
5581                module.getattr("root_custom_anchor_sequence").unwrap();
5582            let root_custom_anchor_sequence =
5583                extract_template(py, &root_custom_anchor_sequence, "yaml_t/yaml_t_str").unwrap();
5584            let rendered =
5585                render_document(py, &parse_template(&root_custom_anchor_sequence).unwrap())
5586                    .unwrap();
5587            assert_eq!(rendered.text, "---\n!custom &root\n- 1\n- 2");
5588            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5589            assert_eq!(yaml_sequence_len(&documents[0]), Some(2));
5590
5591            let flow_newline = module.getattr("flow_newline").unwrap();
5592            let flow_newline = extract_template(py, &flow_newline, "yaml_t/yaml_t_str").unwrap();
5593            let rendered = render_document(py, &parse_template(&flow_newline).unwrap()).unwrap();
5594            assert_eq!(rendered.text, "{ a: 1, b: [ 2, 3 ] }");
5595            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5596            let mapping = yaml_mapping(&documents[0]).expect("expected root flow mapping");
5597            assert_eq!(mapping.len(), 2);
5598        });
5599    }
5600
5601    #[test]
5602    fn renders_merge_and_collection_shape_families() {
5603        Python::with_gil(|py| {
5604            let module = PyModule::from_code(
5605                py,
5606                pyo3::ffi::c_str!(
5607                    "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"
5608                ),
5609                pyo3::ffi::c_str!("test_yaml_merge_collection_shapes.py"),
5610                pyo3::ffi::c_str!("test_yaml_merge_collection_shapes"),
5611            )
5612            .unwrap();
5613
5614            let merge = module.getattr("merge").unwrap();
5615            let merge = extract_template(py, &merge, "yaml_t/yaml_t_str").unwrap();
5616            let rendered = render_document(py, &parse_template(&merge).unwrap()).unwrap();
5617            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5618            let derived = yaml_mapping_entry(&documents[0], "derived").expect("derived");
5619            assert_integer_entry(derived, "c", 3);
5620            assert!(
5621                yaml_mapping_entry(derived, "a").is_some()
5622                    || yaml_mapping_entry(derived, "<<").is_some()
5623            );
5624            assert!(
5625                yaml_mapping_entry(derived, "b").is_some()
5626                    || yaml_mapping_entry(derived, "<<").is_some()
5627            );
5628
5629            let flow_nested_alias_merge = module.getattr("flow_nested_alias_merge").unwrap();
5630            let flow_nested_alias_merge =
5631                extract_template(py, &flow_nested_alias_merge, "yaml_t/yaml_t_str").unwrap();
5632            let rendered =
5633                render_document(py, &parse_template(&flow_nested_alias_merge).unwrap()).unwrap();
5634            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5635            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5636            let value = value.as_vec().expect("value seq");
5637            assert_eq!(value.len(), 2);
5638
5639            let alias_seq_value = module.getattr("alias_seq_value").unwrap();
5640            let alias_seq_value =
5641                extract_template(py, &alias_seq_value, "yaml_t/yaml_t_str").unwrap();
5642            let rendered = render_document(py, &parse_template(&alias_seq_value).unwrap()).unwrap();
5643            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5644            assert_eq!(
5645                yaml_sequence_len(yaml_mapping_entry(&documents[0], "a").expect("a")),
5646                Some(2)
5647            );
5648            assert_eq!(
5649                yaml_sequence_len(yaml_mapping_entry(&documents[0], "b").expect("b")),
5650                Some(2)
5651            );
5652
5653            for (name, expected_len) in [
5654                ("empty_flow_sequence", 0usize),
5655                ("flow_seq_missing_value_before_end", 2usize),
5656            ] {
5657                let template = module.getattr(name).unwrap();
5658                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5659                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5660                let documents = parse_rendered_yaml(&rendered.text).unwrap();
5661                let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5662                assert_eq!(yaml_sequence_len(value), Some(expected_len), "{name}");
5663            }
5664
5665            let empty_flow_mapping = module.getattr("empty_flow_mapping").unwrap();
5666            let empty_flow_mapping =
5667                extract_template(py, &empty_flow_mapping, "yaml_t/yaml_t_str").unwrap();
5668            let rendered =
5669                render_document(py, &parse_template(&empty_flow_mapping).unwrap()).unwrap();
5670            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5671            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5672            assert_eq!(yaml_mapping(value).expect("mapping").len(), 0);
5673
5674            let flow_mapping_missing_value = module.getattr("flow_mapping_missing_value").unwrap();
5675            let flow_mapping_missing_value =
5676                extract_template(py, &flow_mapping_missing_value, "yaml_t/yaml_t_str").unwrap();
5677            let rendered =
5678                render_document(py, &parse_template(&flow_mapping_missing_value).unwrap()).unwrap();
5679            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5680            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5681            assert!(yaml_mapping_entry(value, "a").is_some());
5682
5683            let indentless_sequence_value = module.getattr("indentless_sequence_value").unwrap();
5684            let indentless_sequence_value =
5685                extract_template(py, &indentless_sequence_value, "yaml_t/yaml_t_str").unwrap();
5686            let rendered =
5687                render_document(py, &parse_template(&indentless_sequence_value).unwrap()).unwrap();
5688            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5689            assert_eq!(
5690                yaml_sequence_len(yaml_mapping_entry(&documents[0], "a").expect("a")),
5691                Some(2)
5692            );
5693
5694            let sequence_of_mappings = module.getattr("sequence_of_mappings").unwrap();
5695            let sequence_of_mappings =
5696                extract_template(py, &sequence_of_mappings, "yaml_t/yaml_t_str").unwrap();
5697            let rendered =
5698                render_document(py, &parse_template(&sequence_of_mappings).unwrap()).unwrap();
5699            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5700            assert_eq!(documents[0].as_vec().expect("top-level seq").len(), 2);
5701
5702            let mapping_of_sequence_of_mappings =
5703                module.getattr("mapping_of_sequence_of_mappings").unwrap();
5704            let mapping_of_sequence_of_mappings =
5705                extract_template(py, &mapping_of_sequence_of_mappings, "yaml_t/yaml_t_str")
5706                    .unwrap();
5707            let rendered = render_document(
5708                py,
5709                &parse_template(&mapping_of_sequence_of_mappings).unwrap(),
5710            )
5711            .unwrap();
5712            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5713            assert_eq!(
5714                yaml_sequence_len(yaml_mapping_entry(&documents[0], "items").expect("items")),
5715                Some(2)
5716            );
5717
5718            let sequence_of_sequences = module.getattr("sequence_of_sequences").unwrap();
5719            let sequence_of_sequences =
5720                extract_template(py, &sequence_of_sequences, "yaml_t/yaml_t_str").unwrap();
5721            let rendered =
5722                render_document(py, &parse_template(&sequence_of_sequences).unwrap()).unwrap();
5723            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5724            let top = documents[0].as_vec().expect("top-level seq");
5725            assert_eq!(top.len(), 2);
5726            assert_eq!(yaml_sequence_len(&top[0]), Some(2));
5727            assert_eq!(yaml_sequence_len(&top[1]), Some(1));
5728        });
5729    }
5730
5731    #[test]
5732    fn renders_flow_scalar_key_and_comment_edge_families() {
5733        Python::with_gil(|py| {
5734            let module = PyModule::from_code(
5735                py,
5736                pyo3::ffi::c_str!(
5737                    "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"
5738                ),
5739                pyo3::ffi::c_str!("test_yaml_flow_scalar_edge_families.py"),
5740                pyo3::ffi::c_str!("test_yaml_flow_scalar_edge_families"),
5741            )
5742            .unwrap();
5743
5744            let flow_plain_scalar_with_space =
5745                module.getattr("flow_plain_scalar_with_space").unwrap();
5746            let flow_plain_scalar_with_space =
5747                extract_template(py, &flow_plain_scalar_with_space, "yaml_t/yaml_t_str").unwrap();
5748            let rendered =
5749                render_document(py, &parse_template(&flow_plain_scalar_with_space).unwrap())
5750                    .unwrap();
5751            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5752            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5753            let value = value.as_vec().expect("value seq");
5754            assert_eq!(yaml_scalar_text(&value[0]), Some("1 2"));
5755
5756            let mapping_empty_flow_values = module.getattr("mapping_empty_flow_values").unwrap();
5757            let mapping_empty_flow_values =
5758                extract_template(py, &mapping_empty_flow_values, "yaml_t/yaml_t_str").unwrap();
5759            let rendered =
5760                render_document(py, &parse_template(&mapping_empty_flow_values).unwrap()).unwrap();
5761            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5762            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5763            assert_eq!(
5764                yaml_sequence_len(yaml_mapping_entry(value, "a").expect("a")),
5765                Some(0)
5766            );
5767            assert_eq!(
5768                yaml_mapping(yaml_mapping_entry(value, "b").expect("b"))
5769                    .expect("b mapping")
5770                    .len(),
5771                0
5772            );
5773
5774            let flow_mapping_empty_key_and_values =
5775                module.getattr("flow_mapping_empty_key_and_values").unwrap();
5776            let flow_mapping_empty_key_and_values =
5777                extract_template(py, &flow_mapping_empty_key_and_values, "yaml_t/yaml_t_str")
5778                    .unwrap();
5779            let rendered = render_document(
5780                py,
5781                &parse_template(&flow_mapping_empty_key_and_values).unwrap(),
5782            )
5783            .unwrap();
5784            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5785            assert_eq!(
5786                yaml_sequence_len(yaml_mapping_entry(&documents[0], "").expect("empty key")),
5787                Some(0)
5788            );
5789            assert_eq!(
5790                yaml_mapping(yaml_mapping_entry(&documents[0], "foo").expect("foo"))
5791                    .expect("foo mapping")
5792                    .len(),
5793                0
5794            );
5795
5796            let flow_null_key = module.getattr("flow_null_key").unwrap();
5797            let flow_null_key = extract_template(py, &flow_null_key, "yaml_t/yaml_t_str").unwrap();
5798            let rendered = render_document(py, &parse_template(&flow_null_key).unwrap()).unwrap();
5799            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5800            assert_eq!(documents[0].as_mapping().expect("mapping").len(), 2);
5801
5802            let block_null_key = module.getattr("block_null_key").unwrap();
5803            let block_null_key =
5804                extract_template(py, &block_null_key, "yaml_t/yaml_t_str").unwrap();
5805            let rendered = render_document(py, &parse_template(&block_null_key).unwrap()).unwrap();
5806            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5807            assert_eq!(documents[0].as_mapping().expect("mapping").len(), 1);
5808
5809            let quoted_null_key = module.getattr("quoted_null_key").unwrap();
5810            let quoted_null_key =
5811                extract_template(py, &quoted_null_key, "yaml_t/yaml_t_str").unwrap();
5812            let rendered = render_document(py, &parse_template(&quoted_null_key).unwrap()).unwrap();
5813            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5814            assert_eq!(documents[0].as_mapping().expect("mapping").len(), 1);
5815
5816            let plain_question_mark_scalar = module.getattr("plain_question_mark_scalar").unwrap();
5817            let plain_question_mark_scalar =
5818                extract_template(py, &plain_question_mark_scalar, "yaml_t/yaml_t_str").unwrap();
5819            let rendered =
5820                render_document(py, &parse_template(&plain_question_mark_scalar).unwrap()).unwrap();
5821            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5822            assert_string_entry(&documents[0], "value", "?x");
5823
5824            let plain_colon_scalar_flow = module.getattr("plain_colon_scalar_flow").unwrap();
5825            let plain_colon_scalar_flow =
5826                extract_template(py, &plain_colon_scalar_flow, "yaml_t/yaml_t_str").unwrap();
5827            let rendered =
5828                render_document(py, &parse_template(&plain_colon_scalar_flow).unwrap()).unwrap();
5829            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5830            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5831            let value = value.as_vec().expect("value seq");
5832            assert_eq!(yaml_scalar_text(&value[0]), Some("a:b"));
5833            assert_eq!(yaml_scalar_text(&value[1]), Some("c:d"));
5834
5835            let flow_mapping_plain_key_questions =
5836                module.getattr("flow_mapping_plain_key_questions").unwrap();
5837            let flow_mapping_plain_key_questions =
5838                extract_template(py, &flow_mapping_plain_key_questions, "yaml_t/yaml_t_str")
5839                    .unwrap();
5840            let rendered = render_document(
5841                py,
5842                &parse_template(&flow_mapping_plain_key_questions).unwrap(),
5843            )
5844            .unwrap();
5845            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5846            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5847            assert_eq!(yaml_mapping(value).expect("mapping").len(), 2);
5848
5849            let flow_hash_mapping_four = module.getattr("flow_hash_mapping_four").unwrap();
5850            let flow_hash_mapping_four =
5851                extract_template(py, &flow_hash_mapping_four, "yaml_t/yaml_t_str").unwrap();
5852            let rendered =
5853                render_document(py, &parse_template(&flow_hash_mapping_four).unwrap()).unwrap();
5854            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5855            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5856            assert_eq!(yaml_mapping(value).expect("mapping").len(), 4);
5857
5858            let flow_hash_seq_seven = module.getattr("flow_hash_seq_seven").unwrap();
5859            let flow_hash_seq_seven =
5860                extract_template(py, &flow_hash_seq_seven, "yaml_t/yaml_t_str").unwrap();
5861            let rendered =
5862                render_document(py, &parse_template(&flow_hash_seq_seven).unwrap()).unwrap();
5863            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5864            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5865            assert_eq!(yaml_sequence_len(value), Some(7));
5866
5867            let comment_after_flow_plain_colon =
5868                module.getattr("comment_after_flow_plain_colon").unwrap();
5869            let comment_after_flow_plain_colon =
5870                extract_template(py, &comment_after_flow_plain_colon, "yaml_t/yaml_t_str").unwrap();
5871            let rendered = render_document(
5872                py,
5873                &parse_template(&comment_after_flow_plain_colon).unwrap(),
5874            )
5875            .unwrap();
5876            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5877            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5878            let value = value.as_vec().expect("value seq");
5879            assert_eq!(yaml_scalar_text(&value[0]), Some("a:b"));
5880
5881            let flow_plain_comment_after_colon_deeper = module
5882                .getattr("flow_plain_comment_after_colon_deeper")
5883                .unwrap();
5884            let flow_plain_comment_after_colon_deeper = extract_template(
5885                py,
5886                &flow_plain_comment_after_colon_deeper,
5887                "yaml_t/yaml_t_str",
5888            )
5889            .unwrap();
5890            let rendered = render_document(
5891                py,
5892                &parse_template(&flow_plain_comment_after_colon_deeper).unwrap(),
5893            )
5894            .unwrap();
5895            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5896            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5897            let value = value.as_vec().expect("value seq");
5898            assert_eq!(yaml_scalar_text(&value[0]), Some("a:b:c:d"));
5899        });
5900    }
5901
5902    #[test]
5903    fn renders_flow_collection_comment_and_verbatim_tag_families() {
5904        Python::with_gil(|py| {
5905            let module = PyModule::from_code(
5906                py,
5907                pyo3::ffi::c_str!(
5908                    "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"
5909                ),
5910                pyo3::ffi::c_str!("test_yaml_flow_collection_comment_and_tag_families.py"),
5911                pyo3::ffi::c_str!("test_yaml_flow_collection_comment_and_tag_families"),
5912            )
5913            .unwrap();
5914
5915            let verbatim_tag = module.getattr("verbatim_tag").unwrap();
5916            let verbatim_tag = extract_template(py, &verbatim_tag, "yaml_t/yaml_t_str").unwrap();
5917            let rendered = render_document(py, &parse_template(&verbatim_tag).unwrap()).unwrap();
5918            assert_eq!(rendered.text, "value: !<tag:yaml.org,2002:str> hello");
5919            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5920            assert_string_entry(&documents[0], "value", "hello");
5921
5922            for (name, expected_text) in [
5923                ("flow_wrapped_sequence", "key: [ a, b ]"),
5924                ("flow_sequence_comment", "key: [ a, b ]"),
5925            ] {
5926                let template = module.getattr(name).unwrap();
5927                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5928                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5929                assert_eq!(rendered.text, expected_text, "{name}");
5930                let documents = parse_rendered_yaml(&rendered.text).unwrap();
5931                let value = yaml_mapping_entry(&documents[0], "key").expect("sequence key");
5932                assert_eq!(yaml_sequence_len(value), Some(2), "{name}");
5933            }
5934
5935            for (name, expected_text) in [
5936                ("flow_wrapped_mapping", "key: { a: 1, b: 2 }"),
5937                ("flow_mapping_comment", "key: { a: 1, b: 2 }"),
5938            ] {
5939                let template = module.getattr(name).unwrap();
5940                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5941                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5942                assert_eq!(rendered.text, expected_text, "{name}");
5943                let documents = parse_rendered_yaml(&rendered.text).unwrap();
5944                let value = yaml_mapping_entry(&documents[0], "key").expect("mapping key");
5945                assert_eq!(yaml_mapping(value).expect("mapping").len(), 2, "{name}");
5946            }
5947
5948            let alias_in_flow_mapping_value =
5949                module.getattr("alias_in_flow_mapping_value").unwrap();
5950            let alias_in_flow_mapping_value =
5951                extract_template(py, &alias_in_flow_mapping_value, "yaml_t/yaml_t_str").unwrap();
5952            let rendered =
5953                render_document(py, &parse_template(&alias_in_flow_mapping_value).unwrap())
5954                    .unwrap();
5955            assert_eq!(rendered.text, "base: &a { x: 1 }\nvalue: { ref: *a }");
5956            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5957            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5958            let referenced = yaml_mapping_entry(value, "ref").expect("ref key");
5959            assert_integer_entry(referenced, "x", 1);
5960
5961            let flow_null_and_alias = module.getattr("flow_null_and_alias").unwrap();
5962            let flow_null_and_alias =
5963                extract_template(py, &flow_null_and_alias, "yaml_t/yaml_t_str").unwrap();
5964            let rendered =
5965                render_document(py, &parse_template(&flow_null_and_alias).unwrap()).unwrap();
5966            assert_eq!(rendered.text, "base: &a { x: 1 }\nvalue: { null: *a }");
5967            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5968            let value = yaml_mapping_entry(&documents[0], "value").expect("value key");
5969            assert_eq!(yaml_mapping(value).expect("mapping").len(), 1);
5970        });
5971    }
5972
5973    #[test]
5974    fn renders_verbatim_root_scalar_text_and_data() {
5975        Python::with_gil(|py| {
5976            let module = PyModule::from_code(
5977                py,
5978                pyo3::ffi::c_str!("template=t'--- !<tag:yaml.org,2002:str> hello\\n'\n"),
5979                pyo3::ffi::c_str!("test_yaml_verbatim_root_scalar.py"),
5980                pyo3::ffi::c_str!("test_yaml_verbatim_root_scalar"),
5981            )
5982            .unwrap();
5983
5984            let template = module.getattr("template").unwrap();
5985            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
5986            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
5987            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:str> hello");
5988            let documents = parse_rendered_yaml(&rendered.text).unwrap();
5989            assert_eq!(yaml_scalar_text(&documents[0]), Some("hello"));
5990        });
5991    }
5992
5993    #[test]
5994    fn renders_verbatim_root_anchor_scalar_text_and_data() {
5995        Python::with_gil(|py| {
5996            let module = PyModule::from_code(
5997                py,
5998                pyo3::ffi::c_str!("template=t'--- !<tag:yaml.org,2002:str> &root hello\\n'\n"),
5999                pyo3::ffi::c_str!("test_yaml_verbatim_root_anchor_scalar.py"),
6000                pyo3::ffi::c_str!("test_yaml_verbatim_root_anchor_scalar"),
6001            )
6002            .unwrap();
6003
6004            let template = module.getattr("template").unwrap();
6005            let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6006            let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6007            assert_eq!(rendered.text, "---\n!<tag:yaml.org,2002:str> &root hello");
6008            let documents = parse_rendered_yaml(&rendered.text).unwrap();
6009            assert_eq!(yaml_scalar_text(&documents[0]), Some("hello"));
6010        });
6011    }
6012
6013    #[test]
6014    fn renders_spec_chapter_2_examples_text_and_data() {
6015        Python::with_gil(|py| {
6016            let module = PyModule::from_code(
6017                py,
6018                pyo3::ffi::c_str!(
6019                    "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"
6020                ),
6021                pyo3::ffi::c_str!("test_yaml_spec_chapter_2_examples.py"),
6022                pyo3::ffi::c_str!("test_yaml_spec_chapter_2_examples"),
6023            )
6024            .unwrap();
6025
6026            for (name, expected_text) in [
6027                ("players", "- Mark McGwire\n- Sammy Sosa\n- Ken Griffey"),
6028                (
6029                    "clubs",
6030                    "american:\n  - Boston Red Sox\n  - Detroit Tigers\n  - New York Yankees\nnational:\n  - New York Mets\n  - Chicago Cubs\n  - Atlanta Braves",
6031                ),
6032                (
6033                    "stats_seq",
6034                    "-\n  name: Mark McGwire\n  hr: 65\n  avg: 0.278\n-\n  name: Sammy Sosa\n  hr: 63\n  avg: 0.288",
6035                ),
6036                (
6037                    "map_of_maps",
6038                    "Mark McGwire: { hr: 65, avg: 0.278 }\nSammy Sosa: { hr: 63, avg: 0.288 }",
6039                ),
6040                (
6041                    "two_docs",
6042                    "---\n- Mark McGwire\n- Sammy Sosa\n- Ken Griffey\n---\n- Chicago Cubs\n- St Louis Cardinals",
6043                ),
6044                (
6045                    "play_feed",
6046                    "---\ntime: 20:03:20\nplayer: Sammy Sosa\naction: strike (miss)\n...\n---\ntime: 20:03:47\nplayer: Sammy Sosa\naction: grand slam\n...",
6047                ),
6048            ] {
6049                let template = module.getattr(name).unwrap();
6050                let template = extract_template(py, &template, "yaml_t/yaml_t_str").unwrap();
6051                let rendered = render_document(py, &parse_template(&template).unwrap()).unwrap();
6052                assert_eq!(rendered.text, expected_text, "{name}");
6053                let documents = parse_rendered_yaml(&rendered.text).unwrap();
6054                match name {
6055                    "players" => assert_eq!(yaml_sequence_len(&documents[0]), Some(3)),
6056                    "clubs" => assert_eq!(yaml_mapping(&documents[0]).expect("clubs").len(), 2),
6057                    "stats_seq" => assert_eq!(yaml_sequence_len(&documents[0]), Some(2)),
6058                    "map_of_maps" => {
6059                        assert_eq!(yaml_mapping(&documents[0]).expect("map_of_maps").len(), 2)
6060                    }
6061                    "two_docs" => assert_eq!(documents.len(), 2),
6062                    "play_feed" => assert_eq!(documents.len(), 2),
6063                    _ => unreachable!(),
6064                }
6065            }
6066        });
6067    }
6068
6069    #[test]
6070    fn test_parse_rendered_yaml_surfaces_parse_failures() {
6071        let err =
6072            parse_rendered_yaml("value: *missing\n").expect_err("expected YAML parse failure");
6073        assert_eq!(err.kind, ErrorKind::Parse);
6074        assert!(err.message.contains("Rendered YAML could not be reparsed"));
6075    }
6076}