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:
parent
b57d272e99
commit
4d16de01d0
@ -22,6 +22,7 @@
|
||||
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 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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'> </span>",
|
||||
(width * cnt.stable)/tot));
|
||||
try!(write!(f, "<span class='summary Unstable' \
|
||||
style='width: {}px; display: inline-block'> </span>",
|
||||
(width * cnt.unstable)/tot));
|
||||
try!(write!(f, "<span class='summary Experimental' \
|
||||
style='width: {}px; display: inline-block'> </span>",
|
||||
(width * cnt.experimental)/tot));
|
||||
try!(write!(f, "<span class='summary Deprecated' \
|
||||
style='width: {}px; display: inline-block'> </span>",
|
||||
(width * cnt.deprecated)/tot));
|
||||
try!(write!(f, "<span class='summary Unmarked' \
|
||||
style='width: {}px; display: inline-block'> </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>")
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,8 @@
|
||||
|
||||
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::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 @@ fn recurse<T>(&mut self, s: String, f: |&mut Context| -> T) -> T {
|
||||
///
|
||||
/// 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 @@ fn krate(self, mut krate: clean::Crate) -> io::IoResult<()> {
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1233,6 +1263,8 @@ fn href(&self) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 @@ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
// 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 @@ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
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 fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write!(w, "</table>")
|
||||
}
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
174
src/librustdoc/stability_summary.rs
Normal file
174
src/librustdoc/stability_summary.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user