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