1use crate::{
2 HasSpan, Parser, Span,
3 attributes::Attrlist,
4 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
5 content::{Content, SubstitutionGroup},
6 span::MatchedItem,
7 strings::CowStr,
8 warnings::{MatchAndWarnings, Warning, WarningType},
9};
10
11#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct RawDelimitedBlock<'src> {
24 content: Content<'src>,
25 content_model: ContentModel,
26 context: CowStr<'src>,
27 source: Span<'src>,
28 title_source: Option<Span<'src>>,
29 title: Option<String>,
30 anchor: Option<Span<'src>>,
31 anchor_reftext: Option<Span<'src>>,
32 attrlist: Option<Attrlist<'src>>,
33 substitution_group: SubstitutionGroup,
34}
35
36impl<'src> RawDelimitedBlock<'src> {
37 pub(crate) fn is_valid_delimiter(line: &Span<'src>) -> bool {
38 let data = line.data();
39
40 if data.len() >= 4 {
45 if data.starts_with("////") {
46 data.split_at(4).1.chars().all(|c| c == '/')
47 } else if data.starts_with("----") {
48 data.split_at(4).1.chars().all(|c| c == '-')
49 } else if data.starts_with("....") {
50 data.split_at(4).1.chars().all(|c| c == '.')
51 } else if data.starts_with("++++") {
52 data.split_at(4).1.chars().all(|c| c == '+')
53 } else {
54 false
55 }
56 } else {
57 false
58 }
59 }
60
61 pub(crate) fn parse(
62 metadata: &BlockMetadata<'src>,
63 parser: &mut Parser,
64 ) -> Option<MatchAndWarnings<'src, Option<MatchedItem<'src, Self>>>> {
65 let delimiter = metadata.block_start.take_normalized_line();
66
67 if delimiter.item.len() < 4 {
68 return None;
69 }
70
71 let (content_model, context, mut substitution_group) =
72 match delimiter.item.data().split_at(4).0 {
73 "////" => (ContentModel::Raw, "comment", SubstitutionGroup::None),
74 "----" => (
75 ContentModel::Verbatim,
76 "listing",
77 SubstitutionGroup::Verbatim,
78 ),
79 "...." => (
80 ContentModel::Verbatim,
81 "literal",
82 SubstitutionGroup::Verbatim,
83 ),
84 "++++" => (ContentModel::Raw, "pass", SubstitutionGroup::Pass),
85 _ => return None,
86 };
87
88 if !Self::is_valid_delimiter(&delimiter.item) {
89 return None;
90 }
91
92 let content_start = delimiter.after;
93 let mut next = content_start;
94
95 while !next.is_empty() {
96 let line = next.take_normalized_line();
97 if line.item.data() == delimiter.item.data() {
98 let content = content_start.trim_remainder(next).trim_trailing_line_end();
99
100 let mut content: Content<'src> = content.into();
101
102 substitution_group =
103 substitution_group.override_via_attrlist(metadata.attrlist.as_ref());
104
105 substitution_group.apply(&mut content, parser, metadata.attrlist.as_ref());
106
107 return Some(MatchAndWarnings {
108 item: Some(MatchedItem {
109 item: Self {
110 content,
111 content_model,
112 context: context.into(),
113 source: metadata
114 .source
115 .trim_remainder(line.after)
116 .trim_trailing_line_end(),
117 title_source: metadata.title_source,
118 title: metadata.title.clone(),
119 anchor: metadata.anchor,
120 anchor_reftext: metadata.anchor_reftext,
121 attrlist: metadata.attrlist.clone(),
122 substitution_group,
123 },
124 after: line.after,
125 }),
126 warnings: vec![],
127 });
128 }
129
130 next = line.after;
131 }
132
133 let content = content_start.trim_remainder(next).trim_trailing_line_end();
134
135 Some(MatchAndWarnings {
136 item: Some(MatchedItem {
137 item: Self {
138 content: content.into(),
139 content_model,
140 context: context.into(),
141 source: metadata
142 .source
143 .trim_remainder(next)
144 .trim_trailing_line_end(),
145 title_source: metadata.title_source,
146 title: metadata.title.clone(),
147 anchor: metadata.anchor,
148 anchor_reftext: metadata.anchor_reftext,
149 attrlist: metadata.attrlist.clone(),
150 substitution_group,
151 },
152 after: next,
153 }),
154 warnings: vec![Warning {
155 source: delimiter.item,
156 warning: WarningType::UnterminatedDelimitedBlock,
157 }],
158 })
159 }
160
161 pub fn content(&self) -> &Content<'src> {
163 &self.content
164 }
165}
166
167impl<'src> IsBlock<'src> for RawDelimitedBlock<'src> {
168 fn content_model(&self) -> ContentModel {
169 self.content_model
170 }
171
172 fn raw_context(&self) -> CowStr<'src> {
173 self.context.clone()
174 }
175
176 fn title_source(&'src self) -> Option<Span<'src>> {
177 self.title_source
178 }
179
180 fn title(&self) -> Option<&str> {
181 self.title.as_deref()
182 }
183
184 fn anchor(&'src self) -> Option<Span<'src>> {
185 self.anchor
186 }
187
188 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
189 self.anchor_reftext
190 }
191
192 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
193 self.attrlist.as_ref()
194 }
195
196 fn substitution_group(&'src self) -> SubstitutionGroup {
197 self.substitution_group.clone()
198 }
199}
200
201impl<'src> HasSpan<'src> for RawDelimitedBlock<'src> {
202 fn span(&self) -> Span<'src> {
203 self.source
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 #![allow(clippy::unwrap_used)]
210
211 mod is_valid_delimiter {
212 use crate::blocks::RawDelimitedBlock;
213
214 #[test]
215 fn comment() {
216 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
217 "////"
218 )));
219 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
220 "/////"
221 )));
222 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
223 "/////////"
224 )));
225
226 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
227 "///"
228 )));
229 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
230 "//-/"
231 )));
232 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
233 "////-"
234 )));
235 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
236 "//////////x"
237 )));
238 }
239
240 #[test]
241 fn example() {
242 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
243 "===="
244 )));
245 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
246 "====="
247 )));
248 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
249 "==="
250 )));
251 }
252
253 #[test]
254 fn listing() {
255 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
256 "----"
257 )));
258 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
259 "-----"
260 )));
261 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
262 "---------"
263 )));
264
265 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
266 "---"
267 )));
268 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
269 "--/-"
270 )));
271 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
272 "----/"
273 )));
274 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
275 "----------x"
276 )));
277 }
278
279 #[test]
280 fn literal() {
281 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
282 "...."
283 )));
284 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
285 "....."
286 )));
287 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
288 "........."
289 )));
290
291 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
292 "..."
293 )));
294 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
295 "../."
296 )));
297 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
298 "..../"
299 )));
300 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
301 "..........x"
302 )));
303 }
304
305 #[test]
306 fn sidebar() {
307 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
308 "****"
309 )));
310 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
311 "*****"
312 )));
313 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
314 "***"
315 )));
316 }
317
318 #[test]
319 fn table() {
320 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
321 "|==="
322 )));
323 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
324 ",==="
325 )));
326 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
327 ":==="
328 )));
329 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
330 "!==="
331 )));
332 }
333
334 #[test]
335 fn pass() {
336 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
337 "++++"
338 )));
339 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
340 "+++++"
341 )));
342 assert!(RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
343 "+++++++++"
344 )));
345
346 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
347 "+++"
348 )));
349 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
350 "++/+"
351 )));
352 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
353 "++++/"
354 )));
355 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
356 "++++++++++x"
357 )));
358 }
359
360 #[test]
361 fn quote() {
362 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
363 "____"
364 )));
365 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
366 "_____"
367 )));
368 assert!(!RawDelimitedBlock::is_valid_delimiter(&crate::Span::new(
369 "___"
370 )));
371 }
372 }
373
374 mod parse {
375 use pretty_assertions_sorted::assert_eq;
376
377 use crate::{
378 Parser, blocks::metadata::BlockMetadata, tests::prelude::*, warnings::WarningType,
379 };
380
381 #[test]
382 fn err_invalid_delimiter() {
383 let mut parser = Parser::default();
384 assert!(
385 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new(""), &mut parser)
386 .is_none()
387 );
388
389 let mut parser = Parser::default();
390 assert!(
391 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("..."), &mut parser)
392 .is_none()
393 );
394
395 let mut parser = Parser::default();
396 assert!(
397 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("++++x"), &mut parser)
398 .is_none()
399 );
400
401 let mut parser = Parser::default();
402 assert!(
403 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("____x"), &mut parser)
404 .is_none()
405 );
406
407 let mut parser = Parser::default();
408 assert!(
409 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("====x"), &mut parser)
410 .is_none()
411 );
412
413 let mut parser = Parser::default();
414 assert!(
415 crate::blocks::RawDelimitedBlock::parse(&BlockMetadata::new("==\n=="), &mut parser)
416 .is_none()
417 );
418 }
419
420 #[test]
421 fn err_unterminated() {
422 let mut parser = Parser::default();
423
424 let maw = crate::blocks::RawDelimitedBlock::parse(
425 &BlockMetadata::new("....\nblah blah blah"),
426 &mut parser,
427 )
428 .unwrap();
429
430 assert_eq!(
431 maw.warnings,
432 vec![Warning {
433 source: Span {
434 data: "....",
435 line: 1,
436 col: 1,
437 offset: 0,
438 },
439 warning: WarningType::UnterminatedDelimitedBlock,
440 }]
441 );
442 }
443 }
444
445 mod comment {
446 use pretty_assertions_sorted::assert_eq;
447
448 use crate::{
449 Parser,
450 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
451 content::SubstitutionGroup,
452 tests::prelude::*,
453 };
454
455 #[test]
456 fn empty() {
457 let mut parser = Parser::default();
458 let maw = crate::blocks::RawDelimitedBlock::parse(
459 &BlockMetadata::new("////\n////"),
460 &mut parser,
461 )
462 .unwrap();
463
464 let mi = maw.item.unwrap().clone();
465
466 assert_eq!(
467 mi.item,
468 RawDelimitedBlock {
469 content: Content {
470 original: Span {
471 data: "",
472 line: 2,
473 col: 1,
474 offset: 5,
475 },
476 rendered: "",
477 },
478 content_model: ContentModel::Raw,
479 context: "comment",
480 source: Span {
481 data: "////\n////",
482 line: 1,
483 col: 1,
484 offset: 0,
485 },
486 title_source: None,
487 title: None,
488 anchor: None,
489 anchor_reftext: None,
490 attrlist: None,
491 substitution_group: SubstitutionGroup::None,
492 }
493 );
494
495 assert_eq!(mi.item.content_model(), ContentModel::Raw);
496 assert_eq!(mi.item.raw_context().as_ref(), "comment");
497 assert_eq!(mi.item.resolved_context().as_ref(), "comment");
498 assert!(mi.item.declared_style().is_none());
499 assert!(mi.item.content().is_empty());
500 assert!(mi.item.id().is_none());
501 assert!(mi.item.roles().is_empty());
502 assert!(mi.item.options().is_empty());
503 assert!(mi.item.title_source().is_none());
504 assert!(mi.item.title().is_none());
505 assert!(mi.item.anchor().is_none());
506 assert!(mi.item.anchor_reftext().is_none());
507 assert!(mi.item.attrlist().is_none());
508 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::None);
509 }
510
511 #[test]
512 fn multiple_lines() {
513 let mut parser = Parser::default();
514
515 let maw = crate::blocks::RawDelimitedBlock::parse(
516 &BlockMetadata::new("////\nline1 \nline2\n////"),
517 &mut parser,
518 )
519 .unwrap();
520
521 let mi = maw.item.unwrap().clone();
522
523 assert_eq!(
524 mi.item,
525 RawDelimitedBlock {
526 content: Content {
527 original: Span {
528 data: "line1 \nline2",
529 line: 2,
530 col: 1,
531 offset: 5,
532 },
533 rendered: "line1 \nline2",
534 },
535 content_model: ContentModel::Raw,
536 context: "comment",
537 source: Span {
538 data: "////\nline1 \nline2\n////",
539 line: 1,
540 col: 1,
541 offset: 0,
542 },
543 title_source: None,
544 title: None,
545 anchor: None,
546 anchor_reftext: None,
547 attrlist: None,
548 substitution_group: SubstitutionGroup::None,
549 }
550 );
551
552 assert_eq!(mi.item.content_model(), ContentModel::Raw);
553 assert_eq!(mi.item.raw_context().as_ref(), "comment");
554 assert_eq!(mi.item.resolved_context().as_ref(), "comment");
555 assert!(mi.item.declared_style().is_none());
556 assert!(mi.item.id().is_none());
557 assert!(mi.item.roles().is_empty());
558 assert!(mi.item.options().is_empty());
559 assert!(mi.item.title_source().is_none());
560 assert!(mi.item.title().is_none());
561 assert!(mi.item.anchor().is_none());
562 assert!(mi.item.anchor_reftext().is_none());
563 assert!(mi.item.attrlist().is_none());
564 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::None);
565
566 assert_eq!(
567 mi.item.content(),
568 Content {
569 original: Span {
570 data: "line1 \nline2",
571 line: 2,
572 col: 1,
573 offset: 5,
574 },
575 rendered: "line1 \nline2",
576 }
577 );
578 }
579
580 #[test]
581 fn ignores_delimiter_prefix() {
582 let mut parser = Parser::default();
583
584 let maw = crate::blocks::RawDelimitedBlock::parse(
585 &BlockMetadata::new("////\nline1 \n/////\nline2\n////"),
586 &mut parser,
587 )
588 .unwrap();
589
590 let mi = maw.item.unwrap().clone();
591
592 assert_eq!(
593 mi.item,
594 RawDelimitedBlock {
595 content: Content {
596 original: Span {
597 data: "line1 \n/////\nline2",
598 line: 2,
599 col: 1,
600 offset: 5,
601 },
602 rendered: "line1 \n/////\nline2",
603 },
604 content_model: ContentModel::Raw,
605 context: "comment",
606 source: Span {
607 data: "////\nline1 \n/////\nline2\n////",
608 line: 1,
609 col: 1,
610 offset: 0,
611 },
612 title_source: None,
613 title: None,
614 anchor: None,
615 anchor_reftext: None,
616 attrlist: None,
617 substitution_group: SubstitutionGroup::None,
618 }
619 );
620
621 assert_eq!(mi.item.content_model(), ContentModel::Raw);
622 assert_eq!(mi.item.raw_context().as_ref(), "comment");
623 assert_eq!(mi.item.resolved_context().as_ref(), "comment");
624 assert!(mi.item.declared_style().is_none());
625 assert!(mi.item.id().is_none());
626 assert!(mi.item.roles().is_empty());
627 assert!(mi.item.options().is_empty());
628 assert!(mi.item.title_source().is_none());
629 assert!(mi.item.title().is_none());
630 assert!(mi.item.anchor().is_none());
631 assert!(mi.item.anchor_reftext().is_none());
632 assert!(mi.item.attrlist().is_none());
633 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::None);
634
635 assert_eq!(
636 mi.item.content(),
637 Content {
638 original: Span {
639 data: "line1 \n/////\nline2",
640 line: 2,
641 col: 1,
642 offset: 5,
643 },
644 rendered: "line1 \n/////\nline2",
645 }
646 );
647 }
648 }
649
650 mod example {
651 use crate::{Parser, blocks::metadata::BlockMetadata};
652
653 #[test]
654 fn empty() {
655 let mut parser = Parser::default();
656 assert!(
657 crate::blocks::RawDelimitedBlock::parse(
658 &BlockMetadata::new("====\n===="),
659 &mut parser
660 )
661 .is_none()
662 );
663 }
664
665 #[test]
666 fn multiple_lines() {
667 let mut parser = Parser::default();
668 assert!(
669 crate::blocks::RawDelimitedBlock::parse(
670 &BlockMetadata::new("====\nline1 \nline2\n===="),
671 &mut parser
672 )
673 .is_none()
674 );
675 }
676 }
677
678 mod listing {
679 use pretty_assertions_sorted::assert_eq;
680
681 use crate::{
682 Parser,
683 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
684 content::{SubstitutionGroup, SubstitutionStep},
685 tests::prelude::*,
686 };
687
688 #[test]
689 fn empty() {
690 let mut parser = Parser::default();
691
692 let maw = crate::blocks::RawDelimitedBlock::parse(
693 &BlockMetadata::new("----\n----"),
694 &mut parser,
695 )
696 .unwrap();
697
698 let mi = maw.item.unwrap().clone();
699
700 assert_eq!(
701 mi.item,
702 RawDelimitedBlock {
703 content: Content {
704 original: Span {
705 data: "",
706 line: 2,
707 col: 1,
708 offset: 5,
709 },
710 rendered: "",
711 },
712 content_model: ContentModel::Verbatim,
713 context: "listing",
714 source: Span {
715 data: "----\n----",
716 line: 1,
717 col: 1,
718 offset: 0,
719 },
720 title_source: None,
721 title: None,
722 anchor: None,
723 anchor_reftext: None,
724 attrlist: None,
725 substitution_group: SubstitutionGroup::Verbatim,
726 }
727 );
728
729 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
730 assert_eq!(mi.item.raw_context().as_ref(), "listing");
731 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
732 assert!(mi.item.declared_style().is_none());
733 assert!(mi.item.content().is_empty());
734 assert!(mi.item.id().is_none());
735 assert!(mi.item.roles().is_empty());
736 assert!(mi.item.options().is_empty());
737 assert!(mi.item.title_source().is_none());
738 assert!(mi.item.title().is_none());
739 assert!(mi.item.anchor().is_none());
740 assert!(mi.item.anchor_reftext().is_none());
741 assert!(mi.item.attrlist().is_none());
742 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Verbatim);
743 }
744
745 #[test]
746 fn multiple_lines() {
747 let mut parser = Parser::default();
748
749 let maw = crate::blocks::RawDelimitedBlock::parse(
750 &BlockMetadata::new("----\nline1 \nline2\n----"),
751 &mut parser,
752 )
753 .unwrap();
754
755 let mi = maw.item.unwrap().clone();
756
757 assert_eq!(
758 mi.item,
759 RawDelimitedBlock {
760 content: Content {
761 original: Span {
762 data: "line1 \nline2",
763 line: 2,
764 col: 1,
765 offset: 5,
766 },
767 rendered: "line1 \nline2",
768 },
769 content_model: ContentModel::Verbatim,
770 context: "listing",
771 source: Span {
772 data: "----\nline1 \nline2\n----",
773 line: 1,
774 col: 1,
775 offset: 0,
776 },
777 title_source: None,
778 title: None,
779 anchor: None,
780 anchor_reftext: None,
781 attrlist: None,
782 substitution_group: SubstitutionGroup::Verbatim,
783 }
784 );
785
786 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
787 assert_eq!(mi.item.raw_context().as_ref(), "listing");
788 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
789 assert!(mi.item.declared_style().is_none());
790 assert!(mi.item.id().is_none());
791 assert!(mi.item.roles().is_empty());
792 assert!(mi.item.options().is_empty());
793 assert!(mi.item.title_source().is_none());
794 assert!(mi.item.title().is_none());
795 assert!(mi.item.anchor().is_none());
796 assert!(mi.item.anchor_reftext().is_none());
797 assert!(mi.item.attrlist().is_none());
798 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Verbatim);
799
800 assert_eq!(
801 mi.item.content(),
802 Content {
803 original: Span {
804 data: "line1 \nline2",
805 line: 2,
806 col: 1,
807 offset: 5,
808 },
809 rendered: "line1 \nline2",
810 }
811 );
812 }
813
814 #[test]
815 fn overrides_sub_group_via_subs_attribute() {
816 let mut parser = Parser::default();
817
818 let maw = crate::blocks::RawDelimitedBlock::parse(
819 &BlockMetadata::new("[subs=quotes]\n----\nline1 < *line2*\n----"),
820 &mut parser,
821 )
822 .unwrap();
823
824 let mi = maw.item.unwrap().clone();
825
826 assert_eq!(
827 mi.item,
828 RawDelimitedBlock {
829 content: Content {
830 original: Span {
831 data: "line1 < *line2*",
832 line: 3,
833 col: 1,
834 offset: 19,
835 },
836 rendered: "line1 < <strong>line2</strong>",
837 },
838 content_model: ContentModel::Verbatim,
839 context: "listing",
840 source: Span {
841 data: "[subs=quotes]\n----\nline1 < *line2*\n----",
842 line: 1,
843 col: 1,
844 offset: 0,
845 },
846 title_source: None,
847 title: None,
848 anchor: None,
849 anchor_reftext: None,
850 attrlist: Some(Attrlist {
851 attributes: &[ElementAttribute {
852 name: Some("subs"),
853 value: "quotes",
854 shorthand_items: &[],
855 },],
856 anchor: None,
857 source: Span {
858 data: "subs=quotes",
859 line: 1,
860 col: 2,
861 offset: 1,
862 },
863 },),
864 substitution_group: SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes]),
865 }
866 );
867
868 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
869 assert_eq!(mi.item.raw_context().as_ref(), "listing");
870 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
871 assert!(mi.item.declared_style().is_none());
872 assert!(mi.item.id().is_none());
873 assert!(mi.item.roles().is_empty());
874 assert!(mi.item.options().is_empty());
875 assert!(mi.item.title_source().is_none());
876 assert!(mi.item.title().is_none());
877 assert!(mi.item.anchor().is_none());
878 assert!(mi.item.anchor_reftext().is_none());
879
880 assert_eq!(
881 mi.item.attrlist().unwrap(),
882 Attrlist {
883 attributes: &[ElementAttribute {
884 name: Some("subs"),
885 value: "quotes",
886 shorthand_items: &[],
887 },],
888 anchor: None,
889 source: Span {
890 data: "subs=quotes",
891 line: 1,
892 col: 2,
893 offset: 1,
894 },
895 }
896 );
897
898 assert_eq!(
899 mi.item.substitution_group(),
900 SubstitutionGroup::Custom(vec![SubstitutionStep::Quotes])
901 );
902
903 assert_eq!(
904 mi.item.content(),
905 Content {
906 original: Span {
907 data: "line1 < *line2*",
908 line: 3,
909 col: 1,
910 offset: 19,
911 },
912 rendered: "line1 < <strong>line2</strong>",
913 }
914 );
915 }
916
917 #[test]
918 fn ignores_delimiter_prefix() {
919 let mut parser = Parser::default();
920
921 let maw = crate::blocks::RawDelimitedBlock::parse(
922 &BlockMetadata::new("----\nline1 \n-----\nline2\n----"),
923 &mut parser,
924 )
925 .unwrap();
926
927 let mi = maw.item.unwrap().clone();
928
929 assert_eq!(
930 mi.item,
931 RawDelimitedBlock {
932 content: Content {
933 original: Span {
934 data: "line1 \n-----\nline2",
935 line: 2,
936 col: 1,
937 offset: 5,
938 },
939 rendered: "line1 \n-----\nline2",
940 },
941 content_model: ContentModel::Verbatim,
942 context: "listing",
943 source: Span {
944 data: "----\nline1 \n-----\nline2\n----",
945 line: 1,
946 col: 1,
947 offset: 0,
948 },
949 title_source: None,
950 title: None,
951 anchor: None,
952 anchor_reftext: None,
953 attrlist: None,
954 substitution_group: SubstitutionGroup::Verbatim,
955 }
956 );
957
958 assert_eq!(mi.item.content_model(), ContentModel::Verbatim);
959 assert_eq!(mi.item.raw_context().as_ref(), "listing");
960 assert_eq!(mi.item.resolved_context().as_ref(), "listing");
961 assert!(mi.item.declared_style().is_none());
962 assert!(mi.item.id().is_none());
963 assert!(mi.item.roles().is_empty());
964 assert!(mi.item.options().is_empty());
965 assert!(mi.item.title_source().is_none());
966 assert!(mi.item.title().is_none());
967 assert!(mi.item.anchor().is_none());
968 assert!(mi.item.anchor_reftext().is_none());
969 assert!(mi.item.attrlist().is_none());
970 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Verbatim);
971
972 assert_eq!(
973 mi.item.content(),
974 Content {
975 original: Span {
976 data: "line1 \n-----\nline2",
977 line: 2,
978 col: 1,
979 offset: 5,
980 },
981 rendered: "line1 \n-----\nline2",
982 }
983 );
984
985 assert_eq!(
986 mi.item.content(),
987 Content {
988 original: Span {
989 data: "line1 \n-----\nline2",
990 line: 2,
991 col: 1,
992 offset: 5,
993 },
994 rendered: "line1 \n-----\nline2",
995 }
996 );
997 }
998 }
999
1000 mod sidebar {
1001 use crate::{Parser, blocks::metadata::BlockMetadata};
1002
1003 #[test]
1004 fn empty() {
1005 let mut parser = Parser::default();
1006 assert!(
1007 crate::blocks::RawDelimitedBlock::parse(
1008 &BlockMetadata::new("****\n****"),
1009 &mut parser
1010 )
1011 .is_none()
1012 );
1013 }
1014
1015 #[test]
1016 fn multiple_lines() {
1017 let mut parser = Parser::default();
1018 assert!(
1019 crate::blocks::RawDelimitedBlock::parse(
1020 &BlockMetadata::new("****\nline1 \nline2\n****"),
1021 &mut parser
1022 )
1023 .is_none()
1024 );
1025 }
1026 }
1027
1028 mod table {
1029 use crate::{Parser, blocks::metadata::BlockMetadata};
1030
1031 #[test]
1032 fn empty() {
1033 let mut parser = Parser::default();
1034 assert!(
1035 crate::blocks::RawDelimitedBlock::parse(
1036 &BlockMetadata::new("|===\n|==="),
1037 &mut parser
1038 )
1039 .is_none()
1040 );
1041
1042 let mut parser = Parser::default();
1043 assert!(
1044 crate::blocks::RawDelimitedBlock::parse(
1045 &BlockMetadata::new(",===\n,==="),
1046 &mut parser
1047 )
1048 .is_none()
1049 );
1050
1051 let mut parser = Parser::default();
1052 assert!(
1053 crate::blocks::RawDelimitedBlock::parse(
1054 &BlockMetadata::new(":===\n:==="),
1055 &mut parser
1056 )
1057 .is_none()
1058 );
1059
1060 let mut parser = Parser::default();
1061 assert!(
1062 crate::blocks::RawDelimitedBlock::parse(
1063 &BlockMetadata::new("!===\n!==="),
1064 &mut parser
1065 )
1066 .is_none()
1067 );
1068 }
1069
1070 #[test]
1071 fn multiple_lines() {
1072 let mut parser = Parser::default();
1073 assert!(
1074 crate::blocks::RawDelimitedBlock::parse(
1075 &BlockMetadata::new("|===\nline1 \nline2\n|==="),
1076 &mut parser
1077 )
1078 .is_none()
1079 );
1080
1081 let mut parser = Parser::default();
1082 assert!(
1083 crate::blocks::RawDelimitedBlock::parse(
1084 &BlockMetadata::new(",===\nline1 \nline2\n,==="),
1085 &mut parser
1086 )
1087 .is_none()
1088 );
1089
1090 let mut parser = Parser::default();
1091 assert!(
1092 crate::blocks::RawDelimitedBlock::parse(
1093 &BlockMetadata::new(":===\nline1 \nline2\n:==="),
1094 &mut parser
1095 )
1096 .is_none()
1097 );
1098
1099 let mut parser = Parser::default();
1100 assert!(
1101 crate::blocks::RawDelimitedBlock::parse(
1102 &BlockMetadata::new("!===\nline1 \nline2\n!==="),
1103 &mut parser
1104 )
1105 .is_none()
1106 );
1107 }
1108 }
1109
1110 mod pass {
1111 use pretty_assertions_sorted::assert_eq;
1112
1113 use crate::{
1114 Parser,
1115 blocks::{ContentModel, IsBlock, metadata::BlockMetadata},
1116 content::SubstitutionGroup,
1117 tests::prelude::*,
1118 };
1119
1120 #[test]
1121 fn empty() {
1122 let mut parser = Parser::default();
1123 let maw = crate::blocks::RawDelimitedBlock::parse(
1124 &BlockMetadata::new("++++\n++++"),
1125 &mut parser,
1126 )
1127 .unwrap();
1128
1129 let mi = maw.item.unwrap().clone();
1130
1131 assert_eq!(
1132 mi.item,
1133 RawDelimitedBlock {
1134 content: Content {
1135 original: Span {
1136 data: "",
1137 line: 2,
1138 col: 1,
1139 offset: 5,
1140 },
1141 rendered: "",
1142 },
1143 content_model: ContentModel::Raw,
1144 context: "pass",
1145 source: Span {
1146 data: "++++\n++++",
1147 line: 1,
1148 col: 1,
1149 offset: 0,
1150 },
1151 title_source: None,
1152 title: None,
1153 anchor: None,
1154 anchor_reftext: None,
1155 attrlist: None,
1156 substitution_group: SubstitutionGroup::Pass,
1157 }
1158 );
1159
1160 assert_eq!(mi.item.content_model(), ContentModel::Raw);
1161 assert_eq!(mi.item.raw_context().as_ref(), "pass");
1162 assert_eq!(mi.item.resolved_context().as_ref(), "pass");
1163 assert!(mi.item.declared_style().is_none());
1164 assert!(mi.item.content().is_empty());
1165 assert!(mi.item.id().is_none());
1166 assert!(mi.item.roles().is_empty());
1167 assert!(mi.item.options().is_empty());
1168 assert!(mi.item.title_source().is_none());
1169 assert!(mi.item.title().is_none());
1170 assert!(mi.item.anchor().is_none());
1171 assert!(mi.item.anchor_reftext().is_none());
1172 assert!(mi.item.attrlist().is_none());
1173 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Pass);
1174 }
1175
1176 #[test]
1177 fn multiple_lines() {
1178 let mut parser = Parser::default();
1179
1180 let maw = crate::blocks::RawDelimitedBlock::parse(
1181 &BlockMetadata::new("++++\nline1 \nline2\n++++"),
1182 &mut parser,
1183 )
1184 .unwrap();
1185
1186 let mi = maw.item.unwrap().clone();
1187
1188 assert_eq!(
1189 mi.item,
1190 RawDelimitedBlock {
1191 content: Content {
1192 original: Span {
1193 data: "line1 \nline2",
1194 line: 2,
1195 col: 1,
1196 offset: 5,
1197 },
1198 rendered: "line1 \nline2",
1199 },
1200 content_model: ContentModel::Raw,
1201 context: "pass",
1202 source: Span {
1203 data: "++++\nline1 \nline2\n++++",
1204 line: 1,
1205 col: 1,
1206 offset: 0,
1207 },
1208 title_source: None,
1209 title: None,
1210 anchor: None,
1211 anchor_reftext: None,
1212 attrlist: None,
1213 substitution_group: SubstitutionGroup::Pass,
1214 }
1215 );
1216
1217 assert_eq!(mi.item.content_model(), ContentModel::Raw);
1218 assert_eq!(mi.item.raw_context().as_ref(), "pass");
1219 assert_eq!(mi.item.resolved_context().as_ref(), "pass");
1220 assert!(mi.item.declared_style().is_none());
1221 assert!(mi.item.id().is_none());
1222 assert!(mi.item.roles().is_empty());
1223 assert!(mi.item.options().is_empty());
1224 assert!(mi.item.title_source().is_none());
1225 assert!(mi.item.title().is_none());
1226 assert!(mi.item.anchor().is_none());
1227 assert!(mi.item.anchor_reftext().is_none());
1228 assert!(mi.item.attrlist().is_none());
1229 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Pass);
1230
1231 assert_eq!(
1232 mi.item.content(),
1233 Content {
1234 original: Span {
1235 data: "line1 \nline2",
1236 line: 2,
1237 col: 1,
1238 offset: 5,
1239 },
1240 rendered: "line1 \nline2",
1241 }
1242 );
1243 }
1244
1245 #[test]
1246 fn ignores_delimiter_prefix() {
1247 let mut parser = Parser::default();
1248
1249 let maw = crate::blocks::RawDelimitedBlock::parse(
1250 &BlockMetadata::new("++++\nline1 \n+++++\nline2\n++++"),
1251 &mut parser,
1252 )
1253 .unwrap();
1254
1255 let mi = maw.item.unwrap().clone();
1256
1257 assert_eq!(
1258 mi.item,
1259 RawDelimitedBlock {
1260 content: Content {
1261 original: Span {
1262 data: "line1 \n+++++\nline2",
1263 line: 2,
1264 col: 1,
1265 offset: 5,
1266 },
1267 rendered: "line1 \n+++++\nline2",
1268 },
1269 content_model: ContentModel::Raw,
1270 context: "pass",
1271 source: Span {
1272 data: "++++\nline1 \n+++++\nline2\n++++",
1273 line: 1,
1274 col: 1,
1275 offset: 0,
1276 },
1277 title_source: None,
1278 title: None,
1279 anchor: None,
1280 anchor_reftext: None,
1281 attrlist: None,
1282 substitution_group: SubstitutionGroup::Pass,
1283 }
1284 );
1285
1286 assert_eq!(mi.item.content_model(), ContentModel::Raw);
1287 assert_eq!(mi.item.raw_context().as_ref(), "pass");
1288 assert_eq!(mi.item.resolved_context().as_ref(), "pass");
1289 assert!(mi.item.declared_style().is_none());
1290 assert!(mi.item.id().is_none());
1291 assert!(mi.item.roles().is_empty());
1292 assert!(mi.item.options().is_empty());
1293 assert!(mi.item.title_source().is_none());
1294 assert!(mi.item.title().is_none());
1295 assert!(mi.item.anchor().is_none());
1296 assert!(mi.item.anchor_reftext().is_none());
1297 assert!(mi.item.attrlist().is_none());
1298 assert_eq!(mi.item.substitution_group(), SubstitutionGroup::Pass);
1299
1300 assert_eq!(
1301 mi.item.content(),
1302 Content {
1303 original: Span {
1304 data: "line1 \n+++++\nline2",
1305 line: 2,
1306 col: 1,
1307 offset: 5,
1308 },
1309 rendered: "line1 \n+++++\nline2",
1310 }
1311 );
1312 }
1313 }
1314
1315 mod quote {
1316 use crate::{Parser, blocks::metadata::BlockMetadata};
1317
1318 #[test]
1319 fn empty() {
1320 let mut parser = Parser::default();
1321 assert!(
1322 crate::blocks::RawDelimitedBlock::parse(
1323 &BlockMetadata::new("____\n____"),
1324 &mut parser
1325 )
1326 .is_none()
1327 );
1328 }
1329
1330 #[test]
1331 fn multiple_lines() {
1332 let mut parser = Parser::default();
1333 assert!(
1334 crate::blocks::RawDelimitedBlock::parse(
1335 &BlockMetadata::new("____\nline1 \nline2\n____"),
1336 &mut parser
1337 )
1338 .is_none()
1339 );
1340 }
1341 }
1342}