Skip to content

Commit 8a16097

Browse files
committed
---
yaml --- r: 149823 b: refs/heads/try2 c: 69b8ef8 h: refs/heads/master i: 149821: 2af62e7 149819: 52789cd 149815: 4c32a45 149807: 205e930 149791: c42aa5c 149759: 16ad96e v: v3
1 parent ef271dc commit 8a16097

File tree

6 files changed

+293
-29
lines changed

6 files changed

+293
-29
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ refs/heads/snap-stage3: 78a7676898d9f80ab540c6df5d4c9ce35bb50463
55
refs/heads/try: 519addf6277dbafccbb4159db4b710c37eaa2ec5
66
refs/tags/release-0.1: 1f5c5126e96c79d22cb7862f75304136e204f105
77
refs/heads/ndm: f3868061cd7988080c30d6d5bf352a5a5fe2460b
8-
refs/heads/try2: e959c8794b80ffad3abd50f773e5a613e13ff7b2
8+
refs/heads/try2: 69b8ef806b3742ba7d41a77cd216713c84f36254
99
refs/heads/dist-snap: ba4081a5a8573875fed17545846f6f6902c8ba8d
1010
refs/tags/release-0.2: c870d2dffb391e14efb05aa27898f1f6333a9596
1111
refs/tags/release-0.3: b5f0d0f648d9a6153664837026ba1be43d3e2503

branches/try2/src/doc/rustdoc.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,28 @@ rustdoc will implicitly add `extern crate <crate>;` where `<crate>` is the name
181181
the crate being tested to the top of each code example. This means that rustdoc
182182
must be able to find a compiled version of the library crate being tested. Extra
183183
search paths may be added via the `-L` flag to `rustdoc`.
184+
185+
# Standalone Markdown files
186+
187+
As well as Rust crates, rustdoc supports rendering pure Markdown files
188+
into HTML and testing the code snippets from them. A Markdown file is
189+
detected by a `.md` or `.markdown` extension.
190+
191+
There are 4 options to modify the output that Rustdoc creates.
192+
- `--markdown-css PATH`: adds a `<link rel="stylesheet">` tag pointing to `PATH`.
193+
- `--markdown-in-header FILE`: includes the contents of `FILE` at the
194+
end of the `<head>...</head>` section.
195+
- `--markdown-before-content FILE`: includes the contents of `FILE`
196+
directly after `<body>`, before the rendered content (including the
197+
title).
198+
- `--markdown-after-content FILE`: includes the contents of `FILE`
199+
directly before `</body>`, after all the rendered content.
200+
201+
All of these can be specified multiple times, and they are output in
202+
the order in which they are specified. The first line of the file must
203+
be the title, prefixed with `%` (e.g. this page has `% Rust
204+
Documentation` on the first line).
205+
206+
Like with a Rust crate, the `--test` argument will run the code
207+
examples to check they compile, and obeys any `--test-args` flags. The
208+
tests are named after the last `#` heading.

branches/try2/src/librustdoc/html/markdown.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
use std::cast;
3030
use std::fmt;
31-
use std::intrinsics;
3231
use std::io;
3332
use std::libc;
3433
use std::local_data;
@@ -258,14 +257,27 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
258257
};
259258
if ignore { return }
260259
vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
261-
let tests: &mut ::test::Collector = intrinsics::transmute(opaque);
260+
let tests = &mut *(opaque as *mut ::test::Collector);
262261
let text = str::from_utf8(text).unwrap();
263262
let mut lines = text.lines().map(|l| stripped_filtered_line(l).unwrap_or(l));
264263
let text = lines.to_owned_vec().connect("\n");
265264
tests.add_test(text, should_fail, no_run);
266265
})
267266
}
268267
}
268+
extern fn header(_ob: *buf, text: *buf, level: libc::c_int, opaque: *libc::c_void) {
269+
unsafe {
270+
let tests = &mut *(opaque as *mut ::test::Collector);
271+
if text.is_null() {
272+
tests.register_header("", level as u32);
273+
} else {
274+
vec::raw::buf_as_slice((*text).data, (*text).size as uint, |text| {
275+
let text = str::from_utf8(text).unwrap();
276+
tests.register_header(text, level as u32);
277+
})
278+
}
279+
}
280+
}
269281

270282
unsafe {
271283
let ob = bufnew(OUTPUT_UNIT);
@@ -276,7 +288,7 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
276288
blockcode: Some(block),
277289
blockquote: None,
278290
blockhtml: None,
279-
header: None,
291+
header: Some(header),
280292
other: mem::init()
281293
};
282294

branches/try2/src/librustdoc/lib.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#[crate_type = "dylib"];
1515
#[crate_type = "rlib"];
1616

17-
#[feature(globs, struct_variant, managed_boxes)];
17+
#[feature(globs, struct_variant, managed_boxes, macro_rules)];
1818

1919
extern crate syntax;
2020
extern crate rustc;
@@ -26,6 +26,7 @@ extern crate collections;
2626
extern crate testing = "test";
2727
extern crate time;
2828

29+
use std::cell::RefCell;
2930
use std::local_data;
3031
use std::io;
3132
use std::io::{File, MemWriter};
@@ -44,6 +45,7 @@ pub mod html {
4445
pub mod markdown;
4546
pub mod render;
4647
}
48+
pub mod markdown;
4749
pub mod passes;
4850
pub mod plugins;
4951
pub mod visit_ast;
@@ -105,6 +107,19 @@ pub fn opts() -> ~[getopts::OptGroup] {
105107
optflag("", "test", "run code examples as tests"),
106108
optmulti("", "test-args", "arguments to pass to the test runner",
107109
"ARGS"),
110+
optmulti("", "markdown-css", "CSS files to include via <link> in a rendered Markdown file",
111+
"FILES"),
112+
optmulti("", "markdown-in-header",
113+
"files to include inline in the <head> section of a rendered Markdown file",
114+
"FILES"),
115+
optmulti("", "markdown-before-content",
116+
"files to include inline between <body> and the content of a rendered \
117+
Markdown file",
118+
"FILES"),
119+
optmulti("", "markdown-after-content",
120+
"files to include inline between the content and </body> of a rendered \
121+
Markdown file",
122+
"FILES"),
108123
]
109124
}
110125

@@ -137,8 +152,24 @@ pub fn main_args(args: &[~str]) -> int {
137152
}
138153
let input = matches.free[0].as_slice();
139154

140-
if matches.opt_present("test") {
141-
return test::run(input, &matches);
155+
let libs = matches.opt_strs("L").map(|s| Path::new(s.as_slice()));
156+
let libs = @RefCell::new(libs.move_iter().collect());
157+
158+
let test_args = matches.opt_strs("test-args");
159+
let test_args = test_args.iter().flat_map(|s| s.words()).map(|s| s.to_owned()).to_owned_vec();
160+
161+
let should_test = matches.opt_present("test");
162+
let markdown_input = input.ends_with(".md") || input.ends_with(".markdown");
163+
164+
let output = matches.opt_str("o").map(|s| Path::new(s));
165+
166+
match (should_test, markdown_input) {
167+
(true, true) => return markdown::test(input, libs, test_args),
168+
(true, false) => return test::run(input, libs, test_args),
169+
170+
(false, true) => return markdown::render(input, output.unwrap_or(Path::new("doc")),
171+
&matches),
172+
(false, false) => {}
142173
}
143174

144175
if matches.opt_strs("passes") == ~[~"list"] {
@@ -163,7 +194,6 @@ pub fn main_args(args: &[~str]) -> int {
163194

164195
info!("going to format");
165196
let started = time::precise_time_ns();
166-
let output = matches.opt_str("o").map(|s| Path::new(s));
167197
match matches.opt_str("w") {
168198
Some(~"html") | None => {
169199
match html::render::run(krate, output.unwrap_or(Path::new("doc"))) {
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright 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.
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+
use std::{str, io};
12+
use std::cell::RefCell;
13+
use std::vec_ng::Vec;
14+
15+
use collections::HashSet;
16+
17+
use getopts;
18+
use testing;
19+
20+
use html::escape::Escape;
21+
use html::markdown::{Markdown, find_testable_code, reset_headers};
22+
use test::Collector;
23+
24+
fn load_string(input: &Path) -> io::IoResult<Option<~str>> {
25+
let mut f = try!(io::File::open(input));
26+
let d = try!(f.read_to_end());
27+
Ok(str::from_utf8_owned(d))
28+
}
29+
macro_rules! load_or_return {
30+
($input: expr, $cant_read: expr, $not_utf8: expr) => {
31+
{
32+
let input = Path::new($input);
33+
match load_string(&input) {
34+
Err(e) => {
35+
let _ = writeln!(&mut io::stderr(),
36+
"error reading `{}`: {}", input.display(), e);
37+
return $cant_read;
38+
}
39+
Ok(None) => {
40+
let _ = writeln!(&mut io::stderr(),
41+
"error reading `{}`: not UTF-8", input.display());
42+
return $not_utf8;
43+
}
44+
Ok(Some(s)) => s
45+
}
46+
}
47+
}
48+
}
49+
50+
/// Separate any lines at the start of the file that begin with `%`.
51+
fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) {
52+
let mut metadata = Vec::new();
53+
for line in s.lines() {
54+
if line.starts_with("%") {
55+
// remove %<whitespace>
56+
metadata.push(line.slice_from(1).trim_left())
57+
} else {
58+
let line_start_byte = s.subslice_offset(line);
59+
return (metadata, s.slice_from(line_start_byte));
60+
}
61+
}
62+
// if we're here, then all lines were metadata % lines.
63+
(metadata, "")
64+
}
65+
66+
fn load_external_files(names: &[~str]) -> Option<~str> {
67+
let mut out = ~"";
68+
for name in names.iter() {
69+
out.push_str(load_or_return!(name.as_slice(), None, None));
70+
out.push_char('\n');
71+
}
72+
Some(out)
73+
}
74+
75+
/// Render `input` (e.g. "foo.md") into an HTML file in `output`
76+
/// (e.g. output = "bar" => "bar/foo.html").
77+
pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int {
78+
let input_p = Path::new(input);
79+
output.push(input_p.filestem().unwrap());
80+
output.set_extension("html");
81+
82+
let mut css = ~"";
83+
for name in matches.opt_strs("markdown-css").iter() {
84+
let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
85+
css.push_str(s)
86+
}
87+
88+
let input_str = load_or_return!(input, 1, 2);
89+
90+
let (in_header, before_content, after_content) =
91+
match (load_external_files(matches.opt_strs("markdown-in-header")),
92+
load_external_files(matches.opt_strs("markdown-before-content")),
93+
load_external_files(matches.opt_strs("markdown-after-content"))) {
94+
(Some(a), Some(b), Some(c)) => (a,b,c),
95+
_ => return 3
96+
};
97+
98+
let mut out = match io::File::create(&output) {
99+
Err(e) => {
100+
let _ = writeln!(&mut io::stderr(),
101+
"error opening `{}` for writing: {}",
102+
output.display(), e);
103+
return 4;
104+
}
105+
Ok(f) => f
106+
};
107+
108+
let (metadata, text) = extract_leading_metadata(input_str);
109+
if metadata.len() == 0 {
110+
let _ = writeln!(&mut io::stderr(),
111+
"invalid markdown file: expecting initial line with `% ...TITLE...`");
112+
return 5;
113+
}
114+
let title = metadata.get(0).as_slice();
115+
116+
reset_headers();
117+
118+
let err = write!(
119+
&mut out,
120+
r#"<!doctype html>
121+
<html lang="en">
122+
<head>
123+
<meta charset="utf-8">
124+
<meta name="generator" content="rustdoc">
125+
<title>{title}</title>
126+
127+
{css}
128+
{in_header}
129+
</head>
130+
<body>
131+
<!--[if lte IE 8]>
132+
<div class="warning">
133+
This old browser is unsupported and will most likely display funky
134+
things.
135+
</div>
136+
<![endif]-->
137+
138+
{before_content}
139+
<h1 class="title">{title}</h1>
140+
{text}
141+
{after_content}
142+
</body>
143+
</html>"#,
144+
title = Escape(title),
145+
css = css,
146+
in_header = in_header,
147+
before_content = before_content,
148+
text = Markdown(text),
149+
after_content = after_content);
150+
151+
match err {
152+
Err(e) => {
153+
let _ = writeln!(&mut io::stderr(),
154+
"error writing to `{}`: {}",
155+
output.display(), e);
156+
6
157+
}
158+
Ok(_) => 0
159+
}
160+
}
161+
162+
/// Run any tests/code examples in the markdown file `input`.
163+
pub fn test(input: &str, libs: @RefCell<HashSet<Path>>, mut test_args: ~[~str]) -> int {
164+
let input_str = load_or_return!(input, 1, 2);
165+
166+
let mut collector = Collector::new(input.to_owned(), libs, true);
167+
find_testable_code(input_str, &mut collector);
168+
test_args.unshift(~"rustdoctest");
169+
testing::test_main(test_args, collector.tests);
170+
0
171+
}

0 commit comments

Comments
 (0)