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