asciidoc_parser/blocks/
compound_delimited.rs

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