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