Skip to content

Commit 61edefc

Browse files
committed
Add unstable rustdoc option --crate-list-heading.
When used, this option creates a subheading within the sidebar “Crates” list for this crate and all crates which pass the same value. These headings are displayed in alphabetical order; crates without the option, or with it set to the empty string, always appear first. The intended use of this option is to allow Cargo to automatically separate a workspace’s crate list into the workspace’s own crates and their external dependencies. Authors could also use RUSTDOCFLAGS or other means to create custom headings for their documentation builds.
1 parent 5ea7dd7 commit 61edefc

File tree

6 files changed

+104
-21
lines changed

6 files changed

+104
-21
lines changed

src/librustdoc/config.rs

+6
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ pub(crate) struct RenderOptions {
243243
/// What sorting mode to use for module pages.
244244
/// `ModuleSorting::Alphabetical` by default.
245245
pub(crate) module_sorting: ModuleSorting,
246+
/// In the sidebar list of all crates, put the current crate(s) under this heading.
247+
/// To put a crate under the unnamed primary heading, which is always listed first,
248+
/// make this the empty string.
249+
pub(crate) crate_list_heading: String,
246250
/// List of themes to extend the docs with. Original argument name is included to assist in
247251
/// displaying errors if it fails a theme check.
248252
pub(crate) themes: Vec<StylePath>,
@@ -767,6 +771,7 @@ impl Options {
767771
} else {
768772
ModuleSorting::Alphabetical
769773
};
774+
let crate_list_heading = matches.opt_str("crate-list-heading").unwrap_or_default();
770775
let resource_suffix = matches.opt_str("resource-suffix").unwrap_or_default();
771776
let markdown_no_toc = matches.opt_present("markdown-no-toc");
772777
let markdown_css = matches.opt_strs("markdown-css");
@@ -867,6 +872,7 @@ impl Options {
867872
id_map,
868873
playground_url,
869874
module_sorting,
875+
crate_list_heading,
870876
themes,
871877
extension_css,
872878
extern_html_root_urls,

src/librustdoc/html/render/write_shared.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//! --resource-suffix flag and are emitted when --emit-type is empty (default)
1414
//! or contains "invocation-specific".
1515
16+
use std::borrow::Cow;
1617
use std::cell::RefCell;
1718
use std::ffi::OsString;
1819
use std::fs::File;
@@ -69,8 +70,13 @@ pub(crate) fn write_shared(
6970
write_search_desc(cx, krate, &desc)?; // does not need to be merged
7071

7172
let crate_name = krate.name(cx.tcx());
72-
let crate_name = crate_name.as_str(); // rand
73-
let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
73+
let crate_name = crate_name.as_str(); // e.g. rand
74+
let crate_name_json = OrderedJson::serialize(AllCratesEntry {
75+
heading: Cow::Borrowed(&opt.crate_list_heading),
76+
crate_name: Cow::Borrowed(crate_name),
77+
})
78+
.unwrap(); // e.g. {"c":"rand","h":""}
79+
7480
let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
7581
let info = CrateInfo {
7682
version: CrateInfoVersion::V1,
@@ -387,6 +393,15 @@ impl AllCratesPart {
387393
}
388394
}
389395

396+
/// Type of a serialized entry in the [`AllCratesPart`] JSON.
397+
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
398+
struct AllCratesEntry<'a> {
399+
#[serde(rename = "h")]
400+
heading: Cow<'a, str>,
401+
#[serde(rename = "c")]
402+
crate_name: Cow<'a, str>,
403+
}
404+
390405
/// Reads `crates.js`, which seems like the best
391406
/// place to obtain the list of externally documented crates if the index
392407
/// page was disabled when documenting the deps.
@@ -408,8 +423,12 @@ fn hack_get_external_crate_names(
408423
let Some(content) = regex.find(&content) else {
409424
return Err(Error::new("could not find crates list in crates.js", path));
410425
};
411-
let content: Vec<String> = try_err!(serde_json::from_str(content.as_str()), &path);
412-
Ok(content)
426+
let crate_names: Vec<String> =
427+
try_err!(serde_json::from_str::<Vec<AllCratesEntry<'static>>>(content.as_str()), &path)
428+
.into_iter()
429+
.map(|entry| entry.crate_name.into_owned())
430+
.collect();
431+
Ok(crate_names)
413432
}
414433

415434
#[derive(Serialize, Deserialize, Clone, Default, Debug)]

src/librustdoc/html/static/js/main.js

+49-16
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,7 @@ function preLoadCss(cssUrl) {
10031003
window.register_type_impls(window.pending_type_impls);
10041004
}
10051005

1006+
// Draw a convenient sidebar of known crates if we have a listing
10061007
function addSidebarCrates() {
10071008
if (!window.ALL_CRATES) {
10081009
return;
@@ -1011,26 +1012,58 @@ function preLoadCss(cssUrl) {
10111012
if (!sidebarElems) {
10121013
return;
10131014
}
1014-
// Draw a convenient sidebar of known crates if we have a listing
1015-
const h3 = document.createElement("h3");
1016-
h3.innerHTML = "Crates";
1017-
const ul = document.createElement("ul");
1018-
ul.className = "block crate";
10191015

1020-
for (const crate of window.ALL_CRATES) {
1021-
const link = document.createElement("a");
1022-
link.href = window.rootPath + crate + "/index.html";
1023-
link.textContent = crate;
1016+
const allCratesHeading = document.createElement("h3");
1017+
allCratesHeading.textContent = "Crates";
10241018

1025-
const li = document.createElement("li");
1026-
if (window.rootPath !== "./" && crate === window.currentCrate) {
1027-
li.className = "current";
1019+
const allCratesSection = document.createElement("section");
1020+
1021+
// window.ALL_CRATES is in unsorted array-of-structs format; reorganize so crates with the
1022+
// same heading are grouped.
1023+
const cratesGroupedByHeading = new Map();
1024+
for (const entry of window.ALL_CRATES) {
1025+
const heading = entry.h;
1026+
const crateName = entry.c;
1027+
let group = cratesGroupedByHeading.get(heading);
1028+
if (group === undefined) {
1029+
group = [];
1030+
cratesGroupedByHeading.set(heading, group);
1031+
}
1032+
group.push(crateName);
1033+
}
1034+
const headings = Array.from(cratesGroupedByHeading.keys());
1035+
headings.sort();
1036+
1037+
// Generate HTML for grouped crates.
1038+
for (const headingText of headings) {
1039+
// Empty string denotes a group with no named heading.
1040+
if (headingText !== "") {
1041+
const crateCategoryHeading = document.createElement("h4");
1042+
crateCategoryHeading.textContent = headingText;
1043+
allCratesSection.appendChild(crateCategoryHeading);
1044+
}
1045+
1046+
const cratesUl = document.createElement("ul");
1047+
cratesUl.className = "block crate";
1048+
1049+
for (const crateName of cratesGroupedByHeading.get(headingText)) {
1050+
const link = document.createElement("a");
1051+
link.href = window.rootPath + crateName + "/index.html";
1052+
link.textContent = crateName;
1053+
1054+
const li = document.createElement("li");
1055+
if (window.rootPath !== "./" && crateName === window.currentCrate) {
1056+
li.className = "current";
1057+
}
1058+
li.appendChild(link);
1059+
cratesUl.appendChild(li);
10281060
}
1029-
li.appendChild(link);
1030-
ul.appendChild(li);
1061+
1062+
allCratesSection.appendChild(cratesUl);
10311063
}
1032-
sidebarElems.appendChild(h3);
1033-
sidebarElems.appendChild(ul);
1064+
1065+
sidebarElems.appendChild(allCratesHeading);
1066+
sidebarElems.appendChild(allCratesSection);
10341067
}
10351068

10361069
function expandAllDocs() {

src/librustdoc/html/static/js/rustdoc.d.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ declare global {
88
/** Make the current theme easy to find */
99
currentTheme: HTMLLinkElement|null;
1010
/** List of all documented crates. */
11-
ALL_CRATES: string[]|undefined;
11+
ALL_CRATES: rustdoc.AllCratesEntry[]|undefined;
1212
/** Used by the popover tooltip code. */
1313
RUSTDOC_TOOLTIP_HOVER_MS: number;
1414
/** Used by the popover tooltip code. */
@@ -350,6 +350,18 @@ declare namespace rustdoc {
350350
bindings: Map<number, FingerprintableType[]>;
351351
};
352352

353+
/** Member of ALL_CRATES. */
354+
interface AllCratesEntry {
355+
/**
356+
* Heading under which the crate should be listed.
357+
*
358+
* May be empty to specify the first, primary heading.
359+
*/
360+
h: string,
361+
/** Crate name. */
362+
c: string,
363+
}
364+
353365
/**
354366
* The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f`
355367
* are arrays with the same length. `q`, `a`, and `c` use a sparse

src/librustdoc/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,14 @@ fn opts() -> Vec<RustcOptGroup> {
383383
"sort modules by where they appear in the program, rather than alphabetically",
384384
"",
385385
),
386+
opt(
387+
Unstable,
388+
Opt,
389+
"",
390+
"crate-list-heading",
391+
"heading under which to list this crate in the sidebar crate list",
392+
"PATH",
393+
),
386394
opt(
387395
Stable,
388396
Opt,

tests/rustdoc/crate-list-heading.rs

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//@ compile-flags: -Zunstable-options --crate-list-heading=helloworld
2+
#![crate_name = "foo"]
3+
4+
//@ hasraw crates.js '"h":"helloworld"'
5+
//@ hasraw crates.js '"c":"foo"'

0 commit comments

Comments
 (0)