rustdoc: Include source files with documentation
All items have source links back to their actual code. Source files can be omitted with the doc(html_no_source) attribute on the crate. Currently there is no syntax highlighting, but that will come with syntax highlighting with all other snippets. Closes #2072
This commit is contained in:
parent
b93678eca5
commit
dd8d565083
6 changed files with 213 additions and 12 deletions
|
@ -84,7 +84,7 @@ impl Clean<Crate> for visit_ast::RustdocVisitor {
|
||||||
#[deriving(Clone, Encodable, Decodable)]
|
#[deriving(Clone, Encodable, Decodable)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
/// Stringified span
|
/// Stringified span
|
||||||
source: ~str,
|
source: Span,
|
||||||
/// Not everything has a name. E.g., impls
|
/// Not everything has a name. E.g., impls
|
||||||
name: Option<~str>,
|
name: Option<~str>,
|
||||||
attrs: ~[Attribute],
|
attrs: ~[Attribute],
|
||||||
|
@ -737,10 +737,28 @@ impl Clean<VariantKind> for ast::variant_kind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clean<~str> for syntax::codemap::Span {
|
#[deriving(Clone, Encodable, Decodable)]
|
||||||
fn clean(&self) -> ~str {
|
pub struct Span {
|
||||||
let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap;
|
filename: ~str,
|
||||||
cm.span_to_str(*self)
|
loline: uint,
|
||||||
|
locol: uint,
|
||||||
|
hiline: uint,
|
||||||
|
hicol: uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clean<Span> for syntax::codemap::Span {
|
||||||
|
fn clean(&self) -> Span {
|
||||||
|
let cm = local_data::get(super::ctxtkey, |x| *x.unwrap()).sess.codemap;
|
||||||
|
let filename = cm.span_to_filename(*self);
|
||||||
|
let lo = cm.lookup_char_pos(self.lo);
|
||||||
|
let hi = cm.lookup_char_pos(self.hi);
|
||||||
|
Span {
|
||||||
|
filename: filename.to_owned(),
|
||||||
|
loline: lo.line,
|
||||||
|
locol: *lo.col,
|
||||||
|
hiline: hi.line,
|
||||||
|
hicol: *hi.col,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1034,7 +1052,7 @@ trait ToSource {
|
||||||
|
|
||||||
impl ToSource for syntax::codemap::Span {
|
impl ToSource for syntax::codemap::Span {
|
||||||
fn to_src(&self) -> ~str {
|
fn to_src(&self) -> ~str {
|
||||||
debug!("converting span %s to snippet", self.clean());
|
debug!("converting span %? to snippet", self.clean());
|
||||||
let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap.clone();
|
let cm = local_data::get(super::ctxtkey, |x| x.unwrap().clone()).sess.codemap.clone();
|
||||||
let sn = match cm.span_to_snippet(*self) {
|
let sn = match cm.span_to_snippet(*self) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
|
|
44
src/librustdoc/html/escape.rs
Normal file
44
src/librustdoc/html/escape.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2013 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::fmt;
|
||||||
|
|
||||||
|
pub struct Escape<'self>(&'self str);
|
||||||
|
|
||||||
|
impl<'self> fmt::Default for Escape<'self> {
|
||||||
|
fn fmt(s: &Escape<'self>, fmt: &mut fmt::Formatter) {
|
||||||
|
// Because the internet is always right, turns out there's not that many
|
||||||
|
// characters to escape: http://stackoverflow.com/questions/7381974
|
||||||
|
let pile_o_bits = s.as_slice();
|
||||||
|
let mut last = 0;
|
||||||
|
for (i, ch) in s.byte_iter().enumerate() {
|
||||||
|
match ch as char {
|
||||||
|
'<' | '>' | '&' | '\'' | '"' => {
|
||||||
|
fmt.buf.write(pile_o_bits.slice(last, i).as_bytes());
|
||||||
|
let s = match ch as char {
|
||||||
|
'>' => ">",
|
||||||
|
'<' => "<",
|
||||||
|
'&' => "&",
|
||||||
|
'\'' => "'",
|
||||||
|
'"' => """,
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
fmt.buf.write(s.as_bytes());
|
||||||
|
last = i + 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if last < s.len() {
|
||||||
|
fmt.buf.write(pile_o_bits.slice_from(last).as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,7 +67,7 @@ pub fn render<T: fmt::Default, S: fmt::Default>(
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<section id='main' class=\"content {ty}\">{content}</section>
|
<section id='main' class=\"content {ty}\">{content}</section>
|
||||||
<section id='search' class=\"content hidden\">{content}</section>
|
<section id='search' class=\"content hidden\"></section>
|
||||||
|
|
||||||
<section class=\"footer\"></section>
|
<section class=\"footer\"></section>
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,14 @@ use std::cell::Cell;
|
||||||
use std::comm::{SharedPort, SharedChan};
|
use std::comm::{SharedPort, SharedChan};
|
||||||
use std::comm;
|
use std::comm;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hashmap::HashMap;
|
use std::hashmap::{HashMap, HashSet};
|
||||||
use std::local_data;
|
use std::local_data;
|
||||||
use std::rt::io::buffered::BufferedWriter;
|
use std::rt::io::buffered::BufferedWriter;
|
||||||
use std::rt::io::file::{FileInfo, DirectoryInfo};
|
use std::rt::io::file::{FileInfo, DirectoryInfo};
|
||||||
use std::rt::io::file;
|
use std::rt::io::file;
|
||||||
use std::rt::io;
|
use std::rt::io;
|
||||||
|
use std::rt::io::Reader;
|
||||||
|
use std::str;
|
||||||
use std::task;
|
use std::task;
|
||||||
use std::unstable::finally::Finally;
|
use std::unstable::finally::Finally;
|
||||||
use std::util;
|
use std::util;
|
||||||
|
@ -33,6 +35,7 @@ use syntax::attr;
|
||||||
use clean;
|
use clean;
|
||||||
use doctree;
|
use doctree;
|
||||||
use fold::DocFolder;
|
use fold::DocFolder;
|
||||||
|
use html::escape::Escape;
|
||||||
use html::format::{VisSpace, Method, PuritySpace};
|
use html::format::{VisSpace, Method, PuritySpace};
|
||||||
use html::layout;
|
use html::layout;
|
||||||
use html::markdown::Markdown;
|
use html::markdown::Markdown;
|
||||||
|
@ -44,6 +47,7 @@ pub struct Context {
|
||||||
dst: Path,
|
dst: Path,
|
||||||
layout: layout::Layout,
|
layout: layout::Layout,
|
||||||
sidebar: HashMap<~str, ~[~str]>,
|
sidebar: HashMap<~str, ~[~str]>,
|
||||||
|
include_sources: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Implementor {
|
enum Implementor {
|
||||||
|
@ -68,6 +72,12 @@ struct Cache {
|
||||||
priv search_index: ~[IndexItem],
|
priv search_index: ~[IndexItem],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SourceCollector<'self> {
|
||||||
|
seen: HashSet<~str>,
|
||||||
|
dst: Path,
|
||||||
|
cx: &'self Context,
|
||||||
|
}
|
||||||
|
|
||||||
struct Item<'self> { cx: &'self Context, item: &'self clean::Item, }
|
struct Item<'self> { cx: &'self Context, item: &'self clean::Item, }
|
||||||
struct Sidebar<'self> { cx: &'self Context, item: &'self clean::Item, }
|
struct Sidebar<'self> { cx: &'self Context, item: &'self clean::Item, }
|
||||||
|
|
||||||
|
@ -79,6 +89,8 @@ struct IndexItem {
|
||||||
parent: Option<ast::NodeId>,
|
parent: Option<ast::NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Source<'self>(&'self str);
|
||||||
|
|
||||||
local_data_key!(pub cache_key: RWArc<Cache>)
|
local_data_key!(pub cache_key: RWArc<Cache>)
|
||||||
local_data_key!(pub current_location_key: ~[~str])
|
local_data_key!(pub current_location_key: ~[~str])
|
||||||
|
|
||||||
|
@ -94,6 +106,7 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
|
||||||
favicon: ~"",
|
favicon: ~"",
|
||||||
crate: crate.name.clone(),
|
crate: crate.name.clone(),
|
||||||
},
|
},
|
||||||
|
include_sources: true,
|
||||||
};
|
};
|
||||||
mkdir(&cx.dst);
|
mkdir(&cx.dst);
|
||||||
|
|
||||||
|
@ -107,6 +120,9 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
|
||||||
clean::NameValue(~"html_logo_url", ref s) => {
|
clean::NameValue(~"html_logo_url", ref s) => {
|
||||||
cx.layout.logo = s.to_owned();
|
cx.layout.logo = s.to_owned();
|
||||||
}
|
}
|
||||||
|
clean::Word(~"html_no_source") => {
|
||||||
|
cx.include_sources = false;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,6 +178,19 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
|
||||||
w.flush();
|
w.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cx.include_sources {
|
||||||
|
let dst = cx.dst.push("src");
|
||||||
|
mkdir(&dst);
|
||||||
|
let dst = dst.push(crate.name);
|
||||||
|
mkdir(&dst);
|
||||||
|
let mut folder = SourceCollector {
|
||||||
|
dst: dst,
|
||||||
|
seen: HashSet::new(),
|
||||||
|
cx: &cx,
|
||||||
|
};
|
||||||
|
crate = folder.fold_crate(crate);
|
||||||
|
}
|
||||||
|
|
||||||
// Now render the whole crate.
|
// Now render the whole crate.
|
||||||
cx.crate(crate, cache);
|
cx.crate(crate, cache);
|
||||||
}
|
}
|
||||||
|
@ -183,7 +212,80 @@ fn mkdir(path: &Path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'self> DocFolder for Cache {
|
fn clean_srcpath(src: &str, f: &fn(&str)) {
|
||||||
|
let p = Path(src);
|
||||||
|
for c in p.components.iter() {
|
||||||
|
if "." == *c {
|
||||||
|
loop
|
||||||
|
}
|
||||||
|
if ".." == *c {
|
||||||
|
f("up");
|
||||||
|
} else {
|
||||||
|
f(c.as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> DocFolder for SourceCollector<'self> {
|
||||||
|
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
|
||||||
|
if !self.seen.contains(&item.source.filename) {
|
||||||
|
self.emit_source(item.source.filename);
|
||||||
|
self.seen.insert(item.source.filename.clone());
|
||||||
|
}
|
||||||
|
self.fold_item_recur(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'self> SourceCollector<'self> {
|
||||||
|
fn emit_source(&self, filename: &str) {
|
||||||
|
let p = Path(filename);
|
||||||
|
|
||||||
|
// Read the contents of the file
|
||||||
|
let mut contents = ~[];
|
||||||
|
{
|
||||||
|
let mut buf = [0, ..1024];
|
||||||
|
let r = do io::io_error::cond.trap(|_| {}).inside {
|
||||||
|
p.open_reader(io::Open)
|
||||||
|
};
|
||||||
|
// If we couldn't open this file, then just returns because it
|
||||||
|
// probably means that it's some standard library macro thing and we
|
||||||
|
// can't have the source to it anyway.
|
||||||
|
let mut r = match r { Some(r) => r, None => return };
|
||||||
|
|
||||||
|
// read everything
|
||||||
|
loop {
|
||||||
|
match r.read(buf) {
|
||||||
|
Some(n) => contents.push_all(buf.slice_to(n)),
|
||||||
|
None => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let contents = str::from_utf8_owned(contents);
|
||||||
|
|
||||||
|
// Create the intermediate directories
|
||||||
|
let mut cur = self.dst.clone();
|
||||||
|
let mut root_path = ~"../../";
|
||||||
|
do clean_srcpath(p.pop().to_str()) |component| {
|
||||||
|
cur = cur.push(component);
|
||||||
|
mkdir(&cur);
|
||||||
|
root_path.push_str("../");
|
||||||
|
}
|
||||||
|
|
||||||
|
let dst = cur.push(*p.components.last() + ".html");
|
||||||
|
let mut w = dst.open_writer(io::CreateOrTruncate);
|
||||||
|
|
||||||
|
let title = format!("{} -- source", *dst.components.last());
|
||||||
|
let page = layout::Page {
|
||||||
|
title: title,
|
||||||
|
ty: "source",
|
||||||
|
root_path: root_path,
|
||||||
|
};
|
||||||
|
layout::render(&mut w as &mut io::Writer, &self.cx.layout,
|
||||||
|
&page, &(""), &Source(contents.as_slice()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocFolder for Cache {
|
||||||
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
|
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
|
||||||
// Register any generics to their corresponding string. This is used
|
// Register any generics to their corresponding string. This is used
|
||||||
// when pretty-printing types
|
// when pretty-printing types
|
||||||
|
@ -380,7 +482,6 @@ impl Context {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes
|
|
||||||
fn crate(self, mut crate: clean::Crate, cache: Cache) {
|
fn crate(self, mut crate: clean::Crate, cache: Cache) {
|
||||||
enum Work {
|
enum Work {
|
||||||
Die,
|
Die,
|
||||||
|
@ -565,6 +666,20 @@ impl<'self> fmt::Default for Item<'self> {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if it.cx.include_sources {
|
||||||
|
let mut path = ~[];
|
||||||
|
do clean_srcpath(it.item.source.filename) |component| {
|
||||||
|
path.push(component.to_owned());
|
||||||
|
}
|
||||||
|
write!(fmt.buf,
|
||||||
|
"<a class='source'
|
||||||
|
href='{root}src/{crate}/{path}.html\\#{line}'>[src]</a>",
|
||||||
|
root = it.cx.root_path,
|
||||||
|
crate = it.cx.layout.crate,
|
||||||
|
path = path.connect("/"),
|
||||||
|
line = it.item.source.loline);
|
||||||
|
}
|
||||||
|
|
||||||
// Write the breadcrumb trail header for the top
|
// Write the breadcrumb trail header for the top
|
||||||
write!(fmt.buf, "<h1 class='fqn'>");
|
write!(fmt.buf, "<h1 class='fqn'>");
|
||||||
match it.item.inner {
|
match it.item.inner {
|
||||||
|
@ -1180,3 +1295,23 @@ fn build_sidebar(m: &clean::Module) -> HashMap<~str, ~[~str]> {
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'self> fmt::Default for Source<'self> {
|
||||||
|
fn fmt(s: &Source<'self>, fmt: &mut fmt::Formatter) {
|
||||||
|
let lines = s.line_iter().len();
|
||||||
|
let mut cols = 0;
|
||||||
|
let mut tmp = lines;
|
||||||
|
while tmp > 0 {
|
||||||
|
cols += 1;
|
||||||
|
tmp /= 10;
|
||||||
|
}
|
||||||
|
write!(fmt.buf, "<pre class='line-numbers'>");
|
||||||
|
for i in range(1, lines + 1) {
|
||||||
|
write!(fmt.buf, "<span id='{0}'>{0:1$u}</span>\n", i, cols);
|
||||||
|
}
|
||||||
|
write!(fmt.buf, "</pre>");
|
||||||
|
write!(fmt.buf, "<pre class='rust'>");
|
||||||
|
write!(fmt.buf, "{}", Escape(s.as_slice()));
|
||||||
|
write!(fmt.buf, "</pre>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -119,6 +119,9 @@ body {
|
||||||
.content h1, .content h2 { margin-left: -20px; }
|
.content h1, .content h2 { margin-left: -20px; }
|
||||||
.content pre { padding: 20px; }
|
.content pre { padding: 20px; }
|
||||||
|
|
||||||
|
.content pre.line-numbers { float: left; border: none; }
|
||||||
|
.line-numbers span { color: #c67e2d; }
|
||||||
|
|
||||||
.content .highlighted {
|
.content .highlighted {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
|
|
|
@ -34,10 +34,11 @@ pub mod core;
|
||||||
pub mod doctree;
|
pub mod doctree;
|
||||||
pub mod fold;
|
pub mod fold;
|
||||||
pub mod html {
|
pub mod html {
|
||||||
pub mod render;
|
pub mod escape;
|
||||||
|
pub mod format;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod markdown;
|
pub mod markdown;
|
||||||
pub mod format;
|
pub mod render;
|
||||||
}
|
}
|
||||||
pub mod passes;
|
pub mod passes;
|
||||||
pub mod plugins;
|
pub mod plugins;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue