maud_pulldown_cmark/
markdown.rs

1use std::marker::PhantomData;
2
3use maud::RenderOnce;
4use pulldown_cmark::{ Parser, Event };
5
6use render;
7
8/// The adapter that allows rendering markdown inside a `maud` macro.
9///
10/// # Examples
11///
12/// ```
13/// # #![feature(plugin)]
14/// # #![plugin(maud_macros)]
15/// # extern crate maud;
16/// # extern crate maud_pulldown_cmark;
17/// # use maud_pulldown_cmark::Markdown;
18/// # fn main() {
19/// let markdown = "
20///  1. A list
21///  2. With some
22///  3. <span>Inline html</span>
23/// ";
24///
25/// let buffer = html! {
26///   div {
27///     (Markdown::from_string(markdown))
28///   }
29/// };
30///
31/// println!("{}", buffer.into_string());
32/// # }
33/// ```
34///
35/// ```
36/// # #![feature(plugin)]
37/// # #![plugin(maud_macros)]
38/// # extern crate maud;
39/// # extern crate pulldown_cmark;
40/// # extern crate maud_pulldown_cmark;
41/// # use pulldown_cmark::{ Parser, Event };
42/// # use maud_pulldown_cmark::Markdown;
43/// # fn main() {
44/// let markdown = "
45///  1. A list
46///  2. With some
47///  3. <span>Inline html</span>
48/// ";
49///
50/// let events = Parser::new(markdown).map(|ev| match ev {
51///   // Escape inline html
52///   Event::Html(html) | Event::InlineHtml(html) => Event::Text(html),
53///   _ => ev,
54/// });
55///
56/// let buffer = html! {
57///   div {
58///     (Markdown::from_events(events))
59///   }
60/// };
61///
62/// println!("{}", buffer.into_string());
63/// # }
64/// ```
65pub struct Markdown<'a, I: 'a + Iterator<Item=Event<'a>>> {
66  events: I,
67  config: render::Config,
68  phantom: PhantomData<&'a I>,
69}
70
71impl<'a> Markdown<'a, Parser<'a>> {
72  /// To allow rendering from a string.
73  pub fn from_string(s: &'a str) -> Markdown<'a, Parser<'a>> {
74    Markdown {
75      events: Parser::new(s),
76      config: render::Config {
77        header_ids: false,
78      },
79      phantom: PhantomData,
80    }
81  }
82}
83
84impl<'a, I: 'a + Iterator<Item=Event<'a>>> Markdown<'a, I> {
85  /// To allow rendering from a stream of events (useful for modifying the output of the general parser).
86  pub fn from_events(events: I) -> Markdown<'a, I> {
87    Markdown {
88      events: events,
89      config: render::Config {
90        header_ids: false,
91      },
92      phantom: PhantomData,
93    }
94  }
95
96  /// Generate ids for all headers, lowercases the text in the header and replaces spaces with -.
97  ///
98  /// # Examples
99  ///
100  /// ```
101  /// # #![feature(plugin)]
102  /// # #![plugin(maud_macros)]
103  /// # extern crate maud;
104  /// # extern crate maud_pulldown_cmark;
105  /// # use maud_pulldown_cmark::Markdown;
106  /// # fn main() {
107  // TODO: Work out how to escape the # so this can go on the next line
108  /// let markdown = "# Header
109  /// ## A Sub Header
110  /// ";
111  ///
112  /// let buffer = html!(
113  ///   (Markdown::from_string(markdown).with_header_ids())
114  /// );
115  ///
116  /// assert_eq!(buffer.into_string(), "<h1 id=\"header\">Header</h1>\n<h2 id=\"a-sub-header\">A Sub Header</h2>\n");
117  /// # }
118  /// ```
119  pub fn with_header_ids(self) -> Markdown<'a, I> {
120    Markdown {
121      config: render::Config {
122        header_ids: true,
123        ..self.config
124      },
125      ..self
126    }
127  }
128}
129
130impl<'a, I: 'a + Iterator<Item=Event<'a>>> RenderOnce for Markdown<'a, I> {
131  fn render_once_to(self, w: &mut String) {
132    render::render_events(self.config, self.events, w);
133  }
134}
135
136#[cfg(test)]
137mod tests {
138  use super::Markdown;
139  use maud::RenderOnce;
140  use pulldown_cmark::{ Parser, Event };
141
142  impl<'a, I: 'a + Iterator<Item=Event<'a>>> Markdown<'a, I> {
143    pub fn render(self) -> String {
144      let mut buffer = String::new();
145      self.render_once_to(&mut buffer);
146      buffer
147    }
148  }
149
150  #[test]
151  pub fn test_from_string() {
152    let markdown = "
153 1. A list
154 2. With some
155 3. <span>Inline html</span>
156    ";
157
158    let buffer = Markdown::from_string(markdown).render();
159    assert_eq!(buffer, "<ol>\n<li>A list</li>\n<li>With some</li>\n<li><span>Inline html</span></li>\n</ol>\n");
160  }
161
162  #[test]
163  pub fn test_from_events() {
164    let markdown = "
165 1. A list
166 2. With some
167 3. <span>Inline html</span>
168    ";
169
170    let events = Parser::new(markdown).map(|ev| match ev {
171      // Escape inline html
172      Event::Html(html) | Event::InlineHtml(html) => Event::Text(html),
173      _ => ev,
174    });
175
176    let buffer = Markdown::from_events(events).render();
177    assert_eq!(buffer, "<ol>\n<li>A list</li>\n<li>With some</li>\n<li>&lt;span&gt;Inline html&lt;/span&gt;</li>\n</ol>\n");
178  }
179
180  #[test]
181  pub fn test_without_header_ids() {
182    let markdown = "
183# Header
184## A Sub Header
185    ";
186
187    let buffer = Markdown::from_string(markdown).render();
188    assert_eq!(buffer, "<h1>Header</h1>\n<h2>A Sub Header</h2>\n");
189  }
190
191  #[test]
192  pub fn test_with_header_ids() {
193    let markdown = "
194# Header
195## A Sub Header
196    ";
197
198    let buffer = Markdown::from_string(markdown).with_header_ids().render();
199    assert_eq!(buffer, "<h1 id=\"header\">Header</h1>\n<h2 id=\"a-sub-header\">A Sub Header</h2>\n");
200  }
201
202  #[test]
203  pub fn test_with_header_ids_and_linked_inline_image() {
204    let markdown = "
205# Header [![an image](http://example.com/image)](http://example.com)
206    ";
207
208    let buffer = Markdown::from_string(markdown).with_header_ids().render();
209    assert_eq!(buffer, "<h1 id=\"header-an-image\">Header <a href=\"http://example.com\"><img src=\"http://example.com/image\" alt=\"an image\" /></a></h1>\n");
210  }
211}