prettify_cmark/
printer.rs

1use std::fmt::{Result, Write};
2
3use pulldown_cmark::{Event, Tag};
4
5use writer::{Frame, Writer};
6
7/// Event-driven pretty printer for CommonMark documents.
8///
9/// The printer can be driven by pushing events into it, which can be obtained
10/// using `pulldown_cmark::Parser`.
11///
12/// # Examples
13///
14/// ```rust
15/// extern crate pulldown_cmark;
16/// extern crate prettify_cmark;
17///
18/// use pulldown_cmark::Parser;
19/// use prettify_cmark::PrettyPrinter;
20///
21/// fn main() {
22///     let events = Parser::new("Lorem _ipsum_!\n\nDolor `sit`.");
23///     let mut printer = PrettyPrinter::new_with_prefix(String::new(), "///");
24///     printer.push_events(events).unwrap();
25///
26///     assert_eq!(printer.into_inner(), "/// Lorem *ipsum*!\n///\n/// Dolor `sit`.")
27/// }
28/// ```
29pub struct PrettyPrinter<W = String> {
30    writer: Writer<W>,
31    needs_break: bool
32}
33
34impl<W: Write> PrettyPrinter<W> {
35    /// Create a new pretty printer that wraps around a writer.
36    pub fn new(write: W) -> PrettyPrinter<W> {
37        PrettyPrinter::new_with_prefix(write, "")
38    }
39
40    /// Create a new pretty printer with a prefix that wraps around
41    /// a writer.
42    ///
43    /// The prefix will be applied to all lines that are produced by
44    /// the printer.
45    pub fn new_with_prefix(write: W, prefix: &str) -> PrettyPrinter<W> {
46        PrettyPrinter {
47            writer: Writer::new(write, prefix.to_string()),
48            needs_break: false
49        }
50    }
51
52    /// Push a single event into the printer.
53    ///
54    /// Events can be obtained using `pulldown_cmark::Parser`.
55    pub fn push_event<'a>(&mut self, event: Event<'a>) -> Result {
56        match event {
57            Event::Start(tag) => {
58                match tag {
59                    Tag::Paragraph => {
60                        self.flush_break()?;
61                    },
62                    Tag::Rule => {
63                        self.flush_break()?;
64                        self.writer.write_text("---")?;
65                    },
66                    Tag::Header(indent) => {
67                        self.flush_break()?;
68                        self.writer.write_text(&"#".repeat(indent as usize))?;
69                        self.writer.write_non_breaking_space()?;
70                    },
71                    Tag::List(start) => {
72                        self.flush_break()?;
73                        self.writer.push_frame(Frame::ListItem(start));
74                    },
75                    Tag::Item => {
76                        match self.writer.pop_frame() {
77                            Some(Frame::ListItem(None)) => {
78                                self.flush_break()?;
79                                self.writer.write_text("-")?;
80                                self.writer.write_non_breaking_space()?;
81                                self.writer.push_frame(Frame::ListItem(None));
82                            },
83                            Some(Frame::ListItem(Some(index))) => {
84                                self.flush_break()?;
85                                write!(self.writer, "{}.", index)?;
86                                self.writer.write_non_breaking_space()?;
87                                self.writer.push_frame(Frame::ListItem(Some(index + 1)));
88                            },
89                            _ => {}
90                        }
91                    },
92                    Tag::BlockQuote => {
93                        self.flush_break()?;
94                        self.writer.write_text(">")?;
95                        self.writer.write_non_breaking_space()?;
96                        self.writer.push_frame(Frame::BlockQuote);
97                    },
98                    Tag::CodeBlock(note) => {
99                        self.flush_break()?;
100                        write!(self.writer, "```{}", note)?;
101                        self.writer.write_hard_break()?;
102                        self.writer.write_indent()?;
103                    },
104                    Tag::Emphasis => {
105                        self.writer.write_text("*")?;
106                    },
107                    Tag::Strong => {
108                        self.writer.write_text("**")?;
109                    },
110                    Tag::Code => {
111                        self.writer.write_text("`")?;
112                    },
113                    Tag::Link(_, _) => {
114                        self.writer.write_text("[")?;
115                    },
116                    Tag::Image(_, _) => {
117                        self.writer.write_text("![")?;
118                    },
119                    Tag::FootnoteDefinition(_) => { /* not supported for now */ },
120                    Tag::Table(_) => { /* not supported for now */ },
121                    Tag::TableHead => { /* not supported for now */ },
122                    Tag::TableRow => { /* not supported for now */ },
123                    Tag::TableCell => { /* not supported for now */ }
124                }
125            },
126            Event::End(tag) => {
127                match tag {
128                    Tag::Paragraph => {
129                        self.needs_break = true;
130                    },
131                    Tag::Rule => {
132                        self.needs_break = true;
133                    },
134                    Tag::Header(_) => {
135                        self.needs_break = true;
136                    },
137                    Tag::List(_) => {
138                        self.writer.pop_frame();
139                        self.needs_break = true;
140                    },
141                    Tag::Item => {
142                        self.needs_break = true;
143                    },
144                    Tag::BlockQuote => {
145                        self.writer.pop_frame();
146                        self.needs_break = true;
147                    },
148                    Tag::CodeBlock(_) => {
149                        self.writer.write_text("```")?;
150                        self.needs_break = true;
151                    },
152                    Tag::Emphasis => {
153                        self.writer.write_text("*")?;
154                    },
155                    Tag::Strong => {
156                        self.writer.write_text("**")?;
157                    },
158                    Tag::Code => {
159                        self.writer.write_text("`")?;
160                    },
161                    Tag::Link(ref url, ref title) | Tag::Image(ref url, ref title) => {
162                        if title.is_empty() {
163                            write!(self.writer, "]({})", url)?;
164                        } else {
165                            write!(self.writer, "]({} \"{}\")", url, title)?;
166                        }
167                    },
168                    Tag::FootnoteDefinition(_) => { /* not supported for now */ },
169                    Tag::Table(_) => { /* not supported for now */ },
170                    Tag::TableHead => { /* not supported for now */ },
171                    Tag::TableRow => { /* not supported for now */ },
172                    Tag::TableCell => { /* not supported for now */ }
173                }
174            },
175            Event::Text(text) => {
176                for (i, line) in text.split('\n').enumerate() {
177                    if i > 0 {
178                        self.writer.write_hard_break()?;
179                        self.writer.write_indent()?;
180                    }
181                    self.writer.write_text(line)?;
182                }
183            },
184            Event::Html(_html) => {
185                // not supported for now
186            },
187            Event::InlineHtml(html) => {
188                self.writer.write_text(html.as_ref())?
189            },
190            Event::FootnoteReference(_) => {
191                // not supported for now
192            },
193            Event::SoftBreak => {
194                self.writer.write_soft_break()?
195            },
196            Event::HardBreak => {
197                self.writer.write_text("\\")?;
198                self.writer.write_hard_break()?;
199                self.writer.write_indent()?;
200            }
201        };
202
203        Ok(())
204    }
205
206    /// Push a series of events into the printer.
207    ///
208    /// Events can be obtained using `pulldown_cmark::Parser`.
209    pub fn push_events<'a, I>(&mut self, events: I) -> Result
210        where I: IntoIterator<Item=Event<'a>>
211    {
212        for event in events {
213            self.push_event(event)?;
214        }
215        Ok(())
216    }
217
218    /// Unwrap the printer, returning the underlying writer.
219    pub fn into_inner(self) -> W {
220        self.writer.into_inner()
221    }
222
223    fn flush_break(&mut self) -> Result {
224        if self.needs_break {
225            self.writer.write_hard_break()?;
226            self.writer.write_indent()?;
227            self.writer.write_hard_break()?;
228            self.writer.write_indent()?;
229        }
230        self.needs_break = false;
231        Ok(())
232    }
233}
234
235impl Default for PrettyPrinter {
236    fn default() -> PrettyPrinter {
237        PrettyPrinter::new(String::new())
238    }
239}