28
28
29
29
use std:: cast;
30
30
use std:: fmt;
31
+ use std:: intrinsics;
31
32
use std:: io;
32
33
use std:: libc;
34
+ use std:: local_data;
33
35
use std:: mem;
34
36
use std:: str;
35
- use std:: intrinsics;
36
37
use std:: vec;
38
+ use collections:: HashMap ;
37
39
38
40
use html:: highlight;
41
+ use html:: escape:: Escape ;
39
42
40
43
/// A unit struct which has the `fmt::Show` trait implemented. When
41
44
/// formatted, this struct will emit the HTML corresponding to the rendered
@@ -52,8 +55,11 @@ static MKDEXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
52
55
type sd_markdown = libc:: c_void ; // this is opaque to us
53
56
54
57
struct sd_callbacks {
55
- blockcode : extern "C" fn ( * buf , * buf , * buf , * libc:: c_void ) ,
56
- other : [ libc:: size_t , ..25 ] ,
58
+ blockcode : Option < extern "C" fn ( * buf , * buf , * buf , * libc:: c_void ) > ,
59
+ blockquote : Option < extern "C" fn ( * buf , * buf , * libc:: c_void ) > ,
60
+ blockhtml : Option < extern "C" fn ( * buf , * buf , * libc:: c_void ) > ,
61
+ header : Option < extern "C" fn ( * buf , * buf , libc:: c_int , * libc:: c_void ) > ,
62
+ other : [ libc:: size_t , ..22 ] ,
57
63
}
58
64
59
65
struct html_toc_data {
@@ -115,6 +121,8 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
115
121
}
116
122
}
117
123
124
+ local_data_key ! ( used_header_map: HashMap <~str , uint>)
125
+
118
126
pub fn render ( w : & mut io:: Writer , s : & str ) -> fmt:: Result {
119
127
extern fn block ( ob : * buf , text : * buf , lang : * buf , opaque : * libc:: c_void ) {
120
128
unsafe {
@@ -155,6 +163,45 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
155
163
}
156
164
}
157
165
166
+ extern fn header ( ob : * buf , text : * buf , level : libc:: c_int ,
167
+ _opaque : * libc:: c_void ) {
168
+ // sundown does this, we may as well too
169
+ "\n " . with_c_str ( |p| unsafe { bufputs ( ob, p) } ) ;
170
+
171
+ // Extract the text provided
172
+ let s = if text. is_null ( ) {
173
+ ~""
174
+ } else {
175
+ unsafe {
176
+ str:: raw:: from_buf_len ( ( * text) . data , ( * text) . size as uint )
177
+ }
178
+ } ;
179
+
180
+ // Transform the contents of the header into a hyphenated string
181
+ let id = s. words ( ) . map ( |s| {
182
+ match s. to_ascii_opt ( ) {
183
+ Some ( s) => s. to_lower ( ) . into_str ( ) ,
184
+ None => s. to_owned ( )
185
+ }
186
+ } ) . to_owned_vec ( ) . connect ( "-" ) ;
187
+
188
+ // Make sure our hyphenated ID is unique for this page
189
+ let id = local_data:: get_mut ( used_header_map, |map| {
190
+ let map = map. unwrap ( ) ;
191
+ match map. find_mut ( & id) {
192
+ None => { }
193
+ Some ( a) => { * a += 1 ; return format ! ( "{}-{}" , id, * a - 1 ) }
194
+ }
195
+ map. insert ( id. clone ( ) , 1 ) ;
196
+ id. clone ( )
197
+ } ) ;
198
+
199
+ // Render the HTML
200
+ let text = format ! ( r#"<h{lvl} id="{id}">{}</h{lvl}>"# ,
201
+ Escape ( s. as_slice( ) ) , lvl = level, id = id) ;
202
+ text. with_c_str ( |p| unsafe { bufputs ( ob, p) } ) ;
203
+ }
204
+
158
205
// This code is all lifted from examples/sundown.c in the sundown repo
159
206
unsafe {
160
207
let ob = bufnew ( OUTPUT_UNIT ) ;
@@ -175,9 +222,10 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
175
222
sdhtml_renderer ( & callbacks, & options, 0 ) ;
176
223
let opaque = my_opaque {
177
224
opt : options,
178
- dfltblk : callbacks. blockcode ,
225
+ dfltblk : callbacks. blockcode . unwrap ( ) ,
179
226
} ;
180
- callbacks. blockcode = block;
227
+ callbacks. blockcode = Some ( block) ;
228
+ callbacks. header = Some ( header) ;
181
229
let markdown = sd_markdown_new ( extensions, 16 , & callbacks,
182
230
& opaque as * my_opaque as * libc:: c_void ) ;
183
231
@@ -225,7 +273,10 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
225
273
MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK |
226
274
MKDEXT_STRIKETHROUGH ;
227
275
let callbacks = sd_callbacks {
228
- blockcode : block,
276
+ blockcode : Some ( block) ,
277
+ blockquote : None ,
278
+ blockhtml : None ,
279
+ header : None ,
229
280
other : mem:: init ( )
230
281
} ;
231
282
@@ -239,6 +290,18 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
239
290
}
240
291
}
241
292
293
+ /// By default this markdown renderer generates anchors for each header in the
294
+ /// rendered document. The anchor name is the contents of the header spearated
295
+ /// by hyphens, and a task-local map is used to disambiguate among duplicate
296
+ /// headers (numbers are appended).
297
+ ///
298
+ /// This method will reset the local table for these headers. This is typically
299
+ /// used at the beginning of rendering an entire HTML page to reset from the
300
+ /// previous state (if any).
301
+ pub fn reset_headers ( ) {
302
+ local_data:: set ( used_header_map, HashMap :: new ( ) )
303
+ }
304
+
242
305
impl < ' a > fmt:: Show for Markdown < ' a > {
243
306
fn fmt ( & self , fmt : & mut fmt:: Formatter ) -> fmt:: Result {
244
307
let Markdown ( md) = * self ;
0 commit comments