1 // Copyright 2013-2014 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.
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.
11 //! Markdown formatting for rustdoc
13 //! This module implements markdown formatting through the hoedown 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:
20 //! use rustdoc::html::markdown::Markdown;
22 //! let s = "My *markdown* _text_";
23 //! let html = format!("{}", Markdown(s));
24 //! // ... something using html
27 #![allow(non_camel_case_types)]
30 use std::cell::RefCell;
34 use collections::HashMap;
36 use html::toc::TocBuilder;
39 /// A unit struct which has the `fmt::Show` trait implemented. When
40 /// formatted, this struct will emit the HTML corresponding to the rendered
41 /// version of the contained markdown string.
42 pub struct Markdown<'a>(pub &'a str);
43 /// A unit struct like `Markdown`, that renders the markdown with a
44 /// table of contents.
45 pub struct MarkdownWithToc<'a>(pub &'a str);
47 static DEF_OUNIT: libc::size_t = 64;
48 static HOEDOWN_EXT_NO_INTRA_EMPHASIS: libc::c_uint = 1 << 10;
49 static HOEDOWN_EXT_TABLES: libc::c_uint = 1 << 0;
50 static HOEDOWN_EXT_FENCED_CODE: libc::c_uint = 1 << 1;
51 static HOEDOWN_EXT_AUTOLINK: libc::c_uint = 1 << 3;
52 static HOEDOWN_EXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
53 static HOEDOWN_EXT_SUPERSCRIPT: libc::c_uint = 1 << 8;
54 static HOEDOWN_EXT_FOOTNOTES: libc::c_uint = 1 << 2;
56 static HOEDOWN_EXTENSIONS: libc::c_uint =
57 HOEDOWN_EXT_NO_INTRA_EMPHASIS | HOEDOWN_EXT_TABLES |
58 HOEDOWN_EXT_FENCED_CODE | HOEDOWN_EXT_AUTOLINK |
59 HOEDOWN_EXT_STRIKETHROUGH | HOEDOWN_EXT_SUPERSCRIPT |
60 HOEDOWN_EXT_FOOTNOTES;
62 type hoedown_document = libc::c_void; // this is opaque to us
64 struct hoedown_renderer {
65 opaque: *mut hoedown_html_renderer_state,
66 blockcode: Option<extern "C" fn(*mut hoedown_buffer, *hoedown_buffer,
67 *hoedown_buffer, *mut libc::c_void)>,
68 blockquote: Option<extern "C" fn(*mut hoedown_buffer, *hoedown_buffer,
70 blockhtml: Option<extern "C" fn(*mut hoedown_buffer, *hoedown_buffer,
72 header: Option<extern "C" fn(*mut hoedown_buffer, *hoedown_buffer,
73 libc::c_int, *mut libc::c_void)>,
74 other: [libc::size_t, ..28],
77 struct hoedown_html_renderer_state {
78 opaque: *mut libc::c_void,
79 toc_data: html_toc_data,
81 link_attributes: Option<extern "C" fn(*mut hoedown_buffer, *hoedown_buffer,
85 struct html_toc_data {
86 header_count: libc::c_int,
87 current_level: libc::c_int,
88 level_offset: libc::c_int,
89 nesting_level: libc::c_int,
93 dfltblk: extern "C" fn(*mut hoedown_buffer, *hoedown_buffer,
94 *hoedown_buffer, *mut libc::c_void),
95 toc_builder: Option<TocBuilder>,
98 struct hoedown_buffer {
106 #[link(name = "hoedown", kind = "static")]
108 fn hoedown_html_renderer_new(render_flags: libc::c_uint,
109 nesting_level: libc::c_int)
110 -> *mut hoedown_renderer;
111 fn hoedown_html_renderer_free(renderer: *mut hoedown_renderer);
113 fn hoedown_document_new(rndr: *mut hoedown_renderer,
114 extensions: libc::c_uint,
115 max_nesting: libc::size_t) -> *mut hoedown_document;
116 fn hoedown_document_render(doc: *mut hoedown_document,
117 ob: *mut hoedown_buffer,
119 doc_size: libc::size_t);
120 fn hoedown_document_free(md: *mut hoedown_document);
122 fn hoedown_buffer_new(unit: libc::size_t) -> *mut hoedown_buffer;
123 fn hoedown_buffer_puts(b: *mut hoedown_buffer, c: *libc::c_char);
124 fn hoedown_buffer_free(b: *mut hoedown_buffer);
128 /// Returns Some(code) if `s` is a line that should be stripped from
129 /// documentation but used in example code. `code` is the portion of
130 /// `s` that should be used in tests. (None for lines that should be
132 fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
133 let trimmed = s.trim();
134 if trimmed.starts_with("# ") {
135 Some(trimmed.slice_from(2))
141 local_data_key!(used_header_map: RefCell<HashMap<String, uint>>)
143 pub fn render(w: &mut fmt::Formatter, s: &str, print_toc: bool) -> fmt::Result {
144 extern fn block(ob: *mut hoedown_buffer, text: *hoedown_buffer,
145 lang: *hoedown_buffer, opaque: *mut libc::c_void) {
147 if text.is_null() { return }
149 let opaque = opaque as *mut hoedown_html_renderer_state;
150 let my_opaque: &MyOpaque = &*((*opaque).opaque as *MyOpaque);
151 slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
152 let text = str::from_utf8(text).unwrap();
153 debug!("docblock: ==============\n{}\n=======", text);
154 let mut lines = text.lines().filter(|l| {
155 stripped_filtered_line(*l).is_none()
157 let text = lines.collect::<Vec<&str>>().connect("\n");
159 let buf = hoedown_buffer {
160 data: text.as_bytes().as_ptr(),
161 size: text.len() as libc::size_t,
162 asize: text.len() as libc::size_t,
165 let rendered = if lang.is_null() {
168 slice::raw::buf_as_slice((*lang).data,
169 (*lang).size as uint, |rlang| {
170 let rlang = str::from_utf8(rlang).unwrap();
171 if rlang.contains("notrust") {
172 (my_opaque.dfltblk)(ob, &buf, lang,
173 opaque as *mut libc::c_void);
182 let output = highlight::highlight(text.as_slice(),
185 output.with_ref(|r| {
186 hoedown_buffer_puts(ob, r)
193 extern fn header(ob: *mut hoedown_buffer, text: *hoedown_buffer,
194 level: libc::c_int, opaque: *mut libc::c_void) {
195 // hoedown does this, we may as well too
196 "\n".with_c_str(|p| unsafe { hoedown_buffer_puts(ob, p) });
198 // Extract the text provided
199 let s = if text.is_null() {
203 str::raw::from_buf_len((*text).data, (*text).size as uint)
207 // Transform the contents of the header into a hyphenated string
208 let id = (s.as_slice().words().map(|s| {
209 match s.to_ascii_opt() {
210 Some(s) => s.to_lower().into_str().to_string(),
211 None => s.to_string()
213 }).collect::<Vec<String>>().connect("-")).to_string();
215 // This is a terrible hack working around how hoedown gives us rendered
216 // html for text rather than the raw text.
217 let id = id.replace("<code>", "").replace("</code>", "").to_string();
219 let opaque = opaque as *mut hoedown_html_renderer_state;
220 let opaque = unsafe { &mut *((*opaque).opaque as *mut MyOpaque) };
222 // Make sure our hyphenated ID is unique for this page
223 let map = used_header_map.get().unwrap();
224 let id = match map.borrow_mut().find_mut(&id) {
226 Some(a) => { *a += 1; format_strbuf!("{}-{}", id, *a - 1) }
228 map.borrow_mut().insert(id.clone(), 1);
230 let sec = match opaque.toc_builder {
231 Some(ref mut builder) => {
232 builder.push(level as u32, s.to_string(), id.clone())
238 let text = format!(r#"<h{lvl} id="{id}" class='section-header'><a
239 href="\#{id}">{sec_len,plural,=0{}other{{sec} }}{}</a></h{lvl}>"#,
240 s, lvl = level, id = id,
241 sec_len = sec.len(), sec = sec);
243 text.with_c_str(|p| unsafe { hoedown_buffer_puts(ob, p) });
247 let ob = hoedown_buffer_new(DEF_OUNIT);
248 let renderer = hoedown_html_renderer_new(0, 0);
249 let mut opaque = MyOpaque {
250 dfltblk: (*renderer).blockcode.unwrap(),
251 toc_builder: if print_toc {Some(TocBuilder::new())} else {None}
253 (*(*renderer).opaque).opaque = &mut opaque as *mut _ as *mut libc::c_void;
254 (*renderer).blockcode = Some(block);
255 (*renderer).header = Some(header);
257 let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
258 hoedown_document_render(document, ob, s.as_ptr(),
259 s.len() as libc::size_t);
260 hoedown_document_free(document);
262 hoedown_html_renderer_free(renderer);
264 let mut ret = match opaque.toc_builder {
265 Some(b) => write!(w, "<nav id=\"TOC\">{}</nav>", b.into_toc()),
270 ret = slice::raw::buf_as_slice((*ob).data, (*ob).size as uint, |buf| {
274 hoedown_buffer_free(ob);
279 pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
280 extern fn block(_ob: *mut hoedown_buffer, text: *hoedown_buffer,
281 lang: *hoedown_buffer, opaque: *mut libc::c_void) {
283 if text.is_null() { return }
284 let (should_fail, no_run, ignore, notrust) = if lang.is_null() {
285 (false, false, false, false)
287 slice::raw::buf_as_slice((*lang).data,
288 (*lang).size as uint, |lang| {
289 let s = str::from_utf8(lang).unwrap();
290 (s.contains("should_fail"),
291 s.contains("no_run"),
292 s.contains("ignore"),
293 s.contains("notrust"))
296 if notrust { return }
297 slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
298 let opaque = opaque as *mut hoedown_html_renderer_state;
299 let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
300 let text = str::from_utf8(text).unwrap();
301 let mut lines = text.lines().map(|l| {
302 stripped_filtered_line(l).unwrap_or(l)
304 let text = lines.collect::<Vec<&str>>().connect("\n");
305 tests.add_test(text.to_string(), should_fail, no_run, ignore);
310 extern fn header(_ob: *mut hoedown_buffer, text: *hoedown_buffer,
311 level: libc::c_int, opaque: *mut libc::c_void) {
313 let opaque = opaque as *mut hoedown_html_renderer_state;
314 let tests = &mut *((*opaque).opaque as *mut ::test::Collector);
316 tests.register_header("", level as u32);
318 slice::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
319 let text = str::from_utf8(text).unwrap();
320 tests.register_header(text, level as u32);
327 let ob = hoedown_buffer_new(DEF_OUNIT);
328 let renderer = hoedown_html_renderer_new(0, 0);
329 (*renderer).blockcode = Some(block);
330 (*renderer).header = Some(header);
331 (*(*renderer).opaque).opaque = tests as *mut _ as *mut libc::c_void;
333 let document = hoedown_document_new(renderer, HOEDOWN_EXTENSIONS, 16);
334 hoedown_document_render(document, ob, doc.as_ptr(),
335 doc.len() as libc::size_t);
336 hoedown_document_free(document);
338 hoedown_html_renderer_free(renderer);
339 hoedown_buffer_free(ob);
343 /// By default this markdown renderer generates anchors for each header in the
344 /// rendered document. The anchor name is the contents of the header spearated
345 /// by hyphens, and a task-local map is used to disambiguate among duplicate
346 /// headers (numbers are appended).
348 /// This method will reset the local table for these headers. This is typically
349 /// used at the beginning of rendering an entire HTML page to reset from the
350 /// previous state (if any).
351 pub fn reset_headers() {
352 used_header_map.replace(Some(RefCell::new(HashMap::new())));
355 impl<'a> fmt::Show for Markdown<'a> {
356 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
357 let Markdown(md) = *self;
358 // This is actually common enough to special-case
359 if md.len() == 0 { return Ok(()) }
360 render(fmt, md.as_slice(), false)
364 impl<'a> fmt::Show for MarkdownWithToc<'a> {
365 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
366 let MarkdownWithToc(md) = *self;
367 render(fmt, md.as_slice(), true)