From 4d16de01d0beb84dc4a351022ea5cb587b4ab557 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 4 Jul 2014 00:51:46 -0700 Subject: [PATCH] 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 --- src/librustdoc/html/format.rs | 70 +++++++++++ src/librustdoc/html/render.rs | 56 ++++++++- src/librustdoc/html/static/main.css | 11 +- src/librustdoc/lib.rs | 1 + src/librustdoc/stability_summary.rs | 174 ++++++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 6 deletions(-) create mode 100644 src/librustdoc/stability_summary.rs diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 382e299d28d..84c0f0b97a1 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -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, "")); + try!(write!(f, "\ + {}", + Vec::from_slice(context.slice_from(1)) + .append_one("index.html").connect("/"), + path)); + try!(write!(f, "")); + try!(write!(f, " ", + (width * cnt.stable)/tot)); + try!(write!(f, " ", + (width * cnt.unstable)/tot)); + try!(write!(f, " ", + (width * cnt.experimental)/tot)); + try!(write!(f, " ", + (width * cnt.deprecated)/tot)); + try!(write!(f, " ", + (width * cnt.unmarked)/tot)); + try!(write!(f, "")); + + for submodule in m.submodules.iter() { + try!(fmt_inner(f, context, submodule)); + } + context.pop(); + Ok(()) + } + + let mut context = Vec::new(); + + try!(write!(f, +r"

Stability dashboard: crate {}

+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: +
+ stable,
+ unstable,
+ experimental,
+ deprecated,
+ unmarked +
+The counts do not include methods or trait +implementations that are visible only through a re-exported type.", +self.name)); + try!(write!(f, "")) + try!(fmt_inner(f, &mut context, self)); + write!(f, "
") + } +} diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index 70edbcf86e1..3761f918332 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -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 { @@ -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, "")); + + // Write stability dashboard link + match self.item.inner { + clean::ModuleItem(ref m) if m.is_crate => { + try!(write!(fmt, "[stability dashboard] ")); + } + _ => {} + }; + // 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, - "[src]", + try!(write!(fmt, "[src]", self.item.def_id.node, l)); } None => {} } } + + try!(write!(fmt, "")); + try!(write!(fmt, "\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::>(); @@ -1514,6 +1559,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context, } } } + write!(w, "") } diff --git a/src/librustdoc/html/static/main.css b/src/librustdoc/html/static/main.css index f65198fcfe2..4f790f96750 100644 --- a/src/librustdoc/html/static/main.css +++ b/src/librustdoc/html/static/main.css @@ -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 */ diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index fc2fe00afbc..76b9f11089f 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -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; diff --git a/src/librustdoc/stability_summary.rs b/src/librustdoc/stability_summary.rs new file mode 100644 index 00000000000..18e90d5d621 --- /dev/null +++ b/src/librustdoc/stability_summary.rs @@ -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 or the MIT license +// , 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 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, +} + +impl PartialOrd for ModuleSummary { + fn partial_cmp(&self, other: &ModuleSummary) -> Option { + 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) { + // 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() + } + } +}