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::{BreakType, 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::Break(Break {
1197                            type_: BreakType::Thematic,
1198                            source: Span {
1199                                data: "---",
1200                                line: 4,
1201                                col: 1,
1202                                offset: 11,
1203                            },
1204                            title_source: None,
1205                            title: None,
1206                            anchor: None,
1207                            attrlist: None,
1208                        },),
1209                        Block::Simple(SimpleBlock {
1210                            content: Content {
1211                                original: Span {
1212                                    data: "block2\n---",
1213                                    line: 5,
1214                                    col: 1,
1215                                    offset: 15,
1216                                },
1217                                rendered: "block2\n---",
1218                            },
1219                            source: Span {
1220                                data: "block2\n---",
1221                                line: 5,
1222                                col: 1,
1223                                offset: 15,
1224                            },
1225                            title_source: None,
1226                            title: None,
1227                            anchor: None,
1228                            anchor_reftext: None,
1229                            attrlist: None,
1230                        },),
1231                    ],
1232                    context: "open",
1233                    source: Span {
1234                        data: "--\nblock1\n\n---\nblock2\n---\n--",
1235                        line: 1,
1236                        col: 1,
1237                        offset: 0,
1238                    },
1239                    title_source: None,
1240                    title: None,
1241                    anchor: None,
1242                    anchor_reftext: None,
1243                    attrlist: None,
1244                }
1245            );
1246
1247            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1248            assert_eq!(mi.item.raw_context().as_ref(), "open");
1249            assert_eq!(mi.item.resolved_context().as_ref(), "open");
1250            assert!(mi.item.declared_style().is_none());
1251            assert!(mi.item.id().is_none());
1252            assert!(mi.item.roles().is_empty());
1253            assert!(mi.item.options().is_empty());
1254            assert!(mi.item.title_source().is_none());
1255            assert!(mi.item.title().is_none());
1256            assert!(mi.item.anchor().is_none());
1257            assert!(mi.item.anchor_reftext().is_none());
1258            assert!(mi.item.attrlist().is_none());
1259            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1260
1261            let mut blocks = mi.item.nested_blocks();
1262            assert_eq!(
1263                blocks.next().unwrap(),
1264                &Block::Simple(SimpleBlock {
1265                    content: Content {
1266                        original: Span {
1267                            data: "block1",
1268                            line: 2,
1269                            col: 1,
1270                            offset: 3,
1271                        },
1272                        rendered: "block1",
1273                    },
1274                    source: Span {
1275                        data: "block1",
1276                        line: 2,
1277                        col: 1,
1278                        offset: 3,
1279                    },
1280                    title_source: None,
1281                    title: None,
1282                    anchor: None,
1283                    anchor_reftext: None,
1284                    attrlist: None,
1285                },)
1286            );
1287
1288            assert_eq!(
1289                blocks.next().unwrap(),
1290                &Block::Break(Break {
1291                    type_: BreakType::Thematic,
1292                    source: Span {
1293                        data: "---",
1294                        line: 4,
1295                        col: 1,
1296                        offset: 11,
1297                    },
1298                    title_source: None,
1299                    title: None,
1300                    anchor: None,
1301                    attrlist: None,
1302                },)
1303            );
1304
1305            assert_eq!(
1306                blocks.next().unwrap(),
1307                &Block::Simple(SimpleBlock {
1308                    content: Content {
1309                        original: Span {
1310                            data: "block2\n---",
1311                            line: 5,
1312                            col: 1,
1313                            offset: 15,
1314                        },
1315                        rendered: "block2\n---",
1316                    },
1317                    source: Span {
1318                        data: "block2\n---",
1319                        line: 5,
1320                        col: 1,
1321                        offset: 15,
1322                    },
1323                    title_source: None,
1324                    title: None,
1325                    anchor: None,
1326                    anchor_reftext: None,
1327                    attrlist: None,
1328                },)
1329            );
1330
1331            assert!(blocks.next().is_none());
1332        }
1333    }
1334
1335    mod sidebar {
1336        use pretty_assertions_sorted::assert_eq;
1337
1338        use crate::{
1339            Parser,
1340            blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
1341            content::SubstitutionGroup,
1342            tests::prelude::*,
1343        };
1344
1345        #[test]
1346        fn empty() {
1347            let mut parser = Parser::default();
1348
1349            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1350                &BlockMetadata::new("****\n****"),
1351                &mut parser,
1352            )
1353            .unwrap();
1354
1355            let mi = maw.item.unwrap().clone();
1356
1357            assert_eq!(
1358                mi.item,
1359                CompoundDelimitedBlock {
1360                    blocks: &[],
1361                    context: "sidebar",
1362                    source: Span {
1363                        data: "****\n****",
1364                        line: 1,
1365                        col: 1,
1366                        offset: 0,
1367                    },
1368                    title_source: None,
1369                    title: None,
1370                    anchor: None,
1371                    anchor_reftext: None,
1372                    attrlist: None,
1373                }
1374            );
1375
1376            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1377            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1378            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1379            assert!(mi.item.declared_style().is_none());
1380            assert!(mi.item.nested_blocks().next().is_none());
1381            assert!(mi.item.id().is_none());
1382            assert!(mi.item.roles().is_empty());
1383            assert!(mi.item.options().is_empty());
1384            assert!(mi.item.title_source().is_none());
1385            assert!(mi.item.title().is_none());
1386            assert!(mi.item.anchor().is_none());
1387            assert!(mi.item.anchor_reftext().is_none());
1388            assert!(mi.item.attrlist().is_none());
1389            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1390        }
1391
1392        #[test]
1393        fn multiple_blocks() {
1394            let mut parser = Parser::default();
1395
1396            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1397                &BlockMetadata::new("****\nblock1\n\nblock2\n****"),
1398                &mut parser,
1399            )
1400            .unwrap();
1401
1402            let mi = maw.item.unwrap().clone();
1403
1404            assert_eq!(
1405                mi.item,
1406                CompoundDelimitedBlock {
1407                    blocks: &[
1408                        Block::Simple(SimpleBlock {
1409                            content: Content {
1410                                original: Span {
1411                                    data: "block1",
1412                                    line: 2,
1413                                    col: 1,
1414                                    offset: 5,
1415                                },
1416                                rendered: "block1",
1417                            },
1418                            source: Span {
1419                                data: "block1",
1420                                line: 2,
1421                                col: 1,
1422                                offset: 5,
1423                            },
1424                            title_source: None,
1425                            title: None,
1426                            anchor: None,
1427                            anchor_reftext: None,
1428                            attrlist: None,
1429                        },),
1430                        Block::Simple(SimpleBlock {
1431                            content: Content {
1432                                original: Span {
1433                                    data: "block2",
1434                                    line: 4,
1435                                    col: 1,
1436                                    offset: 13,
1437                                },
1438                                rendered: "block2",
1439                            },
1440                            source: Span {
1441                                data: "block2",
1442                                line: 4,
1443                                col: 1,
1444                                offset: 13,
1445                            },
1446                            title_source: None,
1447                            title: None,
1448                            anchor: None,
1449                            anchor_reftext: None,
1450                            attrlist: None,
1451                        },),
1452                    ],
1453                    context: "sidebar",
1454                    source: Span {
1455                        data: "****\nblock1\n\nblock2\n****",
1456                        line: 1,
1457                        col: 1,
1458                        offset: 0,
1459                    },
1460                    title_source: None,
1461                    title: None,
1462                    anchor: None,
1463                    anchor_reftext: None,
1464                    attrlist: None,
1465                }
1466            );
1467
1468            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1469            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1470            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1471            assert!(mi.item.declared_style().is_none());
1472            assert!(mi.item.id().is_none());
1473            assert!(mi.item.roles().is_empty());
1474            assert!(mi.item.options().is_empty());
1475            assert!(mi.item.title_source().is_none());
1476            assert!(mi.item.title().is_none());
1477            assert!(mi.item.anchor().is_none());
1478            assert!(mi.item.anchor_reftext().is_none());
1479            assert!(mi.item.attrlist().is_none());
1480            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1481
1482            let mut blocks = mi.item.nested_blocks();
1483            assert_eq!(
1484                blocks.next().unwrap(),
1485                &Block::Simple(SimpleBlock {
1486                    content: Content {
1487                        original: Span {
1488                            data: "block1",
1489                            line: 2,
1490                            col: 1,
1491                            offset: 5,
1492                        },
1493                        rendered: "block1",
1494                    },
1495                    source: Span {
1496                        data: "block1",
1497                        line: 2,
1498                        col: 1,
1499                        offset: 5,
1500                    },
1501                    title_source: None,
1502                    title: None,
1503                    anchor: None,
1504                    anchor_reftext: None,
1505                    attrlist: None,
1506                },)
1507            );
1508
1509            assert_eq!(
1510                blocks.next().unwrap(),
1511                &Block::Simple(SimpleBlock {
1512                    content: Content {
1513                        original: Span {
1514                            data: "block2",
1515                            line: 4,
1516                            col: 1,
1517                            offset: 13,
1518                        },
1519                        rendered: "block2",
1520                    },
1521                    source: Span {
1522                        data: "block2",
1523                        line: 4,
1524                        col: 1,
1525                        offset: 13,
1526                    },
1527                    title_source: None,
1528                    title: None,
1529                    anchor: None,
1530                    anchor_reftext: None,
1531                    attrlist: None,
1532                },)
1533            );
1534
1535            assert!(blocks.next().is_none());
1536        }
1537
1538        #[test]
1539        fn nested_blocks() {
1540            let mut parser = Parser::default();
1541
1542            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1543                &BlockMetadata::new("****\nblock1\n\n*****\nblock2\n*****\n****"),
1544                &mut parser,
1545            )
1546            .unwrap();
1547
1548            let mi = maw.item.unwrap().clone();
1549
1550            assert_eq!(
1551                mi.item,
1552                CompoundDelimitedBlock {
1553                    blocks: &[
1554                        Block::Simple(SimpleBlock {
1555                            content: Content {
1556                                original: Span {
1557                                    data: "block1",
1558                                    line: 2,
1559                                    col: 1,
1560                                    offset: 5,
1561                                },
1562                                rendered: "block1",
1563                            },
1564                            source: Span {
1565                                data: "block1",
1566                                line: 2,
1567                                col: 1,
1568                                offset: 5,
1569                            },
1570                            title_source: None,
1571                            title: None,
1572                            anchor: None,
1573                            anchor_reftext: None,
1574                            attrlist: None,
1575                        },),
1576                        Block::CompoundDelimited(CompoundDelimitedBlock {
1577                            blocks: &[Block::Simple(SimpleBlock {
1578                                content: Content {
1579                                    original: Span {
1580                                        data: "block2",
1581                                        line: 5,
1582                                        col: 1,
1583                                        offset: 19,
1584                                    },
1585                                    rendered: "block2",
1586                                },
1587                                source: Span {
1588                                    data: "block2",
1589                                    line: 5,
1590                                    col: 1,
1591                                    offset: 19,
1592                                },
1593                                title_source: None,
1594                                title: None,
1595                                anchor: None,
1596                                anchor_reftext: None,
1597                                attrlist: None,
1598                            },),],
1599                            context: "sidebar",
1600                            source: Span {
1601                                data: "*****\nblock2\n*****",
1602                                line: 4,
1603                                col: 1,
1604                                offset: 13,
1605                            },
1606                            title_source: None,
1607                            title: None,
1608                            anchor: None,
1609                            anchor_reftext: None,
1610                            attrlist: None,
1611                        })
1612                    ],
1613                    context: "sidebar",
1614                    source: Span {
1615                        data: "****\nblock1\n\n*****\nblock2\n*****\n****",
1616                        line: 1,
1617                        col: 1,
1618                        offset: 0,
1619                    },
1620                    title_source: None,
1621                    title: None,
1622                    anchor: None,
1623                    anchor_reftext: None,
1624                    attrlist: None,
1625                }
1626            );
1627
1628            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1629            assert_eq!(mi.item.raw_context().as_ref(), "sidebar");
1630            assert_eq!(mi.item.resolved_context().as_ref(), "sidebar");
1631            assert!(mi.item.declared_style().is_none());
1632            assert!(mi.item.id().is_none());
1633            assert!(mi.item.roles().is_empty());
1634            assert!(mi.item.options().is_empty());
1635            assert!(mi.item.title_source().is_none());
1636            assert!(mi.item.title().is_none());
1637            assert!(mi.item.anchor().is_none());
1638            assert!(mi.item.anchor_reftext().is_none());
1639            assert!(mi.item.attrlist().is_none());
1640            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1641
1642            let mut blocks = mi.item.nested_blocks();
1643            assert_eq!(
1644                blocks.next().unwrap(),
1645                &Block::Simple(SimpleBlock {
1646                    content: Content {
1647                        original: Span {
1648                            data: "block1",
1649                            line: 2,
1650                            col: 1,
1651                            offset: 5,
1652                        },
1653                        rendered: "block1",
1654                    },
1655                    source: Span {
1656                        data: "block1",
1657                        line: 2,
1658                        col: 1,
1659                        offset: 5,
1660                    },
1661                    title_source: None,
1662                    title: None,
1663                    anchor: None,
1664                    anchor_reftext: None,
1665                    attrlist: None,
1666                },)
1667            );
1668
1669            assert_eq!(
1670                blocks.next().unwrap(),
1671                &Block::CompoundDelimited(CompoundDelimitedBlock {
1672                    blocks: &[Block::Simple(SimpleBlock {
1673                        content: Content {
1674                            original: Span {
1675                                data: "block2",
1676                                line: 5,
1677                                col: 1,
1678                                offset: 19,
1679                            },
1680                            rendered: "block2",
1681                        },
1682                        source: Span {
1683                            data: "block2",
1684                            line: 5,
1685                            col: 1,
1686                            offset: 19,
1687                        },
1688                        title_source: None,
1689                        title: None,
1690                        anchor: None,
1691                        anchor_reftext: None,
1692                        attrlist: None,
1693                    },),],
1694                    context: "sidebar",
1695                    source: Span {
1696                        data: "*****\nblock2\n*****",
1697                        line: 4,
1698                        col: 1,
1699                        offset: 13,
1700                    },
1701                    title_source: None,
1702                    title: None,
1703                    anchor: None,
1704                    anchor_reftext: None,
1705                    attrlist: None,
1706                })
1707            );
1708
1709            assert!(blocks.next().is_none());
1710        }
1711    }
1712
1713    mod table {
1714        use crate::{Parser, blocks::metadata::BlockMetadata};
1715
1716        #[test]
1717        fn empty() {
1718            let mut parser = Parser::default();
1719            assert!(
1720                crate::blocks::CompoundDelimitedBlock::parse(
1721                    &BlockMetadata::new("|===\n|==="),
1722                    &mut parser
1723                )
1724                .is_none()
1725            );
1726
1727            let mut parser = Parser::default();
1728            assert!(
1729                crate::blocks::CompoundDelimitedBlock::parse(
1730                    &BlockMetadata::new(",===\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(":===\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("!===\n!==="),
1749                    &mut parser
1750                )
1751                .is_none()
1752            );
1753        }
1754
1755        #[test]
1756        fn multiple_lines() {
1757            let mut parser = Parser::default();
1758            assert!(
1759                crate::blocks::CompoundDelimitedBlock::parse(
1760                    &BlockMetadata::new("|===\nline1  \nline2\n|==="),
1761                    &mut parser
1762                )
1763                .is_none()
1764            );
1765
1766            let mut parser = Parser::default();
1767            assert!(
1768                crate::blocks::CompoundDelimitedBlock::parse(
1769                    &BlockMetadata::new(",===\nline1  \nline2\n,==="),
1770                    &mut parser
1771                )
1772                .is_none()
1773            );
1774
1775            let mut parser = Parser::default();
1776            assert!(
1777                crate::blocks::CompoundDelimitedBlock::parse(
1778                    &BlockMetadata::new(":===\nline1  \nline2\n:==="),
1779                    &mut parser
1780                )
1781                .is_none()
1782            );
1783
1784            let mut parser = Parser::default();
1785            assert!(
1786                crate::blocks::CompoundDelimitedBlock::parse(
1787                    &BlockMetadata::new("!===\nline1  \nline2\n!==="),
1788                    &mut parser
1789                )
1790                .is_none()
1791            );
1792        }
1793    }
1794
1795    mod pass {
1796        use crate::{Parser, blocks::metadata::BlockMetadata};
1797
1798        #[test]
1799        fn empty() {
1800            let mut parser = Parser::default();
1801            assert!(
1802                crate::blocks::CompoundDelimitedBlock::parse(
1803                    &BlockMetadata::new("++++\n++++"),
1804                    &mut parser
1805                )
1806                .is_none()
1807            );
1808        }
1809
1810        #[test]
1811        fn multiple_lines() {
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 quote {
1824        use pretty_assertions_sorted::assert_eq;
1825
1826        use crate::{
1827            Parser,
1828            blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
1829            content::SubstitutionGroup,
1830            tests::prelude::*,
1831        };
1832
1833        #[test]
1834        fn empty() {
1835            let mut parser = Parser::default();
1836
1837            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1838                &BlockMetadata::new("____\n____"),
1839                &mut parser,
1840            )
1841            .unwrap();
1842
1843            let mi = maw.item.unwrap().clone();
1844
1845            assert_eq!(
1846                mi.item,
1847                CompoundDelimitedBlock {
1848                    blocks: &[],
1849                    context: "quote",
1850                    source: Span {
1851                        data: "____\n____",
1852                        line: 1,
1853                        col: 1,
1854                        offset: 0,
1855                    },
1856                    title_source: None,
1857                    title: None,
1858                    anchor: None,
1859                    anchor_reftext: None,
1860                    attrlist: None,
1861                }
1862            );
1863
1864            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1865            assert_eq!(mi.item.raw_context().as_ref(), "quote");
1866            assert_eq!(mi.item.resolved_context().as_ref(), "quote");
1867            assert!(mi.item.declared_style().is_none());
1868            assert!(mi.item.nested_blocks().next().is_none());
1869            assert!(mi.item.id().is_none());
1870            assert!(mi.item.roles().is_empty());
1871            assert!(mi.item.options().is_empty());
1872            assert!(mi.item.title_source().is_none());
1873            assert!(mi.item.title().is_none());
1874            assert!(mi.item.anchor().is_none());
1875            assert!(mi.item.anchor_reftext().is_none());
1876            assert!(mi.item.attrlist().is_none());
1877            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1878        }
1879
1880        #[test]
1881        fn multiple_blocks() {
1882            let mut parser = Parser::default();
1883
1884            let maw = crate::blocks::CompoundDelimitedBlock::parse(
1885                &BlockMetadata::new("____\nblock1\n\nblock2\n____"),
1886                &mut parser,
1887            )
1888            .unwrap();
1889
1890            let mi = maw.item.unwrap().clone();
1891
1892            assert_eq!(
1893                mi.item,
1894                CompoundDelimitedBlock {
1895                    blocks: &[
1896                        Block::Simple(SimpleBlock {
1897                            content: Content {
1898                                original: Span {
1899                                    data: "block1",
1900                                    line: 2,
1901                                    col: 1,
1902                                    offset: 5,
1903                                },
1904                                rendered: "block1",
1905                            },
1906                            source: Span {
1907                                data: "block1",
1908                                line: 2,
1909                                col: 1,
1910                                offset: 5,
1911                            },
1912                            title_source: None,
1913                            title: None,
1914                            anchor: None,
1915                            anchor_reftext: None,
1916                            attrlist: None,
1917                        },),
1918                        Block::Simple(SimpleBlock {
1919                            content: Content {
1920                                original: Span {
1921                                    data: "block2",
1922                                    line: 4,
1923                                    col: 1,
1924                                    offset: 13,
1925                                },
1926                                rendered: "block2",
1927                            },
1928                            source: Span {
1929                                data: "block2",
1930                                line: 4,
1931                                col: 1,
1932                                offset: 13,
1933                            },
1934                            title_source: None,
1935                            title: None,
1936                            anchor: None,
1937                            anchor_reftext: None,
1938                            attrlist: None,
1939                        },),
1940                    ],
1941                    context: "quote",
1942                    source: Span {
1943                        data: "____\nblock1\n\nblock2\n____",
1944                        line: 1,
1945                        col: 1,
1946                        offset: 0,
1947                    },
1948                    title_source: None,
1949                    title: None,
1950                    anchor: None,
1951                    anchor_reftext: None,
1952                    attrlist: None,
1953                }
1954            );
1955
1956            assert_eq!(mi.item.content_model(), ContentModel::Compound);
1957            assert_eq!(mi.item.raw_context().as_ref(), "quote");
1958            assert_eq!(mi.item.resolved_context().as_ref(), "quote");
1959            assert!(mi.item.declared_style().is_none());
1960            assert!(mi.item.id().is_none());
1961            assert!(mi.item.roles().is_empty());
1962            assert!(mi.item.options().is_empty());
1963            assert!(mi.item.title_source().is_none());
1964            assert!(mi.item.title().is_none());
1965            assert!(mi.item.anchor().is_none());
1966            assert!(mi.item.anchor_reftext().is_none());
1967            assert!(mi.item.attrlist().is_none());
1968            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
1969
1970            let mut blocks = mi.item.nested_blocks();
1971            assert_eq!(
1972                blocks.next().unwrap(),
1973                &Block::Simple(SimpleBlock {
1974                    content: Content {
1975                        original: Span {
1976                            data: "block1",
1977                            line: 2,
1978                            col: 1,
1979                            offset: 5,
1980                        },
1981                        rendered: "block1",
1982                    },
1983                    source: Span {
1984                        data: "block1",
1985                        line: 2,
1986                        col: 1,
1987                        offset: 5,
1988                    },
1989                    title_source: None,
1990                    title: None,
1991                    anchor: None,
1992                    anchor_reftext: None,
1993                    attrlist: None,
1994                },)
1995            );
1996
1997            assert_eq!(
1998                blocks.next().unwrap(),
1999                &Block::Simple(SimpleBlock {
2000                    content: Content {
2001                        original: Span {
2002                            data: "block2",
2003                            line: 4,
2004                            col: 1,
2005                            offset: 13,
2006                        },
2007                        rendered: "block2",
2008                    },
2009                    source: Span {
2010                        data: "block2",
2011                        line: 4,
2012                        col: 1,
2013                        offset: 13,
2014                    },
2015                    title_source: None,
2016                    title: None,
2017                    anchor: None,
2018                    anchor_reftext: None,
2019                    attrlist: None,
2020                },)
2021            );
2022
2023            assert!(blocks.next().is_none());
2024        }
2025
2026        #[test]
2027        fn nested_blocks() {
2028            let mut parser = Parser::default();
2029
2030            let maw = crate::blocks::CompoundDelimitedBlock::parse(
2031                &BlockMetadata::new("____\nblock1\n\n_____\nblock2\n_____\n____"),
2032                &mut parser,
2033            )
2034            .unwrap();
2035
2036            let mi = maw.item.unwrap().clone();
2037
2038            assert_eq!(
2039                mi.item,
2040                CompoundDelimitedBlock {
2041                    blocks: &[
2042                        Block::Simple(SimpleBlock {
2043                            content: Content {
2044                                original: Span {
2045                                    data: "block1",
2046                                    line: 2,
2047                                    col: 1,
2048                                    offset: 5,
2049                                },
2050                                rendered: "block1",
2051                            },
2052                            source: Span {
2053                                data: "block1",
2054                                line: 2,
2055                                col: 1,
2056                                offset: 5,
2057                            },
2058                            title_source: None,
2059                            title: None,
2060                            anchor: None,
2061                            anchor_reftext: None,
2062                            attrlist: None,
2063                        },),
2064                        Block::CompoundDelimited(CompoundDelimitedBlock {
2065                            blocks: &[Block::Simple(SimpleBlock {
2066                                content: Content {
2067                                    original: Span {
2068                                        data: "block2",
2069                                        line: 5,
2070                                        col: 1,
2071                                        offset: 19,
2072                                    },
2073                                    rendered: "block2",
2074                                },
2075                                source: Span {
2076                                    data: "block2",
2077                                    line: 5,
2078                                    col: 1,
2079                                    offset: 19,
2080                                },
2081                                title_source: None,
2082                                title: None,
2083                                anchor: None,
2084                                anchor_reftext: None,
2085                                attrlist: None,
2086                            },),],
2087                            context: "quote",
2088                            source: Span {
2089                                data: "_____\nblock2\n_____",
2090                                line: 4,
2091                                col: 1,
2092                                offset: 13,
2093                            },
2094                            title_source: None,
2095                            title: None,
2096                            anchor: None,
2097                            anchor_reftext: None,
2098                            attrlist: None,
2099                        })
2100                    ],
2101                    context: "quote",
2102                    source: Span {
2103                        data: "____\nblock1\n\n_____\nblock2\n_____\n____",
2104                        line: 1,
2105                        col: 1,
2106                        offset: 0,
2107                    },
2108                    title_source: None,
2109                    title: None,
2110                    anchor: None,
2111                    anchor_reftext: None,
2112                    attrlist: None,
2113                }
2114            );
2115
2116            assert_eq!(mi.item.content_model(), ContentModel::Compound);
2117            assert_eq!(mi.item.raw_context().as_ref(), "quote");
2118            assert_eq!(mi.item.resolved_context().as_ref(), "quote");
2119            assert!(mi.item.declared_style().is_none());
2120            assert!(mi.item.id().is_none());
2121            assert!(mi.item.roles().is_empty());
2122            assert!(mi.item.options().is_empty());
2123            assert!(mi.item.title_source().is_none());
2124            assert!(mi.item.title().is_none());
2125            assert!(mi.item.anchor().is_none());
2126            assert!(mi.item.anchor_reftext().is_none());
2127            assert!(mi.item.attrlist().is_none());
2128            assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Normal);
2129
2130            let mut blocks = mi.item.nested_blocks();
2131            assert_eq!(
2132                blocks.next().unwrap(),
2133                &Block::Simple(SimpleBlock {
2134                    content: Content {
2135                        original: Span {
2136                            data: "block1",
2137                            line: 2,
2138                            col: 1,
2139                            offset: 5,
2140                        },
2141                        rendered: "block1",
2142                    },
2143                    source: Span {
2144                        data: "block1",
2145                        line: 2,
2146                        col: 1,
2147                        offset: 5,
2148                    },
2149                    title_source: None,
2150                    title: None,
2151                    anchor: None,
2152                    anchor_reftext: None,
2153                    attrlist: None,
2154                },)
2155            );
2156
2157            assert_eq!(
2158                blocks.next().unwrap(),
2159                &Block::CompoundDelimited(CompoundDelimitedBlock {
2160                    blocks: &[Block::Simple(SimpleBlock {
2161                        content: Content {
2162                            original: Span {
2163                                data: "block2",
2164                                line: 5,
2165                                col: 1,
2166                                offset: 19,
2167                            },
2168                            rendered: "block2",
2169                        },
2170                        source: Span {
2171                            data: "block2",
2172                            line: 5,
2173                            col: 1,
2174                            offset: 19,
2175                        },
2176                        title_source: None,
2177                        title: None,
2178                        anchor: None,
2179                        anchor_reftext: None,
2180                        attrlist: None,
2181                    },),],
2182                    context: "quote",
2183                    source: Span {
2184                        data: "_____\nblock2\n_____",
2185                        line: 4,
2186                        col: 1,
2187                        offset: 13,
2188                    },
2189                    title_source: None,
2190                    title: None,
2191                    anchor: None,
2192                    anchor_reftext: None,
2193                    attrlist: None,
2194                })
2195            );
2196
2197            assert!(blocks.next().is_none());
2198        }
2199    }
2200
2201    #[test]
2202    fn impl_debug() {
2203        let mut parser = Parser::default();
2204
2205        let cdb = crate::blocks::CompoundDelimitedBlock::parse(
2206            &BlockMetadata::new("====\nblock1\n\nblock2\n===="),
2207            &mut parser,
2208        )
2209        .unwrap()
2210        .unwrap_if_no_warnings()
2211        .unwrap()
2212        .item;
2213
2214        assert_eq!(
2215            format!("{cdb:#?}"),
2216            r#"CompoundDelimitedBlock {
2217    blocks: &[
2218        Block::Simple(
2219            SimpleBlock {
2220                content: Content {
2221                    original: Span {
2222                        data: "block1",
2223                        line: 2,
2224                        col: 1,
2225                        offset: 5,
2226                    },
2227                    rendered: "block1",
2228                },
2229                source: Span {
2230                    data: "block1",
2231                    line: 2,
2232                    col: 1,
2233                    offset: 5,
2234                },
2235                title_source: None,
2236                title: None,
2237                anchor: None,
2238                anchor_reftext: None,
2239                attrlist: None,
2240            },
2241        ),
2242        Block::Simple(
2243            SimpleBlock {
2244                content: Content {
2245                    original: Span {
2246                        data: "block2",
2247                        line: 4,
2248                        col: 1,
2249                        offset: 13,
2250                    },
2251                    rendered: "block2",
2252                },
2253                source: Span {
2254                    data: "block2",
2255                    line: 4,
2256                    col: 1,
2257                    offset: 13,
2258                },
2259                title_source: None,
2260                title: None,
2261                anchor: None,
2262                anchor_reftext: None,
2263                attrlist: None,
2264            },
2265        ),
2266    ],
2267    context: "example",
2268    source: Span {
2269        data: "====\nblock1\n\nblock2\n====",
2270        line: 1,
2271        col: 1,
2272        offset: 0,
2273    },
2274    title_source: None,
2275    title: None,
2276    anchor: None,
2277    anchor_reftext: None,
2278    attrlist: None,
2279}"#
2280        );
2281    }
2282}