1use crate::Markdown;
2use crate::MarkdownInline;
3use crate::MarkdownText;
4
5use nom::{
6 branch::alt,
7 bytes::complete::{is_not, tag, take, take_while1},
8 character::is_digit,
9 combinator::{map, not},
10 multi::{many0, many1},
11 sequence::{delimited, pair, preceded, terminated, tuple},
12 IResult,
13};
14
15pub fn parse_markdown(i: &str) -> IResult<&str, Vec<Markdown>> {
16 many1(alt((
17 map(parse_header, |e| Markdown::Heading(e.0, e.1)),
18 map(parse_unordered_list, |e| Markdown::UnorderedList(e)),
19 map(parse_ordered_list, |e| Markdown::OrderedList(e)),
20 map(parse_code_block, |e| {
21 Markdown::Codeblock(e.0.to_string(), e.1.to_string())
22 }),
23 map(parse_markdown_text, |e| Markdown::Line(e)),
24 )))(i)
25}
26
27fn parse_boldtext(i: &str) -> IResult<&str, &str> {
28 delimited(tag("**"), is_not("**"), tag("**"))(i)
29}
30
31fn parse_italics(i: &str) -> IResult<&str, &str> {
32 delimited(tag("*"), is_not("*"), tag("*"))(i)
33}
34
35fn parse_inline_code(i: &str) -> IResult<&str, &str> {
36 delimited(tag("`"), is_not("`"), tag("`"))(i)
37}
38
39fn parse_link(i: &str) -> IResult<&str, (&str, &str)> {
40 pair(
41 delimited(tag("["), is_not("]"), tag("]")),
42 delimited(tag("("), is_not(")"), tag(")")),
43 )(i)
44}
45
46fn parse_image(i: &str) -> IResult<&str, (&str, &str)> {
47 pair(
48 delimited(tag("!["), is_not("]"), tag("]")),
49 delimited(tag("("), is_not(")"), tag(")")),
50 )(i)
51}
52
53fn parse_plaintext(i: &str) -> IResult<&str, String> {
59 map(
60 many1(preceded(
61 not(alt((tag("*"), tag("`"), tag("["), tag("!["), tag("\n")))),
62 take(1u8),
63 )),
64 |vec| vec.join(""),
65 )(i)
66}
67
68fn parse_markdown_inline(i: &str) -> IResult<&str, MarkdownInline> {
69 alt((
70 map(parse_italics, |s: &str| {
71 MarkdownInline::Italic(s.to_string())
72 }),
73 map(parse_inline_code, |s: &str| {
74 MarkdownInline::InlineCode(s.to_string())
75 }),
76 map(parse_boldtext, |s: &str| {
77 MarkdownInline::Bold(s.to_string())
78 }),
79 map(parse_image, |(tag, url): (&str, &str)| {
80 MarkdownInline::Image(tag.to_string(), url.to_string())
81 }),
82 map(parse_link, |(tag, url): (&str, &str)| {
83 MarkdownInline::Link(tag.to_string(), url.to_string())
84 }),
85 map(parse_plaintext, |s| MarkdownInline::Plaintext(s)),
86 ))(i)
87}
88
89fn parse_markdown_text(i: &str) -> IResult<&str, MarkdownText> {
90 terminated(many0(parse_markdown_inline), tag("\n"))(i)
91}
92
93fn parse_header_tag(i: &str) -> IResult<&str, usize> {
95 map(
96 terminated(take_while1(|c| c == '#'), tag(" ")),
97 |s: &str| s.len(),
98 )(i)
99}
100
101fn parse_header(i: &str) -> IResult<&str, (usize, MarkdownText)> {
103 tuple((parse_header_tag, parse_markdown_text))(i)
104}
105
106fn parse_unordered_list_tag(i: &str) -> IResult<&str, &str> {
107 terminated(tag("-"), tag(" "))(i)
108}
109
110fn parse_unordered_list_element(i: &str) -> IResult<&str, MarkdownText> {
111 preceded(parse_unordered_list_tag, parse_markdown_text)(i)
112}
113
114fn parse_unordered_list(i: &str) -> IResult<&str, Vec<MarkdownText>> {
115 many1(parse_unordered_list_element)(i)
116}
117
118fn parse_ordered_list_tag(i: &str) -> IResult<&str, &str> {
119 terminated(
120 terminated(take_while1(|d| is_digit(d as u8)), tag(".")),
121 tag(" "),
122 )(i)
123}
124
125fn parse_ordered_list_element(i: &str) -> IResult<&str, MarkdownText> {
126 preceded(parse_ordered_list_tag, parse_markdown_text)(i)
127}
128
129fn parse_ordered_list(i: &str) -> IResult<&str, Vec<MarkdownText>> {
130 many1(parse_ordered_list_element)(i)
131}
132
133fn parse_code_block(i: &str) -> IResult<&str, (String, &str)> {
134 tuple((parse_code_block_lang, parse_code_block_body))(i)
135}
136
137fn parse_code_block_body(i: &str) -> IResult<&str, &str> {
138 delimited(tag("\n"), is_not("```"), tag("```"))(i)
139}
140
141fn parse_code_block_lang(i: &str) -> IResult<&str, String> {
142 alt((
143 preceded(tag("```"), parse_plaintext),
144 map(tag("```"), |_| "__UNKNOWN__".to_string()),
145 ))(i)
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use nom::{error::Error, error::ErrorKind, Err as NomErr};
152
153 #[test]
154 fn test_parse_italics() {
155 assert_eq!(
156 parse_italics("*here is italic*"),
157 Ok(("", "here is italic"))
158 );
159 assert_eq!(
160 parse_italics("*here is italic"),
161 Err(NomErr::Error(Error {
162 input: "",
163 code: ErrorKind::Tag
164 }))
165 );
166
167 assert_eq!(
168 parse_italics("here is italic*"),
169 Err(NomErr::Error(Error {
170 input: "here is italic*",
171 code: ErrorKind::Tag,
172 }))
173 );
174 assert_eq!(
175 parse_italics("here is italic"),
176 Err(NomErr::Error(Error {
177 input: "here is italic",
178 code: ErrorKind::Tag
179 }))
180 );
181 assert_eq!(
182 parse_italics("*"),
183 Err(NomErr::Error(Error {
184 input: "",
185 code: ErrorKind::IsNot
186 }))
187 );
188 assert_eq!(
189 parse_italics("**"),
190 Err(NomErr::Error(Error {
191 input: "*",
192 code: ErrorKind::IsNot
193 }))
194 );
195 assert_eq!(
196 parse_italics(""),
197 Err(NomErr::Error(Error {
198 input: "",
199 code: ErrorKind::Tag
200 }))
201 );
202 assert_eq!(
203 parse_italics("**we are doing bold**"),
204 Err(NomErr::Error(Error {
205 input: "*we are doing bold**",
206 code: ErrorKind::IsNot
207 }))
208 );
209 }
210
211 #[test]
212 fn test_parse_boldtext() {
213 assert_eq!(parse_boldtext("**here is bold**"), Ok(("", "here is bold")));
214 assert_eq!(
215 parse_boldtext("**here is bold"),
216 Err(NomErr::Error(Error {
217 input: "",
218 code: ErrorKind::Tag
219 }))
220 );
221 assert_eq!(
222 parse_boldtext("here is bold**"),
223 Err(NomErr::Error(Error {
224 input: "here is bold**",
225 code: ErrorKind::Tag
226 }))
227 );
228 assert_eq!(
229 parse_boldtext("here is bold"),
230 Err(NomErr::Error(Error {
231 input: "here is bold",
232 code: ErrorKind::Tag
233 }))
234 );
235 assert_eq!(
236 parse_boldtext("****"),
237 Err(NomErr::Error(Error {
238 input: "**",
239 code: ErrorKind::IsNot
240 }))
241 );
242 assert_eq!(
243 parse_boldtext("**"),
244 Err(NomErr::Error(Error {
245 input: "",
246 code: ErrorKind::IsNot
247 }))
248 );
249 assert_eq!(
250 parse_boldtext("*"),
251 Err(NomErr::Error(Error {
252 input: "*",
253 code: ErrorKind::Tag
254 }))
255 );
256 assert_eq!(
257 parse_boldtext(""),
258 Err(NomErr::Error(Error {
259 input: "",
260 code: ErrorKind::Tag
261 }))
262 );
263 assert_eq!(
264 parse_boldtext("*this is italic*"),
265 Err(NomErr::Error(Error {
266 input: "*this is italic*",
267 code: ErrorKind::Tag
268 }))
269 );
270 }
271
272 #[test]
273 fn test_parse_inline_code() {
274 assert_eq!(
275 parse_boldtext("**here is bold**\n"),
276 Ok(("\n", "here is bold"))
277 );
278 assert_eq!(
279 parse_inline_code("`here is code"),
280 Err(NomErr::Error(Error {
281 input: "",
282 code: ErrorKind::Tag
283 }))
284 );
285 assert_eq!(
286 parse_inline_code("here is code`"),
287 Err(NomErr::Error(Error {
288 input: "here is code`",
289 code: ErrorKind::Tag
290 }))
291 );
292 assert_eq!(
293 parse_inline_code("``"),
294 Err(NomErr::Error(Error {
295 input: "`",
296 code: ErrorKind::IsNot
297 }))
298 );
299 assert_eq!(
300 parse_inline_code("`"),
301 Err(NomErr::Error(Error {
302 input: "",
303 code: ErrorKind::IsNot
304 }))
305 );
306 assert_eq!(
307 parse_inline_code(""),
308 Err(NomErr::Error(Error {
309 input: "",
310 code: ErrorKind::Tag
311 }))
312 );
313 }
314
315 #[test]
316 fn test_parse_link() {
317 assert_eq!(
318 parse_link("[title](https://www.example.com)"),
319 Ok(("", ("title", "https://www.example.com")))
320 );
321 assert_eq!(
322 parse_inline_code(""),
323 Err(NomErr::Error(Error {
324 input: "",
325 code: ErrorKind::Tag
326 }))
327 );
328 }
329
330 #[test]
331 fn test_parse_image() {
332 assert_eq!(
333 parse_image(""),
334 Ok(("", ("alt text", "image.jpg")))
335 );
336 assert_eq!(
337 parse_inline_code(""),
338 Err(NomErr::Error(Error {
339 input: "",
340 code: ErrorKind::Tag
341 }))
342 );
343 }
344
345 #[test]
346 fn test_parse_plaintext() {
347 assert_eq!(
348 parse_plaintext("1234567890"),
349 Ok(("", String::from("1234567890")))
350 );
351 assert_eq!(
352 parse_plaintext("oh my gosh!"),
353 Ok(("", String::from("oh my gosh!")))
354 );
355 assert_eq!(
356 parse_plaintext("oh my gosh"),
372 Err(NomErr::Error(Error {
373 input: "[link babey](and then somewhat)",
374 code: ErrorKind::Not
375 }))
376 );
377 assert_eq!(
378 parse_plaintext("`codeblock for bums`"),
379 Err(NomErr::Error(Error {
380 input: "`codeblock for bums`",
381 code: ErrorKind::Not
382 }))
383 );
384 assert_eq!(
385 parse_plaintext(""),
386 Err(NomErr::Error(Error {
387 input: "",
388 code: ErrorKind::Not
389 }))
390 );
391 assert_eq!(
392 parse_plaintext("here is plaintext"),
393 Ok(("", String::from("here is plaintext")))
394 );
395 assert_eq!(
396 parse_plaintext("here is plaintext!"),
397 Ok(("", String::from("here is plaintext!")))
398 );
399 assert_eq!(
400 parse_plaintext("here is plaintext"),
430 Err(NomErr::Error(Error {
431 input: "[title](https://www.example.com)",
432 code: ErrorKind::Not
433 }))
434 );
435 assert_eq!(
436 parse_plaintext(""),
437 Err(NomErr::Error(Error {
438 input: "",
439 code: ErrorKind::Not
440 }))
441 );
442 assert_eq!(
443 parse_plaintext(""),
444 Err(NomErr::Error(Error {
445 input: "",
446 code: ErrorKind::Eof
447 }))
448 );
449 }
450
451 #[test]
452 fn test_parse_markdown_inline() {
453 assert_eq!(
454 parse_markdown_inline("*here is italic*"),
455 Ok(("", MarkdownInline::Italic(String::from("here is italic"))))
456 );
457 assert_eq!(
458 parse_markdown_inline("**here is bold**"),
459 Ok(("", MarkdownInline::Bold(String::from("here is bold"))))
460 );
461 assert_eq!(
462 parse_markdown_inline("`here is code`"),
463 Ok(("", MarkdownInline::InlineCode(String::from("here is code"))))
464 );
465 assert_eq!(
466 parse_markdown_inline("[title](https://www.example.com)"),
467 Ok((
468 "",
469 (MarkdownInline::Link(
470 String::from("title"),
471 String::from("https://www.example.com")
472 ))
473 ))
474 );
475 assert_eq!(
476 parse_markdown_inline(""),
477 Ok((
478 "",
479 (MarkdownInline::Image(String::from("alt text"), String::from("image.jpg")))
480 ))
481 );
482 assert_eq!(
483 parse_markdown_inline("here is plaintext!"),
484 Ok((
485 "",
486 MarkdownInline::Plaintext(String::from("here is plaintext!"))
487 ))
488 );
489 assert_eq!(
490 parse_markdown_inline("here is some plaintext *but what if we italicize?"),
491 Ok((
492 "*but what if we italicize?",
493 MarkdownInline::Plaintext(String::from("here is some plaintext "))
494 ))
495 );
496 assert_eq!(
497 parse_markdown_inline(
498 r#"here is some plaintext
499*but what if we italicize?"#
500 ),
501 Ok((
502 "\n*but what if we italicize?",
503 MarkdownInline::Plaintext(String::from("here is some plaintext "))
504 ))
505 );
506 assert_eq!(
507 parse_markdown_inline("\n"),
508 Err(NomErr::Error(Error {
509 input: "\n",
510 code: ErrorKind::Not
511 }))
512 );
513 assert_eq!(
514 parse_markdown_inline(""),
515 Err(NomErr::Error(Error {
516 input: "",
517 code: ErrorKind::Eof
518 }))
519 );
520 }
521
522 #[test]
523 fn test_parse_markdown_text() {
524 assert_eq!(parse_markdown_text("\n"), Ok(("", vec![])));
525 assert_eq!(
526 parse_markdown_text("here is some plaintext\n"),
527 Ok((
528 "",
529 vec![MarkdownInline::Plaintext(String::from(
530 "here is some plaintext"
531 ))]
532 ))
533 );
534 assert_eq!(
535 parse_markdown_text("here is some plaintext *but what if we italicize?*\n"),
536 Ok((
537 "",
538 vec![
539 MarkdownInline::Plaintext(String::from("here is some plaintext ")),
540 MarkdownInline::Italic(String::from("but what if we italicize?")),
541 ]
542 ))
543 );
544 assert_eq!(
545 parse_markdown_text("here is some plaintext *but what if we italicize?* I guess it doesnt **matter** in my `code`\n"),
546 Ok(("", vec![
547 MarkdownInline::Plaintext(String::from("here is some plaintext ")),
548 MarkdownInline::Italic(String::from("but what if we italicize?")),
549 MarkdownInline::Plaintext(String::from(" I guess it doesnt ")),
550 MarkdownInline::Bold(String::from("matter")),
551 MarkdownInline::Plaintext(String::from(" in my ")),
552 MarkdownInline::InlineCode(String::from("code")),
553 ]))
554 );
555 assert_eq!(
556 parse_markdown_text("here is some plaintext *but what if we italicize?*\n"),
557 Ok((
558 "",
559 vec![
560 MarkdownInline::Plaintext(String::from("here is some plaintext ")),
561 MarkdownInline::Italic(String::from("but what if we italicize?")),
562 ]
563 ))
564 );
565 assert_eq!(
566 parse_markdown_text("here is some plaintext *but what if we italicize?"),
567 Err(NomErr::Error(Error {
568 input: "*but what if we italicize?",
569 code: ErrorKind::Tag
570 })) );
572 }
573
574 #[test]
575 fn test_parse_header_tag() {
576 assert_eq!(parse_header_tag("# "), Ok(("", 1)));
577 assert_eq!(parse_header_tag("### "), Ok(("", 3)));
578 assert_eq!(parse_header_tag("# h1"), Ok(("h1", 1)));
579 assert_eq!(parse_header_tag("# h1"), Ok(("h1", 1)));
580 assert_eq!(
581 parse_header_tag(" "),
582 Err(NomErr::Error(Error {
583 input: " ",
584 code: ErrorKind::TakeWhile1
585 }))
586 );
587 assert_eq!(
588 parse_header_tag("#"),
589 Err(NomErr::Error(Error {
590 input: "",
591 code: ErrorKind::Tag
592 }))
593 );
594 }
595
596 #[test]
597 fn test_parse_header() {
598 assert_eq!(
599 parse_header("# h1\n"),
600 Ok(("", (1, vec![MarkdownInline::Plaintext(String::from("h1"))])))
601 );
602 assert_eq!(
603 parse_header("## h2\n"),
604 Ok(("", (2, vec![MarkdownInline::Plaintext(String::from("h2"))])))
605 );
606 assert_eq!(
607 parse_header("### h3\n"),
608 Ok((
609 "",
610 (3, vec![MarkdownInline::Plaintext(String::from(" h3"))])
611 ))
612 );
613 assert_eq!(
614 parse_header("###h3"),
615 Err(NomErr::Error(Error {
616 input: "h3",
617 code: ErrorKind::Tag
618 }))
619 );
620 assert_eq!(
621 parse_header("###"),
622 Err(NomErr::Error(Error {
623 input: "",
624 code: ErrorKind::Tag
625 }))
626 );
627 assert_eq!(
628 parse_header(""),
629 Err(NomErr::Error(Error {
630 input: "",
631 code: ErrorKind::TakeWhile1
632 }))
633 );
634 assert_eq!(
635 parse_header("#"),
636 Err(NomErr::Error(Error {
637 input: "",
638 code: ErrorKind::Tag
639 }))
640 );
641 assert_eq!(parse_header("# \n"), Ok(("", (1, vec![]))));
642 assert_eq!(
643 parse_header("# test"),
644 Err(NomErr::Error(Error {
645 input: "",
646 code: ErrorKind::Tag
647 }))
648 );
649 }
650
651 #[test]
652 fn test_parse_unordered_list_tag() {
653 assert_eq!(parse_unordered_list_tag("- "), Ok(("", "-")));
654 assert_eq!(
655 parse_unordered_list_tag("- and some more"),
656 Ok(("and some more", "-"))
657 );
658 assert_eq!(
659 parse_unordered_list_tag("-"),
660 Err(NomErr::Error(Error {
661 input: "",
662 code: ErrorKind::Tag
663 }))
664 );
665 assert_eq!(
666 parse_unordered_list_tag("-and some more"),
667 Err(NomErr::Error(Error {
668 input: "and some more",
669 code: ErrorKind::Tag
670 }))
671 );
672 assert_eq!(
673 parse_unordered_list_tag("--"),
674 Err(NomErr::Error(Error {
675 input: "-",
676 code: ErrorKind::Tag
677 }))
678 );
679 assert_eq!(
680 parse_unordered_list_tag(""),
681 Err(NomErr::Error(Error {
682 input: "",
683 code: ErrorKind::Tag
684 }))
685 );
686 }
687
688 #[test]
689 fn test_parse_unordered_list_element() {
690 assert_eq!(
691 parse_unordered_list_element("- this is an element\n"),
692 Ok((
693 "",
694 vec![MarkdownInline::Plaintext(String::from(
695 "this is an element"
696 ))]
697 ))
698 );
699 assert_eq!(
700 parse_unordered_list_element(
701 r#"- this is an element
702- this is another element
703"#
704 ),
705 Ok((
706 "- this is another element\n",
707 vec![MarkdownInline::Plaintext(String::from(
708 "this is an element"
709 ))]
710 ))
711 );
712 assert_eq!(
713 parse_unordered_list_element(""),
714 Err(NomErr::Error(Error {
715 input: "",
716 code: ErrorKind::Tag
717 }))
718 );
719 assert_eq!(parse_unordered_list_element("- \n"), Ok(("", vec![])));
720 assert_eq!(
721 parse_unordered_list_element("- "),
722 Err(NomErr::Error(Error {
723 input: "",
724 code: ErrorKind::Tag
725 }))
726 );
727 assert_eq!(
728 parse_unordered_list_element("- test"),
729 Err(NomErr::Error(Error {
730 input: "",
731 code: ErrorKind::Tag
732 }))
733 );
734 assert_eq!(
735 parse_unordered_list_element("-"),
736 Err(NomErr::Error(Error {
737 input: "",
738 code: ErrorKind::Tag
739 }))
740 );
741 }
742
743 #[test]
744 fn test_parse_unordered_list() {
745 assert_eq!(
746 parse_unordered_list("- this is an element"),
747 Err(NomErr::Error(Error {
748 input: "",
749 code: ErrorKind::Tag
750 }))
751 );
752 assert_eq!(
753 parse_unordered_list("- this is an element\n"),
754 Ok((
755 "",
756 vec![vec![MarkdownInline::Plaintext(String::from(
757 "this is an element"
758 ))]]
759 ))
760 );
761 assert_eq!(
762 parse_unordered_list(
763 r#"- this is an element
764- here is another
765"#
766 ),
767 Ok((
768 "",
769 vec![
770 vec![MarkdownInline::Plaintext(String::from(
771 "this is an element"
772 ))],
773 vec![MarkdownInline::Plaintext(String::from("here is another"))]
774 ]
775 ))
776 );
777 }
778
779 #[test]
780 fn test_parse_ordered_list_tag() {
781 assert_eq!(parse_ordered_list_tag("1. "), Ok(("", "1")));
782 assert_eq!(parse_ordered_list_tag("1234567. "), Ok(("", "1234567")));
783 assert_eq!(
784 parse_ordered_list_tag("3. and some more"),
785 Ok(("and some more", "3"))
786 );
787 assert_eq!(
788 parse_ordered_list_tag("1"),
789 Err(NomErr::Error(Error {
790 input: "",
791 code: ErrorKind::Tag
792 }))
793 );
794 assert_eq!(
795 parse_ordered_list_tag("1.and some more"),
796 Err(NomErr::Error(Error {
797 input: "and some more",
798 code: ErrorKind::Tag
799 }))
800 );
801 assert_eq!(
802 parse_ordered_list_tag("1111."),
803 Err(NomErr::Error(Error {
804 input: "",
805 code: ErrorKind::Tag
806 }))
807 );
808 assert_eq!(
809 parse_ordered_list_tag(""),
810 Err(NomErr::Error(Error {
811 input: "",
812 code: ErrorKind::TakeWhile1
813 }))
814 );
815 }
816
817 #[test]
818 fn test_parse_ordered_list_element() {
819 assert_eq!(
820 parse_ordered_list_element("1. this is an element\n"),
821 Ok((
822 "",
823 vec![MarkdownInline::Plaintext(String::from(
824 "this is an element"
825 ))]
826 ))
827 );
828 assert_eq!(
829 parse_ordered_list_element(
830 r#"1. this is an element
8311. here is another
832"#
833 ),
834 Ok((
835 "1. here is another\n",
836 vec![MarkdownInline::Plaintext(String::from(
837 "this is an element"
838 ))]
839 ))
840 );
841 assert_eq!(
842 parse_ordered_list_element(""),
843 Err(NomErr::Error(Error {
844 input: "",
845 code: ErrorKind::TakeWhile1
846 }))
847 );
848 assert_eq!(
849 parse_ordered_list_element(""),
850 Err(NomErr::Error(Error {
851 input: "",
852 code: ErrorKind::TakeWhile1
853 }))
854 );
855 assert_eq!(parse_ordered_list_element("1. \n"), Ok(("", vec![])));
856 assert_eq!(
857 parse_ordered_list_element("1. test"),
858 Err(NomErr::Error(Error {
859 input: "",
860 code: ErrorKind::Tag
861 }))
862 );
863 assert_eq!(
864 parse_ordered_list_element("1. "),
865 Err(NomErr::Error(Error {
866 input: "",
867 code: ErrorKind::Tag
868 }))
869 );
870 assert_eq!(
871 parse_ordered_list_element("1."),
872 Err(NomErr::Error(Error {
873 input: "",
874 code: ErrorKind::Tag
875 }))
876 );
877 }
878
879 #[test]
880 fn test_parse_ordered_list() {
881 assert_eq!(
882 parse_ordered_list("1. this is an element\n"),
883 Ok((
884 "",
885 vec![vec![MarkdownInline::Plaintext(String::from(
886 "this is an element"
887 ))]]
888 ))
889 );
890 assert_eq!(
891 parse_ordered_list("1. test"),
892 Err(NomErr::Error(Error {
893 input: "",
894 code: ErrorKind::Tag
895 }))
896 );
897 assert_eq!(
898 parse_ordered_list(
899 r#"1. this is an element
9002. here is another
901"#
902 ),
903 Ok((
904 "",
905 vec![
906 vec!(MarkdownInline::Plaintext(String::from(
907 "this is an element"
908 ))),
909 vec![MarkdownInline::Plaintext(String::from("here is another"))]
910 ]
911 ))
912 );
913 }
914
915 #[test]
916 fn test_parse_codeblock() {
917 assert_eq!(
918 parse_code_block(
919 r#"```bash
920pip install foobar
921```"#
922 ),
923 Ok((
924 "",
925 (
926 String::from("bash"),
927 r#"pip install foobar
928"#
929 )
930 ))
931 );
932 assert_eq!(
933 parse_code_block(
934 r#"```python
935import foobar
936
937foobar.pluralize('word') # returns 'words'
938foobar.pluralize('goose') # returns 'geese'
939foobar.singularize('phenomena') # returns 'phenomenon'
940```"#
941 ),
942 Ok((
943 "",
944 (
945 String::from("python"),
946 r#"import foobar
947
948foobar.pluralize('word') # returns 'words'
949foobar.pluralize('goose') # returns 'geese'
950foobar.singularize('phenomena') # returns 'phenomenon'
951"#
952 )
953 ))
954 );
955 }
960
961 #[test]
962 fn test_parse_codeblock_no_language() {
963 assert_eq!(
964 parse_code_block(
965 r#"```
966pip install foobar
967```"#
968 ),
969 Ok((
970 "",
971 (
972 String::from("__UNKNOWN__"),
973 r#"pip install foobar
974"#
975 )
976 ))
977 );
978 }
979
980 #[test]
981 fn test_parse_markdown() {
982 assert_eq!(
983 parse_markdown(
984 r#"# Foobar
985
986Foobar is a Python library for dealing with word pluralization.
987
988```bash
989pip install foobar
990```
991## Installation
992
993Use the package manager [pip](https://pip.pypa.io/en/stable/) to install foobar.
994```python
995import foobar
996
997foobar.pluralize('word') # returns 'words'
998foobar.pluralize('goose') # returns 'geese'
999foobar.singularize('phenomena') # returns 'phenomenon'
1000```"#
1001 ),
1002 Ok((
1003 "",
1004 vec![
1005 Markdown::Heading(1, vec![MarkdownInline::Plaintext(String::from("Foobar"))]),
1006 Markdown::Line(vec![]),
1007 Markdown::Line(vec![MarkdownInline::Plaintext(String::from(
1008 "Foobar is a Python library for dealing with word pluralization."
1009 ))]),
1010 Markdown::Line(vec![]),
1011 Markdown::Codeblock(String::from("bash"), String::from("pip install foobar\n")),
1012 Markdown::Line(vec![]),
1013 Markdown::Heading(
1014 2,
1015 vec![MarkdownInline::Plaintext(String::from("Installation"))]
1016 ),
1017 Markdown::Line(vec![]),
1018 Markdown::Line(vec![
1019 MarkdownInline::Plaintext(String::from("Use the package manager ")),
1020 MarkdownInline::Link(
1021 String::from("pip"),
1022 String::from("https://pip.pypa.io/en/stable/")
1023 ),
1024 MarkdownInline::Plaintext(String::from(" to install foobar.")),
1025 ]),
1026 Markdown::Codeblock(
1027 String::from("python"),
1028 String::from(
1029 r#"import foobar
1030
1031foobar.pluralize('word') # returns 'words'
1032foobar.pluralize('goose') # returns 'geese'
1033foobar.singularize('phenomena') # returns 'phenomenon'
1034"#
1035 )
1036 ),
1037 ]
1038 ))
1039 )
1040 }
1041}