Moved write_shared
to its own file
This commit is contained in:
parent
14983b9812
commit
fd14e38612
@ -16,6 +16,7 @@
|
||||
|
||||
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,
|
||||
|
@ -31,26 +31,22 @@
|
||||
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::{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 @@
|
||||
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<String>);
|
||||
@ -318,529 +313,6 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
"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<String> = 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<String>, Vec<String>)> {
|
||||
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<String>, Vec<String>)> {
|
||||
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<OsString, Hierarchy>,
|
||||
elems: FxHashSet<OsString>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
files.sort_unstable();
|
||||
let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().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!(
|
||||
"<h1 class=\"fqn\">\
|
||||
<span class=\"in-band\">List of all crates</span>\
|
||||
</h1><ul class=\"crate mod\">{}</ul>",
|
||||
krates
|
||||
.iter()
|
||||
.map(|s| {
|
||||
format!(
|
||||
"<li><a class=\"crate mod\" href=\"{}index.html\">{}</a></li>",
|
||||
ensure_trailing_slash(s),
|
||||
s
|
||||
)
|
||||
})
|
||||
.collect::<String>()
|
||||
);
|
||||
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<String>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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, "<a class=\"srclink\" href=\"{}\" title=\"goto source code\">[src]</a>", 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
|
||||
|
542
src/librustdoc/html/render/write_shared.rs
Normal file
542
src/librustdoc/html/render/write_shared.rs
Normal file
@ -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<String> = 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<String>, Vec<String>)> {
|
||||
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<String>, Vec<String>)> {
|
||||
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<OsString, Hierarchy>,
|
||||
elems: FxHashSet<OsString>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
files.sort_unstable();
|
||||
let subs = subs.iter().map(|s| s.to_json_string()).collect::<Vec<_>>().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!(
|
||||
"<h1 class=\"fqn\">\
|
||||
<span class=\"in-band\">List of all crates</span>\
|
||||
</h1><ul class=\"crate mod\">{}</ul>",
|
||||
krates
|
||||
.iter()
|
||||
.map(|s| {
|
||||
format!(
|
||||
"<li><a class=\"crate mod\" href=\"{}index.html\">{}</a></li>",
|
||||
ensure_trailing_slash(s),
|
||||
s
|
||||
)
|
||||
})
|
||||
.collect::<String>()
|
||||
);
|
||||
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<String>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user