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