1
1
//! Render README files to HTML.
2
2
3
3
use ammonia:: { Builder , UrlRelative , UrlRelativeEvaluate } ;
4
+ use comrak:: nodes:: { AstNode , NodeValue } ;
4
5
use htmlescape:: encode_minimal;
5
6
use std:: borrow:: Cow ;
6
7
use std:: path:: Path ;
@@ -57,7 +58,10 @@ impl<'a> MarkdownRenderer<'a> {
57
58
58
59
/// Renders the given markdown to HTML using the current settings.
59
60
fn to_html ( & self , text : & str ) -> String {
60
- use comrak:: { ComrakExtensionOptions , ComrakOptions , ComrakRenderOptions } ;
61
+ use comrak:: {
62
+ format_html, parse_document, Arena , ComrakExtensionOptions , ComrakOptions ,
63
+ ComrakRenderOptions ,
64
+ } ;
61
65
62
66
let options = ComrakOptions {
63
67
render : ComrakRenderOptions {
@@ -75,11 +79,41 @@ impl<'a> MarkdownRenderer<'a> {
75
79
} ,
76
80
..ComrakOptions :: default ( )
77
81
} ;
78
- let rendered = comrak:: markdown_to_html ( text, & options) ;
82
+
83
+ let arena = Arena :: new ( ) ;
84
+ let root = parse_document ( & arena, text, & options) ;
85
+
86
+ // Tweak annotations of code blocks.
87
+ iter_nodes ( root, & |node| {
88
+ if let NodeValue :: CodeBlock ( ref mut ncb) = node. data . borrow_mut ( ) . value {
89
+ let mut orig_annot = String :: from_utf8 ( ncb. info . to_vec ( ) ) . unwrap ( ) ;
90
+
91
+ // Ignore characters after a comma for syntax highlighting to work correctly.
92
+ if let Some ( offset) = orig_annot. find ( ',' ) {
93
+ let _ = orig_annot. drain ( offset..orig_annot. len ( ) ) ;
94
+ ncb. info = orig_annot. as_bytes ( ) . to_vec ( ) ;
95
+ }
96
+ }
97
+ } ) ;
98
+
99
+ let mut html = Vec :: new ( ) ;
100
+ format_html ( root, & options, & mut html) . unwrap ( ) ;
101
+ let rendered = String :: from_utf8 ( html) . unwrap ( ) ;
79
102
self . html_sanitizer . clean ( & rendered) . to_string ( )
80
103
}
81
104
}
82
105
106
+ /// Iterate the nodes in the CommonMark AST, used in comrak.
107
+ fn iter_nodes < ' a , F > ( node : & ' a AstNode < ' a > , f : & F )
108
+ where
109
+ F : Fn ( & ' a AstNode < ' a > ) ,
110
+ {
111
+ f ( node) ;
112
+ for c in node. children ( ) {
113
+ iter_nodes ( c, f) ;
114
+ }
115
+ }
116
+
83
117
/// Add trailing slash and remove `.git` suffix of base URL.
84
118
fn canon_base_url ( mut base_url : String ) -> String {
85
119
if !base_url. ends_with ( '/' ) {
@@ -117,7 +151,7 @@ struct MediaUrl {
117
151
add_sanitize_query : bool ,
118
152
}
119
153
120
- /// Determine whether the given URL has a media file externsion .
154
+ /// Determine whether the given URL has a media file extension .
121
155
/// Also check if `sanitize=true` must be added to the query string,
122
156
/// which is required to load SVGs properly from GitHub.
123
157
fn is_media_url ( url : & str ) -> MediaUrl {
@@ -334,6 +368,15 @@ mod tests {
334
368
assert ! ( result. contains( "<code class=\" language-rust\" >" ) ) ;
335
369
}
336
370
371
+ #[ test]
372
+ fn code_block_with_syntax_highlighting_even_if_annot_has_no_run ( ) {
373
+ let code_block = r#"```rust , no_run \
374
+ println!("Hello World"); \
375
+ ```"# ;
376
+ let result = markdown_to_html ( code_block, None ) ;
377
+ assert ! ( result. contains( "<code class=\" language-rust\" >" ) ) ;
378
+ }
379
+
337
380
#[ test]
338
381
fn text_with_forbidden_class_attribute ( ) {
339
382
let text = "<p class='bad-class'>Hello World!</p>" ;
0 commit comments