Skip to main content

asciidoc_parser/blocks/
compound_delimited.rs

1use std::slice::Iter;
2
3use crate::{
4    HasSpan, Parser, Span,
5    attributes::Attrlist,
6    blocks::{
7        Block, ContentModel, IsBlock, caption::assign_block_caption, metadata::BlockMetadata,
8        parse_utils::parse_blocks_until,
9    },
10    internal::debug::DebugSliceReference,
11    span::MatchedItem,
12    strings::CowStr,
13    warnings::{MatchAndWarnings, Warning, WarningType},
14};
15
16/// A delimited block that can contain other blocks.
17///
18/// The following delimiters are recognized as compound delimited blocks:
19///
20/// | Delimiter | Content type |
21/// |-----------|--------------|
22/// | `====`    | Example      |
23/// | `--`      | Open         |
24/// | `****`    | Sidebar      |
25///
26/// The quote delimiter (`____`) is also recognized by `is_valid_delimiter` (so
27/// paragraph and list parsing stop at it), but a `____` block is parsed as a
28/// [`QuoteBlock`](crate::blocks::QuoteBlock) rather than a
29/// `CompoundDelimitedBlock`.
30#[derive(Clone, Eq, PartialEq)]
31pub struct CompoundDelimitedBlock<'src> {
32    blocks: Vec<Block<'src>>,
33    context: CowStr<'src>,
34    source: Span<'src>,
35    title_source: Option<Span<'src>>,
36    title: Option<String>,
37    caption: Option<String>,
38    number: Option<usize>,
39    anchor: Option<Span<'src>>,
40    anchor_reftext: Option<Span<'src>>,
41    attrlist: Option<Attrlist<'src>>,
42}
43
44impl<'src> CompoundDelimitedBlock<'src> {
45    pub(crate) fn is_valid_delimiter(line: &Span<'src>) -> bool {
46        let data = line.data();
47
48        if data == "--" {
49            return true;
50        }
51
52        // TO DO (https://github.com/asciidoc-rs/asciidoc-parser/issues/145):
53        // Seek spec clarity: Do the characters after the fourth char
54        // have to match the first four?
55
56        if data.len() >= 4 {
57            if data.starts_with("====") {
58                data.split_at(4).1.chars().all(|c| c == '=')
59            } else if data.starts_with("****") {
60                data.split_at(4).1.chars().all(|c| c == '*')
61            } else if data.starts_with("____") {
62                data.split_at(4).1.chars().all(|c| c == '_')
63            } else {
64                false
65            }
66        } else {
67            false
68        }
69    }
70
71    /// Consume this block and return its nested blocks.
72    pub(crate) fn into_nested_blocks(self) -> Vec<Block<'src>> {
73        self.blocks
74    }
75
76    pub(crate) fn parse(
77        metadata: &BlockMetadata<'src>,
78        parser: &mut Parser,
79    ) -> Option<MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>>> {
80        let delimiter = metadata.block_start.take_normalized_line();
81        let maybe_delimiter_text = delimiter.item.data();
82
83        // TO DO (https://github.com/asciidoc-rs/asciidoc-parser/issues/146):
84        // Seek spec clarity on whether three hyphens can be used to
85        // delimit an open block. Assuming yes for now.
86        let context = match maybe_delimiter_text
87            .split_at_checked(maybe_delimiter_text.len().min(4))?
88            .0
89        {
90            "====" => "example",
91            "--" => "open",
92            "****" => "sidebar",
93            // Quote-delimited blocks (`____`) are handled by `QuoteBlock`, which
94            // intercepts them before this parser is reached. `is_valid_delimiter`
95            // still recognizes them so that paragraph and list parsing stop at a
96            // `____` line.
97            _ => return None,
98        };
99
100        if !Self::is_valid_delimiter(&delimiter.item) {
101            return None;
102        }
103
104        let mut next = delimiter.after;
105        let (closing_delimiter, after) = loop {
106            if next.is_empty() {
107                break (next, next);
108            }
109
110            let line = next.take_normalized_line();
111            if line.item.data() == delimiter.item.data() {
112                break (line.item, line.after);
113            }
114            next = line.after;
115        };
116
117        let inside_delimiters = delimiter.after.trim_remainder(closing_delimiter);
118
119        let maw_blocks = parse_blocks_until(inside_delimiters, |_| false, parser);
120
121        let blocks = maw_blocks.item;
122        let source = metadata
123            .source
124            .trim_remainder(closing_delimiter.discard_all());
125
126        // The caption (and its number) are assigned here, after the nested
127        // blocks have been parsed, so that a nested captioned block is numbered
128        // before its container (matching Asciidoctor's parse order). Among the
129        // compound contexts only `example` is captionable.
130        let caption = assign_block_caption(
131            parser,
132            context,
133            metadata.attrlist.as_ref(),
134            metadata.title.is_some(),
135        );
136        let number = caption.as_ref().and_then(|c| c.number);
137        let caption = caption.map(|c| c.prefix);
138
139        Some(MatchAndWarnings {
140            item: Some(MatchedItem {
141                item: Self {
142                    blocks: blocks.item,
143                    context: context.into(),
144                    source: source.trim_trailing_whitespace(),
145                    title_source: metadata.title_source,
146                    title: metadata.title.clone(),
147                    caption,
148                    number,
149                    anchor: metadata.anchor,
150                    anchor_reftext: metadata.anchor_reftext,
151                    attrlist: metadata.attrlist.clone(),
152                },
153                after,
154            }),
155            warnings: if closing_delimiter.is_empty() {
156                let mut warnings = maw_blocks.warnings;
157                warnings.insert(
158                    0,
159                    Warning {
160                        source: delimiter.item,
161                        warning: WarningType::UnterminatedDelimitedBlock,
162                    },
163                );
164                warnings
165            } else {
166                maw_blocks.warnings
167            },
168        })
169    }
170}
171
172impl<'src> IsBlock<'src> for CompoundDelimitedBlock<'src> {
173    fn content_model(&self) -> ContentModel {
174        ContentModel::Compound
175    }
176
177    fn raw_context(&self) -> CowStr<'src> {
178        self.context.clone()
179    }
180
181    fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
182        self.blocks.iter()
183    }
184
185    fn nested_blocks_mut(&mut self) -> &mut [Block<'src>] {
186        &mut self.blocks
187    }
188
189    fn title_source(&'src self) -> Option<Span<'src>> {
190        self.title_source
191    }
192
193    fn title(&self) -> Option<&str> {
194        self.title.as_deref()
195    }
196
197    fn caption(&self) -> Option<&str> {
198        self.caption.as_deref()
199    }
200
201    fn number(&self) -> Option<usize> {
202        self.number
203    }
204
205    fn anchor(&'src self) -> Option<Span<'src>> {
206        self.anchor
207    }
208
209    fn anchor_reftext(&'src self) -> Option<Span<'src>> {
210        self.anchor_reftext
211    }
212
213    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
214        self.attrlist.as_ref()
215    }
216}
217
218impl<'src> HasSpan<'src> for CompoundDelimitedBlock<'src> {
219    fn span(&self) -> Span<'src> {
220        self.source
221    }
222}
223
224impl std::fmt::Debug for CompoundDelimitedBlock<'_> {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        f.debug_struct("CompoundDelimitedBlock")
227            .field("blocks", &DebugSliceReference(&self.blocks))
228            .field("context", &self.context)
229            .field("source", &self.source)
230            .field("title_source", &self.title_source)
231            .field("title", &self.title)
232            .field("caption", &self.caption)
233            .field("number", &self.number)
234            .field("anchor", &self.anchor)
235            .field("anchor_reftext", &self.anchor_reftext)
236            .field("attrlist", &self.attrlist)
237            .finish()
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    #![allow(clippy::unwrap_used)]
244
245    use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
246
247    mod is_valid_delimiter {
248        use crate::blocks::CompoundDelimitedBlock;
249
250        #[test]
251        fn comment() {
252            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
253                &crate::Span::new("////")
254            ));
255            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
256                &crate::Span::new("/////")
257            ));
258            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
259                &crate::Span::new("/////////")
260            ));
261
262            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
263                &crate::Span::new("///")
264            ));
265            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
266                &crate::Span::new("//-/")
267            ));
268            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
269                &crate::Span::new("////-")
270            ));
271            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
272                &crate::Span::new("//////////x")
273            ));
274            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
275                &crate::Span::new("//😀/")
276            ));
277        }
278
279        #[test]
280        fn example() {
281            assert!(CompoundDelimitedBlock::is_valid_delimiter(
282                &crate::Span::new("====")
283            ));
284            assert!(CompoundDelimitedBlock::is_valid_delimiter(
285                &crate::Span::new("=====")
286            ));
287            assert!(CompoundDelimitedBlock::is_valid_delimiter(
288                &crate::Span::new("=======")
289            ));
290
291            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
292                &crate::Span::new("===")
293            ));
294            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
295                &crate::Span::new("==-=")
296            ));
297            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
298                &crate::Span::new("====-")
299            ));
300            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
301                &crate::Span::new("==========x")
302            ));
303        }
304
305        #[test]
306        fn listing() {
307            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
308                &crate::Span::new("----")
309            ));
310            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
311                &crate::Span::new("-----")
312            ));
313            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
314                &crate::Span::new("---------")
315            ));
316        }
317
318        #[test]
319        fn literal() {
320            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
321                &crate::Span::new("....")
322            ));
323            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
324                &crate::Span::new(".....")
325            ));
326            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
327                &crate::Span::new(".........")
328            ));
329        }
330
331        #[test]
332        fn sidebar() {
333            assert!(CompoundDelimitedBlock::is_valid_delimiter(
334                &crate::Span::new("****")
335            ));
336            assert!(CompoundDelimitedBlock::is_valid_delimiter(
337                &crate::Span::new("*****")
338            ));
339            assert!(CompoundDelimitedBlock::is_valid_delimiter(
340                &crate::Span::new("*********")
341            ));
342
343            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
344                &crate::Span::new("***")
345            ));
346            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
347                &crate::Span::new("**-*")
348            ));
349            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
350                &crate::Span::new("****-")
351            ));
352            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
353                &crate::Span::new("**********x")
354            ));
355        }
356
357        #[test]
358        fn table() {
359            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
360                &crate::Span::new("|===")
361            ));
362            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
363                &crate::Span::new(",===")
364            ));
365            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
366                &crate::Span::new(":===")
367            ));
368            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
369                &crate::Span::new("!===")
370            ));
371        }
372
373        #[test]
374        fn pass() {
375            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
376                &crate::Span::new("++++")
377            ));
378            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
379                &crate::Span::new("+++++")
380            ));
381            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
382                &crate::Span::new("+++++++++")
383            ));
384        }
385
386        #[test]
387        fn quote() {
388            assert!(CompoundDelimitedBlock::is_valid_delimiter(
389                &crate::Span::new("____")
390            ));
391            assert!(CompoundDelimitedBlock::is_valid_delimiter(
392                &crate::Span::new("_____")
393            ));
394            assert!(CompoundDelimitedBlock::is_valid_delimiter(
395                &crate::Span::new("_________")
396            ));
397
398            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
399                &crate::Span::new("___")
400            ));
401            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
402                &crate::Span::new("__-_")
403            ));
404            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
405                &crate::Span::new("____-")
406            ));
407            assert!(!CompoundDelimitedBlock::is_valid_delimiter(
408                &crate::Span::new("_________x")
409            ));
410        }
411    }
412
413    mod parse {
414        use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
415
416        #[test]
417        fn err_invalid_delimiter() {
418            let mut parser = Parser::default();
419            assert!(
420                crate::blocks::CompoundDelimitedBlock::parse(&BlockMetadata::new(""), &mut parser)
421                    .is_none()
422            );
423
424            let mut parser = Parser::default();
425            assert!(
426                crate::blocks::CompoundDelimitedBlock::parse(
427                    &BlockMetadata::new("///"),
428                    &mut parser
429                )
430                .is_none()
431            );
432
433            let mut parser = Parser::default();
434            assert!(
435                crate::blocks::CompoundDelimitedBlock::parse(
436                    &BlockMetadata::new("////x"),
437                    &mut parser
438                )
439                .is_none()
440            );
441
442            let mut parser = Parser::default();
443            assert!(
444                crate::blocks::CompoundDelimitedBlock::parse(
445                    &BlockMetadata::new("--x"),
446                    &mut parser
447                )
448                .is_none()
449            );
450
451            let mut parser = Parser::default();
452            assert!(
453                crate::blocks::CompoundDelimitedBlock::parse(
454                    &BlockMetadata::new("****x"),
455                    &mut parser
456                )
457                .is_none()
458            );
459
460            let mut parser = Parser::default();
461            assert!(
462                crate::blocks::CompoundDelimitedBlock::parse(
463                    &BlockMetadata::new("__\n__"),
464                    &mut parser
465                )
466                .is_none()
467            );
468        }
469
470        #[test]
471        fn err_unterminated() {
472            let mut parser = Parser::default();
473
474            let maw = crate::blocks::CompoundDelimitedBlock::parse(
475                &BlockMetadata::new("====\nblah blah blah"),
476                &mut parser,
477            )
478            .unwrap();
479
480            assert_eq!(
481                maw.item.unwrap().item,
482                CompoundDelimitedBlock {
483                    blocks: &[Block::Simple(SimpleBlock {
484                        content: Content {
485                            original: Span {
486                                data: "blah blah blah",
487                                line: 2,
488                                col: 1,
489                                offset: 5,
490                            },
491                            rendered: "blah blah blah",
492                        },
493                        source: Span {
494                            data: "blah blah blah",
495                            line: 2,
496                            col: 1,
497                            offset: 5,
498                        },
499                        style: SimpleBlockStyle::Paragraph,
500                        title_source: None,
501                        title: None,
502                        caption: None,
503                        number: None,
504                        anchor: None,
505                        anchor_reftext: None,
506                        attrlist: None,
507                    },),],
508                    context: "example",
509                    source: Span {
510                        data: "====\nblah blah blah",
511                        line: 1,
512                        col: 1,
513                        offset: 0,
514                    },
515                    title_source: None,
516                    title: None,
517                    caption: None,
518                    number: None,
519                    anchor: None,
520                    anchor_reftext: None,
521                    attrlist: None,
522                },
523            );
524
525            assert_eq!(
526                maw.warnings,
527                vec![Warning {
528                    source: Span {
529                        data: "====",
530                        line: 1,
531                        col: 1,
532                        offset: 0,
533                    },
534                    warning: WarningType::UnterminatedDelimitedBlock,
535                }]
536            );
537        }
538    }
539
540    mod comment {
541        use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
542
543        #[test]
544        fn empty() {
545            let mut parser = Parser::default();
546            assert!(
547                crate::blocks::CompoundDelimitedBlock::parse(
548                    &BlockMetadata::new("////\n////"),
549                    &mut parser
550                )
551                .is_none()
552            );
553        }
554
555        #[test]
556        fn multiple_lines() {
557            let mut parser = Parser::default();
558            assert!(
559                crate::blocks::CompoundDelimitedBlock::parse(
560                    &BlockMetadata::new("////\nline1  \nline2\n////"),
561                    &mut parser
562                )
563                .is_none()
564            );
565        }
566    }
567
568    mod example {
569        use crate::{
570            blocks::{ContentModel, metadata::BlockMetadata},
571            tests::prelude::*,
572        };
573
574        #[test]
575        fn empty() {
576            let mut parser = Parser::default();
577
578            let maw = crate::blocks::CompoundDelimitedBlock::parse(
579                &BlockMetadata::new("====\n===="),
580                &mut parser,
581            )
582            .unwrap();
583
584            let mi = maw.item.unwrap().clone();
585
586            assert_eq!(
587                mi.item,
588                CompoundDelimitedBlock {
589                    blocks: &[],
590                    context: "example",
591                    source: Span {
592                        data: "====\n====",
593                        line: 1,
594                        col: 1,
595                        offset: 0,
596                    },
597                    title_source: None,
598                    title: None,
599                    caption: None,
600                    number: None,
601                    anchor: None,
602                    anchor_reftext: None,
603                    attrlist: None,
604                }
605            );
606
607            assert_eq!(mi.item.content_model(), ContentModel::Compound);
608            assert!(mi.item.rendered_content().is_none());
609            assert_eq!(mi.item.raw_context().as_ref(), "example");
610            assert_eq!(mi.item.resolved_context().as_ref(), "example");
611            assert!(mi.item.declared_style().is_none());
612            assert!(mi.item.nested_blocks().next().is_none());
613            assert!(mi.item.id().is_none());
614            assert!(mi.item.roles().is_empty());
615            assert!(mi.item.options().is_empty());
616            assert!(mi.item.title_source().is_none());
617            assert!(mi.item.title().is_none());
618            assert!(mi.item.anchor().is_none());
619            assert!(mi.item.anchor_reftext().is_none());
620            assert!(mi.item.attrlist().is_none());
621            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
622        }
623
624        #[test]
625        fn multiple_blocks() {
626            let mut parser = Parser::default();
627
628            let maw = crate::blocks::CompoundDelimitedBlock::parse(
629                &BlockMetadata::new("====\nblock1\n\nblock2\n===="),
630                &mut parser,
631            )
632            .unwrap();
633
634            let mi = maw.item.unwrap().clone();
635
636            assert_eq!(
637                mi.item,
638                CompoundDelimitedBlock {
639                    blocks: &[
640                        Block::Simple(SimpleBlock {
641                            content: Content {
642                                original: Span {
643                                    data: "block1",
644                                    line: 2,
645                                    col: 1,
646                                    offset: 5,
647                                },
648                                rendered: "block1",
649                            },
650                            source: Span {
651                                data: "block1",
652                                line: 2,
653                                col: 1,
654                                offset: 5,
655                            },
656                            style: SimpleBlockStyle::Paragraph,
657                            title_source: None,
658                            title: None,
659                            caption: None,
660                            number: None,
661                            anchor: None,
662                            anchor_reftext: None,
663                            attrlist: None,
664                        },),
665                        Block::Simple(SimpleBlock {
666                            content: Content {
667                                original: Span {
668                                    data: "block2",
669                                    line: 4,
670                                    col: 1,
671                                    offset: 13,
672                                },
673                                rendered: "block2",
674                            },
675                            source: Span {
676                                data: "block2",
677                                line: 4,
678                                col: 1,
679                                offset: 13,
680                            },
681                            style: SimpleBlockStyle::Paragraph,
682                            title_source: None,
683                            title: None,
684                            caption: None,
685                            number: None,
686                            anchor: None,
687                            anchor_reftext: None,
688                            attrlist: None,
689                        },),
690                    ],
691                    context: "example",
692                    source: Span {
693                        data: "====\nblock1\n\nblock2\n====",
694                        line: 1,
695                        col: 1,
696                        offset: 0,
697                    },
698                    title_source: None,
699                    title: None,
700                    caption: None,
701                    number: None,
702                    anchor: None,
703                    anchor_reftext: None,
704                    attrlist: None,
705                }
706            );
707
708            assert_eq!(mi.item.content_model(), ContentModel::Compound);
709            assert_eq!(mi.item.raw_context().as_ref(), "example");
710            assert_eq!(mi.item.resolved_context().as_ref(), "example");
711            assert!(mi.item.declared_style().is_none());
712            assert!(mi.item.id().is_none());
713            assert!(mi.item.roles().is_empty());
714            assert!(mi.item.options().is_empty());
715            assert!(mi.item.title_source().is_none());
716            assert!(mi.item.title().is_none());
717            assert!(mi.item.anchor().is_none());
718            assert!(mi.item.anchor_reftext().is_none());
719            assert!(mi.item.attrlist().is_none());
720            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
721
722            let mut blocks = mi.item.nested_blocks();
723            assert_eq!(
724                blocks.next().unwrap(),
725                &Block::Simple(SimpleBlock {
726                    content: Content {
727                        original: Span {
728                            data: "block1",
729                            line: 2,
730                            col: 1,
731                            offset: 5,
732                        },
733                        rendered: "block1",
734                    },
735                    source: Span {
736                        data: "block1",
737                        line: 2,
738                        col: 1,
739                        offset: 5,
740                    },
741                    style: SimpleBlockStyle::Paragraph,
742                    title_source: None,
743                    title: None,
744                    caption: None,
745                    number: None,
746                    anchor: None,
747                    anchor_reftext: None,
748                    attrlist: None,
749                },)
750            );
751
752            assert_eq!(
753                blocks.next().unwrap(),
754                &Block::Simple(SimpleBlock {
755                    content: Content {
756                        original: Span {
757                            data: "block2",
758                            line: 4,
759                            col: 1,
760                            offset: 13,
761                        },
762                        rendered: "block2",
763                    },
764                    source: Span {
765                        data: "block2",
766                        line: 4,
767                        col: 1,
768                        offset: 13,
769                    },
770                    style: SimpleBlockStyle::Paragraph,
771                    title_source: None,
772                    title: None,
773                    caption: None,
774                    number: None,
775                    anchor: None,
776                    anchor_reftext: None,
777                    attrlist: None,
778                },)
779            );
780
781            assert!(blocks.next().is_none());
782        }
783
784        #[test]
785        fn nested_blocks() {
786            let mut parser = Parser::default();
787
788            let maw = crate::blocks::CompoundDelimitedBlock::parse(
789                &BlockMetadata::new("====\nblock1\n\n=====\nblock2\n=====\n===="),
790                &mut parser,
791            )
792            .unwrap();
793
794            let mi = maw.item.unwrap().clone();
795
796            assert_eq!(
797                mi.item,
798                CompoundDelimitedBlock {
799                    blocks: &[
800                        Block::Simple(SimpleBlock {
801                            content: Content {
802                                original: Span {
803                                    data: "block1",
804                                    line: 2,
805                                    col: 1,
806                                    offset: 5,
807                                },
808                                rendered: "block1",
809                            },
810                            source: Span {
811                                data: "block1",
812                                line: 2,
813                                col: 1,
814                                offset: 5,
815                            },
816                            style: SimpleBlockStyle::Paragraph,
817                            title_source: None,
818                            title: None,
819                            caption: None,
820                            number: None,
821                            anchor: None,
822                            anchor_reftext: None,
823                            attrlist: None,
824                        },),
825                        Block::CompoundDelimited(CompoundDelimitedBlock {
826                            blocks: &[Block::Simple(SimpleBlock {
827                                content: Content {
828                                    original: Span {
829                                        data: "block2",
830                                        line: 5,
831                                        col: 1,
832                                        offset: 19,
833                                    },
834                                    rendered: "block2",
835                                },
836                                source: Span {
837                                    data: "block2",
838                                    line: 5,
839                                    col: 1,
840                                    offset: 19,
841                                },
842                                style: SimpleBlockStyle::Paragraph,
843                                title_source: None,
844                                title: None,
845                                caption: None,
846                                number: None,
847                                anchor: None,
848                                anchor_reftext: None,
849                                attrlist: None,
850                            },),],
851                            context: "example",
852                            source: Span {
853                                data: "=====\nblock2\n=====",
854                                line: 4,
855                                col: 1,
856                                offset: 13,
857                            },
858                            title_source: None,
859                            title: None,
860                            caption: None,
861                            number: None,
862                            anchor: None,
863                            anchor_reftext: None,
864                            attrlist: None,
865                        })
866                    ],
867                    context: "example",
868                    source: Span {
869                        data: "====\nblock1\n\n=====\nblock2\n=====\n====",
870                        line: 1,
871                        col: 1,
872                        offset: 0,
873                    },
874                    title_source: None,
875                    title: None,
876                    caption: None,
877                    number: None,
878                    anchor: None,
879                    anchor_reftext: None,
880                    attrlist: None,
881                }
882            );
883
884            assert_eq!(mi.item.content_model(), ContentModel::Compound);
885            assert_eq!(mi.item.raw_context().as_ref(), "example");
886            assert_eq!(mi.item.resolved_context().as_ref(), "example");
887            assert!(mi.item.declared_style().is_none());
888            assert!(mi.item.id().is_none());
889            assert!(mi.item.roles().is_empty());
890            assert!(mi.item.options().is_empty());
891            assert!(mi.item.title_source().is_none());
892            assert!(mi.item.title().is_none());
893            assert!(mi.item.anchor().is_none());
894            assert!(mi.item.anchor_reftext().is_none());
895            assert!(mi.item.attrlist().is_none());
896            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
897
898            let mut blocks = mi.item.nested_blocks();
899            assert_eq!(
900                blocks.next().unwrap(),
901                &Block::Simple(SimpleBlock {
902                    content: Content {
903                        original: Span {
904                            data: "block1",
905                            line: 2,
906                            col: 1,
907                            offset: 5,
908                        },
909                        rendered: "block1",
910                    },
911                    source: Span {
912                        data: "block1",
913                        line: 2,
914                        col: 1,
915                        offset: 5,
916                    },
917                    style: SimpleBlockStyle::Paragraph,
918                    title_source: None,
919                    title: None,
920                    caption: None,
921                    number: None,
922                    anchor: None,
923                    anchor_reftext: None,
924                    attrlist: None,
925                },)
926            );
927
928            assert_eq!(
929                blocks.next().unwrap(),
930                &Block::CompoundDelimited(CompoundDelimitedBlock {
931                    blocks: &[Block::Simple(SimpleBlock {
932                        content: Content {
933                            original: Span {
934                                data: "block2",
935                                line: 5,
936                                col: 1,
937                                offset: 19,
938                            },
939                            rendered: "block2",
940                        },
941                        source: Span {
942                            data: "block2",
943                            line: 5,
944                            col: 1,
945                            offset: 19,
946                        },
947                        style: SimpleBlockStyle::Paragraph,
948                        title_source: None,
949                        title: None,
950                        caption: None,
951                        number: None,
952                        anchor: None,
953                        anchor_reftext: None,
954                        attrlist: None,
955                    },),],
956                    context: "example",
957                    source: Span {
958                        data: "=====\nblock2\n=====",
959                        line: 4,
960                        col: 1,
961                        offset: 13,
962                    },
963                    title_source: None,
964                    title: None,
965                    caption: None,
966                    number: None,
967                    anchor: None,
968                    anchor_reftext: None,
969                    attrlist: None,
970                })
971            );
972
973            assert!(blocks.next().is_none());
974        }
975        #[test]
976        fn no_panic_for_utf8_code_point_using_more_than_one_byte() {
977            let mut parser = Parser::default();
978            assert!(
979                crate::blocks::CompoundDelimitedBlock::parse(
980                    &BlockMetadata::new("===😀"),
981                    &mut parser
982                )
983                .is_none()
984            );
985        }
986    }
987
988    mod listing {
989        use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
990
991        #[test]
992        fn empty() {
993            let mut parser = Parser::default();
994            assert!(
995                crate::blocks::CompoundDelimitedBlock::parse(
996                    &BlockMetadata::new("----\n----"),
997                    &mut parser
998                )
999                .is_none()
1000            );
1001        }
1002
1003        #[test]
1004        fn multiple_lines() {
1005            let mut parser = Parser::default();
1006            assert!(
1007                crate::blocks::CompoundDelimitedBlock::parse(
1008                    &BlockMetadata::new("----\nline1  \nline2\n----"),
1009                    &mut parser
1010                )
1011                .is_none()
1012            );
1013        }
1014    }
1015
1016    mod literal {
1017        use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
1018
1019        #[test]
1020        fn empty() {
1021            let mut parser = Parser::default();
1022            assert!(
1023                crate::blocks::CompoundDelimitedBlock::parse(
1024                    &BlockMetadata::new("....\n...."),
1025                    &mut parser
1026                )
1027                .is_none()
1028            );
1029        }
1030
1031        #[test]
1032        fn multiple_lines() {
1033            let mut parser = Parser::default();
1034            assert!(
1035                crate::blocks::CompoundDelimitedBlock::parse(
1036                    &BlockMetadata::new("....\nline1  \nline2\n...."),
1037                    &mut parser
1038                )
1039                .is_none()
1040            );
1041        }
1042    }
1043
1044    mod open {
1045        use crate::{
1046            blocks::{BreakType, ContentModel, metadata::BlockMetadata},
1047            tests::prelude::*,
1048        };
1049
1050        #[test]
1051        fn empty() {
1052            let mut parser = Parser::default();
1053
1054            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1055                &BlockMetadata::new("--\n--"),
1056                &mut parser,
1057            )
1058            .unwrap();
1059
1060            let mi = maw.item.unwrap().clone();
1061
1062            assert_eq!(
1063                mi.item,
1064                CompoundDelimitedBlock {
1065                    blocks: &[],
1066                    context: "open",
1067                    source: Span {
1068                        data: "--\n--",
1069                        line: 1,
1070                        col: 1,
1071                        offset: 0,
1072                    },
1073                    title_source: None,
1074                    title: None,
1075                    caption: None,
1076                    number: None,
1077                    anchor: None,
1078                    anchor_reftext: None,
1079                    attrlist: None,
1080                }
1081            );
1082
1083            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1084            assert_eq!(mi.item.raw_context().as_ref(), "open");
1085            assert_eq!(mi.item.resolved_context().as_ref(), "open");
1086            assert!(mi.item.declared_style().is_none());
1087            assert!(mi.item.nested_blocks().next().is_none());
1088            assert!(mi.item.id().is_none());
1089            assert!(mi.item.roles().is_empty());
1090            assert!(mi.item.options().is_empty());
1091            assert!(mi.item.title_source().is_none());
1092            assert!(mi.item.title().is_none());
1093            assert!(mi.item.anchor().is_none());
1094            assert!(mi.item.anchor_reftext().is_none());
1095            assert!(mi.item.attrlist().is_none());
1096            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1097        }
1098
1099        #[test]
1100        fn multiple_blocks() {
1101            let mut parser = Parser::default();
1102
1103            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1104                &BlockMetadata::new("--\nblock1\n\nblock2\n--"),
1105                &mut parser,
1106            )
1107            .unwrap();
1108
1109            let mi = maw.item.unwrap().clone();
1110
1111            assert_eq!(
1112                mi.item,
1113                CompoundDelimitedBlock {
1114                    blocks: &[
1115                        Block::Simple(SimpleBlock {
1116                            content: Content {
1117                                original: Span {
1118                                    data: "block1",
1119                                    line: 2,
1120                                    col: 1,
1121                                    offset: 3,
1122                                },
1123                                rendered: "block1",
1124                            },
1125                            source: Span {
1126                                data: "block1",
1127                                line: 2,
1128                                col: 1,
1129                                offset: 3,
1130                            },
1131                            style: SimpleBlockStyle::Paragraph,
1132                            title_source: None,
1133                            title: None,
1134                            caption: None,
1135                            number: None,
1136                            anchor: None,
1137                            anchor_reftext: None,
1138                            attrlist: None,
1139                        },),
1140                        Block::Simple(SimpleBlock {
1141                            content: Content {
1142                                original: Span {
1143                                    data: "block2",
1144                                    line: 4,
1145                                    col: 1,
1146                                    offset: 11,
1147                                },
1148                                rendered: "block2",
1149                            },
1150                            source: Span {
1151                                data: "block2",
1152                                line: 4,
1153                                col: 1,
1154                                offset: 11,
1155                            },
1156                            style: SimpleBlockStyle::Paragraph,
1157                            title_source: None,
1158                            title: None,
1159                            caption: None,
1160                            number: None,
1161                            anchor: None,
1162                            anchor_reftext: None,
1163                            attrlist: None,
1164                        },),
1165                    ],
1166                    context: "open",
1167                    source: Span {
1168                        data: "--\nblock1\n\nblock2\n--",
1169                        line: 1,
1170                        col: 1,
1171                        offset: 0,
1172                    },
1173                    title_source: None,
1174                    title: None,
1175                    caption: None,
1176                    number: None,
1177                    anchor: None,
1178                    anchor_reftext: None,
1179                    attrlist: None,
1180                }
1181            );
1182
1183            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1184            assert_eq!(mi.item.raw_context().as_ref(), "open");
1185            assert_eq!(mi.item.resolved_context().as_ref(), "open");
1186            assert!(mi.item.declared_style().is_none());
1187            assert!(mi.item.id().is_none());
1188            assert!(mi.item.roles().is_empty());
1189            assert!(mi.item.options().is_empty());
1190            assert!(mi.item.title_source().is_none());
1191            assert!(mi.item.title().is_none());
1192            assert!(mi.item.anchor().is_none());
1193            assert!(mi.item.anchor_reftext().is_none());
1194            assert!(mi.item.attrlist().is_none());
1195            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1196
1197            let mut blocks = mi.item.nested_blocks();
1198            assert_eq!(
1199                blocks.next().unwrap(),
1200                &Block::Simple(SimpleBlock {
1201                    content: Content {
1202                        original: Span {
1203                            data: "block1",
1204                            line: 2,
1205                            col: 1,
1206                            offset: 3,
1207                        },
1208                        rendered: "block1",
1209                    },
1210                    source: Span {
1211                        data: "block1",
1212                        line: 2,
1213                        col: 1,
1214                        offset: 3,
1215                    },
1216                    style: SimpleBlockStyle::Paragraph,
1217                    title_source: None,
1218                    title: None,
1219                    caption: None,
1220                    number: None,
1221                    anchor: None,
1222                    anchor_reftext: None,
1223                    attrlist: None,
1224                },)
1225            );
1226
1227            assert_eq!(
1228                blocks.next().unwrap(),
1229                &Block::Simple(SimpleBlock {
1230                    content: Content {
1231                        original: Span {
1232                            data: "block2",
1233                            line: 4,
1234                            col: 1,
1235                            offset: 11,
1236                        },
1237                        rendered: "block2",
1238                    },
1239                    source: Span {
1240                        data: "block2",
1241                        line: 4,
1242                        col: 1,
1243                        offset: 11,
1244                    },
1245                    style: SimpleBlockStyle::Paragraph,
1246                    title_source: None,
1247                    title: None,
1248                    caption: None,
1249                    number: None,
1250                    anchor: None,
1251                    anchor_reftext: None,
1252                    attrlist: None,
1253                },)
1254            );
1255
1256            assert!(blocks.next().is_none());
1257        }
1258
1259        #[test]
1260        fn nested_blocks() {
1261            // Spec says three hyphens does NOT mark an open block.
1262            let mut parser = Parser::default();
1263
1264            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1265                &BlockMetadata::new("--\nblock1\n\n---\nblock2\n---\n--"),
1266                &mut parser,
1267            )
1268            .unwrap();
1269
1270            let mi = maw.item.unwrap().clone();
1271
1272            assert_eq!(
1273                mi.item,
1274                CompoundDelimitedBlock {
1275                    blocks: &[
1276                        Block::Simple(SimpleBlock {
1277                            content: Content {
1278                                original: Span {
1279                                    data: "block1",
1280                                    line: 2,
1281                                    col: 1,
1282                                    offset: 3,
1283                                },
1284                                rendered: "block1",
1285                            },
1286                            source: Span {
1287                                data: "block1",
1288                                line: 2,
1289                                col: 1,
1290                                offset: 3,
1291                            },
1292                            style: SimpleBlockStyle::Paragraph,
1293                            title_source: None,
1294                            title: None,
1295                            caption: None,
1296                            number: None,
1297                            anchor: None,
1298                            anchor_reftext: None,
1299                            attrlist: None,
1300                        },),
1301                        Block::Break(Break {
1302                            type_: BreakType::Thematic,
1303                            source: Span {
1304                                data: "---",
1305                                line: 4,
1306                                col: 1,
1307                                offset: 11,
1308                            },
1309                            title_source: None,
1310                            title: None,
1311                            anchor: None,
1312                            attrlist: None,
1313                        },),
1314                        Block::Simple(SimpleBlock {
1315                            content: Content {
1316                                original: Span {
1317                                    data: "block2\n---",
1318                                    line: 5,
1319                                    col: 1,
1320                                    offset: 15,
1321                                },
1322                                rendered: "block2\n---",
1323                            },
1324                            source: Span {
1325                                data: "block2\n---",
1326                                line: 5,
1327                                col: 1,
1328                                offset: 15,
1329                            },
1330                            style: SimpleBlockStyle::Paragraph,
1331                            title_source: None,
1332                            title: None,
1333                            caption: None,
1334                            number: None,
1335                            anchor: None,
1336                            anchor_reftext: None,
1337                            attrlist: None,
1338                        },),
1339                    ],
1340                    context: "open",
1341                    source: Span {
1342                        data: "--\nblock1\n\n---\nblock2\n---\n--",
1343                        line: 1,
1344                        col: 1,
1345                        offset: 0,
1346                    },
1347                    title_source: None,
1348                    title: None,
1349                    caption: None,
1350                    number: None,
1351                    anchor: None,
1352                    anchor_reftext: None,
1353                    attrlist: None,
1354                }
1355            );
1356
1357            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1358            assert_eq!(mi.item.raw_context().as_ref(), "open");
1359            assert_eq!(mi.item.resolved_context().as_ref(), "open");
1360            assert!(mi.item.declared_style().is_none());
1361            assert!(mi.item.id().is_none());
1362            assert!(mi.item.roles().is_empty());
1363            assert!(mi.item.options().is_empty());
1364            assert!(mi.item.title_source().is_none());
1365            assert!(mi.item.title().is_none());
1366            assert!(mi.item.anchor().is_none());
1367            assert!(mi.item.anchor_reftext().is_none());
1368            assert!(mi.item.attrlist().is_none());
1369            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1370
1371            let mut blocks = mi.item.nested_blocks();
1372            assert_eq!(
1373                blocks.next().unwrap(),
1374                &Block::Simple(SimpleBlock {
1375                    content: Content {
1376                        original: Span {
1377                            data: "block1",
1378                            line: 2,
1379                            col: 1,
1380                            offset: 3,
1381                        },
1382                        rendered: "block1",
1383                    },
1384                    source: Span {
1385                        data: "block1",
1386                        line: 2,
1387                        col: 1,
1388                        offset: 3,
1389                    },
1390                    style: SimpleBlockStyle::Paragraph,
1391                    title_source: None,
1392                    title: None,
1393                    caption: None,
1394                    number: None,
1395                    anchor: None,
1396                    anchor_reftext: None,
1397                    attrlist: None,
1398                },)
1399            );
1400
1401            assert_eq!(
1402                blocks.next().unwrap(),
1403                &Block::Break(Break {
1404                    type_: BreakType::Thematic,
1405                    source: Span {
1406                        data: "---",
1407                        line: 4,
1408                        col: 1,
1409                        offset: 11,
1410                    },
1411                    title_source: None,
1412                    title: None,
1413                    anchor: None,
1414                    attrlist: None,
1415                },)
1416            );
1417
1418            assert_eq!(
1419                blocks.next().unwrap(),
1420                &Block::Simple(SimpleBlock {
1421                    content: Content {
1422                        original: Span {
1423                            data: "block2\n---",
1424                            line: 5,
1425                            col: 1,
1426                            offset: 15,
1427                        },
1428                        rendered: "block2\n---",
1429                    },
1430                    source: Span {
1431                        data: "block2\n---",
1432                        line: 5,
1433                        col: 1,
1434                        offset: 15,
1435                    },
1436                    style: SimpleBlockStyle::Paragraph,
1437                    title_source: None,
1438                    title: None,
1439                    caption: None,
1440                    number: None,
1441                    anchor: None,
1442                    anchor_reftext: None,
1443                    attrlist: None,
1444                },)
1445            );
1446
1447            assert!(blocks.next().is_none());
1448        }
1449    }
1450
1451    mod sidebar {
1452        use crate::{
1453            blocks::{ContentModel, metadata::BlockMetadata},
1454            tests::prelude::*,
1455        };
1456
1457        #[test]
1458        fn empty() {
1459            let mut parser = Parser::default();
1460
1461            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1462                &BlockMetadata::new("****\n****"),
1463                &mut parser,
1464            )
1465            .unwrap();
1466
1467            let mi = maw.item.unwrap().clone();
1468
1469            assert_eq!(
1470                mi.item,
1471                CompoundDelimitedBlock {
1472                    blocks: &[],
1473                    context: "sidebar",
1474                    source: Span {
1475                        data: "****\n****",
1476                        line: 1,
1477                        col: 1,
1478                        offset: 0,
1479                    },
1480                    title_source: None,
1481                    title: None,
1482                    caption: None,
1483                    number: None,
1484                    anchor: None,
1485                    anchor_reftext: None,
1486                    attrlist: None,
1487                }
1488            );
1489
1490            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1491            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1492            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1493            assert!(mi.item.declared_style().is_none());
1494            assert!(mi.item.nested_blocks().next().is_none());
1495            assert!(mi.item.id().is_none());
1496            assert!(mi.item.roles().is_empty());
1497            assert!(mi.item.options().is_empty());
1498            assert!(mi.item.title_source().is_none());
1499            assert!(mi.item.title().is_none());
1500            assert!(mi.item.anchor().is_none());
1501            assert!(mi.item.anchor_reftext().is_none());
1502            assert!(mi.item.attrlist().is_none());
1503            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1504        }
1505
1506        #[test]
1507        fn multiple_blocks() {
1508            let mut parser = Parser::default();
1509
1510            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1511                &BlockMetadata::new("****\nblock1\n\nblock2\n****"),
1512                &mut parser,
1513            )
1514            .unwrap();
1515
1516            let mi = maw.item.unwrap().clone();
1517
1518            assert_eq!(
1519                mi.item,
1520                CompoundDelimitedBlock {
1521                    blocks: &[
1522                        Block::Simple(SimpleBlock {
1523                            content: Content {
1524                                original: Span {
1525                                    data: "block1",
1526                                    line: 2,
1527                                    col: 1,
1528                                    offset: 5,
1529                                },
1530                                rendered: "block1",
1531                            },
1532                            source: Span {
1533                                data: "block1",
1534                                line: 2,
1535                                col: 1,
1536                                offset: 5,
1537                            },
1538                            style: SimpleBlockStyle::Paragraph,
1539                            title_source: None,
1540                            title: None,
1541                            caption: None,
1542                            number: None,
1543                            anchor: None,
1544                            anchor_reftext: None,
1545                            attrlist: None,
1546                        },),
1547                        Block::Simple(SimpleBlock {
1548                            content: Content {
1549                                original: Span {
1550                                    data: "block2",
1551                                    line: 4,
1552                                    col: 1,
1553                                    offset: 13,
1554                                },
1555                                rendered: "block2",
1556                            },
1557                            source: Span {
1558                                data: "block2",
1559                                line: 4,
1560                                col: 1,
1561                                offset: 13,
1562                            },
1563                            style: SimpleBlockStyle::Paragraph,
1564                            title_source: None,
1565                            title: None,
1566                            caption: None,
1567                            number: None,
1568                            anchor: None,
1569                            anchor_reftext: None,
1570                            attrlist: None,
1571                        },),
1572                    ],
1573                    context: "sidebar",
1574                    source: Span {
1575                        data: "****\nblock1\n\nblock2\n****",
1576                        line: 1,
1577                        col: 1,
1578                        offset: 0,
1579                    },
1580                    title_source: None,
1581                    title: None,
1582                    caption: None,
1583                    number: None,
1584                    anchor: None,
1585                    anchor_reftext: None,
1586                    attrlist: None,
1587                }
1588            );
1589
1590            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1591            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1592            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1593            assert!(mi.item.declared_style().is_none());
1594            assert!(mi.item.id().is_none());
1595            assert!(mi.item.roles().is_empty());
1596            assert!(mi.item.options().is_empty());
1597            assert!(mi.item.title_source().is_none());
1598            assert!(mi.item.title().is_none());
1599            assert!(mi.item.anchor().is_none());
1600            assert!(mi.item.anchor_reftext().is_none());
1601            assert!(mi.item.attrlist().is_none());
1602            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1603
1604            let mut blocks = mi.item.nested_blocks();
1605            assert_eq!(
1606                blocks.next().unwrap(),
1607                &Block::Simple(SimpleBlock {
1608                    content: Content {
1609                        original: Span {
1610                            data: "block1",
1611                            line: 2,
1612                            col: 1,
1613                            offset: 5,
1614                        },
1615                        rendered: "block1",
1616                    },
1617                    source: Span {
1618                        data: "block1",
1619                        line: 2,
1620                        col: 1,
1621                        offset: 5,
1622                    },
1623                    style: SimpleBlockStyle::Paragraph,
1624                    title_source: None,
1625                    title: None,
1626                    caption: None,
1627                    number: None,
1628                    anchor: None,
1629                    anchor_reftext: None,
1630                    attrlist: None,
1631                },)
1632            );
1633
1634            assert_eq!(
1635                blocks.next().unwrap(),
1636                &Block::Simple(SimpleBlock {
1637                    content: Content {
1638                        original: Span {
1639                            data: "block2",
1640                            line: 4,
1641                            col: 1,
1642                            offset: 13,
1643                        },
1644                        rendered: "block2",
1645                    },
1646                    source: Span {
1647                        data: "block2",
1648                        line: 4,
1649                        col: 1,
1650                        offset: 13,
1651                    },
1652                    style: SimpleBlockStyle::Paragraph,
1653                    title_source: None,
1654                    title: None,
1655                    caption: None,
1656                    number: None,
1657                    anchor: None,
1658                    anchor_reftext: None,
1659                    attrlist: None,
1660                },)
1661            );
1662
1663            assert!(blocks.next().is_none());
1664        }
1665
1666        #[test]
1667        fn nested_blocks() {
1668            let mut parser = Parser::default();
1669
1670            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1671                &BlockMetadata::new("****\nblock1\n\n*****\nblock2\n*****\n****"),
1672                &mut parser,
1673            )
1674            .unwrap();
1675
1676            let mi = maw.item.unwrap().clone();
1677
1678            assert_eq!(
1679                mi.item,
1680                CompoundDelimitedBlock {
1681                    blocks: &[
1682                        Block::Simple(SimpleBlock {
1683                            content: Content {
1684                                original: Span {
1685                                    data: "block1",
1686                                    line: 2,
1687                                    col: 1,
1688                                    offset: 5,
1689                                },
1690                                rendered: "block1",
1691                            },
1692                            source: Span {
1693                                data: "block1",
1694                                line: 2,
1695                                col: 1,
1696                                offset: 5,
1697                            },
1698                            style: SimpleBlockStyle::Paragraph,
1699                            title_source: None,
1700                            title: None,
1701                            caption: None,
1702                            number: None,
1703                            anchor: None,
1704                            anchor_reftext: None,
1705                            attrlist: None,
1706                        },),
1707                        Block::CompoundDelimited(CompoundDelimitedBlock {
1708                            blocks: &[Block::Simple(SimpleBlock {
1709                                content: Content {
1710                                    original: Span {
1711                                        data: "block2",
1712                                        line: 5,
1713                                        col: 1,
1714                                        offset: 19,
1715                                    },
1716                                    rendered: "block2",
1717                                },
1718                                source: Span {
1719                                    data: "block2",
1720                                    line: 5,
1721                                    col: 1,
1722                                    offset: 19,
1723                                },
1724                                style: SimpleBlockStyle::Paragraph,
1725                                title_source: None,
1726                                title: None,
1727                                caption: None,
1728                                number: None,
1729                                anchor: None,
1730                                anchor_reftext: None,
1731                                attrlist: None,
1732                            },),],
1733                            context: "sidebar",
1734                            source: Span {
1735                                data: "*****\nblock2\n*****",
1736                                line: 4,
1737                                col: 1,
1738                                offset: 13,
1739                            },
1740                            title_source: None,
1741                            title: None,
1742                            caption: None,
1743                            number: None,
1744                            anchor: None,
1745                            anchor_reftext: None,
1746                            attrlist: None,
1747                        })
1748                    ],
1749                    context: "sidebar",
1750                    source: Span {
1751                        data: "****\nblock1\n\n*****\nblock2\n*****\n****",
1752                        line: 1,
1753                        col: 1,
1754                        offset: 0,
1755                    },
1756                    title_source: None,
1757                    title: None,
1758                    caption: None,
1759                    number: None,
1760                    anchor: None,
1761                    anchor_reftext: None,
1762                    attrlist: None,
1763                }
1764            );
1765
1766            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1767            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1768            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1769            assert!(mi.item.declared_style().is_none());
1770            assert!(mi.item.id().is_none());
1771            assert!(mi.item.roles().is_empty());
1772            assert!(mi.item.options().is_empty());
1773            assert!(mi.item.title_source().is_none());
1774            assert!(mi.item.title().is_none());
1775            assert!(mi.item.anchor().is_none());
1776            assert!(mi.item.anchor_reftext().is_none());
1777            assert!(mi.item.attrlist().is_none());
1778            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1779
1780            let mut blocks = mi.item.nested_blocks();
1781            assert_eq!(
1782                blocks.next().unwrap(),
1783                &Block::Simple(SimpleBlock {
1784                    content: Content {
1785                        original: Span {
1786                            data: "block1",
1787                            line: 2,
1788                            col: 1,
1789                            offset: 5,
1790                        },
1791                        rendered: "block1",
1792                    },
1793                    source: Span {
1794                        data: "block1",
1795                        line: 2,
1796                        col: 1,
1797                        offset: 5,
1798                    },
1799                    style: SimpleBlockStyle::Paragraph,
1800                    title_source: None,
1801                    title: None,
1802                    caption: None,
1803                    number: None,
1804                    anchor: None,
1805                    anchor_reftext: None,
1806                    attrlist: None,
1807                },)
1808            );
1809
1810            assert_eq!(
1811                blocks.next().unwrap(),
1812                &Block::CompoundDelimited(CompoundDelimitedBlock {
1813                    blocks: &[Block::Simple(SimpleBlock {
1814                        content: Content {
1815                            original: Span {
1816                                data: "block2",
1817                                line: 5,
1818                                col: 1,
1819                                offset: 19,
1820                            },
1821                            rendered: "block2",
1822                        },
1823                        source: Span {
1824                            data: "block2",
1825                            line: 5,
1826                            col: 1,
1827                            offset: 19,
1828                        },
1829                        style: SimpleBlockStyle::Paragraph,
1830                        title_source: None,
1831                        title: None,
1832                        caption: None,
1833                        number: None,
1834                        anchor: None,
1835                        anchor_reftext: None,
1836                        attrlist: None,
1837                    },),],
1838                    context: "sidebar",
1839                    source: Span {
1840                        data: "*****\nblock2\n*****",
1841                        line: 4,
1842                        col: 1,
1843                        offset: 13,
1844                    },
1845                    title_source: None,
1846                    title: None,
1847                    caption: None,
1848                    number: None,
1849                    anchor: None,
1850                    anchor_reftext: None,
1851                    attrlist: None,
1852                })
1853            );
1854
1855            assert!(blocks.next().is_none());
1856        }
1857    }
1858
1859    mod table {
1860        use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
1861
1862        #[test]
1863        fn empty() {
1864            let mut parser = Parser::default();
1865            assert!(
1866                crate::blocks::CompoundDelimitedBlock::parse(
1867                    &BlockMetadata::new("|===\n|==="),
1868                    &mut parser
1869                )
1870                .is_none()
1871            );
1872
1873            let mut parser = Parser::default();
1874            assert!(
1875                crate::blocks::CompoundDelimitedBlock::parse(
1876                    &BlockMetadata::new(",===\n,==="),
1877                    &mut parser
1878                )
1879                .is_none()
1880            );
1881
1882            let mut parser = Parser::default();
1883            assert!(
1884                crate::blocks::CompoundDelimitedBlock::parse(
1885                    &BlockMetadata::new(":===\n:==="),
1886                    &mut parser
1887                )
1888                .is_none()
1889            );
1890
1891            let mut parser = Parser::default();
1892            assert!(
1893                crate::blocks::CompoundDelimitedBlock::parse(
1894                    &BlockMetadata::new("!===\n!==="),
1895                    &mut parser
1896                )
1897                .is_none()
1898            );
1899        }
1900
1901        #[test]
1902        fn multiple_lines() {
1903            let mut parser = Parser::default();
1904            assert!(
1905                crate::blocks::CompoundDelimitedBlock::parse(
1906                    &BlockMetadata::new("|===\nline1  \nline2\n|==="),
1907                    &mut parser
1908                )
1909                .is_none()
1910            );
1911
1912            let mut parser = Parser::default();
1913            assert!(
1914                crate::blocks::CompoundDelimitedBlock::parse(
1915                    &BlockMetadata::new(",===\nline1  \nline2\n,==="),
1916                    &mut parser
1917                )
1918                .is_none()
1919            );
1920
1921            let mut parser = Parser::default();
1922            assert!(
1923                crate::blocks::CompoundDelimitedBlock::parse(
1924                    &BlockMetadata::new(":===\nline1  \nline2\n:==="),
1925                    &mut parser
1926                )
1927                .is_none()
1928            );
1929
1930            let mut parser = Parser::default();
1931            assert!(
1932                crate::blocks::CompoundDelimitedBlock::parse(
1933                    &BlockMetadata::new("!===\nline1  \nline2\n!==="),
1934                    &mut parser
1935                )
1936                .is_none()
1937            );
1938        }
1939    }
1940
1941    mod pass {
1942        use crate::{blocks::metadata::BlockMetadata, tests::prelude::*};
1943
1944        #[test]
1945        fn empty() {
1946            let mut parser = Parser::default();
1947            assert!(
1948                crate::blocks::CompoundDelimitedBlock::parse(
1949                    &BlockMetadata::new("++++\n++++"),
1950                    &mut parser
1951                )
1952                .is_none()
1953            );
1954        }
1955
1956        #[test]
1957        fn multiple_lines() {
1958            let mut parser = Parser::default();
1959            assert!(
1960                crate::blocks::CompoundDelimitedBlock::parse(
1961                    &BlockMetadata::new("++++\nline1  \nline2\n++++"),
1962                    &mut parser
1963                )
1964                .is_none()
1965            );
1966        }
1967    }
1968
1969    #[test]
1970    fn impl_debug() {
1971        let mut parser = Parser::default();
1972
1973        let cdb = crate::blocks::CompoundDelimitedBlock::parse(
1974            &BlockMetadata::new("====\nblock1\n\nblock2\n===="),
1975            &mut parser,
1976        )
1977        .unwrap()
1978        .unwrap_if_no_warnings()
1979        .unwrap()
1980        .item;
1981
1982        assert_eq!(
1983            format!("{cdb:#?}"),
1984            r#"CompoundDelimitedBlock {
1985    blocks: &[
1986        Block::Simple(
1987            SimpleBlock {
1988                content: Content {
1989                    original: Span {
1990                        data: "block1",
1991                        line: 2,
1992                        col: 1,
1993                        offset: 5,
1994                    },
1995                    rendered: "block1",
1996                },
1997                source: Span {
1998                    data: "block1",
1999                    line: 2,
2000                    col: 1,
2001                    offset: 5,
2002                },
2003                style: SimpleBlockStyle::Paragraph,
2004                title_source: None,
2005                title: None,
2006                caption: None,
2007                number: None,
2008                anchor: None,
2009                anchor_reftext: None,
2010                attrlist: None,
2011            },
2012        ),
2013        Block::Simple(
2014            SimpleBlock {
2015                content: Content {
2016                    original: Span {
2017                        data: "block2",
2018                        line: 4,
2019                        col: 1,
2020                        offset: 13,
2021                    },
2022                    rendered: "block2",
2023                },
2024                source: Span {
2025                    data: "block2",
2026                    line: 4,
2027                    col: 1,
2028                    offset: 13,
2029                },
2030                style: SimpleBlockStyle::Paragraph,
2031                title_source: None,
2032                title: None,
2033                caption: None,
2034                number: None,
2035                anchor: None,
2036                anchor_reftext: None,
2037                attrlist: None,
2038            },
2039        ),
2040    ],
2041    context: "example",
2042    source: Span {
2043        data: "====\nblock1\n\nblock2\n====",
2044        line: 1,
2045        col: 1,
2046        offset: 0,
2047    },
2048    title_source: None,
2049    title: None,
2050    caption: None,
2051    number: None,
2052    anchor: None,
2053    anchor_reftext: None,
2054    attrlist: None,
2055}"#
2056        );
2057    }
2058}