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