markdown_to_html/
parser.rs

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
53// we want to match many things that are not any of our specail tags
54// but since we have no tools available to match and consume in the negative case (without regex)
55// we need to match against our tags, then consume one char
56// we repeat this until we run into one of our special characters
57// then we join our array of characters into a String
58fn 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
93// this guy matches the literal character #
94fn 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
101// this combines a tuple of the header tag and the rest of the line
102fn 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("![alt text](image.jpg)"),
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!["),
357            Ok(("![", String::from("oh my gosh")))
358        );
359        assert_eq!(
360            parse_plaintext("oh my gosh!*"),
361            Ok(("*", String::from("oh my gosh!")))
362        );
363        assert_eq!(
364            parse_plaintext("*bold babey bold*"),
365            Err(NomErr::Error(Error {
366                input: "*bold babey bold*",
367                code: ErrorKind::Not
368            }))
369        );
370        assert_eq!(
371            parse_plaintext("[link babey](and then somewhat)"),
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("![ but wait theres more](jk)"),
386            Err(NomErr::Error(Error {
387                input: "![ but wait theres more](jk)",
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![image starting"),
401            Ok(("![image starting", String::from("here is plaintext")))
402        );
403        assert_eq!(
404            parse_plaintext("here is plaintext\n"),
405            Ok(("\n", String::from("here is plaintext")))
406        );
407        assert_eq!(
408            parse_plaintext("*here is italic*"),
409            Err(NomErr::Error(Error {
410                input: "*here is italic*",
411                code: ErrorKind::Not
412            }))
413        );
414        assert_eq!(
415            parse_plaintext("**here is bold**"),
416            Err(NomErr::Error(Error {
417                input: "**here is bold**",
418                code: ErrorKind::Not
419            }))
420        );
421        assert_eq!(
422            parse_plaintext("`here is code`"),
423            Err(NomErr::Error(Error {
424                input: "`here is code`",
425                code: ErrorKind::Not
426            }))
427        );
428        assert_eq!(
429            parse_plaintext("[title](https://www.example.com)"),
430            Err(NomErr::Error(Error {
431                input: "[title](https://www.example.com)",
432                code: ErrorKind::Not
433            }))
434        );
435        assert_eq!(
436            parse_plaintext("![alt text](image.jpg)"),
437            Err(NomErr::Error(Error {
438                input: "![alt text](image.jpg)",
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("![alt text](image.jpg)"),
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            })) // Ok(("*but what if we italicize?", vec![MarkdownInline::Plaintext(String::from("here is some plaintext "))]))
571        );
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        // assert_eq!(
956        // 	parse_code_block("```bash\n pip `install` foobar\n```"),
957        // 	Ok(("", "bash\n pip `install` foobar\n"))
958        // );
959    }
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}