Skip to main content

rustdown_cli/
markdown.rs

1use crate::render::MarkdownRenderer;
2use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser as MdParser, Tag};
3use std::io;
4use termcolor::{Color, ColorSpec, WriteColor};
5
6pub fn render_markdown(
7    md: &str,
8    preserve_fences: bool,
9    output: &mut impl WriteColor,
10) -> io::Result<()> {
11    let mut options = Options::empty();
12    options.insert(Options::ENABLE_STRIKETHROUGH);
13    options.insert(Options::ENABLE_TABLES);
14    options.insert(Options::ENABLE_FOOTNOTES);
15    options.insert(Options::ENABLE_TASKLISTS);
16
17    let parser = MdParser::new_ext(md, options);
18    let mut renderer = MarkdownRenderer::new(output, preserve_fences);
19
20    for event in parser {
21        match event {
22            Event::Start(tag) => match tag {
23                Tag::Paragraph => {
24                    // nothing to do at paragraph start
25                }
26                Tag::Heading { level, .. } => {
27                    let num = match level {
28                        HeadingLevel::H1 => 1,
29                        HeadingLevel::H2 => 2,
30                        HeadingLevel::H3 => 3,
31                        HeadingLevel::H4 => 4,
32                        HeadingLevel::H5 => 5,
33                        HeadingLevel::H6 => 6,
34                    };
35                    renderer.start_heading(num);
36                }
37                Tag::List(start_number) => {
38                    renderer.start_list(start_number)?;
39                }
40                Tag::Item => {
41                    renderer.render_list_item_start()?;
42                }
43                Tag::Emphasis => renderer.set_emphasis(true),
44                Tag::Strong => renderer.set_strong(true),
45                Tag::Strikethrough => renderer.set_strikethrough(true),
46                Tag::Link { dest_url, .. } => renderer.start_link(&dest_url),
47                Tag::Image { dest_url, .. } => renderer.start_image(&dest_url),
48                Tag::CodeBlock(kind) => {
49                    renderer.start_code_block(match kind {
50                        CodeBlockKind::Fenced(info) => pulldown_cmark::CodeBlockKind::Fenced(info),
51                        CodeBlockKind::Indented => pulldown_cmark::CodeBlockKind::Indented,
52                    })?;
53                }
54                Tag::BlockQuote(_) => {
55                    renderer.start_blockquote()?;
56                }
57                Tag::Table(_) => {
58                    renderer.start_table()?;
59                }
60                Tag::TableHead => renderer.start_table_head(),
61                Tag::TableRow => renderer.start_table_row(),
62                Tag::TableCell => renderer.start_table_cell(),
63                _ => {}
64            },
65            Event::End(tag_end) => match tag_end {
66                pulldown_cmark::TagEnd::TableCell => renderer.end_table_cell(),
67                pulldown_cmark::TagEnd::TableRow => renderer.end_table_row(),
68                pulldown_cmark::TagEnd::TableHead => renderer.end_table_head(),
69                pulldown_cmark::TagEnd::Table => renderer.end_table()?,
70                pulldown_cmark::TagEnd::Paragraph => renderer.end_paragraph()?,
71                pulldown_cmark::TagEnd::Heading(_) => renderer.end_heading()?,
72                pulldown_cmark::TagEnd::List(_) => renderer.end_list(),
73                pulldown_cmark::TagEnd::Item => renderer.end_item()?,
74                pulldown_cmark::TagEnd::Emphasis => renderer.set_emphasis(false),
75                pulldown_cmark::TagEnd::Strong => renderer.set_strong(false),
76                pulldown_cmark::TagEnd::Strikethrough => renderer.set_strikethrough(false),
77                pulldown_cmark::TagEnd::Link => renderer.end_link()?,
78                pulldown_cmark::TagEnd::Image => renderer.end_image()?,
79                pulldown_cmark::TagEnd::CodeBlock => renderer.end_code_block()?,
80                pulldown_cmark::TagEnd::BlockQuote(_) => renderer.end_blockquote(),
81                _ => {}
82            },
83            Event::Text(text) => renderer.write_event_text(&text)?,
84            Event::Code(code) => renderer.write_event_code(&code)?,
85            Event::Html(html) => {
86                write!(renderer.output, "{}", html)?;
87            }
88            Event::SoftBreak => renderer.soft_break()?,
89            Event::HardBreak => renderer.hard_break()?,
90            Event::Rule => renderer.render_rule()?,
91            Event::FootnoteReference(name) => {
92                let mut spec = ColorSpec::new();
93                spec.set_fg(Some(Color::Blue)).set_bold(true);
94                renderer.output.set_color(&spec)?;
95                write!(renderer.output, "[^{}]", name)?;
96                renderer.output.reset()?;
97            }
98            Event::TaskListMarker(checked) => renderer.render_task_list_item(checked)?,
99            _ => {}
100        }
101    }
102
103    renderer.flush()?;
104    Ok(())
105}