Skip to content

Commit 72ecd79

Browse files
committed
Auto merge of rust-lang#40919 - GuillaumeGomez:fix-new-rustdoc, r=frewsxcv,steveklabnik
Add support for image, rules and footnotes Part of rust-lang#40912. r? @rust-lang/docs PS: the footnotes are waiting for pulldown-cmark/pulldown-cmark#21 to be merged to be fully working.
2 parents 5e122f5 + ef01ae7 commit 72ecd79

File tree

5 files changed

+232
-32
lines changed

5 files changed

+232
-32
lines changed

src/librustdoc/html/markdown.rs

+159-30
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
use std::ascii::AsciiExt;
2929
use std::cell::RefCell;
30+
use std::collections::HashMap;
3031
use std::default::Default;
3132
use std::fmt::{self, Write};
3233
use std::str;
@@ -115,15 +116,19 @@ macro_rules! event_loop_break {
115116
match event {
116117
$($end_event)|* => break,
117118
Event::Text(ref s) => {
119+
debug!("Text");
118120
inner($id, s);
119121
if $escape {
120122
$buf.push_str(&format!("{}", Escape(s)));
121123
} else {
122124
$buf.push_str(s);
123125
}
124126
}
125-
Event::SoftBreak | Event::HardBreak if !$buf.is_empty() => {
126-
$buf.push(' ');
127+
Event::SoftBreak => {
128+
debug!("SoftBreak");
129+
if !$buf.is_empty() {
130+
$buf.push(' ');
131+
}
127132
}
128133
x => {
129134
looper($parser, &mut $buf, Some(x), $toc_builder, $shorter, $id);
@@ -133,11 +138,38 @@ macro_rules! event_loop_break {
133138
}}
134139
}
135140

141+
struct ParserWrapper<'a> {
142+
parser: Parser<'a>,
143+
// The key is the footnote reference. The value is the footnote definition and the id.
144+
footnotes: HashMap<String, (String, u16)>,
145+
}
146+
147+
impl<'a> ParserWrapper<'a> {
148+
pub fn new(s: &'a str) -> ParserWrapper<'a> {
149+
ParserWrapper {
150+
parser: Parser::new_ext(s, pulldown_cmark::OPTION_ENABLE_TABLES |
151+
pulldown_cmark::OPTION_ENABLE_FOOTNOTES),
152+
footnotes: HashMap::new(),
153+
}
154+
}
155+
156+
pub fn next(&mut self) -> Option<Event<'a>> {
157+
self.parser.next()
158+
}
159+
160+
pub fn get_entry(&mut self, key: &str) -> &mut (String, u16) {
161+
let new_id = self.footnotes.keys().count() + 1;
162+
let key = key.to_owned();
163+
self.footnotes.entry(key).or_insert((String::new(), new_id as u16))
164+
}
165+
}
166+
136167
pub fn render(w: &mut fmt::Formatter,
137168
s: &str,
138169
print_toc: bool,
139170
shorter: MarkdownOutputStyle) -> fmt::Result {
140-
fn code_block(parser: &mut Parser, buffer: &mut String, lang: &str) {
171+
fn code_block(parser: &mut ParserWrapper, buffer: &mut String, lang: &str) {
172+
debug!("CodeBlock");
141173
let mut origtext = String::new();
142174
while let Some(event) = parser.next() {
143175
match event {
@@ -215,8 +247,9 @@ pub fn render(w: &mut fmt::Formatter,
215247
});
216248
}
217249

218-
fn heading(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
219-
shorter: MarkdownOutputStyle, level: i32) {
250+
fn heading(parser: &mut ParserWrapper, buffer: &mut String,
251+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle, level: i32) {
252+
debug!("Heading");
220253
let mut ret = String::new();
221254
let mut id = String::new();
222255
event_loop_break!(parser, toc_builder, shorter, ret, true, &mut Some(&mut id),
@@ -249,32 +282,53 @@ pub fn render(w: &mut fmt::Formatter,
249282
ret, lvl = level, id = id, sec = sec));
250283
}
251284

252-
fn inline_code(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
253-
shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
285+
fn inline_code(parser: &mut ParserWrapper, buffer: &mut String,
286+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
287+
id: &mut Option<&mut String>) {
288+
debug!("InlineCode");
254289
let mut content = String::new();
255290
event_loop_break!(parser, toc_builder, shorter, content, false, id, Event::End(Tag::Code));
256291
buffer.push_str(&format!("<code>{}</code>",
257292
Escape(&collapse_whitespace(content.trim_right()))));
258293
}
259294

260-
fn link(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
295+
fn link(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
296+
shorter: MarkdownOutputStyle, url: &str, title: &str,
297+
id: &mut Option<&mut String>) {
298+
debug!("Link");
299+
let mut content = String::new();
300+
event_loop_break!(parser, toc_builder, shorter, content, true, id,
301+
Event::End(Tag::Link(_, _)));
302+
if title.is_empty() {
303+
buffer.push_str(&format!("<a href=\"{}\">{}</a>", url, content));
304+
} else {
305+
buffer.push_str(&format!("<a href=\"{}\" title=\"{}\">{}</a>",
306+
url, Escape(title), content));
307+
}
308+
}
309+
310+
fn image(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
261311
shorter: MarkdownOutputStyle, url: &str, mut title: String,
262312
id: &mut Option<&mut String>) {
313+
debug!("Image");
263314
event_loop_break!(parser, toc_builder, shorter, title, true, id,
264-
Event::End(Tag::Link(_, _)));
265-
buffer.push_str(&format!("<a href=\"{}\">{}</a>", url, title));
315+
Event::End(Tag::Image(_, _)));
316+
buffer.push_str(&format!("<img src=\"{}\" alt=\"{}\">", url, title));
266317
}
267318

268-
fn paragraph(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
269-
shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
319+
fn paragraph(parser: &mut ParserWrapper, buffer: &mut String,
320+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
321+
id: &mut Option<&mut String>) {
322+
debug!("Paragraph");
270323
let mut content = String::new();
271324
event_loop_break!(parser, toc_builder, shorter, content, true, id,
272325
Event::End(Tag::Paragraph));
273326
buffer.push_str(&format!("<p>{}</p>", content.trim_right()));
274327
}
275328

276-
fn table_cell(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
277-
shorter: MarkdownOutputStyle) {
329+
fn table_cell(parser: &mut ParserWrapper, buffer: &mut String,
330+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
331+
debug!("TableCell");
278332
let mut content = String::new();
279333
event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
280334
Event::End(Tag::TableHead) |
@@ -284,8 +338,9 @@ pub fn render(w: &mut fmt::Formatter,
284338
buffer.push_str(&format!("<td>{}</td>", content.trim()));
285339
}
286340

287-
fn table_row(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
288-
shorter: MarkdownOutputStyle) {
341+
fn table_row(parser: &mut ParserWrapper, buffer: &mut String,
342+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
343+
debug!("TableRow");
289344
let mut content = String::new();
290345
while let Some(event) = parser.next() {
291346
match event {
@@ -303,8 +358,9 @@ pub fn render(w: &mut fmt::Formatter,
303358
buffer.push_str(&format!("<tr>{}</tr>", content));
304359
}
305360

306-
fn table_head(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
307-
shorter: MarkdownOutputStyle) {
361+
fn table_head(parser: &mut ParserWrapper, buffer: &mut String,
362+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
363+
debug!("TableHead");
308364
let mut content = String::new();
309365
while let Some(event) = parser.next() {
310366
match event {
@@ -322,8 +378,9 @@ pub fn render(w: &mut fmt::Formatter,
322378
}
323379
}
324380

325-
fn table(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
381+
fn table(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
326382
shorter: MarkdownOutputStyle) {
383+
debug!("Table");
327384
let mut content = String::new();
328385
let mut rows = String::new();
329386
while let Some(event) = parser.next() {
@@ -347,16 +404,18 @@ pub fn render(w: &mut fmt::Formatter,
347404
}));
348405
}
349406

350-
fn blockquote(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
351-
shorter: MarkdownOutputStyle) {
407+
fn blockquote(parser: &mut ParserWrapper, buffer: &mut String,
408+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
409+
debug!("BlockQuote");
352410
let mut content = String::new();
353411
event_loop_break!(parser, toc_builder, shorter, content, true, &mut None,
354412
Event::End(Tag::BlockQuote));
355413
buffer.push_str(&format!("<blockquote>{}</blockquote>", content.trim_right()));
356414
}
357415

358-
fn list_item(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
359-
shorter: MarkdownOutputStyle) {
416+
fn list_item(parser: &mut ParserWrapper, buffer: &mut String,
417+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle) {
418+
debug!("ListItem");
360419
let mut content = String::new();
361420
while let Some(event) = parser.next() {
362421
match event {
@@ -372,8 +431,9 @@ pub fn render(w: &mut fmt::Formatter,
372431
buffer.push_str(&format!("<li>{}</li>", content));
373432
}
374433

375-
fn list(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
434+
fn list(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
376435
shorter: MarkdownOutputStyle) {
436+
debug!("List");
377437
let mut content = String::new();
378438
while let Some(event) = parser.next() {
379439
match event {
@@ -389,23 +449,45 @@ pub fn render(w: &mut fmt::Formatter,
389449
buffer.push_str(&format!("<ul>{}</ul>", content));
390450
}
391451

392-
fn emphasis(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
393-
shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
452+
fn emphasis(parser: &mut ParserWrapper, buffer: &mut String,
453+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
454+
id: &mut Option<&mut String>) {
455+
debug!("Emphasis");
394456
let mut content = String::new();
395457
event_loop_break!(parser, toc_builder, shorter, content, false, id,
396458
Event::End(Tag::Emphasis));
397459
buffer.push_str(&format!("<em>{}</em>", content));
398460
}
399461

400-
fn strong(parser: &mut Parser, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
462+
fn strong(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
401463
shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
464+
debug!("Strong");
402465
let mut content = String::new();
403466
event_loop_break!(parser, toc_builder, shorter, content, false, id,
404467
Event::End(Tag::Strong));
405468
buffer.push_str(&format!("<strong>{}</strong>", content));
406469
}
407470

408-
fn looper<'a>(parser: &'a mut Parser, buffer: &mut String, next_event: Option<Event<'a>>,
471+
fn footnote(parser: &mut ParserWrapper, buffer: &mut String,
472+
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
473+
id: &mut Option<&mut String>) {
474+
debug!("FootnoteDefinition");
475+
let mut content = String::new();
476+
event_loop_break!(parser, toc_builder, shorter, content, true, id,
477+
Event::End(Tag::FootnoteDefinition(_)));
478+
buffer.push_str(&content);
479+
}
480+
481+
fn rule(parser: &mut ParserWrapper, buffer: &mut String, toc_builder: &mut Option<TocBuilder>,
482+
shorter: MarkdownOutputStyle, id: &mut Option<&mut String>) {
483+
debug!("Rule");
484+
let mut content = String::new();
485+
event_loop_break!(parser, toc_builder, shorter, content, true, id,
486+
Event::End(Tag::Rule));
487+
buffer.push_str("<hr>");
488+
}
489+
490+
fn looper<'a>(parser: &'a mut ParserWrapper, buffer: &mut String, next_event: Option<Event<'a>>,
409491
toc_builder: &mut Option<TocBuilder>, shorter: MarkdownOutputStyle,
410492
id: &mut Option<&mut String>) -> bool {
411493
if let Some(event) = next_event {
@@ -423,7 +505,10 @@ pub fn render(w: &mut fmt::Formatter,
423505
paragraph(parser, buffer, toc_builder, shorter, id);
424506
}
425507
Event::Start(Tag::Link(ref url, ref t)) => {
426-
link(parser, buffer, toc_builder, shorter, url, t.as_ref().to_owned(), id);
508+
link(parser, buffer, toc_builder, shorter, url, t.as_ref(), id);
509+
}
510+
Event::Start(Tag::Image(ref url, ref t)) => {
511+
image(parser, buffer, toc_builder, shorter, url, t.as_ref().to_owned(), id);
427512
}
428513
Event::Start(Tag::Table(_)) => {
429514
table(parser, buffer, toc_builder, shorter);
@@ -440,7 +525,42 @@ pub fn render(w: &mut fmt::Formatter,
440525
Event::Start(Tag::Strong) => {
441526
strong(parser, buffer, toc_builder, shorter, id);
442527
}
528+
Event::Start(Tag::Rule) => {
529+
rule(parser, buffer, toc_builder, shorter, id);
530+
}
531+
Event::Start(Tag::FootnoteDefinition(ref def)) => {
532+
debug!("FootnoteDefinition");
533+
let mut content = String::new();
534+
let def = def.as_ref();
535+
footnote(parser, &mut content, toc_builder, shorter, id);
536+
let entry = parser.get_entry(def);
537+
let cur_id = (*entry).1;
538+
(*entry).0.push_str(&format!("<li id=\"ref{}\">{}&nbsp;<a href=\"#supref{0}\" \
539+
rev=\"footnote\">↩</a></p></li>",
540+
cur_id,
541+
if content.ends_with("</p>") {
542+
&content[..content.len() - 4]
543+
} else {
544+
&content
545+
}));
546+
}
547+
Event::FootnoteReference(ref reference) => {
548+
debug!("FootnoteReference");
549+
let entry = parser.get_entry(reference.as_ref());
550+
buffer.push_str(&format!("<sup id=\"supref{0}\"><a href=\"#ref{0}\">{0}</a>\
551+
</sup>",
552+
(*entry).1));
553+
}
554+
Event::HardBreak => {
555+
debug!("HardBreak");
556+
if shorter.is_fancy() {
557+
buffer.push_str("<br>");
558+
} else if !buffer.is_empty() {
559+
buffer.push(' ');
560+
}
561+
}
443562
Event::Html(h) | Event::InlineHtml(h) => {
563+
debug!("Html/InlineHtml");
444564
buffer.push_str(&*h);
445565
}
446566
_ => {}
@@ -457,13 +577,22 @@ pub fn render(w: &mut fmt::Formatter,
457577
None
458578
};
459579
let mut buffer = String::new();
460-
let mut parser = Parser::new_ext(s, pulldown_cmark::OPTION_ENABLE_TABLES);
580+
let mut parser = ParserWrapper::new(s);
461581
loop {
462582
let next_event = parser.next();
463583
if !looper(&mut parser, &mut buffer, next_event, &mut toc_builder, shorter, &mut None) {
464584
break
465585
}
466586
}
587+
if !parser.footnotes.is_empty() {
588+
let mut v: Vec<_> = parser.footnotes.values().collect();
589+
v.sort_by(|a, b| a.1.cmp(&b.1));
590+
buffer.push_str(&format!("<div class=\"footnotes\"><hr><ol>{}</ol></div>",
591+
v.iter()
592+
.map(|s| s.0.as_str())
593+
.collect::<Vec<_>>()
594+
.join("")));
595+
}
467596
let mut ret = toc_builder.map_or(Ok(()), |builder| {
468597
write!(w, "<nav id=\"TOC\">{}</nav>", builder.into_toc())
469598
});

src/librustdoc/passes/unindent_comments.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fn unindent(s: &str) -> String {
8282
});
8383

8484
if !lines.is_empty() {
85-
let mut unindented = vec![ lines[0].trim().to_string() ];
85+
let mut unindented = vec![ lines[0].trim_left().to_string() ];
8686
unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
8787
if line.chars().all(|c| c.is_whitespace()) {
8888
line.to_string()
@@ -160,4 +160,15 @@ mod unindent_tests {
160160
let r = unindent(&s);
161161
assert_eq!(r, "line1\nline2");
162162
}
163+
164+
#[test]
165+
fn should_not_trim() {
166+
let s = "\t line1 \n\t line2".to_string();
167+
let r = unindent(&s);
168+
assert_eq!(r, "line1 \nline2");
169+
170+
let s = " \tline1 \n \tline2".to_string();
171+
let r = unindent(&s);
172+
assert_eq!(r, "line1 \nline2");
173+
}
163174
}

src/test/rustdoc/check-hard-break.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2017 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+
#![crate_name = "foo"]
12+
13+
// ignore-tidy-end-whitespace
14+
15+
// @has foo/fn.f.html
16+
// @has - '<p>hard break:<br>after hard break</p>'
17+
/// hard break:
18+
/// after hard break
19+
pub fn f() {}

0 commit comments

Comments
 (0)