Skip to content

Add page to list all crate's items #49504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 205 additions & 36 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,8 @@ impl<'a> SourceCollector<'a> {
href.push_str(component);
href.push('/');
});
let mut fname = p.file_name().expect("source has no filename")
let mut fname = p.file_name()
.expect("source has no filename")
.to_os_string();
fname.push(".html");
cur.push(&fname);
Expand Down Expand Up @@ -1373,6 +1374,135 @@ impl<'a> Cache {
}
}

#[derive(Debug, Eq, PartialEq, Hash)]
struct ItemEntry {
url: String,
name: String,
}

impl ItemEntry {
fn new(mut url: String, name: String) -> ItemEntry {
while url.starts_with('/') {
url.remove(0);
}
ItemEntry {
url,
name,
}
}
}

impl fmt::Display for ItemEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<a href='{}'>{}</a>", self.url, Escape(&self.name))
}
}

impl PartialOrd for ItemEntry {
fn partial_cmp(&self, other: &ItemEntry) -> Option<::std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for ItemEntry {
fn cmp(&self, other: &ItemEntry) -> ::std::cmp::Ordering {
self.name.cmp(&other.name)
}
}

#[derive(Debug)]
struct AllTypes {
structs: HashSet<ItemEntry>,
enums: HashSet<ItemEntry>,
unions: HashSet<ItemEntry>,
primitives: HashSet<ItemEntry>,
traits: HashSet<ItemEntry>,
macros: HashSet<ItemEntry>,
functions: HashSet<ItemEntry>,
typedefs: HashSet<ItemEntry>,
statics: HashSet<ItemEntry>,
constants: HashSet<ItemEntry>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for the future: these could all get replaced with FxHashSets later on, if this becomes a performance problem.

}

impl AllTypes {
fn new() -> AllTypes {
AllTypes {
structs: HashSet::with_capacity(100),
enums: HashSet::with_capacity(100),
unions: HashSet::with_capacity(100),
primitives: HashSet::with_capacity(26),
traits: HashSet::with_capacity(100),
macros: HashSet::with_capacity(100),
functions: HashSet::with_capacity(100),
typedefs: HashSet::with_capacity(100),
statics: HashSet::with_capacity(100),
constants: HashSet::with_capacity(100),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initializing everything here with 100 items (except the primitives, since there's not an unbounded number of those) seems like way overkill, but i don't know how the average crate measures up here. Is there a way to measure crates.io for this? >_>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just picked a random number. But at least with this, it's done.

}
}

fn append(&mut self, item_name: String, item_type: &ItemType) {
let mut url: Vec<_> = item_name.split("::").skip(1).collect();
if let Some(name) = url.pop() {
let new_url = format!("{}/{}.{}.html", url.join("/"), item_type, name);
url.push(name);
let name = url.join("::");
match *item_type {
ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)),
ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)),
ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)),
ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)),
ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)),
ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)),
ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)),
ItemType::Typedef => self.typedefs.insert(ItemEntry::new(new_url, name)),
ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)),
ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)),
_ => true,
};
}
}
}

fn print_entries(f: &mut fmt::Formatter, e: &HashSet<ItemEntry>, title: &str,
class: &str) -> fmt::Result {
if !e.is_empty() {
let mut e: Vec<&ItemEntry> = e.iter().collect();
e.sort();
write!(f, "<h3 id='{}'>{}</h3><ul class='{} docblock'>{}</ul>",
title,
Escape(title),
class,
e.iter().map(|s| format!("<li>{}</li>", s)).collect::<String>())?;
}
Ok(())
}

impl fmt::Display for AllTypes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"<h1 class='fqn'>\
<span class='in-band'>List of all items</span>\
<span class='out-of-band'>\
<span id='render-detail'>\
<a id=\"toggle-all-docs\" href=\"javascript:void(0)\" title=\"collapse all docs\">\
[<span class='inner'>&#x2212;</span>]\
</a>\
</span>
</span>
</h1>")?;
print_entries(f, &self.structs, "Structs", "structs")?;
print_entries(f, &self.enums, "Enums", "enums")?;
print_entries(f, &self.unions, "Unions", "unions")?;
print_entries(f, &self.primitives, "Primitives", "primitives")?;
print_entries(f, &self.traits, "Traits", "traits")?;
print_entries(f, &self.macros, "Macros", "macros")?;
print_entries(f, &self.functions, "Functions", "functions")?;
print_entries(f, &self.typedefs, "Typedefs", "typedefs")?;
print_entries(f, &self.statics, "Statics", "statics")?;
print_entries(f, &self.constants, "Constants", "constants")
}
}

impl Context {
/// String representation of how to get back to the root path of the 'doc/'
/// folder in terms of a relative URL.
Expand Down Expand Up @@ -1414,16 +1544,52 @@ impl Context {
Some(i) => i,
None => return Ok(()),
};
let final_file = self.dst.join(&krate.name)
.join("all.html");
let crate_name = krate.name.clone();
item.name = Some(krate.name);

// Render the crate documentation
let mut work = vec![(self, item)];
let mut all = AllTypes::new();

while let Some((mut cx, item)) = work.pop() {
cx.item(item, |cx, item| {
work.push((cx.clone(), item))
})?
{
// Render the crate documentation
let mut work = vec![(self.clone(), item)];

while let Some((mut cx, item)) = work.pop() {
cx.item(item, &mut all, |cx, item| {
work.push((cx.clone(), item))
})?
}
}

let mut w = BufWriter::new(try_err!(File::create(&final_file), &final_file));
let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
if !root_path.ends_with('/') {
root_path.push('/');
}
let page = layout::Page {
title: "List of all items in this crate",
css_class: "mod",
root_path: "../",
description: "List of all items in this crate",
keywords: BASIC_KEYWORDS,
resource_suffix: &self.shared.resource_suffix,
};
let sidebar = if let Some(ref version) = cache().crate_version {
format!("<p class='location'>Crate {}</p>\
<div class='block version'>\
<p>Version {}</p>\
</div>\
<a id='all-types' href='index.html'><p>Back to index</p></a>",
crate_name, version)
} else {
String::new()
};
try_err!(layout::render(&mut w, &self.shared.layout,
&page, &sidebar, &all,
self.shared.css_file_extension.is_some(),
&self.shared.themes),
&final_file);
Ok(())
}

Expand Down Expand Up @@ -1496,8 +1662,8 @@ impl Context {
/// all sub-items which need to be rendered.
///
/// The rendering driver uses this closure to queue up more work.
fn item<F>(&mut self, item: clean::Item, mut f: F) -> Result<(), Error> where
F: FnMut(&mut Context, clean::Item),
fn item<F>(&mut self, item: clean::Item, all: &mut AllTypes, mut f: F) -> Result<(), Error>
where F: FnMut(&mut Context, clean::Item),
{
// Stripped modules survive the rustdoc passes (i.e. `strip-private`)
// if they contain impls for public types. These modules can also
Expand Down Expand Up @@ -1544,7 +1710,7 @@ impl Context {
}

for item in m.items {
f(this,item);
f(this, item);
}

Ok(())
Expand All @@ -1562,13 +1728,14 @@ impl Context {
let mut dst = try_err!(File::create(&joint_dst), &joint_dst);
try_err!(dst.write_all(&buf), &joint_dst);

all.append(full_path(self, &item), &item_type);
// Redirect from a sane URL using the namespace to Rustdoc's
// URL for the page.
let redir_name = format!("{}.{}.html", name, item_type.name_space());
let redir_dst = self.dst.join(redir_name);
if let Ok(redirect_out) = OpenOptions::new().create_new(true)
.write(true)
.open(&redir_dst) {
.write(true)
.open(&redir_dst) {
let mut redirect_out = BufWriter::new(redirect_out);
try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst);
}
Expand Down Expand Up @@ -1730,11 +1897,12 @@ impl<'a> fmt::Display for Item<'a> {
version)?;
}
write!(fmt,
r##"<span id='render-detail'>
<a id="toggle-all-docs" href="javascript:void(0)" title="collapse all docs">
[<span class='inner'>&#x2212;</span>]
</a>
</span>"##)?;
"<span id='render-detail'>\
<a id=\"toggle-all-docs\" href=\"javascript:void(0)\" \
title=\"collapse all docs\">\
[<span class='inner'>&#x2212;</span>]\
</a>\
</span>")?;

// Write `src` tag
//
Expand Down Expand Up @@ -3540,33 +3708,34 @@ impl<'a> fmt::Display for Sidebar<'a> {

if it.is_struct() || it.is_trait() || it.is_primitive() || it.is_union()
|| it.is_enum() || it.is_mod() || it.is_typedef() {
write!(fmt, "<p class='location'>")?;
match it.inner {
clean::StructItem(..) => write!(fmt, "Struct ")?,
clean::TraitItem(..) => write!(fmt, "Trait ")?,
clean::PrimitiveItem(..) => write!(fmt, "Primitive Type ")?,
clean::UnionItem(..) => write!(fmt, "Union ")?,
clean::EnumItem(..) => write!(fmt, "Enum ")?,
clean::TypedefItem(..) => write!(fmt, "Type Definition ")?,
clean::ForeignTypeItem => write!(fmt, "Foreign Type ")?,
clean::ModuleItem(..) => if it.is_crate() {
write!(fmt, "Crate ")?;
} else {
write!(fmt, "Module ")?;
write!(fmt, "<p class='location'>{}{}</p>",
match it.inner {
clean::StructItem(..) => "Struct ",
clean::TraitItem(..) => "Trait ",
clean::PrimitiveItem(..) => "Primitive Type ",
clean::UnionItem(..) => "Union ",
clean::EnumItem(..) => "Enum ",
clean::TypedefItem(..) => "Type Definition ",
clean::ForeignTypeItem => "Foreign Type ",
clean::ModuleItem(..) => if it.is_crate() {
"Crate "
} else {
"Module "
},
_ => "",
},
_ => (),
}
write!(fmt, "{}", it.name.as_ref().unwrap())?;
write!(fmt, "</p>")?;
it.name.as_ref().unwrap())?;
}

if it.is_crate() {
if let Some(ref version) = cache().crate_version {
write!(fmt,
"<div class='block version'>\
<p>Version {}</p>\
</div>",
version)?;
</div>
<a id='all-types' href='all.html'><p>See all {}'s items</p></a>",
version,
it.name.as_ref().unwrap())?;
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/librustdoc/html/static/rustdoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -1291,3 +1291,21 @@ kbd {
font-size: 19px;
display: block;
}

#main > ul {
padding-left: 10px;
}
#main > ul > li {
list-style: none;
}
#all-types {
text-align: center;
border: 1px solid;
margin: 0 10px;
margin-bottom: 10px;
display: block;
border-radius: 7px;
}
#all-types > p {
margin: 5px 0;
}
7 changes: 7 additions & 0 deletions src/librustdoc/html/static/themes/dark.css
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,10 @@ kbd {
background: #f0f0f0;
}
}

#all-types {
background-color: #505050;
}
#all-types:hover {
background-color: #606060;
}
7 changes: 7 additions & 0 deletions src/librustdoc/html/static/themes/light.css
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,10 @@ kbd {
background: #fff;
}
}

#all-types {
background-color: #fff;
}
#all-types:hover {
background-color: #f9f9f9;
}
30 changes: 30 additions & 0 deletions src/test/rustdoc/all.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2018 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.

#![crate_name = "foo"]

// @has foo/all.html '//a[@href="struct.Struct.html"]' 'Struct'
// @has foo/all.html '//a[@href="enum.Enum.html"]' 'Enum'
// @has foo/all.html '//a[@href="union.Union.html"]' 'Union'
// @has foo/all.html '//a[@href="constant.CONST.html"]' 'CONST'
// @has foo/all.html '//a[@href="static.STATIC.html"]' 'STATIC'
// @has foo/all.html '//a[@href="fn.function.html"]' 'function'

pub struct Struct;
pub enum Enum {
X,
Y,
}
pub union Union {
x: u32,
}
pub const CONST: u32 = 0;
pub static STATIC: &str = "baguette";
pub fn function() {}