3 use pulldown_cmark
::{Parser, Event, Tag, html, Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES}
;
9 /// Wrapper around the pulldown-cmark parser and renderer to render markdown
11 pub fn render_markdown(text
: &str, curly_quotes
: bool
) -> String
{
12 let mut s
= String
::with_capacity(text
.len() * 3 / 2);
14 let mut opts
= Options
::empty();
15 opts
.insert(OPTION_ENABLE_TABLES
);
16 opts
.insert(OPTION_ENABLE_FOOTNOTES
);
18 let p
= Parser
::new_ext(text
, opts
);
19 let mut converter
= EventQuoteConverter
::new(curly_quotes
);
20 let events
= p
.map(clean_codeblock_headers
).map(|event
| converter
.convert(event
));
22 html
::push_html(&mut s
, events
);
26 struct EventQuoteConverter
{
31 impl EventQuoteConverter
{
32 fn new(enabled
: bool
) -> Self {
33 EventQuoteConverter { enabled: enabled, convert_text: true }
36 fn convert
<'a
>(&mut self, event
: Event
<'a
>) -> Event
<'a
> {
42 Event
::Start(Tag
::CodeBlock(_
)) |
43 Event
::Start(Tag
::Code
) => {
44 self.convert_text
= false;
47 Event
::End(Tag
::CodeBlock(_
)) |
48 Event
::End(Tag
::Code
) => {
49 self.convert_text
= true;
52 Event
::Text(ref text
) if self.convert_text
=> Event
::Text(Cow
::from(convert_quotes_to_curly(text
))),
58 fn clean_codeblock_headers(event
: Event
) -> Event
{
60 Event
::Start(Tag
::CodeBlock(ref info
)) => {
61 let info
: String
= info
63 .filter(|ch
| !ch
.is_whitespace())
66 Event
::Start(Tag
::CodeBlock(Cow
::from(info
)))
73 fn convert_quotes_to_curly(original_text
: &str) -> String
{
74 // We'll consider the start to be "whitespace".
75 let mut preceded_by_whitespace
= true;
79 .map(|original_char
| {
80 let converted_char
= match original_char
{
81 '
\''
=> if preceded_by_whitespace { '‘' }
else { '’' }
,
82 '
"' => if preceded_by_whitespace { '“' } else { '”' },
86 preceded_by_whitespace = original_char.is_whitespace();
96 use super::super::render_markdown;
99 fn it_can_keep_quotes_straight() {
100 assert_eq!(render_markdown("'one'
", false), "<p
>'one'
</p
>\n");
104 fn it_can_make_quotes_curly_except_when_they_are_in_code() {
111 let expected = r#"<p
>‘one’
</p
>
114 <p
><code
>'three'
</code
> ‘four’
</p
>
116 assert_eq!(render_markdown(input, true), expected);
120 fn whitespace_outside_of_codeblock_header_is_preserved() {
122 some text with spaces
125 // code inside is unchanged
128 more text with spaces
131 let expected = r#"<p
>some text with spaces
</p
>
132 <pre
><code class
="language-rust">fn main() {
133 // code inside is unchanged
136 <p
>more text with spaces
</p
>
138 assert_eq!(render_markdown(input, false), expected);
139 assert_eq!(render_markdown(input, true), expected);
143 fn rust_code_block_properties_are_passed_as_space_delimited_class() {
145 ```rust
,no_run
,should_panic
,property_3
149 let expected = r#"<pre
><code class
="language-rust,no_run,should_panic,property_3"></code
></pre
>
151 assert_eq!(render_markdown(input, false), expected);
152 assert_eq!(render_markdown(input, true), expected);
156 fn rust_code_block_properties_with_whitespace_are_passed_as_space_delimited_class() {
158 ```rust
, no_run
,,,should_panic
, ,property_3
162 let expected = r#"<pre
><code class
="language-rust,no_run,,,should_panic,,property_3"></code
></pre
>
164 assert_eq!(render_markdown(input, false), expected);
165 assert_eq!(render_markdown(input, true), expected);
169 fn rust_code_block_without_properties_has_proper_html_class() {
175 let expected = r#"<pre
><code class
="language-rust"></code
></pre
>
177 assert_eq!(render_markdown(input, false), expected);
178 assert_eq!(render_markdown(input, true), expected);
184 assert_eq!(render_markdown(input, false), expected);
185 assert_eq!(render_markdown(input, true), expected);
190 mod convert_quotes_to_curly {
191 use super::super::convert_quotes_to_curly;
194 fn it_converts_single_quotes() {
195 assert_eq!(convert_quotes_to_curly("'one'
, 'two'
"), "‘one’
, ‘two’
");
199 fn it_converts_double_quotes() {
200 assert_eq!(convert_quotes_to_curly(r#""one", "two""#), "“one”
, “two”
");
204 fn it_treats_tab_as_whitespace() {
205 assert_eq!(convert_quotes_to_curly("\t'one'
"), "\t‘one’
");