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, blocks::metadata::BlockMetadata, tests::prelude::*, warnings::WarningType,
371        };
372
373        #[test]
374        fn err_invalid_delimiter() {
375            let mut parser = Parser::default();
376            assert!(
377                crate::blocks::CompoundDelimitedBlock::parse(&BlockMetadata::new(""), &mut parser)
378                    .is_none()
379            );
380
381            let mut parser = Parser::default();
382            assert!(
383                crate::blocks::CompoundDelimitedBlock::parse(
384                    &BlockMetadata::new("///"),
385                    &mut parser
386                )
387                .is_none()
388            );
389
390            let mut parser = Parser::default();
391            assert!(
392                crate::blocks::CompoundDelimitedBlock::parse(
393                    &BlockMetadata::new("////x"),
394                    &mut parser
395                )
396                .is_none()
397            );
398
399            let mut parser = Parser::default();
400            assert!(
401                crate::blocks::CompoundDelimitedBlock::parse(
402                    &BlockMetadata::new("--x"),
403                    &mut parser
404                )
405                .is_none()
406            );
407
408            let mut parser = Parser::default();
409            assert!(
410                crate::blocks::CompoundDelimitedBlock::parse(
411                    &BlockMetadata::new("****x"),
412                    &mut parser
413                )
414                .is_none()
415            );
416
417            let mut parser = Parser::default();
418            assert!(
419                crate::blocks::CompoundDelimitedBlock::parse(
420                    &BlockMetadata::new("__\n__"),
421                    &mut parser
422                )
423                .is_none()
424            );
425        }
426
427        #[test]
428        fn err_unterminated() {
429            let mut parser = Parser::default();
430
431            let maw = crate::blocks::CompoundDelimitedBlock::parse(
432                &BlockMetadata::new("====\nblah blah blah"),
433                &mut parser,
434            )
435            .unwrap();
436
437            assert_eq!(
438                maw.item.unwrap().item,
439                CompoundDelimitedBlock {
440                    blocks: &[Block::Simple(SimpleBlock {
441                        content: Content {
442                            original: Span {
443                                data: "blah blah blah",
444                                line: 2,
445                                col: 1,
446                                offset: 5,
447                            },
448                            rendered: "blah blah blah",
449                        },
450                        source: Span {
451                            data: "blah blah blah",
452                            line: 2,
453                            col: 1,
454                            offset: 5,
455                        },
456                        title_source: None,
457                        title: None,
458                        anchor: None,
459                        anchor_reftext: None,
460                        attrlist: None,
461                    },),],
462                    context: "example",
463                    source: Span {
464                        data: "====\nblah blah blah",
465                        line: 1,
466                        col: 1,
467                        offset: 0,
468                    },
469                    title_source: None,
470                    title: None,
471                    anchor: None,
472                    anchor_reftext: None,
473                    attrlist: None,
474                },
475            );
476
477            assert_eq!(
478                maw.warnings,
479                vec![Warning {
480                    source: Span {
481                        data: "====",
482                        line: 1,
483                        col: 1,
484                        offset: 0,
485                    },
486                    warning: WarningType::UnterminatedDelimitedBlock,
487                }]
488            );
489        }
490    }
491
492    mod comment {
493        use crate::{Parser, blocks::metadata::BlockMetadata};
494
495        #[test]
496        fn empty() {
497            let mut parser = Parser::default();
498            assert!(
499                crate::blocks::CompoundDelimitedBlock::parse(
500                    &BlockMetadata::new("////\n////"),
501                    &mut parser
502                )
503                .is_none()
504            );
505        }
506
507        #[test]
508        fn multiple_lines() {
509            let mut parser = Parser::default();
510            assert!(
511                crate::blocks::CompoundDelimitedBlock::parse(
512                    &BlockMetadata::new("////\nline1  \nline2\n////"),
513                    &mut parser
514                )
515                .is_none()
516            );
517        }
518    }
519
520    mod example {
521        use pretty_assertions_sorted::assert_eq;
522
523        use crate::{
524            Parser,
525            blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
526            content::SubstitutionGroup,
527            tests::prelude::*,
528        };
529
530        #[test]
531        fn empty() {
532            let mut parser = Parser::default();
533
534            let maw = crate::blocks::CompoundDelimitedBlock::parse(
535                &BlockMetadata::new("====\n===="),
536                &mut parser,
537            )
538            .unwrap();
539
540            let mi = maw.item.unwrap().clone();
541
542            assert_eq!(
543                mi.item,
544                CompoundDelimitedBlock {
545                    blocks: &[],
546                    context: "example",
547                    source: Span {
548                        data: "====\n====",
549                        line: 1,
550                        col: 1,
551                        offset: 0,
552                    },
553                    title_source: None,
554                    title: None,
555                    anchor: None,
556                    anchor_reftext: None,
557                    attrlist: None,
558                }
559            );
560
561            assert_eq!(mi.item.content_model(), ContentModel::Compound);
562            assert_eq!(mi.item.raw_context().as_ref(), "example");
563            assert_eq!(mi.item.resolved_context().as_ref(), "example");
564            assert!(mi.item.declared_style().is_none());
565            assert!(mi.item.nested_blocks().next().is_none());
566            assert!(mi.item.id().is_none());
567            assert!(mi.item.roles().is_empty());
568            assert!(mi.item.options().is_empty());
569            assert!(mi.item.title_source().is_none());
570            assert!(mi.item.title().is_none());
571            assert!(mi.item.anchor().is_none());
572            assert!(mi.item.anchor_reftext().is_none());
573            assert!(mi.item.attrlist().is_none());
574            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
575        }
576
577        #[test]
578        fn multiple_blocks() {
579            let mut parser = Parser::default();
580
581            let maw = crate::blocks::CompoundDelimitedBlock::parse(
582                &BlockMetadata::new("====\nblock1\n\nblock2\n===="),
583                &mut parser,
584            )
585            .unwrap();
586
587            let mi = maw.item.unwrap().clone();
588
589            assert_eq!(
590                mi.item,
591                CompoundDelimitedBlock {
592                    blocks: &[
593                        Block::Simple(SimpleBlock {
594                            content: Content {
595                                original: Span {
596                                    data: "block1",
597                                    line: 2,
598                                    col: 1,
599                                    offset: 5,
600                                },
601                                rendered: "block1",
602                            },
603                            source: Span {
604                                data: "block1",
605                                line: 2,
606                                col: 1,
607                                offset: 5,
608                            },
609                            title_source: None,
610                            title: None,
611                            anchor: None,
612                            anchor_reftext: None,
613                            attrlist: None,
614                        },),
615                        Block::Simple(SimpleBlock {
616                            content: Content {
617                                original: Span {
618                                    data: "block2",
619                                    line: 4,
620                                    col: 1,
621                                    offset: 13,
622                                },
623                                rendered: "block2",
624                            },
625                            source: Span {
626                                data: "block2",
627                                line: 4,
628                                col: 1,
629                                offset: 13,
630                            },
631                            title_source: None,
632                            title: None,
633                            anchor: None,
634                            anchor_reftext: None,
635                            attrlist: None,
636                        },),
637                    ],
638                    context: "example",
639                    source: Span {
640                        data: "====\nblock1\n\nblock2\n====",
641                        line: 1,
642                        col: 1,
643                        offset: 0,
644                    },
645                    title_source: None,
646                    title: None,
647                    anchor: None,
648                    anchor_reftext: None,
649                    attrlist: None,
650                }
651            );
652
653            assert_eq!(mi.item.content_model(), ContentModel::Compound);
654            assert_eq!(mi.item.raw_context().as_ref(), "example");
655            assert_eq!(mi.item.resolved_context().as_ref(), "example");
656            assert!(mi.item.declared_style().is_none());
657            assert!(mi.item.id().is_none());
658            assert!(mi.item.roles().is_empty());
659            assert!(mi.item.options().is_empty());
660            assert!(mi.item.title_source().is_none());
661            assert!(mi.item.title().is_none());
662            assert!(mi.item.anchor().is_none());
663            assert!(mi.item.anchor_reftext().is_none());
664            assert!(mi.item.attrlist().is_none());
665            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
666
667            let mut blocks = mi.item.nested_blocks();
668            assert_eq!(
669                blocks.next().unwrap(),
670                &Block::Simple(SimpleBlock {
671                    content: Content {
672                        original: Span {
673                            data: "block1",
674                            line: 2,
675                            col: 1,
676                            offset: 5,
677                        },
678                        rendered: "block1",
679                    },
680                    source: Span {
681                        data: "block1",
682                        line: 2,
683                        col: 1,
684                        offset: 5,
685                    },
686                    title_source: None,
687                    title: None,
688                    anchor: None,
689                    anchor_reftext: None,
690                    attrlist: None,
691                },)
692            );
693
694            assert_eq!(
695                blocks.next().unwrap(),
696                &Block::Simple(SimpleBlock {
697                    content: Content {
698                        original: Span {
699                            data: "block2",
700                            line: 4,
701                            col: 1,
702                            offset: 13,
703                        },
704                        rendered: "block2",
705                    },
706                    source: Span {
707                        data: "block2",
708                        line: 4,
709                        col: 1,
710                        offset: 13,
711                    },
712                    title_source: None,
713                    title: None,
714                    anchor: None,
715                    anchor_reftext: None,
716                    attrlist: None,
717                },)
718            );
719
720            assert!(blocks.next().is_none());
721        }
722
723        #[test]
724        fn nested_blocks() {
725            let mut parser = Parser::default();
726
727            let maw = crate::blocks::CompoundDelimitedBlock::parse(
728                &BlockMetadata::new("====\nblock1\n\n=====\nblock2\n=====\n===="),
729                &mut parser,
730            )
731            .unwrap();
732
733            let mi = maw.item.unwrap().clone();
734
735            assert_eq!(
736                mi.item,
737                CompoundDelimitedBlock {
738                    blocks: &[
739                        Block::Simple(SimpleBlock {
740                            content: Content {
741                                original: Span {
742                                    data: "block1",
743                                    line: 2,
744                                    col: 1,
745                                    offset: 5,
746                                },
747                                rendered: "block1",
748                            },
749                            source: Span {
750                                data: "block1",
751                                line: 2,
752                                col: 1,
753                                offset: 5,
754                            },
755                            title_source: None,
756                            title: None,
757                            anchor: None,
758                            anchor_reftext: None,
759                            attrlist: None,
760                        },),
761                        Block::CompoundDelimited(CompoundDelimitedBlock {
762                            blocks: &[Block::Simple(SimpleBlock {
763                                content: Content {
764                                    original: Span {
765                                        data: "block2",
766                                        line: 5,
767                                        col: 1,
768                                        offset: 19,
769                                    },
770                                    rendered: "block2",
771                                },
772                                source: Span {
773                                    data: "block2",
774                                    line: 5,
775                                    col: 1,
776                                    offset: 19,
777                                },
778                                title_source: None,
779                                title: None,
780                                anchor: None,
781                                anchor_reftext: None,
782                                attrlist: None,
783                            },),],
784                            context: "example",
785                            source: Span {
786                                data: "=====\nblock2\n=====",
787                                line: 4,
788                                col: 1,
789                                offset: 13,
790                            },
791                            title_source: None,
792                            title: None,
793                            anchor: None,
794                            anchor_reftext: None,
795                            attrlist: None,
796                        })
797                    ],
798                    context: "example",
799                    source: Span {
800                        data: "====\nblock1\n\n=====\nblock2\n=====\n====",
801                        line: 1,
802                        col: 1,
803                        offset: 0,
804                    },
805                    title_source: None,
806                    title: None,
807                    anchor: None,
808                    anchor_reftext: None,
809                    attrlist: None,
810                }
811            );
812
813            assert_eq!(mi.item.content_model(), ContentModel::Compound);
814            assert_eq!(mi.item.raw_context().as_ref(), "example");
815            assert_eq!(mi.item.resolved_context().as_ref(), "example");
816            assert!(mi.item.declared_style().is_none());
817            assert!(mi.item.id().is_none());
818            assert!(mi.item.roles().is_empty());
819            assert!(mi.item.options().is_empty());
820            assert!(mi.item.title_source().is_none());
821            assert!(mi.item.title().is_none());
822            assert!(mi.item.anchor().is_none());
823            assert!(mi.item.anchor_reftext().is_none());
824            assert!(mi.item.attrlist().is_none());
825            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
826
827            let mut blocks = mi.item.nested_blocks();
828            assert_eq!(
829                blocks.next().unwrap(),
830                &Block::Simple(SimpleBlock {
831                    content: Content {
832                        original: Span {
833                            data: "block1",
834                            line: 2,
835                            col: 1,
836                            offset: 5,
837                        },
838                        rendered: "block1",
839                    },
840                    source: Span {
841                        data: "block1",
842                        line: 2,
843                        col: 1,
844                        offset: 5,
845                    },
846                    title_source: None,
847                    title: None,
848                    anchor: None,
849                    anchor_reftext: None,
850                    attrlist: None,
851                },)
852            );
853
854            assert_eq!(
855                blocks.next().unwrap(),
856                &Block::CompoundDelimited(CompoundDelimitedBlock {
857                    blocks: &[Block::Simple(SimpleBlock {
858                        content: Content {
859                            original: Span {
860                                data: "block2",
861                                line: 5,
862                                col: 1,
863                                offset: 19,
864                            },
865                            rendered: "block2",
866                        },
867                        source: Span {
868                            data: "block2",
869                            line: 5,
870                            col: 1,
871                            offset: 19,
872                        },
873                        title_source: None,
874                        title: None,
875                        anchor: None,
876                        anchor_reftext: None,
877                        attrlist: None,
878                    },),],
879                    context: "example",
880                    source: Span {
881                        data: "=====\nblock2\n=====",
882                        line: 4,
883                        col: 1,
884                        offset: 13,
885                    },
886                    title_source: None,
887                    title: None,
888                    anchor: None,
889                    anchor_reftext: None,
890                    attrlist: None,
891                })
892            );
893
894            assert!(blocks.next().is_none());
895        }
896    }
897
898    mod listing {
899        use crate::{Parser, blocks::metadata::BlockMetadata};
900
901        #[test]
902        fn empty() {
903            let mut parser = Parser::default();
904            assert!(
905                crate::blocks::CompoundDelimitedBlock::parse(
906                    &BlockMetadata::new("----\n----"),
907                    &mut parser
908                )
909                .is_none()
910            );
911        }
912
913        #[test]
914        fn multiple_lines() {
915            let mut parser = Parser::default();
916            assert!(
917                crate::blocks::CompoundDelimitedBlock::parse(
918                    &BlockMetadata::new("----\nline1  \nline2\n----"),
919                    &mut parser
920                )
921                .is_none()
922            );
923        }
924    }
925
926    mod literal {
927        use crate::{Parser, blocks::metadata::BlockMetadata};
928
929        #[test]
930        fn empty() {
931            let mut parser = Parser::default();
932            assert!(
933                crate::blocks::CompoundDelimitedBlock::parse(
934                    &BlockMetadata::new("....\n...."),
935                    &mut parser
936                )
937                .is_none()
938            );
939        }
940
941        #[test]
942        fn multiple_lines() {
943            let mut parser = Parser::default();
944            assert!(
945                crate::blocks::CompoundDelimitedBlock::parse(
946                    &BlockMetadata::new("....\nline1  \nline2\n...."),
947                    &mut parser
948                )
949                .is_none()
950            );
951        }
952    }
953
954    mod open {
955        use pretty_assertions_sorted::assert_eq;
956
957        use crate::{
958            Parser,
959            blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
960            content::SubstitutionGroup,
961            tests::prelude::*,
962        };
963
964        #[test]
965        fn empty() {
966            let mut parser = Parser::default();
967
968            let maw = crate::blocks::CompoundDelimitedBlock::parse(
969                &BlockMetadata::new("--\n--"),
970                &mut parser,
971            )
972            .unwrap();
973
974            let mi = maw.item.unwrap().clone();
975
976            assert_eq!(
977                mi.item,
978                CompoundDelimitedBlock {
979                    blocks: &[],
980                    context: "open",
981                    source: Span {
982                        data: "--\n--",
983                        line: 1,
984                        col: 1,
985                        offset: 0,
986                    },
987                    title_source: None,
988                    title: None,
989                    anchor: None,
990                    anchor_reftext: None,
991                    attrlist: None,
992                }
993            );
994
995            assert_eq!(mi.item.content_model(), ContentModel::Compound);
996            assert_eq!(mi.item.raw_context().as_ref(), "open");
997            assert_eq!(mi.item.resolved_context().as_ref(), "open");
998            assert!(mi.item.declared_style().is_none());
999            assert!(mi.item.nested_blocks().next().is_none());
1000            assert!(mi.item.id().is_none());
1001            assert!(mi.item.roles().is_empty());
1002            assert!(mi.item.options().is_empty());
1003            assert!(mi.item.title_source().is_none());
1004            assert!(mi.item.title().is_none());
1005            assert!(mi.item.anchor().is_none());
1006            assert!(mi.item.anchor_reftext().is_none());
1007            assert!(mi.item.attrlist().is_none());
1008            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1009        }
1010
1011        #[test]
1012        fn multiple_blocks() {
1013            let mut parser = Parser::default();
1014
1015            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1016                &BlockMetadata::new("--\nblock1\n\nblock2\n--"),
1017                &mut parser,
1018            )
1019            .unwrap();
1020
1021            let mi = maw.item.unwrap().clone();
1022
1023            assert_eq!(
1024                mi.item,
1025                CompoundDelimitedBlock {
1026                    blocks: &[
1027                        Block::Simple(SimpleBlock {
1028                            content: Content {
1029                                original: Span {
1030                                    data: "block1",
1031                                    line: 2,
1032                                    col: 1,
1033                                    offset: 3,
1034                                },
1035                                rendered: "block1",
1036                            },
1037                            source: Span {
1038                                data: "block1",
1039                                line: 2,
1040                                col: 1,
1041                                offset: 3,
1042                            },
1043                            title_source: None,
1044                            title: None,
1045                            anchor: None,
1046                            anchor_reftext: None,
1047                            attrlist: None,
1048                        },),
1049                        Block::Simple(SimpleBlock {
1050                            content: Content {
1051                                original: Span {
1052                                    data: "block2",
1053                                    line: 4,
1054                                    col: 1,
1055                                    offset: 11,
1056                                },
1057                                rendered: "block2",
1058                            },
1059                            source: Span {
1060                                data: "block2",
1061                                line: 4,
1062                                col: 1,
1063                                offset: 11,
1064                            },
1065                            title_source: None,
1066                            title: None,
1067                            anchor: None,
1068                            anchor_reftext: None,
1069                            attrlist: None,
1070                        },),
1071                    ],
1072                    context: "open",
1073                    source: Span {
1074                        data: "--\nblock1\n\nblock2\n--",
1075                        line: 1,
1076                        col: 1,
1077                        offset: 0,
1078                    },
1079                    title_source: None,
1080                    title: None,
1081                    anchor: None,
1082                    anchor_reftext: None,
1083                    attrlist: None,
1084                }
1085            );
1086
1087            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1088            assert_eq!(mi.item.raw_context().as_ref(), "open");
1089            assert_eq!(mi.item.resolved_context().as_ref(), "open");
1090            assert!(mi.item.declared_style().is_none());
1091            assert!(mi.item.id().is_none());
1092            assert!(mi.item.roles().is_empty());
1093            assert!(mi.item.options().is_empty());
1094            assert!(mi.item.title_source().is_none());
1095            assert!(mi.item.title().is_none());
1096            assert!(mi.item.anchor().is_none());
1097            assert!(mi.item.anchor_reftext().is_none());
1098            assert!(mi.item.attrlist().is_none());
1099            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1100
1101            let mut blocks = mi.item.nested_blocks();
1102            assert_eq!(
1103                blocks.next().unwrap(),
1104                &Block::Simple(SimpleBlock {
1105                    content: Content {
1106                        original: Span {
1107                            data: "block1",
1108                            line: 2,
1109                            col: 1,
1110                            offset: 3,
1111                        },
1112                        rendered: "block1",
1113                    },
1114                    source: Span {
1115                        data: "block1",
1116                        line: 2,
1117                        col: 1,
1118                        offset: 3,
1119                    },
1120                    title_source: None,
1121                    title: None,
1122                    anchor: None,
1123                    anchor_reftext: None,
1124                    attrlist: None,
1125                },)
1126            );
1127
1128            assert_eq!(
1129                blocks.next().unwrap(),
1130                &Block::Simple(SimpleBlock {
1131                    content: Content {
1132                        original: Span {
1133                            data: "block2",
1134                            line: 4,
1135                            col: 1,
1136                            offset: 11,
1137                        },
1138                        rendered: "block2",
1139                    },
1140                    source: Span {
1141                        data: "block2",
1142                        line: 4,
1143                        col: 1,
1144                        offset: 11,
1145                    },
1146                    title_source: None,
1147                    title: None,
1148                    anchor: None,
1149                    anchor_reftext: None,
1150                    attrlist: None,
1151                },)
1152            );
1153
1154            assert!(blocks.next().is_none());
1155        }
1156
1157        #[test]
1158        fn nested_blocks() {
1159            // Spec says three hyphens does NOT mark an open block.
1160            let mut parser = Parser::default();
1161
1162            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1163                &BlockMetadata::new("--\nblock1\n\n---\nblock2\n---\n--"),
1164                &mut parser,
1165            )
1166            .unwrap();
1167
1168            let mi = maw.item.unwrap().clone();
1169
1170            assert_eq!(
1171                mi.item,
1172                CompoundDelimitedBlock {
1173                    blocks: &[
1174                        Block::Simple(SimpleBlock {
1175                            content: Content {
1176                                original: Span {
1177                                    data: "block1",
1178                                    line: 2,
1179                                    col: 1,
1180                                    offset: 3,
1181                                },
1182                                rendered: "block1",
1183                            },
1184                            source: Span {
1185                                data: "block1",
1186                                line: 2,
1187                                col: 1,
1188                                offset: 3,
1189                            },
1190                            title_source: None,
1191                            title: None,
1192                            anchor: None,
1193                            anchor_reftext: None,
1194                            attrlist: None,
1195                        },),
1196                        Block::Simple(SimpleBlock {
1197                            content: Content {
1198                                original: Span {
1199                                    data: "---\nblock2\n---",
1200                                    line: 4,
1201                                    col: 1,
1202                                    offset: 11,
1203                                },
1204                                rendered: "---\nblock2\n---",
1205                            },
1206                            source: Span {
1207                                data: "---\nblock2\n---",
1208                                line: 4,
1209                                col: 1,
1210                                offset: 11,
1211                            },
1212                            title_source: None,
1213                            title: None,
1214                            anchor: None,
1215                            anchor_reftext: None,
1216                            attrlist: None,
1217                        })
1218                    ],
1219                    context: "open",
1220                    source: Span {
1221                        data: "--\nblock1\n\n---\nblock2\n---\n--",
1222                        line: 1,
1223                        col: 1,
1224                        offset: 0,
1225                    },
1226                    title_source: None,
1227                    title: None,
1228                    anchor: None,
1229                    anchor_reftext: None,
1230                    attrlist: None,
1231                }
1232            );
1233
1234            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1235            assert_eq!(mi.item.raw_context().as_ref(), "open");
1236            assert_eq!(mi.item.resolved_context().as_ref(), "open");
1237            assert!(mi.item.declared_style().is_none());
1238            assert!(mi.item.id().is_none());
1239            assert!(mi.item.roles().is_empty());
1240            assert!(mi.item.options().is_empty());
1241            assert!(mi.item.title_source().is_none());
1242            assert!(mi.item.title().is_none());
1243            assert!(mi.item.anchor().is_none());
1244            assert!(mi.item.anchor_reftext().is_none());
1245            assert!(mi.item.attrlist().is_none());
1246            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1247
1248            let mut blocks = mi.item.nested_blocks();
1249            assert_eq!(
1250                blocks.next().unwrap(),
1251                &Block::Simple(SimpleBlock {
1252                    content: Content {
1253                        original: Span {
1254                            data: "block1",
1255                            line: 2,
1256                            col: 1,
1257                            offset: 3,
1258                        },
1259                        rendered: "block1",
1260                    },
1261                    source: Span {
1262                        data: "block1",
1263                        line: 2,
1264                        col: 1,
1265                        offset: 3,
1266                    },
1267                    title_source: None,
1268                    title: None,
1269                    anchor: None,
1270                    anchor_reftext: None,
1271                    attrlist: None,
1272                },)
1273            );
1274
1275            assert_eq!(
1276                blocks.next().unwrap(),
1277                &Block::Simple(SimpleBlock {
1278                    content: Content {
1279                        original: Span {
1280                            data: "---\nblock2\n---",
1281                            line: 4,
1282                            col: 1,
1283                            offset: 11,
1284                        },
1285                        rendered: "---\nblock2\n---",
1286                    },
1287                    source: Span {
1288                        data: "---\nblock2\n---",
1289                        line: 4,
1290                        col: 1,
1291                        offset: 11,
1292                    },
1293                    title_source: None,
1294                    title: None,
1295                    anchor: None,
1296                    anchor_reftext: None,
1297                    attrlist: None,
1298                })
1299            );
1300
1301            assert!(blocks.next().is_none());
1302        }
1303    }
1304
1305    mod sidebar {
1306        use pretty_assertions_sorted::assert_eq;
1307
1308        use crate::{
1309            Parser,
1310            blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
1311            content::SubstitutionGroup,
1312            tests::prelude::*,
1313        };
1314
1315        #[test]
1316        fn empty() {
1317            let mut parser = Parser::default();
1318
1319            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1320                &BlockMetadata::new("****\n****"),
1321                &mut parser,
1322            )
1323            .unwrap();
1324
1325            let mi = maw.item.unwrap().clone();
1326
1327            assert_eq!(
1328                mi.item,
1329                CompoundDelimitedBlock {
1330                    blocks: &[],
1331                    context: "sidebar",
1332                    source: Span {
1333                        data: "****\n****",
1334                        line: 1,
1335                        col: 1,
1336                        offset: 0,
1337                    },
1338                    title_source: None,
1339                    title: None,
1340                    anchor: None,
1341                    anchor_reftext: None,
1342                    attrlist: None,
1343                }
1344            );
1345
1346            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1347            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1348            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1349            assert!(mi.item.declared_style().is_none());
1350            assert!(mi.item.nested_blocks().next().is_none());
1351            assert!(mi.item.id().is_none());
1352            assert!(mi.item.roles().is_empty());
1353            assert!(mi.item.options().is_empty());
1354            assert!(mi.item.title_source().is_none());
1355            assert!(mi.item.title().is_none());
1356            assert!(mi.item.anchor().is_none());
1357            assert!(mi.item.anchor_reftext().is_none());
1358            assert!(mi.item.attrlist().is_none());
1359            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1360        }
1361
1362        #[test]
1363        fn multiple_blocks() {
1364            let mut parser = Parser::default();
1365
1366            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1367                &BlockMetadata::new("****\nblock1\n\nblock2\n****"),
1368                &mut parser,
1369            )
1370            .unwrap();
1371
1372            let mi = maw.item.unwrap().clone();
1373
1374            assert_eq!(
1375                mi.item,
1376                CompoundDelimitedBlock {
1377                    blocks: &[
1378                        Block::Simple(SimpleBlock {
1379                            content: Content {
1380                                original: Span {
1381                                    data: "block1",
1382                                    line: 2,
1383                                    col: 1,
1384                                    offset: 5,
1385                                },
1386                                rendered: "block1",
1387                            },
1388                            source: Span {
1389                                data: "block1",
1390                                line: 2,
1391                                col: 1,
1392                                offset: 5,
1393                            },
1394                            title_source: None,
1395                            title: None,
1396                            anchor: None,
1397                            anchor_reftext: None,
1398                            attrlist: None,
1399                        },),
1400                        Block::Simple(SimpleBlock {
1401                            content: Content {
1402                                original: Span {
1403                                    data: "block2",
1404                                    line: 4,
1405                                    col: 1,
1406                                    offset: 13,
1407                                },
1408                                rendered: "block2",
1409                            },
1410                            source: Span {
1411                                data: "block2",
1412                                line: 4,
1413                                col: 1,
1414                                offset: 13,
1415                            },
1416                            title_source: None,
1417                            title: None,
1418                            anchor: None,
1419                            anchor_reftext: None,
1420                            attrlist: None,
1421                        },),
1422                    ],
1423                    context: "sidebar",
1424                    source: Span {
1425                        data: "****\nblock1\n\nblock2\n****",
1426                        line: 1,
1427                        col: 1,
1428                        offset: 0,
1429                    },
1430                    title_source: None,
1431                    title: None,
1432                    anchor: None,
1433                    anchor_reftext: None,
1434                    attrlist: None,
1435                }
1436            );
1437
1438            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1439            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1440            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1441            assert!(mi.item.declared_style().is_none());
1442            assert!(mi.item.id().is_none());
1443            assert!(mi.item.roles().is_empty());
1444            assert!(mi.item.options().is_empty());
1445            assert!(mi.item.title_source().is_none());
1446            assert!(mi.item.title().is_none());
1447            assert!(mi.item.anchor().is_none());
1448            assert!(mi.item.anchor_reftext().is_none());
1449            assert!(mi.item.attrlist().is_none());
1450            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1451
1452            let mut blocks = mi.item.nested_blocks();
1453            assert_eq!(
1454                blocks.next().unwrap(),
1455                &Block::Simple(SimpleBlock {
1456                    content: Content {
1457                        original: Span {
1458                            data: "block1",
1459                            line: 2,
1460                            col: 1,
1461                            offset: 5,
1462                        },
1463                        rendered: "block1",
1464                    },
1465                    source: Span {
1466                        data: "block1",
1467                        line: 2,
1468                        col: 1,
1469                        offset: 5,
1470                    },
1471                    title_source: None,
1472                    title: None,
1473                    anchor: None,
1474                    anchor_reftext: None,
1475                    attrlist: None,
1476                },)
1477            );
1478
1479            assert_eq!(
1480                blocks.next().unwrap(),
1481                &Block::Simple(SimpleBlock {
1482                    content: Content {
1483                        original: Span {
1484                            data: "block2",
1485                            line: 4,
1486                            col: 1,
1487                            offset: 13,
1488                        },
1489                        rendered: "block2",
1490                    },
1491                    source: Span {
1492                        data: "block2",
1493                        line: 4,
1494                        col: 1,
1495                        offset: 13,
1496                    },
1497                    title_source: None,
1498                    title: None,
1499                    anchor: None,
1500                    anchor_reftext: None,
1501                    attrlist: None,
1502                },)
1503            );
1504
1505            assert!(blocks.next().is_none());
1506        }
1507
1508        #[test]
1509        fn nested_blocks() {
1510            let mut parser = Parser::default();
1511
1512            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1513                &BlockMetadata::new("****\nblock1\n\n*****\nblock2\n*****\n****"),
1514                &mut parser,
1515            )
1516            .unwrap();
1517
1518            let mi = maw.item.unwrap().clone();
1519
1520            assert_eq!(
1521                mi.item,
1522                CompoundDelimitedBlock {
1523                    blocks: &[
1524                        Block::Simple(SimpleBlock {
1525                            content: Content {
1526                                original: Span {
1527                                    data: "block1",
1528                                    line: 2,
1529                                    col: 1,
1530                                    offset: 5,
1531                                },
1532                                rendered: "block1",
1533                            },
1534                            source: Span {
1535                                data: "block1",
1536                                line: 2,
1537                                col: 1,
1538                                offset: 5,
1539                            },
1540                            title_source: None,
1541                            title: None,
1542                            anchor: None,
1543                            anchor_reftext: None,
1544                            attrlist: None,
1545                        },),
1546                        Block::CompoundDelimited(CompoundDelimitedBlock {
1547                            blocks: &[Block::Simple(SimpleBlock {
1548                                content: Content {
1549                                    original: Span {
1550                                        data: "block2",
1551                                        line: 5,
1552                                        col: 1,
1553                                        offset: 19,
1554                                    },
1555                                    rendered: "block2",
1556                                },
1557                                source: Span {
1558                                    data: "block2",
1559                                    line: 5,
1560                                    col: 1,
1561                                    offset: 19,
1562                                },
1563                                title_source: None,
1564                                title: None,
1565                                anchor: None,
1566                                anchor_reftext: None,
1567                                attrlist: None,
1568                            },),],
1569                            context: "sidebar",
1570                            source: Span {
1571                                data: "*****\nblock2\n*****",
1572                                line: 4,
1573                                col: 1,
1574                                offset: 13,
1575                            },
1576                            title_source: None,
1577                            title: None,
1578                            anchor: None,
1579                            anchor_reftext: None,
1580                            attrlist: None,
1581                        })
1582                    ],
1583                    context: "sidebar",
1584                    source: Span {
1585                        data: "****\nblock1\n\n*****\nblock2\n*****\n****",
1586                        line: 1,
1587                        col: 1,
1588                        offset: 0,
1589                    },
1590                    title_source: None,
1591                    title: None,
1592                    anchor: None,
1593                    anchor_reftext: None,
1594                    attrlist: None,
1595                }
1596            );
1597
1598            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1599            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1600            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1601            assert!(mi.item.declared_style().is_none());
1602            assert!(mi.item.id().is_none());
1603            assert!(mi.item.roles().is_empty());
1604            assert!(mi.item.options().is_empty());
1605            assert!(mi.item.title_source().is_none());
1606            assert!(mi.item.title().is_none());
1607            assert!(mi.item.anchor().is_none());
1608            assert!(mi.item.anchor_reftext().is_none());
1609            assert!(mi.item.attrlist().is_none());
1610            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1611
1612            let mut blocks = mi.item.nested_blocks();
1613            assert_eq!(
1614                blocks.next().unwrap(),
1615                &Block::Simple(SimpleBlock {
1616                    content: Content {
1617                        original: Span {
1618                            data: "block1",
1619                            line: 2,
1620                            col: 1,
1621                            offset: 5,
1622                        },
1623                        rendered: "block1",
1624                    },
1625                    source: Span {
1626                        data: "block1",
1627                        line: 2,
1628                        col: 1,
1629                        offset: 5,
1630                    },
1631                    title_source: None,
1632                    title: None,
1633                    anchor: None,
1634                    anchor_reftext: None,
1635                    attrlist: None,
1636                },)
1637            );
1638
1639            assert_eq!(
1640                blocks.next().unwrap(),
1641                &Block::CompoundDelimited(CompoundDelimitedBlock {
1642                    blocks: &[Block::Simple(SimpleBlock {
1643                        content: Content {
1644                            original: Span {
1645                                data: "block2",
1646                                line: 5,
1647                                col: 1,
1648                                offset: 19,
1649                            },
1650                            rendered: "block2",
1651                        },
1652                        source: Span {
1653                            data: "block2",
1654                            line: 5,
1655                            col: 1,
1656                            offset: 19,
1657                        },
1658                        title_source: None,
1659                        title: None,
1660                        anchor: None,
1661                        anchor_reftext: None,
1662                        attrlist: None,
1663                    },),],
1664                    context: "sidebar",
1665                    source: Span {
1666                        data: "*****\nblock2\n*****",
1667                        line: 4,
1668                        col: 1,
1669                        offset: 13,
1670                    },
1671                    title_source: None,
1672                    title: None,
1673                    anchor: None,
1674                    anchor_reftext: None,
1675                    attrlist: None,
1676                })
1677            );
1678
1679            assert!(blocks.next().is_none());
1680        }
1681    }
1682
1683    mod table {
1684        use crate::{Parser, blocks::metadata::BlockMetadata};
1685
1686        #[test]
1687        fn empty() {
1688            let mut parser = Parser::default();
1689            assert!(
1690                crate::blocks::CompoundDelimitedBlock::parse(
1691                    &BlockMetadata::new("|===\n|==="),
1692                    &mut parser
1693                )
1694                .is_none()
1695            );
1696
1697            let mut parser = Parser::default();
1698            assert!(
1699                crate::blocks::CompoundDelimitedBlock::parse(
1700                    &BlockMetadata::new(",===\n,==="),
1701                    &mut parser
1702                )
1703                .is_none()
1704            );
1705
1706            let mut parser = Parser::default();
1707            assert!(
1708                crate::blocks::CompoundDelimitedBlock::parse(
1709                    &BlockMetadata::new(":===\n:==="),
1710                    &mut parser
1711                )
1712                .is_none()
1713            );
1714
1715            let mut parser = Parser::default();
1716            assert!(
1717                crate::blocks::CompoundDelimitedBlock::parse(
1718                    &BlockMetadata::new("!===\n!==="),
1719                    &mut parser
1720                )
1721                .is_none()
1722            );
1723        }
1724
1725        #[test]
1726        fn multiple_lines() {
1727            let mut parser = Parser::default();
1728            assert!(
1729                crate::blocks::CompoundDelimitedBlock::parse(
1730                    &BlockMetadata::new("|===\nline1  \nline2\n|==="),
1731                    &mut parser
1732                )
1733                .is_none()
1734            );
1735
1736            let mut parser = Parser::default();
1737            assert!(
1738                crate::blocks::CompoundDelimitedBlock::parse(
1739                    &BlockMetadata::new(",===\nline1  \nline2\n,==="),
1740                    &mut parser
1741                )
1742                .is_none()
1743            );
1744
1745            let mut parser = Parser::default();
1746            assert!(
1747                crate::blocks::CompoundDelimitedBlock::parse(
1748                    &BlockMetadata::new(":===\nline1  \nline2\n:==="),
1749                    &mut parser
1750                )
1751                .is_none()
1752            );
1753
1754            let mut parser = Parser::default();
1755            assert!(
1756                crate::blocks::CompoundDelimitedBlock::parse(
1757                    &BlockMetadata::new("!===\nline1  \nline2\n!==="),
1758                    &mut parser
1759                )
1760                .is_none()
1761            );
1762        }
1763    }
1764
1765    mod pass {
1766        use crate::{Parser, blocks::metadata::BlockMetadata};
1767
1768        #[test]
1769        fn empty() {
1770            let mut parser = Parser::default();
1771            assert!(
1772                crate::blocks::CompoundDelimitedBlock::parse(
1773                    &BlockMetadata::new("++++\n++++"),
1774                    &mut parser
1775                )
1776                .is_none()
1777            );
1778        }
1779
1780        #[test]
1781        fn multiple_lines() {
1782            let mut parser = Parser::default();
1783            assert!(
1784                crate::blocks::CompoundDelimitedBlock::parse(
1785                    &BlockMetadata::new("++++\nline1  \nline2\n++++"),
1786                    &mut parser
1787                )
1788                .is_none()
1789            );
1790        }
1791    }
1792
1793    mod quote {
1794        use pretty_assertions_sorted::assert_eq;
1795
1796        use crate::{
1797            Parser,
1798            blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
1799            content::SubstitutionGroup,
1800            tests::prelude::*,
1801        };
1802
1803        #[test]
1804        fn empty() {
1805            let mut parser = Parser::default();
1806
1807            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1808                &BlockMetadata::new("____\n____"),
1809                &mut parser,
1810            )
1811            .unwrap();
1812
1813            let mi = maw.item.unwrap().clone();
1814
1815            assert_eq!(
1816                mi.item,
1817                CompoundDelimitedBlock {
1818                    blocks: &[],
1819                    context: "quote",
1820                    source: Span {
1821                        data: "____\n____",
1822                        line: 1,
1823                        col: 1,
1824                        offset: 0,
1825                    },
1826                    title_source: None,
1827                    title: None,
1828                    anchor: None,
1829                    anchor_reftext: None,
1830                    attrlist: None,
1831                }
1832            );
1833
1834            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1835            assert_eq!(mi.item.raw_context().as_ref(), "quote");
1836            assert_eq!(mi.item.resolved_context().as_ref(), "quote");
1837            assert!(mi.item.declared_style().is_none());
1838            assert!(mi.item.nested_blocks().next().is_none());
1839            assert!(mi.item.id().is_none());
1840            assert!(mi.item.roles().is_empty());
1841            assert!(mi.item.options().is_empty());
1842            assert!(mi.item.title_source().is_none());
1843            assert!(mi.item.title().is_none());
1844            assert!(mi.item.anchor().is_none());
1845            assert!(mi.item.anchor_reftext().is_none());
1846            assert!(mi.item.attrlist().is_none());
1847            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1848        }
1849
1850        #[test]
1851        fn multiple_blocks() {
1852            let mut parser = Parser::default();
1853
1854            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1855                &BlockMetadata::new("____\nblock1\n\nblock2\n____"),
1856                &mut parser,
1857            )
1858            .unwrap();
1859
1860            let mi = maw.item.unwrap().clone();
1861
1862            assert_eq!(
1863                mi.item,
1864                CompoundDelimitedBlock {
1865                    blocks: &[
1866                        Block::Simple(SimpleBlock {
1867                            content: Content {
1868                                original: Span {
1869                                    data: "block1",
1870                                    line: 2,
1871                                    col: 1,
1872                                    offset: 5,
1873                                },
1874                                rendered: "block1",
1875                            },
1876                            source: Span {
1877                                data: "block1",
1878                                line: 2,
1879                                col: 1,
1880                                offset: 5,
1881                            },
1882                            title_source: None,
1883                            title: None,
1884                            anchor: None,
1885                            anchor_reftext: None,
1886                            attrlist: None,
1887                        },),
1888                        Block::Simple(SimpleBlock {
1889                            content: Content {
1890                                original: Span {
1891                                    data: "block2",
1892                                    line: 4,
1893                                    col: 1,
1894                                    offset: 13,
1895                                },
1896                                rendered: "block2",
1897                            },
1898                            source: Span {
1899                                data: "block2",
1900                                line: 4,
1901                                col: 1,
1902                                offset: 13,
1903                            },
1904                            title_source: None,
1905                            title: None,
1906                            anchor: None,
1907                            anchor_reftext: None,
1908                            attrlist: None,
1909                        },),
1910                    ],
1911                    context: "quote",
1912                    source: Span {
1913                        data: "____\nblock1\n\nblock2\n____",
1914                        line: 1,
1915                        col: 1,
1916                        offset: 0,
1917                    },
1918                    title_source: None,
1919                    title: None,
1920                    anchor: None,
1921                    anchor_reftext: None,
1922                    attrlist: None,
1923                }
1924            );
1925
1926            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1927            assert_eq!(mi.item.raw_context().as_ref(), "quote");
1928            assert_eq!(mi.item.resolved_context().as_ref(), "quote");
1929            assert!(mi.item.declared_style().is_none());
1930            assert!(mi.item.id().is_none());
1931            assert!(mi.item.roles().is_empty());
1932            assert!(mi.item.options().is_empty());
1933            assert!(mi.item.title_source().is_none());
1934            assert!(mi.item.title().is_none());
1935            assert!(mi.item.anchor().is_none());
1936            assert!(mi.item.anchor_reftext().is_none());
1937            assert!(mi.item.attrlist().is_none());
1938            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1939
1940            let mut blocks = mi.item.nested_blocks();
1941            assert_eq!(
1942                blocks.next().unwrap(),
1943                &Block::Simple(SimpleBlock {
1944                    content: Content {
1945                        original: Span {
1946                            data: "block1",
1947                            line: 2,
1948                            col: 1,
1949                            offset: 5,
1950                        },
1951                        rendered: "block1",
1952                    },
1953                    source: Span {
1954                        data: "block1",
1955                        line: 2,
1956                        col: 1,
1957                        offset: 5,
1958                    },
1959                    title_source: None,
1960                    title: None,
1961                    anchor: None,
1962                    anchor_reftext: None,
1963                    attrlist: None,
1964                },)
1965            );
1966
1967            assert_eq!(
1968                blocks.next().unwrap(),
1969                &Block::Simple(SimpleBlock {
1970                    content: Content {
1971                        original: Span {
1972                            data: "block2",
1973                            line: 4,
1974                            col: 1,
1975                            offset: 13,
1976                        },
1977                        rendered: "block2",
1978                    },
1979                    source: Span {
1980                        data: "block2",
1981                        line: 4,
1982                        col: 1,
1983                        offset: 13,
1984                    },
1985                    title_source: None,
1986                    title: None,
1987                    anchor: None,
1988                    anchor_reftext: None,
1989                    attrlist: None,
1990                },)
1991            );
1992
1993            assert!(blocks.next().is_none());
1994        }
1995
1996        #[test]
1997        fn nested_blocks() {
1998            let mut parser = Parser::default();
1999
2000            let maw = crate::blocks::CompoundDelimitedBlock::parse(
2001                &BlockMetadata::new("____\nblock1\n\n_____\nblock2\n_____\n____"),
2002                &mut parser,
2003            )
2004            .unwrap();
2005
2006            let mi = maw.item.unwrap().clone();
2007
2008            assert_eq!(
2009                mi.item,
2010                CompoundDelimitedBlock {
2011                    blocks: &[
2012                        Block::Simple(SimpleBlock {
2013                            content: Content {
2014                                original: Span {
2015                                    data: "block1",
2016                                    line: 2,
2017                                    col: 1,
2018                                    offset: 5,
2019                                },
2020                                rendered: "block1",
2021                            },
2022                            source: Span {
2023                                data: "block1",
2024                                line: 2,
2025                                col: 1,
2026                                offset: 5,
2027                            },
2028                            title_source: None,
2029                            title: None,
2030                            anchor: None,
2031                            anchor_reftext: None,
2032                            attrlist: None,
2033                        },),
2034                        Block::CompoundDelimited(CompoundDelimitedBlock {
2035                            blocks: &[Block::Simple(SimpleBlock {
2036                                content: Content {
2037                                    original: Span {
2038                                        data: "block2",
2039                                        line: 5,
2040                                        col: 1,
2041                                        offset: 19,
2042                                    },
2043                                    rendered: "block2",
2044                                },
2045                                source: Span {
2046                                    data: "block2",
2047                                    line: 5,
2048                                    col: 1,
2049                                    offset: 19,
2050                                },
2051                                title_source: None,
2052                                title: None,
2053                                anchor: None,
2054                                anchor_reftext: None,
2055                                attrlist: None,
2056                            },),],
2057                            context: "quote",
2058                            source: Span {
2059                                data: "_____\nblock2\n_____",
2060                                line: 4,
2061                                col: 1,
2062                                offset: 13,
2063                            },
2064                            title_source: None,
2065                            title: None,
2066                            anchor: None,
2067                            anchor_reftext: None,
2068                            attrlist: None,
2069                        })
2070                    ],
2071                    context: "quote",
2072                    source: Span {
2073                        data: "____\nblock1\n\n_____\nblock2\n_____\n____",
2074                        line: 1,
2075                        col: 1,
2076                        offset: 0,
2077                    },
2078                    title_source: None,
2079                    title: None,
2080                    anchor: None,
2081                    anchor_reftext: None,
2082                    attrlist: None,
2083                }
2084            );
2085
2086            assert_eq!(mi.item.content_model(), ContentModel::Compound);
2087            assert_eq!(mi.item.raw_context().as_ref(), "quote");
2088            assert_eq!(mi.item.resolved_context().as_ref(), "quote");
2089            assert!(mi.item.declared_style().is_none());
2090            assert!(mi.item.id().is_none());
2091            assert!(mi.item.roles().is_empty());
2092            assert!(mi.item.options().is_empty());
2093            assert!(mi.item.title_source().is_none());
2094            assert!(mi.item.title().is_none());
2095            assert!(mi.item.anchor().is_none());
2096            assert!(mi.item.anchor_reftext().is_none());
2097            assert!(mi.item.attrlist().is_none());
2098            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
2099
2100            let mut blocks = mi.item.nested_blocks();
2101            assert_eq!(
2102                blocks.next().unwrap(),
2103                &Block::Simple(SimpleBlock {
2104                    content: Content {
2105                        original: Span {
2106                            data: "block1",
2107                            line: 2,
2108                            col: 1,
2109                            offset: 5,
2110                        },
2111                        rendered: "block1",
2112                    },
2113                    source: Span {
2114                        data: "block1",
2115                        line: 2,
2116                        col: 1,
2117                        offset: 5,
2118                    },
2119                    title_source: None,
2120                    title: None,
2121                    anchor: None,
2122                    anchor_reftext: None,
2123                    attrlist: None,
2124                },)
2125            );
2126
2127            assert_eq!(
2128                blocks.next().unwrap(),
2129                &Block::CompoundDelimited(CompoundDelimitedBlock {
2130                    blocks: &[Block::Simple(SimpleBlock {
2131                        content: Content {
2132                            original: Span {
2133                                data: "block2",
2134                                line: 5,
2135                                col: 1,
2136                                offset: 19,
2137                            },
2138                            rendered: "block2",
2139                        },
2140                        source: Span {
2141                            data: "block2",
2142                            line: 5,
2143                            col: 1,
2144                            offset: 19,
2145                        },
2146                        title_source: None,
2147                        title: None,
2148                        anchor: None,
2149                        anchor_reftext: None,
2150                        attrlist: None,
2151                    },),],
2152                    context: "quote",
2153                    source: Span {
2154                        data: "_____\nblock2\n_____",
2155                        line: 4,
2156                        col: 1,
2157                        offset: 13,
2158                    },
2159                    title_source: None,
2160                    title: None,
2161                    anchor: None,
2162                    anchor_reftext: None,
2163                    attrlist: None,
2164                })
2165            );
2166
2167            assert!(blocks.next().is_none());
2168        }
2169    }
2170
2171    #[test]
2172    fn impl_debug() {
2173        let mut parser = Parser::default();
2174
2175        let cdb = crate::blocks::CompoundDelimitedBlock::parse(
2176            &BlockMetadata::new("====\nblock1\n\nblock2\n===="),
2177            &mut parser,
2178        )
2179        .unwrap()
2180        .unwrap_if_no_warnings()
2181        .unwrap()
2182        .item;
2183
2184        assert_eq!(
2185            format!("{cdb:#?}"),
2186            r#"CompoundDelimitedBlock {
2187    blocks: &[
2188        Block::Simple(
2189            SimpleBlock {
2190                content: Content {
2191                    original: Span {
2192                        data: "block1",
2193                        line: 2,
2194                        col: 1,
2195                        offset: 5,
2196                    },
2197                    rendered: "block1",
2198                },
2199                source: Span {
2200                    data: "block1",
2201                    line: 2,
2202                    col: 1,
2203                    offset: 5,
2204                },
2205                title_source: None,
2206                title: None,
2207                anchor: None,
2208                anchor_reftext: None,
2209                attrlist: None,
2210            },
2211        ),
2212        Block::Simple(
2213            SimpleBlock {
2214                content: Content {
2215                    original: Span {
2216                        data: "block2",
2217                        line: 4,
2218                        col: 1,
2219                        offset: 13,
2220                    },
2221                    rendered: "block2",
2222                },
2223                source: Span {
2224                    data: "block2",
2225                    line: 4,
2226                    col: 1,
2227                    offset: 13,
2228                },
2229                title_source: None,
2230                title: None,
2231                anchor: None,
2232                anchor_reftext: None,
2233                attrlist: None,
2234            },
2235        ),
2236    ],
2237    context: "example",
2238    source: Span {
2239        data: "====\nblock1\n\nblock2\n====",
2240        line: 1,
2241        col: 1,
2242        offset: 0,
2243    },
2244    title_source: None,
2245    title: None,
2246    anchor: None,
2247    anchor_reftext: None,
2248    attrlist: None,
2249}"#
2250        );
2251    }
2252}