diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index bd1c17185ea..976168a9ac4 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -16,6 +16,7 @@ use rustc_span::symbol::sym;
use super::cache::{build_index, ExternalLocation};
use super::print_item::{full_path, item_path, print_item};
+use super::write_shared::write_shared;
use super::{
print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS,
CURRENT_DEPTH, INITIAL_IDS,
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index fc184bd4dea..1db081b181f 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -31,26 +31,22 @@ crate mod cache;
mod tests;
mod context;
-crate use context::*;
-
mod print_item;
+mod write_shared;
+
+crate use context::*;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::default::Default;
-use std::ffi::OsStr;
-use std::fmt::{self, Write};
-use std::fs::{self, File};
-use std::io::prelude::*;
-use std::io::{self, BufReader};
-use std::path::{Component, Path, PathBuf};
+use std::fmt;
+use std::path::{Path, PathBuf};
use std::str;
use std::string::ToString;
use itertools::Itertools;
use rustc_ast_pretty::pprust;
use rustc_attr::{Deprecation, StabilityLevel};
-use rustc_data_structures::flock;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_hir::def::CtorKind;
@@ -64,7 +60,6 @@ use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use crate::clean::{self, GetDefId, RenderedLink, SelfTy, TypeKind};
-use crate::config::RenderOptions;
use crate::docfs::{DocFS, PathError};
use crate::error::Error;
use crate::formats::cache::Cache;
@@ -75,8 +70,8 @@ use crate::html::format::{
href, print_abi_with_space, print_default_space, print_generic_bounds, Buffer, Function,
PrintWithSpace, WhereClause,
};
+use crate::html::layout;
use crate::html::markdown::{self, ErrorCodes, Markdown, MarkdownHtml, MarkdownSummaryLine};
-use crate::html::{layout, static_files};
/// A pair of name and its optional document.
crate type NameDoc = (String, Option);
@@ -318,529 +313,6 @@ crate const INITIAL_IDS: [&'static str; 15] = [
"implementations",
];
-fn write_shared(
- cx: &Context<'_>,
- krate: &clean::Crate,
- search_index: String,
- options: &RenderOptions,
-) -> Result<(), Error> {
- // Write out the shared files. Note that these are shared among all rustdoc
- // docs placed in the output directory, so this needs to be a synchronized
- // operation with respect to all other rustdocs running around.
- let lock_file = cx.dst.join(".lock");
- let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
-
- // Add all the static files. These may already exist, but we just
- // overwrite them anyway to make sure that they're fresh and up-to-date.
-
- write_minify(
- &cx.shared.fs,
- cx.path("rustdoc.css"),
- static_files::RUSTDOC_CSS,
- options.enable_minification,
- )?;
- write_minify(
- &cx.shared.fs,
- cx.path("settings.css"),
- static_files::SETTINGS_CSS,
- options.enable_minification,
- )?;
- write_minify(
- &cx.shared.fs,
- cx.path("noscript.css"),
- static_files::NOSCRIPT_CSS,
- options.enable_minification,
- )?;
-
- // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
- // then we'll run over the "official" styles.
- let mut themes: FxHashSet = FxHashSet::default();
-
- for entry in &cx.shared.style_files {
- let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path);
- let extension =
- try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
-
- // Handle the official themes
- match theme {
- "light" => write_minify(
- &cx.shared.fs,
- cx.path("light.css"),
- static_files::themes::LIGHT,
- options.enable_minification,
- )?,
- "dark" => write_minify(
- &cx.shared.fs,
- cx.path("dark.css"),
- static_files::themes::DARK,
- options.enable_minification,
- )?,
- "ayu" => write_minify(
- &cx.shared.fs,
- cx.path("ayu.css"),
- static_files::themes::AYU,
- options.enable_minification,
- )?,
- _ => {
- // Handle added third-party themes
- let content = try_err!(fs::read(&entry.path), &entry.path);
- cx.shared
- .fs
- .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?;
- }
- };
-
- themes.insert(theme.to_owned());
- }
-
- let write = |p, c| cx.shared.fs.write(p, c);
- if (*cx.shared).layout.logo.is_empty() {
- write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?;
- }
- if (*cx.shared).layout.favicon.is_empty() {
- write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?;
- write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?;
- write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?;
- }
- write(cx.path("brush.svg"), static_files::BRUSH_SVG)?;
- write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?;
- write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?;
-
- let mut themes: Vec<&String> = themes.iter().collect();
- themes.sort();
- // To avoid theme switch latencies as much as possible, we put everything theme related
- // at the beginning of the html files into another js file.
- let theme_js = format!(
- r#"var themes = document.getElementById("theme-choices");
-var themePicker = document.getElementById("theme-picker");
-
-function showThemeButtonState() {{
- themes.style.display = "block";
- themePicker.style.borderBottomRightRadius = "0";
- themePicker.style.borderBottomLeftRadius = "0";
-}}
-
-function hideThemeButtonState() {{
- themes.style.display = "none";
- themePicker.style.borderBottomRightRadius = "3px";
- themePicker.style.borderBottomLeftRadius = "3px";
-}}
-
-function switchThemeButtonState() {{
- if (themes.style.display === "block") {{
- hideThemeButtonState();
- }} else {{
- showThemeButtonState();
- }}
-}};
-
-function handleThemeButtonsBlur(e) {{
- var active = document.activeElement;
- var related = e.relatedTarget;
-
- if (active.id !== "theme-picker" &&
- (!active.parentNode || active.parentNode.id !== "theme-choices") &&
- (!related ||
- (related.id !== "theme-picker" &&
- (!related.parentNode || related.parentNode.id !== "theme-choices")))) {{
- hideThemeButtonState();
- }}
-}}
-
-themePicker.onclick = switchThemeButtonState;
-themePicker.onblur = handleThemeButtonsBlur;
-{}.forEach(function(item) {{
- var but = document.createElement("button");
- but.textContent = item;
- but.onclick = function(el) {{
- switchTheme(currentTheme, mainTheme, item, true);
- useSystemTheme(false);
- }};
- but.onblur = handleThemeButtonsBlur;
- themes.appendChild(but);
-}});"#,
- serde_json::to_string(&themes).unwrap()
- );
-
- write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?;
- write_minify(
- &cx.shared.fs,
- cx.path("main.js"),
- static_files::MAIN_JS,
- options.enable_minification,
- )?;
- write_minify(
- &cx.shared.fs,
- cx.path("settings.js"),
- static_files::SETTINGS_JS,
- options.enable_minification,
- )?;
- if cx.shared.include_sources {
- write_minify(
- &cx.shared.fs,
- cx.path("source-script.js"),
- static_files::sidebar::SOURCE_SCRIPT,
- options.enable_minification,
- )?;
- }
-
- {
- write_minify(
- &cx.shared.fs,
- cx.path("storage.js"),
- &format!(
- "var resourcesSuffix = \"{}\";{}",
- cx.shared.resource_suffix,
- static_files::STORAGE_JS
- ),
- options.enable_minification,
- )?;
- }
-
- if let Some(ref css) = cx.shared.layout.css_file_extension {
- let out = cx.path("theme.css");
- let buffer = try_err!(fs::read_to_string(css), css);
- if !options.enable_minification {
- cx.shared.fs.write(&out, &buffer)?;
- } else {
- write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
- }
- }
- write_minify(
- &cx.shared.fs,
- cx.path("normalize.css"),
- static_files::NORMALIZE_CSS,
- options.enable_minification,
- )?;
- write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?;
- write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?;
- write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?;
- write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?;
- write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?;
- write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?;
- write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?;
- write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?;
- write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?;
- write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?;
- write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?;
- write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?;
- write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?;
- write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?;
- write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?;
-
- fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec, Vec)> {
- let mut ret = Vec::new();
- let mut krates = Vec::new();
-
- if path.exists() {
- let prefix = format!(r#"{}["{}"]"#, key, krate);
- for line in BufReader::new(File::open(path)?).lines() {
- let line = line?;
- if !line.starts_with(key) {
- continue;
- }
- if line.starts_with(&prefix) {
- continue;
- }
- ret.push(line.to_string());
- krates.push(
- line[key.len() + 2..]
- .split('"')
- .next()
- .map(|s| s.to_owned())
- .unwrap_or_else(String::new),
- );
- }
- }
- Ok((ret, krates))
- }
-
- fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> {
- let mut ret = Vec::new();
- let mut krates = Vec::new();
-
- if path.exists() {
- let prefix = format!("\"{}\"", krate);
- for line in BufReader::new(File::open(path)?).lines() {
- let line = line?;
- if !line.starts_with('"') {
- continue;
- }
- if line.starts_with(&prefix) {
- continue;
- }
- if line.ends_with(",\\") {
- ret.push(line[..line.len() - 2].to_string());
- } else {
- // Ends with "\\" (it's the case for the last added crate line)
- ret.push(line[..line.len() - 1].to_string());
- }
- krates.push(
- line.split('"')
- .find(|s| !s.is_empty())
- .map(|s| s.to_owned())
- .unwrap_or_else(String::new),
- );
- }
- }
- Ok((ret, krates))
- }
-
- use std::ffi::OsString;
-
- #[derive(Debug)]
- struct Hierarchy {
- elem: OsString,
- children: FxHashMap,
- elems: FxHashSet,
- }
-
- impl Hierarchy {
- fn new(elem: OsString) -> Hierarchy {
- Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
- }
-
- fn to_json_string(&self) -> String {
- let mut subs: Vec<&Hierarchy> = self.children.values().collect();
- subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
- let mut files = self
- .elems
- .iter()
- .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
- .collect::>();
- files.sort_unstable();
- let subs = subs.iter().map(|s| s.to_json_string()).collect::>().join(",");
- let dirs =
- if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) };
- let files = files.join(",");
- let files =
- if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) };
- format!(
- "{{\"name\":\"{name}\"{dirs}{files}}}",
- name = self.elem.to_str().expect("invalid osstring conversion"),
- dirs = dirs,
- files = files
- )
- }
- }
-
- if cx.shared.include_sources {
- let mut hierarchy = Hierarchy::new(OsString::new());
- for source in cx
- .shared
- .local_sources
- .iter()
- .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
- {
- let mut h = &mut hierarchy;
- let mut elems = source
- .components()
- .filter_map(|s| match s {
- Component::Normal(s) => Some(s.to_owned()),
- _ => None,
- })
- .peekable();
- loop {
- let cur_elem = elems.next().expect("empty file path");
- if elems.peek().is_none() {
- h.elems.insert(cur_elem);
- break;
- } else {
- let e = cur_elem.clone();
- h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
- }
- }
- }
-
- let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix));
- let (mut all_sources, _krates) =
- try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst);
- all_sources.push(format!(
- "sourcesIndex[\"{}\"] = {};",
- &krate.name,
- hierarchy.to_json_string()
- ));
- all_sources.sort();
- let v = format!(
- "var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n",
- all_sources.join("\n")
- );
- cx.shared.fs.write(&dst, v.as_bytes())?;
- }
-
- // Update the search index and crate list.
- let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
- let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
- all_indexes.push(search_index);
- krates.push(krate.name.to_string());
- krates.sort();
-
- // Sort the indexes by crate so the file will be generated identically even
- // with rustdoc running in parallel.
- all_indexes.sort();
- {
- let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
- v.push_str(&all_indexes.join(",\\\n"));
- v.push_str("\\\n}');\ninitSearch(searchIndex);");
- cx.shared.fs.write(&dst, &v)?;
- }
-
- let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
- let crate_list =
- format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
- cx.shared.fs.write(&crate_list_dst, &crate_list)?;
-
- if options.enable_index_page {
- if let Some(index_page) = options.index_page.clone() {
- let mut md_opts = options.clone();
- md_opts.output = cx.dst.clone();
- md_opts.external_html = (*cx.shared).layout.external_html.clone();
-
- crate::markdown::render(&index_page, md_opts, cx.shared.edition)
- .map_err(|e| Error::new(e, &index_page))?;
- } else {
- let dst = cx.dst.join("index.html");
- let page = layout::Page {
- title: "Index of crates",
- css_class: "mod",
- root_path: "./",
- static_root_path: cx.shared.static_root_path.as_deref(),
- description: "List of crates",
- keywords: BASIC_KEYWORDS,
- resource_suffix: &cx.shared.resource_suffix,
- extra_scripts: &[],
- static_extra_scripts: &[],
- };
-
- let content = format!(
- "\
- List of all crates\
-
",
- krates
- .iter()
- .map(|s| {
- format!(
- "{}",
- ensure_trailing_slash(s),
- s
- )
- })
- .collect::()
- );
- let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
- cx.shared.fs.write(&dst, v.as_bytes())?;
- }
- }
-
- // Update the list of all implementors for traits
- let dst = cx.dst.join("implementors");
- for (&did, imps) in &cx.cache.implementors {
- // Private modules can leak through to this phase of rustdoc, which
- // could contain implementations for otherwise private types. In some
- // rare cases we could find an implementation for an item which wasn't
- // indexed, so we just skip this step in that case.
- //
- // FIXME: this is a vague explanation for why this can't be a `get`, in
- // theory it should be...
- let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) {
- Some(p) => p,
- None => match cx.cache.external_paths.get(&did) {
- Some(p) => p,
- None => continue,
- },
- };
-
- #[derive(Serialize)]
- struct Implementor {
- text: String,
- synthetic: bool,
- types: Vec,
- }
-
- let implementors = imps
- .iter()
- .filter_map(|imp| {
- // If the trait and implementation are in the same crate, then
- // there's no need to emit information about it (there's inlining
- // going on). If they're in different crates then the crate defining
- // the trait will be interested in our implementation.
- //
- // If the implementation is from another crate then that crate
- // should add it.
- if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() {
- None
- } else {
- Some(Implementor {
- text: imp.inner_impl().print(cx.cache(), false).to_string(),
- synthetic: imp.inner_impl().synthetic,
- types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
- })
- }
- })
- .collect::>();
-
- // Only create a js file if we have impls to add to it. If the trait is
- // documented locally though we always create the file to avoid dead
- // links.
- if implementors.is_empty() && !cx.cache.paths.contains_key(&did) {
- continue;
- }
-
- let implementors = format!(
- r#"implementors["{}"] = {};"#,
- krate.name,
- serde_json::to_string(&implementors).unwrap()
- );
-
- let mut mydst = dst.clone();
- for part in &remote_path[..remote_path.len() - 1] {
- mydst.push(part);
- }
- cx.shared.ensure_dir(&mydst)?;
- mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1]));
-
- let (mut all_implementors, _) =
- try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst);
- all_implementors.push(implementors);
- // Sort the implementors by crate so the file will be generated
- // identically even with rustdoc running in parallel.
- all_implementors.sort();
-
- let mut v = String::from("(function() {var implementors = {};\n");
- for implementor in &all_implementors {
- writeln!(v, "{}", *implementor).unwrap();
- }
- v.push_str(
- "if (window.register_implementors) {\
- window.register_implementors(implementors);\
- } else {\
- window.pending_implementors = implementors;\
- }",
- );
- v.push_str("})()");
- cx.shared.fs.write(&mydst, &v)?;
- }
- Ok(())
-}
-
-fn write_minify(
- fs: &DocFS,
- dst: PathBuf,
- contents: &str,
- enable_minification: bool,
-) -> Result<(), Error> {
- if enable_minification {
- if dst.extension() == Some(&OsStr::new("css")) {
- let res = try_none!(minifier::css::minify(contents).ok(), &dst);
- fs.write(dst, res.as_bytes())
- } else {
- fs.write(dst, minifier::js::minify(contents).as_bytes())
- }
- } else {
- fs.write(dst, contents.as_bytes())
- }
-}
-
fn write_srclink(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) {
if let Some(l) = cx.src_href(item) {
write!(buf, "[src]", l)
@@ -2924,7 +2396,6 @@ fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) {
crate const BASIC_KEYWORDS: &str = "rust, rustlang, rust-lang";
-
/// Returns a list of all paths used in the type.
/// This is used to help deduplicate imported impls
/// for reexported types. If any of the contained
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
new file mode 100644
index 00000000000..cbf0f9a4927
--- /dev/null
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -0,0 +1,542 @@
+use std::ffi::OsStr;
+use std::fmt::Write;
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::io::{self, BufReader};
+use std::path::{Component, Path, PathBuf};
+
+use itertools::Itertools;
+use rustc_data_structures::flock;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use serde::Serialize;
+
+use super::{collect_paths_for_type, ensure_trailing_slash, Context, BASIC_KEYWORDS};
+use crate::clean::Crate;
+use crate::config::RenderOptions;
+use crate::docfs::{DocFS, PathError};
+use crate::error::Error;
+use crate::formats::FormatRenderer;
+use crate::html::{layout, static_files};
+
+pub(super) fn write_shared(
+ cx: &Context<'_>,
+ krate: &Crate,
+ search_index: String,
+ options: &RenderOptions,
+) -> Result<(), Error> {
+ // Write out the shared files. Note that these are shared among all rustdoc
+ // docs placed in the output directory, so this needs to be a synchronized
+ // operation with respect to all other rustdocs running around.
+ let lock_file = cx.dst.join(".lock");
+ let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
+
+ // Add all the static files. These may already exist, but we just
+ // overwrite them anyway to make sure that they're fresh and up-to-date.
+
+ write_minify(
+ &cx.shared.fs,
+ cx.path("rustdoc.css"),
+ static_files::RUSTDOC_CSS,
+ options.enable_minification,
+ )?;
+ write_minify(
+ &cx.shared.fs,
+ cx.path("settings.css"),
+ static_files::SETTINGS_CSS,
+ options.enable_minification,
+ )?;
+ write_minify(
+ &cx.shared.fs,
+ cx.path("noscript.css"),
+ static_files::NOSCRIPT_CSS,
+ options.enable_minification,
+ )?;
+
+ // To avoid "light.css" to be overwritten, we'll first run over the received themes and only
+ // then we'll run over the "official" styles.
+ let mut themes: FxHashSet = FxHashSet::default();
+
+ for entry in &cx.shared.style_files {
+ let theme = try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path);
+ let extension =
+ try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
+
+ // Handle the official themes
+ match theme {
+ "light" => write_minify(
+ &cx.shared.fs,
+ cx.path("light.css"),
+ static_files::themes::LIGHT,
+ options.enable_minification,
+ )?,
+ "dark" => write_minify(
+ &cx.shared.fs,
+ cx.path("dark.css"),
+ static_files::themes::DARK,
+ options.enable_minification,
+ )?,
+ "ayu" => write_minify(
+ &cx.shared.fs,
+ cx.path("ayu.css"),
+ static_files::themes::AYU,
+ options.enable_minification,
+ )?,
+ _ => {
+ // Handle added third-party themes
+ let content = try_err!(fs::read(&entry.path), &entry.path);
+ cx.shared
+ .fs
+ .write(cx.path(&format!("{}.{}", theme, extension)), content.as_slice())?;
+ }
+ };
+
+ themes.insert(theme.to_owned());
+ }
+
+ let write = |p, c| cx.shared.fs.write(p, c);
+ if (*cx.shared).layout.logo.is_empty() {
+ write(cx.path("rust-logo.png"), static_files::RUST_LOGO)?;
+ }
+ if (*cx.shared).layout.favicon.is_empty() {
+ write(cx.path("favicon.svg"), static_files::RUST_FAVICON_SVG)?;
+ write(cx.path("favicon-16x16.png"), static_files::RUST_FAVICON_PNG_16)?;
+ write(cx.path("favicon-32x32.png"), static_files::RUST_FAVICON_PNG_32)?;
+ }
+ write(cx.path("brush.svg"), static_files::BRUSH_SVG)?;
+ write(cx.path("wheel.svg"), static_files::WHEEL_SVG)?;
+ write(cx.path("down-arrow.svg"), static_files::DOWN_ARROW_SVG)?;
+
+ let mut themes: Vec<&String> = themes.iter().collect();
+ themes.sort();
+ // To avoid theme switch latencies as much as possible, we put everything theme related
+ // at the beginning of the html files into another js file.
+ let theme_js = format!(
+ r#"var themes = document.getElementById("theme-choices");
+var themePicker = document.getElementById("theme-picker");
+
+function showThemeButtonState() {{
+ themes.style.display = "block";
+ themePicker.style.borderBottomRightRadius = "0";
+ themePicker.style.borderBottomLeftRadius = "0";
+}}
+
+function hideThemeButtonState() {{
+ themes.style.display = "none";
+ themePicker.style.borderBottomRightRadius = "3px";
+ themePicker.style.borderBottomLeftRadius = "3px";
+}}
+
+function switchThemeButtonState() {{
+ if (themes.style.display === "block") {{
+ hideThemeButtonState();
+ }} else {{
+ showThemeButtonState();
+ }}
+}};
+
+function handleThemeButtonsBlur(e) {{
+ var active = document.activeElement;
+ var related = e.relatedTarget;
+
+ if (active.id !== "theme-picker" &&
+ (!active.parentNode || active.parentNode.id !== "theme-choices") &&
+ (!related ||
+ (related.id !== "theme-picker" &&
+ (!related.parentNode || related.parentNode.id !== "theme-choices")))) {{
+ hideThemeButtonState();
+ }}
+}}
+
+themePicker.onclick = switchThemeButtonState;
+themePicker.onblur = handleThemeButtonsBlur;
+{}.forEach(function(item) {{
+ var but = document.createElement("button");
+ but.textContent = item;
+ but.onclick = function(el) {{
+ switchTheme(currentTheme, mainTheme, item, true);
+ useSystemTheme(false);
+ }};
+ but.onblur = handleThemeButtonsBlur;
+ themes.appendChild(but);
+}});"#,
+ serde_json::to_string(&themes).unwrap()
+ );
+
+ write_minify(&cx.shared.fs, cx.path("theme.js"), &theme_js, options.enable_minification)?;
+ write_minify(
+ &cx.shared.fs,
+ cx.path("main.js"),
+ static_files::MAIN_JS,
+ options.enable_minification,
+ )?;
+ write_minify(
+ &cx.shared.fs,
+ cx.path("settings.js"),
+ static_files::SETTINGS_JS,
+ options.enable_minification,
+ )?;
+ if cx.shared.include_sources {
+ write_minify(
+ &cx.shared.fs,
+ cx.path("source-script.js"),
+ static_files::sidebar::SOURCE_SCRIPT,
+ options.enable_minification,
+ )?;
+ }
+
+ {
+ write_minify(
+ &cx.shared.fs,
+ cx.path("storage.js"),
+ &format!(
+ "var resourcesSuffix = \"{}\";{}",
+ cx.shared.resource_suffix,
+ static_files::STORAGE_JS
+ ),
+ options.enable_minification,
+ )?;
+ }
+
+ if let Some(ref css) = cx.shared.layout.css_file_extension {
+ let out = cx.path("theme.css");
+ let buffer = try_err!(fs::read_to_string(css), css);
+ if !options.enable_minification {
+ cx.shared.fs.write(&out, &buffer)?;
+ } else {
+ write_minify(&cx.shared.fs, out, &buffer, options.enable_minification)?;
+ }
+ }
+ write_minify(
+ &cx.shared.fs,
+ cx.path("normalize.css"),
+ static_files::NORMALIZE_CSS,
+ options.enable_minification,
+ )?;
+ write(cx.dst.join("FiraSans-Regular.woff2"), static_files::fira_sans::REGULAR2)?;
+ write(cx.dst.join("FiraSans-Medium.woff2"), static_files::fira_sans::MEDIUM2)?;
+ write(cx.dst.join("FiraSans-Regular.woff"), static_files::fira_sans::REGULAR)?;
+ write(cx.dst.join("FiraSans-Medium.woff"), static_files::fira_sans::MEDIUM)?;
+ write(cx.dst.join("FiraSans-LICENSE.txt"), static_files::fira_sans::LICENSE)?;
+ write(cx.dst.join("SourceSerifPro-Regular.ttf.woff"), static_files::source_serif_pro::REGULAR)?;
+ write(cx.dst.join("SourceSerifPro-Bold.ttf.woff"), static_files::source_serif_pro::BOLD)?;
+ write(cx.dst.join("SourceSerifPro-It.ttf.woff"), static_files::source_serif_pro::ITALIC)?;
+ write(cx.dst.join("SourceSerifPro-LICENSE.md"), static_files::source_serif_pro::LICENSE)?;
+ write(cx.dst.join("SourceCodePro-Regular.woff"), static_files::source_code_pro::REGULAR)?;
+ write(cx.dst.join("SourceCodePro-Semibold.woff"), static_files::source_code_pro::SEMIBOLD)?;
+ write(cx.dst.join("SourceCodePro-LICENSE.txt"), static_files::source_code_pro::LICENSE)?;
+ write(cx.dst.join("LICENSE-MIT.txt"), static_files::LICENSE_MIT)?;
+ write(cx.dst.join("LICENSE-APACHE.txt"), static_files::LICENSE_APACHE)?;
+ write(cx.dst.join("COPYRIGHT.txt"), static_files::COPYRIGHT)?;
+
+ fn collect(path: &Path, krate: &str, key: &str) -> io::Result<(Vec, Vec)> {
+ let mut ret = Vec::new();
+ let mut krates = Vec::new();
+
+ if path.exists() {
+ let prefix = format!(r#"{}["{}"]"#, key, krate);
+ for line in BufReader::new(File::open(path)?).lines() {
+ let line = line?;
+ if !line.starts_with(key) {
+ continue;
+ }
+ if line.starts_with(&prefix) {
+ continue;
+ }
+ ret.push(line.to_string());
+ krates.push(
+ line[key.len() + 2..]
+ .split('"')
+ .next()
+ .map(|s| s.to_owned())
+ .unwrap_or_else(String::new),
+ );
+ }
+ }
+ Ok((ret, krates))
+ }
+
+ fn collect_json(path: &Path, krate: &str) -> io::Result<(Vec, Vec)> {
+ let mut ret = Vec::new();
+ let mut krates = Vec::new();
+
+ if path.exists() {
+ let prefix = format!("\"{}\"", krate);
+ for line in BufReader::new(File::open(path)?).lines() {
+ let line = line?;
+ if !line.starts_with('"') {
+ continue;
+ }
+ if line.starts_with(&prefix) {
+ continue;
+ }
+ if line.ends_with(",\\") {
+ ret.push(line[..line.len() - 2].to_string());
+ } else {
+ // Ends with "\\" (it's the case for the last added crate line)
+ ret.push(line[..line.len() - 1].to_string());
+ }
+ krates.push(
+ line.split('"')
+ .find(|s| !s.is_empty())
+ .map(|s| s.to_owned())
+ .unwrap_or_else(String::new),
+ );
+ }
+ }
+ Ok((ret, krates))
+ }
+
+ use std::ffi::OsString;
+
+ #[derive(Debug)]
+ struct Hierarchy {
+ elem: OsString,
+ children: FxHashMap,
+ elems: FxHashSet,
+ }
+
+ impl Hierarchy {
+ fn new(elem: OsString) -> Hierarchy {
+ Hierarchy { elem, children: FxHashMap::default(), elems: FxHashSet::default() }
+ }
+
+ fn to_json_string(&self) -> String {
+ let mut subs: Vec<&Hierarchy> = self.children.values().collect();
+ subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
+ let mut files = self
+ .elems
+ .iter()
+ .map(|s| format!("\"{}\"", s.to_str().expect("invalid osstring conversion")))
+ .collect::>();
+ files.sort_unstable();
+ let subs = subs.iter().map(|s| s.to_json_string()).collect::>().join(",");
+ let dirs =
+ if subs.is_empty() { String::new() } else { format!(",\"dirs\":[{}]", subs) };
+ let files = files.join(",");
+ let files =
+ if files.is_empty() { String::new() } else { format!(",\"files\":[{}]", files) };
+ format!(
+ "{{\"name\":\"{name}\"{dirs}{files}}}",
+ name = self.elem.to_str().expect("invalid osstring conversion"),
+ dirs = dirs,
+ files = files
+ )
+ }
+ }
+
+ if cx.shared.include_sources {
+ let mut hierarchy = Hierarchy::new(OsString::new());
+ for source in cx
+ .shared
+ .local_sources
+ .iter()
+ .filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
+ {
+ let mut h = &mut hierarchy;
+ let mut elems = source
+ .components()
+ .filter_map(|s| match s {
+ Component::Normal(s) => Some(s.to_owned()),
+ _ => None,
+ })
+ .peekable();
+ loop {
+ let cur_elem = elems.next().expect("empty file path");
+ if elems.peek().is_none() {
+ h.elems.insert(cur_elem);
+ break;
+ } else {
+ let e = cur_elem.clone();
+ h = h.children.entry(cur_elem.clone()).or_insert_with(|| Hierarchy::new(e));
+ }
+ }
+ }
+
+ let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix));
+ let (mut all_sources, _krates) =
+ try_err!(collect(&dst, &krate.name.as_str(), "sourcesIndex"), &dst);
+ all_sources.push(format!(
+ "sourcesIndex[\"{}\"] = {};",
+ &krate.name,
+ hierarchy.to_json_string()
+ ));
+ all_sources.sort();
+ let v = format!(
+ "var N = null;var sourcesIndex = {{}};\n{}\ncreateSourceSidebar();\n",
+ all_sources.join("\n")
+ );
+ cx.shared.fs.write(&dst, v.as_bytes())?;
+ }
+
+ // Update the search index and crate list.
+ let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
+ let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
+ all_indexes.push(search_index);
+ krates.push(krate.name.to_string());
+ krates.sort();
+
+ // Sort the indexes by crate so the file will be generated identically even
+ // with rustdoc running in parallel.
+ all_indexes.sort();
+ {
+ let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
+ v.push_str(&all_indexes.join(",\\\n"));
+ v.push_str("\\\n}');\ninitSearch(searchIndex);");
+ cx.shared.fs.write(&dst, &v)?;
+ }
+
+ let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
+ let crate_list =
+ format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
+ cx.shared.fs.write(&crate_list_dst, &crate_list)?;
+
+ if options.enable_index_page {
+ if let Some(index_page) = options.index_page.clone() {
+ let mut md_opts = options.clone();
+ md_opts.output = cx.dst.clone();
+ md_opts.external_html = (*cx.shared).layout.external_html.clone();
+
+ crate::markdown::render(&index_page, md_opts, cx.shared.edition)
+ .map_err(|e| Error::new(e, &index_page))?;
+ } else {
+ let dst = cx.dst.join("index.html");
+ let page = layout::Page {
+ title: "Index of crates",
+ css_class: "mod",
+ root_path: "./",
+ static_root_path: cx.shared.static_root_path.as_deref(),
+ description: "List of crates",
+ keywords: BASIC_KEYWORDS,
+ resource_suffix: &cx.shared.resource_suffix,
+ extra_scripts: &[],
+ static_extra_scripts: &[],
+ };
+
+ let content = format!(
+ "\
+ List of all crates\
+
",
+ krates
+ .iter()
+ .map(|s| {
+ format!(
+ "{}",
+ ensure_trailing_slash(s),
+ s
+ )
+ })
+ .collect::()
+ );
+ let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
+ cx.shared.fs.write(&dst, v.as_bytes())?;
+ }
+ }
+
+ // Update the list of all implementors for traits
+ let dst = cx.dst.join("implementors");
+ for (&did, imps) in &cx.cache.implementors {
+ // Private modules can leak through to this phase of rustdoc, which
+ // could contain implementations for otherwise private types. In some
+ // rare cases we could find an implementation for an item which wasn't
+ // indexed, so we just skip this step in that case.
+ //
+ // FIXME: this is a vague explanation for why this can't be a `get`, in
+ // theory it should be...
+ let &(ref remote_path, remote_item_type) = match cx.cache.paths.get(&did) {
+ Some(p) => p,
+ None => match cx.cache.external_paths.get(&did) {
+ Some(p) => p,
+ None => continue,
+ },
+ };
+
+ #[derive(Serialize)]
+ struct Implementor {
+ text: String,
+ synthetic: bool,
+ types: Vec,
+ }
+
+ let implementors = imps
+ .iter()
+ .filter_map(|imp| {
+ // If the trait and implementation are in the same crate, then
+ // there's no need to emit information about it (there's inlining
+ // going on). If they're in different crates then the crate defining
+ // the trait will be interested in our implementation.
+ //
+ // If the implementation is from another crate then that crate
+ // should add it.
+ if imp.impl_item.def_id.krate == did.krate || !imp.impl_item.def_id.is_local() {
+ None
+ } else {
+ Some(Implementor {
+ text: imp.inner_impl().print(cx.cache(), false).to_string(),
+ synthetic: imp.inner_impl().synthetic,
+ types: collect_paths_for_type(imp.inner_impl().for_.clone(), cx.cache()),
+ })
+ }
+ })
+ .collect::>();
+
+ // Only create a js file if we have impls to add to it. If the trait is
+ // documented locally though we always create the file to avoid dead
+ // links.
+ if implementors.is_empty() && !cx.cache.paths.contains_key(&did) {
+ continue;
+ }
+
+ let implementors = format!(
+ r#"implementors["{}"] = {};"#,
+ krate.name,
+ serde_json::to_string(&implementors).unwrap()
+ );
+
+ let mut mydst = dst.clone();
+ for part in &remote_path[..remote_path.len() - 1] {
+ mydst.push(part);
+ }
+ cx.shared.ensure_dir(&mydst)?;
+ mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1]));
+
+ let (mut all_implementors, _) =
+ try_err!(collect(&mydst, &krate.name.as_str(), "implementors"), &mydst);
+ all_implementors.push(implementors);
+ // Sort the implementors by crate so the file will be generated
+ // identically even with rustdoc running in parallel.
+ all_implementors.sort();
+
+ let mut v = String::from("(function() {var implementors = {};\n");
+ for implementor in &all_implementors {
+ writeln!(v, "{}", *implementor).unwrap();
+ }
+ v.push_str(
+ "if (window.register_implementors) {\
+ window.register_implementors(implementors);\
+ } else {\
+ window.pending_implementors = implementors;\
+ }",
+ );
+ v.push_str("})()");
+ cx.shared.fs.write(&mydst, &v)?;
+ }
+ Ok(())
+}
+
+fn write_minify(
+ fs: &DocFS,
+ dst: PathBuf,
+ contents: &str,
+ enable_minification: bool,
+) -> Result<(), Error> {
+ if enable_minification {
+ if dst.extension() == Some(&OsStr::new("css")) {
+ let res = try_none!(minifier::css::minify(contents).ok(), &dst);
+ fs.write(dst, res.as_bytes())
+ } else {
+ fs.write(dst, minifier::js::minify(contents).as_bytes())
+ }
+ } else {
+ fs.write(dst, contents.as_bytes())
+ }
+}