rustdoc: Generate hyperlinks between crates

The general idea of hyperlinking between crates is that it should require as
little configuration as possible, if any at all. In this vein, there are two
separate ways to generate hyperlinks between crates:

1. When you're generating documentation for a crate 'foo' into folder 'doc',
   then if foo's external crate dependencies already have documented in the
   folder 'doc', then hyperlinks will be generated. This will work because all
   documentation is in the same folder, allowing links to work seamlessly both
   on the web and on the local filesystem browser.

   The rationale for this use case is a package with multiple libraries/crates
   that all want to link to one another, and you don't want to have to deal with
   going to the web. In theory this could be extended to have a RUST_PATH-style
   searching situtation, but I'm not sure that it would work seamlessly on the
   web as it does on the local filesystem, so I'm not attempting to explore this
   case in this pull request. I believe to fully realize this potential rustdoc
   would have to be acting as a server instead of a static site generator.

2. One of foo's external dependencies has a #[doc(html_root_url = "...")]
   attribute. This means that all hyperlinks to the dependency will be rooted at
   this url.

   This use case encompasses all packages using libstd/libextra. These two
   crates now have this attribute encoded (currently at the /doc/master url) and
   will be read by anything which has a dependency on libstd/libextra. This
   should also work for arbitrary crates in the wild that have online
   documentation. I don't like how the version is hard-wired into the url, but I
   think that this may be a case-by-case thing which doesn't end up being too
   bad in the long run.

Closes #9539
This commit is contained in:
Alex Crichton 2013-10-02 15:39:32 -07:00
parent f00d72b78b
commit d06043ba0b
6 changed files with 254 additions and 125 deletions

View file

@ -28,10 +28,8 @@ use std::vec;
use extra::arc::RWArc;
use extra::json::ToJson;
use extra::sort;
use extra::time;
use syntax::ast;
use syntax::ast_util::is_local;
use syntax::attr;
use clean;
@ -52,6 +50,12 @@ pub struct Context {
include_sources: bool,
}
pub enum ExternalLocation {
Remote(~str), // remote url root of the documentation
Local, // inside local folder
Unknown, // unknown where the documentation is
}
enum Implementor {
PathType(clean::Type),
OtherType(clean::Generics, /* trait */ clean::Type, /* for */ clean::Type),
@ -68,6 +72,8 @@ struct Cache {
traits: HashMap<ast::NodeId, HashMap<~str, ~str>>,
// trait id => implementors of the trait
implementors: HashMap<ast::NodeId, ~[Implementor]>,
// crate number => where is the crate's dox located at
extern_locations: HashMap<ast::CrateNum, ExternalLocation>,
priv stack: ~[~str],
priv parent_stack: ~[ast::NodeId],
@ -142,6 +148,7 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
stack: ~[],
parent_stack: ~[],
search_index: ~[],
extern_locations: HashMap::new(),
};
cache.stack.push(crate.name.clone());
crate = cache.fold_crate(crate);
@ -154,6 +161,7 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
write(dst.push("main.css"), include_str!("static/main.css"));
write(dst.push("normalize.css"), include_str!("static/normalize.css"));
// Publish the search index
{
let dst = dst.push("search-index.js");
let mut w = BufferedWriter::new(dst.open_writer(io::CreateOrTruncate));
@ -180,9 +188,9 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
w.flush();
}
info2!("emitting source files");
let started = time::precise_time_ns();
// Render all source files (this may turn into a giant no-op)
{
info2!("emitting source files");
let dst = cx.dst.push("src");
mkdir(&dst);
let dst = dst.push(crate.name);
@ -194,14 +202,13 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
};
crate = folder.fold_crate(crate);
}
let ended = time::precise_time_ns();
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1e9f64);
info2!("rendering the whole crate");
let started = time::precise_time_ns();
for (&n, e) in crate.externs.iter() {
cache.extern_locations.insert(n, extern_location(e, &cx.dst));
}
// And finally render the whole crate's documentation
cx.crate(crate, cache);
let ended = time::precise_time_ns();
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1e9f64);
}
fn write(dst: Path, contents: &str) {
@ -235,6 +242,38 @@ fn clean_srcpath(src: &str, f: &fn(&str)) {
}
}
fn extern_location(e: &clean::ExternalCrate, dst: &Path) -> ExternalLocation {
// See if there's documentation generated into the local directory
let local_location = dst.push(e.name);
if local_location.is_dir() {
return Local;
}
// Failing that, see if there's an attribute specifying where to find this
// external crate
for attr in e.attrs.iter() {
match *attr {
clean::List(~"doc", ref list) => {
for attr in list.iter() {
match *attr {
clean::NameValue(~"html_root_url", ref s) => {
if s.ends_with("/") {
return Remote(s.to_owned());
}
return Remote(*s + "/");
}
_ => {}
}
}
}
_ => {}
}
}
// Well, at least we tried.
return Unknown;
}
impl<'self> DocFolder for SourceCollector<'self> {
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
if self.cx.include_sources && !self.seen.contains(&item.source.filename) {
@ -353,8 +392,7 @@ impl DocFolder for Cache {
match item.inner {
clean::ImplItem(ref i) => {
match i.trait_ {
Some(clean::ResolvedPath{ did, _ }) if is_local(did) => {
let id = did.node;
Some(clean::ResolvedPath{ id, _ }) => {
let v = do self.implementors.find_or_insert_with(id) |_|{
~[]
};
@ -441,8 +479,8 @@ impl DocFolder for Cache {
}
clean::ImplItem(ref i) => {
match i.for_ {
clean::ResolvedPath{ did, _ } if is_local(did) => {
self.parent_stack.push(did.node); true
clean::ResolvedPath{ id, _ } => {
self.parent_stack.push(id); true
}
_ => false
}
@ -457,8 +495,7 @@ impl DocFolder for Cache {
match item {
clean::Item{ attrs, inner: clean::ImplItem(i), _ } => {
match i.for_ {
clean::ResolvedPath { did, _ } if is_local(did) => {
let id = did.node;
clean::ResolvedPath { id, _ } => {
let v = do self.impls.find_or_insert_with(id) |_| {
~[]
};
@ -1258,7 +1295,7 @@ fn render_impl(w: &mut io::Writer, i: &clean::Impl, dox: &Option<~str>) {
Some(ref ty) => {
write!(w, "{} for ", *ty);
match *ty {
clean::ResolvedPath { did, _ } => Some(did),
clean::ResolvedPath { id, _ } => Some(id),
_ => None,
}
}
@ -1289,8 +1326,7 @@ fn render_impl(w: &mut io::Writer, i: &clean::Impl, dox: &Option<~str>) {
// No documentation? Attempt to slurp in the trait's documentation
let trait_id = match trait_id {
None => continue,
Some(id) if is_local(id) => continue,
Some(id) => id.node,
Some(id) => id,
};
do local_data::get(cache_key) |cache| {
do cache.unwrap().read |cache| {