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