auto merge of #15237 : zzmp/rust/feat/markdown-in-crate-documentation, r=huonw

This makes the `in-header`, `markdown-before-content`, and `markdown-after-content` options available to `rustdoc` when generating documentation for any crate.

Before, these options were only available when creating documentation *from* markdown. Now, they are available when generating documentation from source.

This also updates the `rustdoc -h` output to reflect these changes. It does not update the `man rustdoc` page, nor does it update the documentation in [the `rustdoc` manual](http://doc.rust-lang.org/rustdoc.html).
This commit is contained in:
bors 2014-06-30 07:31:29 +00:00
commit 2735c7bb10
8 changed files with 143 additions and 69 deletions

View File

@ -38,6 +38,15 @@ directory to load plugins from (default: /tmp/rustdoc_ng/plugins)
-L --library-path <val>
directory to add to crate search path
.TP
--html-in-header <val>
file to add to <head>
.TP
--html-before-content <val>
file to add in <body>, before content
.TP
--html-after-content <val>
file to add in <body>, after content
.TP
-h, --help
Print help

View File

@ -35,16 +35,16 @@ DOCS := index intro tutorial guide guide-ffi guide-macros guide-lifetimes \
PDF_DOCS := tutorial rust
RUSTDOC_DEPS_rust := doc/full-toc.inc
RUSTDOC_FLAGS_rust := --markdown-in-header=doc/full-toc.inc
RUSTDOC_FLAGS_rust := --html-in-header=doc/full-toc.inc
L10N_LANGS := ja
# Generally no need to edit below here.
# The options are passed to the documentation generators.
RUSTDOC_HTML_OPTS_NO_CSS = --markdown-before-content=doc/version_info.html \
--markdown-in-header=doc/favicon.inc \
--markdown-after-content=doc/footer.inc \
RUSTDOC_HTML_OPTS_NO_CSS = --html-before-content=doc/version_info.html \
--html-in-header=doc/favicon.inc \
--html-after-content=doc/footer.inc \
--markdown-playground-url='http://play.rust-lang.org/'
RUSTDOC_HTML_OPTS = $(RUSTDOC_HTML_OPTS_NO_CSS) --markdown-css rust.css

View File

@ -103,6 +103,17 @@ rustdoc can also generate JSON, for consumption by other tools, with
`rustdoc --output-format json`, and also consume already-generated JSON with
`rustdoc --input-format json`.
rustdoc also supports personalizing the output from crates' documentation,
similar to markdown options.
- `--html-in-header FILE`: includes the contents of `FILE` at the
end of the `<head>...</head>` section.
- `--html-before-content FILE`: includes the contents of `FILE`
directly after `<body>`, before the rendered content (including the
search bar).
- `--html-after-content FILE`: includes the contents of `FILE`
after all the rendered content.
# Using the Documentation
The web pages generated by rustdoc present the same logical hierarchy that one
@ -238,16 +249,16 @@ detected by a `.md` or `.markdown` extension.
There are 4 options to modify the output that Rustdoc creates.
- `--markdown-css PATH`: adds a `<link rel="stylesheet">` tag pointing to `PATH`.
- `--markdown-in-header FILE`: includes the contents of `FILE` at the
- `--html-in-header FILE`: includes the contents of `FILE` at the
end of the `<head>...</head>` section.
- `--markdown-before-content FILE`: includes the contents of `FILE`
- `--html-before-content FILE`: includes the contents of `FILE`
directly after `<body>`, before the rendered content (including the
title).
- `--markdown-after-content FILE`: includes the contents of `FILE`
- `--html-after-content FILE`: includes the contents of `FILE`
directly before `</body>`, after all the rendered content.
All of these can be specified multiple times, and they are output in
the order in which they are specified. The first line of the file must
the order in which they are specified. The first line of the file being rendered must
be the title, prefixed with `%` (e.g. this page has `% Rust
Documentation` on the first line).

View File

@ -0,0 +1,70 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::{io, str};
#[deriving(Clone)]
pub struct ExternalHtml{
pub in_header: String,
pub before_content: String,
pub after_content: String
}
impl ExternalHtml {
pub fn load(in_header: &[String], before_content: &[String], after_content: &[String])
-> Option<ExternalHtml> {
match (load_external_files(in_header),
load_external_files(before_content),
load_external_files(after_content)) {
(Some(ih), Some(bc), Some(ac)) => Some(ExternalHtml {
in_header: ih,
before_content: bc,
after_content: ac
}),
_ => None
}
}
}
pub fn load_string(input: &Path) -> io::IoResult<Option<String>> {
let mut f = try!(io::File::open(input));
let d = try!(f.read_to_end());
Ok(str::from_utf8(d.as_slice()).map(|s| s.to_string()))
}
macro_rules! load_or_return {
($input: expr, $cant_read: expr, $not_utf8: expr) => {
{
let input = Path::new($input);
match ::externalfiles::load_string(&input) {
Err(e) => {
let _ = writeln!(&mut io::stderr(),
"error reading `{}`: {}", input.display(), e);
return $cant_read;
}
Ok(None) => {
let _ = writeln!(&mut io::stderr(),
"error reading `{}`: not UTF-8", input.display());
return $not_utf8;
}
Ok(Some(s)) => s
}
}
}
}
pub fn load_external_files(names: &[String]) -> Option<String> {
let mut out = String::new();
for name in names.iter() {
out.push_str(load_or_return!(name.as_slice(), None, None).as_slice());
out.push_char('\n');
}
Some(out)
}

View File

@ -11,10 +11,13 @@
use std::fmt;
use std::io;
use externalfiles::ExternalHtml;
#[deriving(Clone)]
pub struct Layout {
pub logo: String,
pub favicon: String,
pub external_html: ExternalHtml,
pub krate: String,
pub playground_url: String,
}
@ -44,6 +47,7 @@ r##"<!DOCTYPE html>
<link rel="stylesheet" type="text/css" href="{root_path}main.css">
{favicon}
{in_header}
</head>
<body>
<!--[if lte IE 8]>
@ -53,6 +57,8 @@ r##"<!DOCTYPE html>
</div>
<![endif]-->
{before_content}
<section class="sidebar">
{logo}
{sidebar}
@ -105,6 +111,8 @@ r##"<!DOCTYPE html>
</div>
</div>
{after_content}
<script>
window.rootPath = "{root_path}";
window.currentCrate = "{krate}";
@ -133,6 +141,9 @@ r##"<!DOCTYPE html>
} else {
format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon)
},
in_header = layout.external_html.in_header,
before_content = layout.external_html.before_content,
after_content = layout.external_html.after_content,
sidebar = *sidebar,
krate = layout.krate,
play_url = layout.playground_url,

View File

@ -41,6 +41,8 @@ use std::str;
use std::string::String;
use std::sync::Arc;
use externalfiles::ExternalHtml;
use serialize::json::ToJson;
use syntax::ast;
use syntax::ast_util;
@ -78,7 +80,7 @@ pub struct Context {
/// This changes as the context descends into the module hierarchy.
pub dst: Path,
/// This describes the layout of each page, and is not modified after
/// creation of the context (contains info like the favicon)
/// creation of the context (contains info like the favicon and added html).
pub layout: layout::Layout,
/// This map is a list of what should be displayed on the sidebar of the
/// current page. The key is the section header (traits, modules,
@ -220,7 +222,7 @@ local_data_key!(pub cache_key: Arc<Cache>)
local_data_key!(pub current_location_key: Vec<String> )
/// Generates the documentation for `crate` into the directory `dst`
pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
pub fn run(mut krate: clean::Crate, external_html: &ExternalHtml, dst: Path) -> io::IoResult<()> {
let mut cx = Context {
dst: dst,
current: Vec::new(),
@ -229,12 +231,14 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
layout: layout::Layout {
logo: "".to_string(),
favicon: "".to_string(),
external_html: external_html.clone(),
krate: krate.name.clone(),
playground_url: "".to_string(),
},
include_sources: true,
render_redirect_pages: false,
};
try!(mkdir(&cx.dst));
// Crawl the crate attributes looking for attributes which control how we're

View File

@ -32,6 +32,7 @@ use std::io::{File, MemWriter};
use std::str;
use std::gc::Gc;
use serialize::{json, Decodable, Encodable};
use externalfiles::ExternalHtml;
// reexported from `clean` so it can be easily updated with the mod itself
pub use clean::SCHEMA_VERSION;
@ -39,6 +40,8 @@ pub use clean::SCHEMA_VERSION;
pub mod clean;
pub mod core;
pub mod doctree;
#[macro_escape]
pub mod externalfiles;
pub mod fold;
pub mod html {
pub mod highlight;
@ -113,16 +116,17 @@ pub fn opts() -> Vec<getopts::OptGroup> {
"ARGS"),
optmulti("", "markdown-css", "CSS files to include via <link> in a rendered Markdown file",
"FILES"),
optmulti("", "markdown-in-header",
"files to include inline in the <head> section of a rendered Markdown file",
optmulti("", "html-in-header",
"files to include inline in the <head> section of a rendered Markdown file \
or generated documentation",
"FILES"),
optmulti("", "markdown-before-content",
optmulti("", "html-before-content",
"files to include inline between <body> and the content of a rendered \
Markdown file",
Markdown file or generated documentation",
"FILES"),
optmulti("", "markdown-after-content",
optmulti("", "html-after-content",
"files to include inline between the content and </body> of a rendered \
Markdown file",
Markdown file or generated documentation",
"FILES"),
optopt("", "markdown-playground-url",
"URL to send code snippets to", "URL")
@ -179,6 +183,14 @@ pub fn main_args(args: &[String]) -> int {
let output = matches.opt_str("o").map(|s| Path::new(s));
let cfgs = matches.opt_strs("cfg");
let external_html = match ExternalHtml::load(
matches.opt_strs("html-in-header").as_slice(),
matches.opt_strs("html-before-content").as_slice(),
matches.opt_strs("html-after-content").as_slice()) {
Some(eh) => eh,
None => return 3
};
match (should_test, markdown_input) {
(true, true) => {
return markdown::test(input, libs, test_args)
@ -187,7 +199,7 @@ pub fn main_args(args: &[String]) -> int {
return test::run(input, cfgs, libs, test_args)
}
(false, true) => return markdown::render(input, output.unwrap_or(Path::new("doc")),
&matches),
&matches, &external_html),
(false, false) => {}
}
@ -215,7 +227,7 @@ pub fn main_args(args: &[String]) -> int {
let started = time::precise_time_ns();
match matches.opt_str("w").as_ref().map(|s| s.as_slice()) {
Some("html") | None => {
match html::render::run(krate, output.unwrap_or(Path::new("doc"))) {
match html::render::run(krate, &external_html, output.unwrap_or(Path::new("doc"))) {
Ok(()) => {}
Err(e) => fail!("failed to generate documentation: {}", e),
}

View File

@ -9,43 +9,19 @@
// except according to those terms.
use std::collections::HashSet;
use std::{str, io};
use std::io;
use std::string::String;
use getopts;
use testing;
use externalfiles::ExternalHtml;
use html::escape::Escape;
use html::markdown;
use html::markdown::{MarkdownWithToc, find_testable_code, reset_headers};
use test::Collector;
fn load_string(input: &Path) -> io::IoResult<Option<String>> {
let mut f = try!(io::File::open(input));
let d = try!(f.read_to_end());
Ok(str::from_utf8(d.as_slice()).map(|s| s.to_string()))
}
macro_rules! load_or_return {
($input: expr, $cant_read: expr, $not_utf8: expr) => {
{
let input = Path::new($input);
match load_string(&input) {
Err(e) => {
let _ = writeln!(&mut io::stderr(),
"error reading `{}`: {}", input.display(), e);
return $cant_read;
}
Ok(None) => {
let _ = writeln!(&mut io::stderr(),
"error reading `{}`: not UTF-8", input.display());
return $not_utf8;
}
Ok(Some(s)) => s
}
}
}
}
/// Separate any lines at the start of the file that begin with `%`.
fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) {
let mut metadata = Vec::new();
@ -62,18 +38,10 @@ fn extract_leading_metadata<'a>(s: &'a str) -> (Vec<&'a str>, &'a str) {
(metadata, "")
}
fn load_external_files(names: &[String]) -> Option<String> {
let mut out = String::new();
for name in names.iter() {
out.push_str(load_or_return!(name.as_slice(), None, None).as_slice());
out.push_char('\n');
}
Some(out)
}
/// Render `input` (e.g. "foo.md") into an HTML file in `output`
/// (e.g. output = "bar" => "bar/foo.html").
pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int {
pub fn render(input: &str, mut output: Path, matches: &getopts::Matches,
external_html: &ExternalHtml) -> int {
let input_p = Path::new(input);
output.push(input_p.filestem().unwrap());
output.set_extension("html");
@ -91,17 +59,6 @@ pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int
}
let playground = playground.unwrap_or("".to_string());
let (in_header, before_content, after_content) =
match (load_external_files(matches.opt_strs("markdown-in-header")
.as_slice()),
load_external_files(matches.opt_strs("markdown-before-content")
.as_slice()),
load_external_files(matches.opt_strs("markdown-after-content")
.as_slice())) {
(Some(a), Some(b), Some(c)) => (a,b,c),
_ => return 3
};
let mut out = match io::File::create(&output) {
Err(e) => {
let _ = writeln!(&mut io::stderr(),
@ -153,10 +110,10 @@ pub fn render(input: &str, mut output: Path, matches: &getopts::Matches) -> int
</html>"#,
title = Escape(title),
css = css,
in_header = in_header,
before_content = before_content,
in_header = external_html.in_header,
before_content = external_html.before_content,
text = MarkdownWithToc(text),
after_content = after_content,
after_content = external_html.after_content,
playground = playground,
);