Skip to main content

asciidoc_parser/blocks/
compound_delimited.rs

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