rustdoc: Add stability dashboard

This commit adds a crate-level dashboard summarizing the stability
levels of all items for all submodules of the crate.

The information is also written as a json file, intended for consumption
by pages like http://huonw.github.io/isrustfastyet/

Closes #13541
This commit is contained in:
Aaron Turon 2014-07-04 00:51:46 -07:00
parent b57d272e99
commit 4d16de01d0
5 changed files with 306 additions and 6 deletions

View File

@ -22,6 +22,7 @@ use syntax::ast;
use syntax::ast_util;
use clean;
use stability_summary::ModuleSummary;
use html::item_type;
use html::item_type::ItemType;
use html::render;
@ -631,3 +632,72 @@ impl<'a> fmt::Show for ConciseStability<'a> {
}
}
}
impl fmt::Show for ModuleSummary {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt_inner<'a>(f: &mut fmt::Formatter,
context: &mut Vec<&'a str>,
m: &'a ModuleSummary)
-> fmt::Result {
let cnt = m.counts;
let tot = cnt.total();
if tot == 0 { return Ok(()) }
context.push(m.name.as_slice());
let path = context.connect("::");
// the total width of each row's stability summary, in pixels
let width = 500;
try!(write!(f, "<tr>"));
try!(write!(f, "<td class='summary'>\
<a class='summary' href='{}'>{}</a></td>",
Vec::from_slice(context.slice_from(1))
.append_one("index.html").connect("/"),
path));
try!(write!(f, "<td>"));
try!(write!(f, "<span class='summary Stable' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.stable)/tot));
try!(write!(f, "<span class='summary Unstable' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.unstable)/tot));
try!(write!(f, "<span class='summary Experimental' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.experimental)/tot));
try!(write!(f, "<span class='summary Deprecated' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.deprecated)/tot));
try!(write!(f, "<span class='summary Unmarked' \
style='width: {}px; display: inline-block'>&nbsp</span>",
(width * cnt.unmarked)/tot));
try!(write!(f, "</td></tr>"));
for submodule in m.submodules.iter() {
try!(fmt_inner(f, context, submodule));
}
context.pop();
Ok(())
}
let mut context = Vec::new();
try!(write!(f,
r"<h1 class='fqn'>Stability dashboard: crate <a class='mod' href='index.html'>{}</a></h1>
This dashboard summarizes the stability levels for all of the public modules of
the crate, according to the total number of items at each level in the module and its children:
<blockquote>
<a class='stability Stable'></a> stable,<br/>
<a class='stability Unstable'></a> unstable,<br/>
<a class='stability Experimental'></a> experimental,<br/>
<a class='stability Deprecated'></a> deprecated,<br/>
<a class='stability Unmarked'></a> unmarked
</blockquote>
The counts do not include methods or trait
implementations that are visible only through a re-exported type.",
self.name));
try!(write!(f, "<table>"))
try!(fmt_inner(f, &mut context, self));
write!(f, "</table>")
}
}

View File

@ -43,6 +43,8 @@ use std::sync::Arc;
use externalfiles::ExternalHtml;
use serialize::json;
use serialize::Encodable;
use serialize::json::ToJson;
use syntax::ast;
use syntax::ast_util;
@ -59,6 +61,7 @@ use html::item_type;
use html::layout;
use html::markdown::Markdown;
use html::markdown;
use stability_summary;
/// Major driving force in all rustdoc rendering. This contains information
/// about where in the tree-like hierarchy rendering is occurring and controls
@ -249,6 +252,11 @@ pub fn run(mut krate: clean::Crate, external_html: &ExternalHtml, dst: Path) ->
try!(mkdir(&cx.dst));
// Crawl the crate, building a summary of the stability levels. NOTE: this
// summary *must* be computed with the original `krate`; the folding below
// removes the impls from their modules.
let summary = stability_summary::build(&krate);
// Crawl the crate attributes looking for attributes which control how we're
// going to emit HTML
match krate.module.as_ref().map(|m| m.doc_list().unwrap_or(&[])) {
@ -361,7 +369,7 @@ pub fn run(mut krate: clean::Crate, external_html: &ExternalHtml, dst: Path) ->
let krate = try!(render_sources(&mut cx, krate));
// And finally render the whole crate's documentation
cx.krate(krate)
cx.krate(krate, summary)
}
fn build_index(krate: &clean::Crate, cache: &mut Cache) -> io::IoResult<String> {
@ -1045,13 +1053,34 @@ impl Context {
///
/// This currently isn't parallelized, but it'd be pretty easy to add
/// parallelization to this function.
fn krate(self, mut krate: clean::Crate) -> io::IoResult<()> {
fn krate(mut self, mut krate: clean::Crate,
stability: stability_summary::ModuleSummary) -> io::IoResult<()> {
let mut item = match krate.module.take() {
Some(i) => i,
None => return Ok(())
};
item.name = Some(krate.name);
// render stability dashboard
try!(self.recurse(stability.name.clone(), |this| {
let json_dst = &this.dst.join("stability.json");
let mut json_out = BufferedWriter::new(try!(File::create(json_dst)));
try!(stability.encode(&mut json::Encoder::new(&mut json_out)));
let title = stability.name.clone().append(" - Stability dashboard");
let page = layout::Page {
ty: "mod",
root_path: this.root_path.as_slice(),
title: title.as_slice(),
};
let html_dst = &this.dst.join("stability.html");
let mut html_out = BufferedWriter::new(try!(File::create(html_dst)));
layout::render(&mut html_out, &this.layout, &page,
&Sidebar{ cx: this, item: &item },
&stability)
}));
// render the crate documentation
let mut work = vec!((self, item));
loop {
match work.pop() {
@ -1061,6 +1090,7 @@ impl Context {
None => break,
}
}
Ok(())
}
@ -1233,6 +1263,8 @@ impl<'a> Item<'a> {
}
}
impl<'a> fmt::Show for Item<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
// Write the breadcrumb trail header for the top
@ -1269,6 +1301,17 @@ impl<'a> fmt::Show for Item<'a> {
// Write stability level
try!(write!(fmt, "{}", Stability(&self.item.stability)));
// Links to out-of-band information, i.e. src and stability dashboard
try!(write!(fmt, "<span class='out-of-band'>"));
// Write stability dashboard link
match self.item.inner {
clean::ModuleItem(ref m) if m.is_crate => {
try!(write!(fmt, "<a href='stability.html'>[stability dashboard]</a> "));
}
_ => {}
};
// Write `src` tag
//
// When this item is part of a `pub use` in a downstream crate, the
@ -1278,14 +1321,15 @@ impl<'a> fmt::Show for Item<'a> {
if self.cx.include_sources && !is_primitive {
match self.href() {
Some(l) => {
try!(write!(fmt,
"<a class='source' id='src-{}' \
href='{}'>[src]</a>",
try!(write!(fmt, "<a id='src-{}' href='{}'>[src]</a>",
self.item.def_id.node, l));
}
None => {}
}
}
try!(write!(fmt, "</span>"));
try!(write!(fmt, "</h1>\n"));
match self.item.inner {
@ -1355,6 +1399,7 @@ fn document(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result {
fn item_module(w: &mut fmt::Formatter, cx: &Context,
item: &clean::Item, items: &[clean::Item]) -> fmt::Result {
try!(document(w, item));
let mut indices = range(0, items.len()).filter(|i| {
!ignore_private_item(&items[*i])
}).collect::<Vec<uint>>();
@ -1514,6 +1559,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
}
}
}
write!(w, "</table>")
}

View File

@ -238,7 +238,7 @@ nav.sub {
.docblock h2 { font-size: 1.15em; }
.docblock h3, .docblock h4, .docblock h5 { font-size: 1em; }
.content .source {
.content .out-of-band {
float: right;
font-size: 23px;
}
@ -409,6 +409,15 @@ h1 .stability {
.stability.Locked { border-color: #0084B6; color: #00668c; }
.stability.Unmarked { border-color: #FFFFFF; }
.summary {
padding-right: 0px;
}
.summary.Deprecated { background-color: #A071A8; }
.summary.Experimental { background-color: #D46D6A; }
.summary.Unstable { background-color: #D4B16A; }
.summary.Stable { background-color: #54A759; }
.summary.Unmarked { background-color: #FFFFFF; }
:target { background: #FDFFD3; }
/* Code highlighting */

View File

@ -56,6 +56,7 @@ pub mod html {
pub mod markdown;
pub mod passes;
pub mod plugins;
pub mod stability_summary;
pub mod visit_ast;
pub mod test;
mod flock;

View File

@ -0,0 +1,174 @@
// 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.
//! This module crawls a `clean::Crate` and produces a summarization of the
//! stability levels within the crate. The summary contains the module
//! hierarchy, with item counts for every stability level per module. A parent
//! module's count includes its childrens's.
use std::ops::Add;
use std::num::Zero;
use std::iter::AdditiveIterator;
use syntax::attr::{Deprecated, Experimental, Unstable, Stable, Frozen, Locked};
use syntax::ast::Public;
use clean::{Crate, Item, ModuleItem, Module, StructItem, Struct, EnumItem, Enum};
use clean::{ImplItem, Impl, TraitItem, Trait, TraitMethod, Provided, Required};
use clean::{ViewItemItem, PrimitiveItem};
#[deriving(Zero, Encodable, Decodable, PartialEq, Eq)]
/// The counts for each stability level.
pub struct Counts {
pub deprecated: uint,
pub experimental: uint,
pub unstable: uint,
pub stable: uint,
pub frozen: uint,
pub locked: uint,
/// No stability level, inherited or otherwise.
pub unmarked: uint,
}
impl Add<Counts, Counts> for Counts {
fn add(&self, other: &Counts) -> Counts {
Counts {
deprecated: self.deprecated + other.deprecated,
experimental: self.experimental + other.experimental,
unstable: self.unstable + other.unstable,
stable: self.stable + other.stable,
frozen: self.frozen + other.frozen,
locked: self.locked + other.locked,
unmarked: self.unmarked + other.unmarked,
}
}
}
impl Counts {
pub fn total(&self) -> uint {
self.deprecated + self.experimental + self.unstable + self.stable +
self.frozen + self.locked + self.unmarked
}
}
#[deriving(Encodable, Decodable, PartialEq, Eq)]
/// A summarized module, which includes total counts and summarized chilcren
/// modules.
pub struct ModuleSummary {
pub name: String,
pub counts: Counts,
pub submodules: Vec<ModuleSummary>,
}
impl PartialOrd for ModuleSummary {
fn partial_cmp(&self, other: &ModuleSummary) -> Option<Ordering> {
self.name.partial_cmp(&other.name)
}
}
impl Ord for ModuleSummary {
fn cmp(&self, other: &ModuleSummary) -> Ordering {
self.name.cmp(&other.name)
}
}
// is the item considered publically visible?
fn visible(item: &Item) -> bool {
match item.inner {
ImplItem(_) => true,
_ => item.visibility == Some(Public)
}
}
// Produce the summary for an arbitrary item. If the item is a module, include a
// module summary. The counts for items with nested items (e.g. modules, traits,
// impls) include all children counts.
fn summarize_item(item: &Item) -> (Counts, Option<ModuleSummary>) {
// count this item
let item_counts = match item.stability {
None => Counts { unmarked: 1, .. Zero::zero() },
Some(ref stab) => match stab.level {
Deprecated => Counts { deprecated: 1, .. Zero::zero() },
Experimental => Counts { experimental: 1, .. Zero::zero() },
Unstable => Counts { unstable: 1, .. Zero::zero() },
Stable => Counts { stable: 1, .. Zero::zero() },
Frozen => Counts { frozen: 1, .. Zero::zero() },
Locked => Counts { locked: 1, .. Zero::zero() },
}
};
// Count this item's children, if any. Note that a trait impl is
// considered to have no children.
match item.inner {
// Require explicit `pub` to be visible
StructItem(Struct { fields: ref subitems, .. }) |
ImplItem(Impl { methods: ref subitems, trait_: None, .. }) => {
let subcounts = subitems.iter().filter(|i| visible(*i))
.map(summarize_item)
.map(|s| s.val0())
.sum();
(item_counts + subcounts, None)
}
// `pub` automatically
EnumItem(Enum { variants: ref subitems, .. }) => {
let subcounts = subitems.iter().map(summarize_item)
.map(|s| s.val0())
.sum();
(item_counts + subcounts, None)
}
TraitItem(Trait { methods: ref methods, .. }) => {
fn extract_item<'a>(meth: &'a TraitMethod) -> &'a Item {
match *meth {
Provided(ref item) | Required(ref item) => item
}
}
let subcounts = methods.iter().map(extract_item)
.map(summarize_item)
.map(|s| s.val0())
.sum();
(item_counts + subcounts, None)
}
ModuleItem(Module { items: ref items, .. }) => {
let mut counts = item_counts;
let mut submodules = Vec::new();
for (subcounts, submodule) in items.iter().filter(|i| visible(*i))
.map(summarize_item) {
counts = counts + subcounts;
submodule.map(|m| submodules.push(m));
}
submodules.sort();
(counts, Some(ModuleSummary {
name: item.name.as_ref().map_or("".to_string(), |n| n.clone()),
counts: counts,
submodules: submodules,
}))
}
// no stability information for the following items:
ViewItemItem(_) | PrimitiveItem(_) => (Zero::zero(), None),
_ => (item_counts, None)
}
}
/// Summarizes the stability levels in a crate.
pub fn build(krate: &Crate) -> ModuleSummary {
match krate.module {
None => ModuleSummary {
name: krate.name.clone(),
counts: Zero::zero(),
submodules: Vec::new(),
},
Some(ref item) => ModuleSummary {
name: krate.name.clone(), .. summarize_item(item).val1().unwrap()
}
}
}