]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/markdown.rs
Convert most code to new inner attribute syntax.
[rust.git] / src / librustdoc / html / markdown.rs
1 // Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Markdown formatting for rustdoc
12 //!
13 //! This module implements markdown formatting through the sundown C-library
14 //! (bundled into the rust runtime). This module self-contains the C bindings
15 //! and necessary legwork to render markdown, and exposes all of the
16 //! functionality through a unit-struct, `Markdown`, which has an implementation
17 //! of `fmt::Show`. Example usage:
18 //!
19 //! ```rust,ignore
20 //! use rustdoc::html::markdown::Markdown;
21 //!
22 //! let s = "My *markdown* _text_";
23 //! let html = format!("{}", Markdown(s));
24 //! // ... something using html
25 //! ```
26
27 #![allow(non_camel_case_types)]
28
29 use std::cast;
30 use std::fmt;
31 use std::io;
32 use std::libc;
33 use std::local_data;
34 use std::mem;
35 use std::str;
36 use std::slice;
37 use collections::HashMap;
38
39 use html::toc::TocBuilder;
40 use html::highlight;
41
42 /// A unit struct which has the `fmt::Show` trait implemented. When
43 /// formatted, this struct will emit the HTML corresponding to the rendered
44 /// version of the contained markdown string.
45 pub struct Markdown<'a>(&'a str);
46 /// A unit struct like `Markdown`, that renders the markdown with a
47 /// table of contents.
48 pub struct MarkdownWithToc<'a>(&'a str);
49
50 static OUTPUT_UNIT: libc::size_t = 64;
51 static MKDEXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 0;
52 static MKDEXT_TABLES: libc::c_uint = 1 << 1;
53 static MKDEXT_FENCED_CODE: libc::c_uint = 1 << 2;
54 static MKDEXT_AUTOLINK: libc::c_uint = 1 << 3;
55 static MKDEXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
56
57 type sd_markdown = libc::c_void;  // this is opaque to us
58
59 struct sd_callbacks {
60     blockcode: Option<extern "C" fn(*buf, *buf, *buf, *libc::c_void)>,
61     blockquote: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
62     blockhtml: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
63     header: Option<extern "C" fn(*buf, *buf, libc::c_int, *libc::c_void)>,
64     other: [libc::size_t, ..22],
65 }
66
67 struct html_toc_data {
68     header_count: libc::c_int,
69     current_level: libc::c_int,
70     level_offset: libc::c_int,
71 }
72
73 struct html_renderopt {
74     toc_data: html_toc_data,
75     flags: libc::c_uint,
76     link_attributes: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
77 }
78
79 struct my_opaque {
80     opt: html_renderopt,
81     dfltblk: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
82     toc_builder: Option<TocBuilder>,
83 }
84
85 struct buf {
86     data: *u8,
87     size: libc::size_t,
88     asize: libc::size_t,
89     unit: libc::size_t,
90 }
91
92 // sundown FFI
93 #[link(name = "sundown", kind = "static")]
94 extern {
95     fn sdhtml_renderer(callbacks: *sd_callbacks,
96                        options_ptr: *html_renderopt,
97                        render_flags: libc::c_uint);
98     fn sd_markdown_new(extensions: libc::c_uint,
99                        max_nesting: libc::size_t,
100                        callbacks: *sd_callbacks,
101                        opaque: *libc::c_void) -> *sd_markdown;
102     fn sd_markdown_render(ob: *buf,
103                           document: *u8,
104                           doc_size: libc::size_t,
105                           md: *sd_markdown);
106     fn sd_markdown_free(md: *sd_markdown);
107
108     fn bufnew(unit: libc::size_t) -> *buf;
109     fn bufputs(b: *buf, c: *libc::c_char);
110     fn bufrelease(b: *buf);
111
112 }
113
114 /// Returns Some(code) if `s` is a line that should be stripped from
115 /// documentation but used in example code. `code` is the portion of
116 /// `s` that should be used in tests. (None for lines that should be
117 /// left as-is.)
118 fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
119     let trimmed = s.trim();
120     if trimmed.starts_with("# ") {
121         Some(trimmed.slice_from(2))
122     } else {
123         None
124     }
125 }
126
127 local_data_key!(used_header_map: HashMap<~str, uint>)
128
129 pub fn render(w: &mut io::Writer, s: &str, print_toc: bool) -> fmt::Result {
130     extern fn block(ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
131         unsafe {
132             let my_opaque: &my_opaque = cast::transmute(opaque);
133             slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
134                 let text = str::from_utf8(text).unwrap();
135                 let mut lines = text.lines().filter(|l| stripped_filtered_line(*l).is_none());
136                 let text = lines.collect::<~[&str]>().connect("\n");
137
138                 let buf = buf {
139                     data: text.as_bytes().as_ptr(),
140                     size: text.len() as libc::size_t,
141                     asize: text.len() as libc::size_t,
142                     unit: 0,
143                 };
144                 let rendered = if lang.is_null() {
145                     false
146                 } else {
147                     slice::raw::buf_as_slice((*lang).data,
148                                            (*lang).size as uint, |rlang| {
149                         let rlang = str::from_utf8(rlang).unwrap();
150                         if rlang.contains("notrust") {
151                             (my_opaque.dfltblk)(ob, &buf, lang, opaque);
152                             true
153                         } else {
154                             false
155                         }
156                     })
157                 };
158
159                 if !rendered {
160                     let output = highlight::highlight(text, None).to_c_str();
161                     output.with_ref(|r| {
162                         bufputs(ob, r)
163                     })
164                 }
165             })
166         }
167     }
168
169     extern fn header(ob: *buf, text: *buf, level: libc::c_int,
170                      opaque: *libc::c_void) {
171         // sundown does this, we may as well too
172         "\n".with_c_str(|p| unsafe { bufputs(ob, p) });
173
174         // Extract the text provided
175         let s = if text.is_null() {
176             ~""
177         } else {
178             unsafe {
179                 str::raw::from_buf_len((*text).data, (*text).size as uint)
180             }
181         };
182
183         // Transform the contents of the header into a hyphenated string
184         let id = s.words().map(|s| {
185             match s.to_ascii_opt() {
186                 Some(s) => s.to_lower().into_str(),
187                 None => s.to_owned()
188             }
189         }).collect::<~[~str]>().connect("-");
190
191         let opaque = unsafe {&mut *(opaque as *mut my_opaque)};
192
193         // Make sure our hyphenated ID is unique for this page
194         let id = local_data::get_mut(used_header_map, |map| {
195             let map = map.unwrap();
196             match map.find_mut(&id) {
197                 None => {}
198                 Some(a) => { *a += 1; return format!("{}-{}", id, *a - 1) }
199             }
200             map.insert(id.clone(), 1);
201             id.clone()
202         });
203
204         let sec = match opaque.toc_builder {
205             Some(ref mut builder) => {
206                 builder.push(level as u32, s.clone(), id.clone())
207             }
208             None => {""}
209         };
210
211         // Render the HTML
212         let text = format!(r#"<h{lvl} id="{id}" class='section-link'><a
213                            href="\#{id}">{sec_len,plural,=0{}other{{sec} }}{}</a></h{lvl}>"#,
214                            s, lvl = level, id = id,
215                            sec_len = sec.len(), sec = sec);
216
217         text.with_c_str(|p| unsafe { bufputs(ob, p) });
218     }
219
220     // This code is all lifted from examples/sundown.c in the sundown repo
221     unsafe {
222         let ob = bufnew(OUTPUT_UNIT);
223         let extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_TABLES |
224                          MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK |
225                          MKDEXT_STRIKETHROUGH;
226         let options = html_renderopt {
227             toc_data: html_toc_data {
228                 header_count: 0,
229                 current_level: 0,
230                 level_offset: 0,
231             },
232             flags: 0,
233             link_attributes: None,
234         };
235         let mut callbacks: sd_callbacks = mem::init();
236
237         sdhtml_renderer(&callbacks, &options, 0);
238         let mut opaque = my_opaque {
239             opt: options,
240             dfltblk: callbacks.blockcode.unwrap(),
241             toc_builder: if print_toc {Some(TocBuilder::new())} else {None}
242         };
243         callbacks.blockcode = Some(block);
244         callbacks.header = Some(header);
245         let markdown = sd_markdown_new(extensions, 16, &callbacks,
246                                        &mut opaque as *mut my_opaque as *libc::c_void);
247
248
249         sd_markdown_render(ob, s.as_ptr(), s.len() as libc::size_t, markdown);
250         sd_markdown_free(markdown);
251
252         let mut ret = match opaque.toc_builder {
253             Some(b) => write!(w, "<nav id=\"TOC\">{}</nav>", b.into_toc()),
254             None => Ok(())
255         };
256
257         if ret.is_ok() {
258             ret = slice::raw::buf_as_slice((*ob).data, (*ob).size as uint, |buf| {
259                 w.write(buf)
260             });
261         }
262         bufrelease(ob);
263         ret
264     }
265 }
266
267 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
268     extern fn block(_ob: *buf, text: *buf, lang: *buf, opaque: *libc::c_void) {
269         unsafe {
270             if text.is_null() { return }
271             let (should_fail, no_run, ignore) = if lang.is_null() {
272                 (false, false, false)
273             } else {
274                 slice::raw::buf_as_slice((*lang).data,
275                                        (*lang).size as uint, |lang| {
276                     let s = str::from_utf8(lang).unwrap();
277                     (s.contains("should_fail"),
278                      s.contains("no_run"),
279                      s.contains("ignore") || s.contains("notrust"))
280                 })
281             };
282             if ignore { return }
283             slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
284                 let tests = &mut *(opaque as *mut ::test::Collector);
285                 let text = str::from_utf8(text).unwrap();
286                 let mut lines = text.lines().map(|l| stripped_filtered_line(l).unwrap_or(l));
287                 let text = lines.collect::<~[&str]>().connect("\n");
288                 tests.add_test(text, should_fail, no_run);
289             })
290         }
291     }
292     extern fn header(_ob: *buf, text: *buf, level: libc::c_int, opaque: *libc::c_void) {
293         unsafe {
294             let tests = &mut *(opaque as *mut ::test::Collector);
295             if text.is_null() {
296                 tests.register_header("", level as u32);
297             } else {
298                 slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
299                     let text = str::from_utf8(text).unwrap();
300                     tests.register_header(text, level as u32);
301                 })
302             }
303         }
304     }
305
306     unsafe {
307         let ob = bufnew(OUTPUT_UNIT);
308         let extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_TABLES |
309                          MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK |
310                          MKDEXT_STRIKETHROUGH;
311         let callbacks = sd_callbacks {
312             blockcode: Some(block),
313             blockquote: None,
314             blockhtml: None,
315             header: Some(header),
316             other: mem::init()
317         };
318
319         let tests = tests as *mut ::test::Collector as *libc::c_void;
320         let markdown = sd_markdown_new(extensions, 16, &callbacks, tests);
321
322         sd_markdown_render(ob, doc.as_ptr(), doc.len() as libc::size_t,
323                            markdown);
324         sd_markdown_free(markdown);
325         bufrelease(ob);
326     }
327 }
328
329 /// By default this markdown renderer generates anchors for each header in the
330 /// rendered document. The anchor name is the contents of the header spearated
331 /// by hyphens, and a task-local map is used to disambiguate among duplicate
332 /// headers (numbers are appended).
333 ///
334 /// This method will reset the local table for these headers. This is typically
335 /// used at the beginning of rendering an entire HTML page to reset from the
336 /// previous state (if any).
337 pub fn reset_headers() {
338     local_data::set(used_header_map, HashMap::new())
339 }
340
341 impl<'a> fmt::Show for Markdown<'a> {
342     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
343         let Markdown(md) = *self;
344         // This is actually common enough to special-case
345         if md.len() == 0 { return Ok(()) }
346         render(fmt.buf, md.as_slice(), false)
347     }
348 }
349
350 impl<'a> fmt::Show for MarkdownWithToc<'a> {
351     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
352         let MarkdownWithToc(md) = *self;
353         render(fmt.buf, md.as_slice(), true)
354     }
355 }