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.
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 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:
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)];
37 use collections::HashMap;
39 use html::toc::TocBuilder;
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);
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;
57 type sd_markdown = libc::c_void; // this is opaque to us
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],
67 struct html_toc_data {
68 header_count: libc::c_int,
69 current_level: libc::c_int,
70 level_offset: libc::c_int,
73 struct html_renderopt {
74 toc_data: html_toc_data,
76 link_attributes: Option<extern "C" fn(*buf, *buf, *libc::c_void)>,
81 dfltblk: extern "C" fn(*buf, *buf, *buf, *libc::c_void),
82 toc_builder: Option<TocBuilder>,
93 #[link(name = "sundown", kind = "static")]
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,
104 doc_size: libc::size_t,
106 fn sd_markdown_free(md: *sd_markdown);
108 fn bufnew(unit: libc::size_t) -> *buf;
109 fn bufputs(b: *buf, c: *libc::c_char);
110 fn bufrelease(b: *buf);
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
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))
127 local_data_key!(used_header_map: HashMap<~str, uint>)
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) {
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.to_owned_vec().connect("\n");
139 data: text.as_bytes().as_ptr(),
140 size: text.len() as libc::size_t,
141 asize: text.len() as libc::size_t,
144 let rendered = if lang.is_null() {
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);
160 let output = highlight::highlight(text, None).to_c_str();
161 output.with_ref(|r| {
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) });
174 // Extract the text provided
175 let s = if text.is_null() {
179 str::raw::from_buf_len((*text).data, (*text).size as uint)
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(),
189 }).to_owned_vec().connect("-");
191 let opaque = unsafe {&mut *(opaque as *mut my_opaque)};
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) {
198 Some(a) => { *a += 1; return format!("{}-{}", id, *a - 1) }
200 map.insert(id.clone(), 1);
204 let sec = match opaque.toc_builder {
205 Some(ref mut builder) => {
206 builder.push(level as u32, s.clone(), id.clone())
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);
217 text.with_c_str(|p| unsafe { bufputs(ob, p) });
220 // This code is all lifted from examples/sundown.c in the sundown repo
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 {
233 link_attributes: None,
235 let mut callbacks: sd_callbacks = mem::init();
237 sdhtml_renderer(&callbacks, &options, 0);
238 let mut opaque = my_opaque {
240 dfltblk: callbacks.blockcode.unwrap(),
241 toc_builder: if print_toc {Some(TocBuilder::new())} else {None}
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);
249 sd_markdown_render(ob, s.as_ptr(), s.len() as libc::size_t, markdown);
250 sd_markdown_free(markdown);
252 let mut ret = match opaque.toc_builder {
253 Some(b) => write!(w, "<nav id=\"TOC\">{}</nav>", b.into_toc()),
258 ret = slice::raw::buf_as_slice((*ob).data, (*ob).size as uint, |buf| {
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) {
270 if text.is_null() { return }
271 let (should_fail, no_run, ignore) = if lang.is_null() {
272 (false, false, false)
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"))
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.to_owned_vec().connect("\n");
288 tests.add_test(text, should_fail, no_run);
292 extern fn header(_ob: *buf, text: *buf, level: libc::c_int, opaque: *libc::c_void) {
294 let tests = &mut *(opaque as *mut ::test::Collector);
296 tests.register_header("", level as u32);
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);
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),
315 header: Some(header),
319 let tests = tests as *mut ::test::Collector as *libc::c_void;
320 let markdown = sd_markdown_new(extensions, 16, &callbacks, tests);
322 sd_markdown_render(ob, doc.as_ptr(), doc.len() as libc::size_t,
324 sd_markdown_free(markdown);
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).
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())
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)
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)