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