2023-01-04 20:56:46 +00:00
|
|
|
use std::cell::RefCell;
|
2021-02-13 23:17:38 -08:00
|
|
|
use std::fs::{self, File};
|
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::io::{self, BufReader};
|
2022-10-24 01:28:55 -07:00
|
|
|
use std::path::{Component, Path};
|
2023-01-04 20:56:46 +00:00
|
|
|
use std::rc::{Rc, Weak};
|
2021-02-13 23:17:38 -08:00
|
|
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
use rustc_data_structures::flock;
|
|
|
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
2022-08-05 16:36:47 -07:00
|
|
|
use serde::ser::SerializeSeq;
|
|
|
|
use serde::{Serialize, Serializer};
|
2021-02-13 23:17:38 -08:00
|
|
|
|
2023-01-30 12:20:06 -07:00
|
|
|
use super::{collect_paths_for_type, ensure_trailing_slash, Context};
|
2021-02-13 23:17:38 -08:00
|
|
|
use crate::clean::Crate;
|
2021-03-25 12:46:35 -04:00
|
|
|
use crate::config::{EmitType, RenderOptions};
|
2021-03-25 11:40:32 -04:00
|
|
|
use crate::docfs::PathError;
|
2021-02-13 23:17:38 -08:00
|
|
|
use crate::error::Error;
|
|
|
|
use crate::html::{layout, static_files};
|
2021-10-30 02:07:37 +00:00
|
|
|
use crate::{try_err, try_none};
|
2021-02-13 23:17:38 -08:00
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
/// Rustdoc writes out two kinds of shared files:
|
|
|
|
/// - Static files, which are embedded in the rustdoc binary and are written with a
|
|
|
|
/// filename that includes a hash of their contents. These will always have a new
|
|
|
|
/// URL if the contents change, so they are safe to cache with the
|
|
|
|
/// `Cache-Control: immutable` directive. They are written under the static.files/
|
|
|
|
/// directory and are written when --emit-type is empty (default) or contains
|
2022-09-28 23:52:00 -07:00
|
|
|
/// "toolchain-specific". If using the --static-root-path flag, it should point
|
|
|
|
/// to a URL path prefix where each of these filenames can be fetched.
|
2022-10-24 01:28:55 -07:00
|
|
|
/// - Invocation specific files. These are generated based on the crate(s) being
|
|
|
|
/// documented. Their filenames need to be predictable without knowing their
|
|
|
|
/// contents, so they do not include a hash in their filename and are not safe to
|
|
|
|
/// cache with `Cache-Control: immutable`. They include the contents of the
|
|
|
|
/// --resource-suffix flag and are emitted when --emit-type is empty (default)
|
|
|
|
/// or contains "invocation-specific".
|
2021-02-13 23:17:38 -08:00
|
|
|
pub(super) fn write_shared(
|
2022-05-26 20:18:00 +02:00
|
|
|
cx: &mut Context<'_>,
|
2021-02-13 23:17:38 -08:00
|
|
|
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);
|
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
// InvocationSpecific resources should always be dynamic.
|
|
|
|
let write_invocation_specific = |p: &str, make_content: &dyn Fn() -> Result<Vec<u8>, Error>| {
|
2021-03-31 11:13:51 -04:00
|
|
|
let content = make_content()?;
|
2022-10-24 01:28:55 -07:00
|
|
|
if options.emit.is_empty() || options.emit.contains(&EmitType::InvocationSpecific) {
|
|
|
|
let output_filename = static_files::suffix_path(p, &cx.shared.resource_suffix);
|
|
|
|
cx.shared.fs.write(cx.dst.join(output_filename), content)
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-03-31 11:13:51 -04:00
|
|
|
};
|
2021-03-25 11:40:32 -04:00
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
cx.shared
|
|
|
|
.fs
|
|
|
|
.create_dir_all(cx.dst.join("static.files"))
|
|
|
|
.map_err(|e| PathError::new(e, "static.files"))?;
|
2021-02-13 23:17:38 -08:00
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
// Handle added third-party themes
|
2021-02-13 23:17:38 -08:00
|
|
|
for entry in &cx.shared.style_files {
|
2021-11-22 23:23:58 -08:00
|
|
|
let theme = entry.basename()?;
|
2021-02-13 23:17:38 -08:00
|
|
|
let extension =
|
|
|
|
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
|
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
// Skip the official themes. They are written below as part of STATIC_FILES_LIST.
|
|
|
|
if matches!(theme.as_str(), "light" | "dark" | "ayu") {
|
|
|
|
continue;
|
|
|
|
}
|
2021-02-13 23:17:38 -08:00
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
let bytes = try_err!(fs::read(&entry.path), &entry.path);
|
2023-08-15 14:27:29 +02:00
|
|
|
let filename = format!("{theme}{suffix}.{extension}", suffix = cx.shared.resource_suffix);
|
2022-10-24 01:28:55 -07:00
|
|
|
cx.shared.fs.write(cx.dst.join(filename), bytes)?;
|
2021-10-01 13:05:35 -07:00
|
|
|
}
|
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
// When the user adds their own CSS files with --extend-css, we write that as an
|
|
|
|
// invocation-specific file (that is, with a resource suffix).
|
2021-02-13 23:17:38 -08:00
|
|
|
if let Some(ref css) = cx.shared.layout.css_file_extension {
|
|
|
|
let buffer = try_err!(fs::read_to_string(css), css);
|
2022-10-24 01:28:55 -07:00
|
|
|
let path = static_files::suffix_path("theme.css", &cx.shared.resource_suffix);
|
|
|
|
cx.shared.fs.write(cx.dst.join(path), buffer)?;
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
2022-10-24 01:28:55 -07:00
|
|
|
|
|
|
|
if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) {
|
2022-10-29 01:57:39 -07:00
|
|
|
let static_dir = cx.dst.join(Path::new("static.files"));
|
|
|
|
static_files::for_each(|f: &static_files::StaticFile| {
|
|
|
|
let filename = static_dir.join(f.output_filename());
|
|
|
|
cx.shared.fs.write(filename, f.minified())
|
|
|
|
})?;
|
2021-03-05 10:35:22 -05:00
|
|
|
}
|
2021-02-13 23:17:38 -08:00
|
|
|
|
2022-08-04 12:13:16 -07:00
|
|
|
/// Read a file and return all lines that match the `"{crate}":{data},` format,
|
|
|
|
/// and return a tuple `(Vec<DataString>, Vec<CrateNameString>)`.
|
|
|
|
///
|
|
|
|
/// This forms the payload of files that look like this:
|
|
|
|
///
|
|
|
|
/// ```javascript
|
|
|
|
/// var data = {
|
|
|
|
/// "{crate1}":{data},
|
|
|
|
/// "{crate2}":{data}
|
|
|
|
/// };
|
|
|
|
/// use_data(data);
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// The file needs to be formatted so that *only crate data lines start with `"`*.
|
|
|
|
fn collect(path: &Path, krate: &str) -> io::Result<(Vec<String>, Vec<String>)> {
|
2021-02-13 23:17:38 -08:00
|
|
|
let mut ret = Vec::new();
|
|
|
|
let mut krates = Vec::new();
|
|
|
|
|
|
|
|
if path.exists() {
|
2023-08-14 22:25:32 +02:00
|
|
|
let prefix = format!("\"{krate}\"");
|
2021-02-13 23:17:38 -08:00
|
|
|
for line in BufReader::new(File::open(path)?).lines() {
|
|
|
|
let line = line?;
|
2022-08-04 12:13:16 -07:00
|
|
|
if !line.starts_with('"') {
|
2021-02-13 23:17:38 -08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if line.starts_with(&prefix) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-09-03 22:57:22 +02:00
|
|
|
if line.ends_with(',') {
|
2022-08-04 12:13:16 -07:00
|
|
|
ret.push(line[..line.len() - 1].to_string());
|
|
|
|
} else {
|
|
|
|
// No comma (it's the case for the last added crate line)
|
|
|
|
ret.push(line.to_string());
|
|
|
|
}
|
2021-02-13 23:17:38 -08:00
|
|
|
krates.push(
|
2022-08-04 12:13:16 -07:00
|
|
|
line.split('"')
|
|
|
|
.find(|s| !s.is_empty())
|
2021-02-13 23:17:38 -08:00
|
|
|
.map(|s| s.to_owned())
|
|
|
|
.unwrap_or_else(String::new),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok((ret, krates))
|
|
|
|
}
|
|
|
|
|
2023-01-20 16:02:52 +01:00
|
|
|
/// Read a file and return all lines that match the <code>"{crate}":{data},\ </code> format,
|
2022-08-04 12:13:16 -07:00
|
|
|
/// and return a tuple `(Vec<DataString>, Vec<CrateNameString>)`.
|
|
|
|
///
|
|
|
|
/// This forms the payload of files that look like this:
|
|
|
|
///
|
|
|
|
/// ```javascript
|
|
|
|
/// var data = JSON.parse('{\
|
|
|
|
/// "{crate1}":{data},\
|
|
|
|
/// "{crate2}":{data}\
|
|
|
|
/// }');
|
|
|
|
/// use_data(data);
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// The file needs to be formatted so that *only crate data lines start with `"`*.
|
2021-02-13 23:17:38 -08:00
|
|
|
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() {
|
2023-08-14 22:25:32 +02:00
|
|
|
let prefix = format!("\"{krate}\"");
|
2021-02-13 23:17:38 -08:00
|
|
|
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;
|
|
|
|
|
2023-01-04 20:56:46 +00:00
|
|
|
#[derive(Debug, Default)]
|
2021-02-13 23:17:38 -08:00
|
|
|
struct Hierarchy {
|
2023-01-04 20:56:46 +00:00
|
|
|
parent: Weak<Self>,
|
2021-02-13 23:17:38 -08:00
|
|
|
elem: OsString,
|
2023-01-04 20:56:46 +00:00
|
|
|
children: RefCell<FxHashMap<OsString, Rc<Self>>>,
|
|
|
|
elems: RefCell<FxHashSet<OsString>>,
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Hierarchy {
|
2023-01-04 20:56:46 +00:00
|
|
|
fn with_parent(elem: OsString, parent: &Rc<Self>) -> Self {
|
|
|
|
Self { elem, parent: Rc::downgrade(parent), ..Self::default() }
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn to_json_string(&self) -> String {
|
2023-01-04 20:56:46 +00:00
|
|
|
let borrow = self.children.borrow();
|
|
|
|
let mut subs: Vec<_> = borrow.values().collect();
|
2021-02-13 23:17:38 -08:00
|
|
|
subs.sort_unstable_by(|a, b| a.elem.cmp(&b.elem));
|
|
|
|
let mut files = self
|
|
|
|
.elems
|
2023-01-04 20:56:46 +00:00
|
|
|
.borrow()
|
2021-02-13 23:17:38 -08:00
|
|
|
.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(",");
|
2022-08-02 13:24:34 -07:00
|
|
|
let dirs = if subs.is_empty() && files.is_empty() {
|
|
|
|
String::new()
|
|
|
|
} else {
|
2023-08-14 22:25:32 +02:00
|
|
|
format!(",[{subs}]")
|
2022-08-02 13:24:34 -07:00
|
|
|
};
|
2021-02-13 23:17:38 -08:00
|
|
|
let files = files.join(",");
|
2023-08-14 22:25:32 +02:00
|
|
|
let files = if files.is_empty() { String::new() } else { format!(",[{files}]") };
|
2021-02-13 23:17:38 -08:00
|
|
|
format!(
|
2022-08-02 13:24:34 -07:00
|
|
|
"[\"{name}\"{dirs}{files}]",
|
2021-02-13 23:17:38 -08:00
|
|
|
name = self.elem.to_str().expect("invalid osstring conversion"),
|
|
|
|
dirs = dirs,
|
|
|
|
files = files
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-01-04 20:56:46 +00:00
|
|
|
fn add_path(self: &Rc<Self>, path: &Path) {
|
|
|
|
let mut h = Rc::clone(&self);
|
|
|
|
let mut elems = path
|
2021-02-13 23:17:38 -08:00
|
|
|
.components()
|
|
|
|
.filter_map(|s| match s {
|
|
|
|
Component::Normal(s) => Some(s.to_owned()),
|
2023-01-04 20:56:46 +00:00
|
|
|
Component::ParentDir => Some(OsString::from("..")),
|
2021-02-13 23:17:38 -08:00
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.peekable();
|
|
|
|
loop {
|
|
|
|
let cur_elem = elems.next().expect("empty file path");
|
2023-01-04 20:56:46 +00:00
|
|
|
if cur_elem == ".." {
|
|
|
|
if let Some(parent) = h.parent.upgrade() {
|
|
|
|
h = parent;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2021-02-13 23:17:38 -08:00
|
|
|
if elems.peek().is_none() {
|
2023-01-04 20:56:46 +00:00
|
|
|
h.elems.borrow_mut().insert(cur_elem);
|
2021-02-13 23:17:38 -08:00
|
|
|
break;
|
|
|
|
} else {
|
2023-01-04 20:56:46 +00:00
|
|
|
let entry = Rc::clone(
|
|
|
|
h.children
|
|
|
|
.borrow_mut()
|
|
|
|
.entry(cur_elem.clone())
|
|
|
|
.or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))),
|
|
|
|
);
|
|
|
|
h = entry;
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 20:56:46 +00:00
|
|
|
}
|
2021-02-13 23:17:38 -08:00
|
|
|
|
2023-01-04 20:56:46 +00:00
|
|
|
if cx.include_sources {
|
|
|
|
let hierarchy = Rc::new(Hierarchy::default());
|
|
|
|
for source in cx
|
|
|
|
.shared
|
|
|
|
.local_sources
|
|
|
|
.iter()
|
|
|
|
.filter_map(|p| p.0.strip_prefix(&cx.shared.src_root).ok())
|
|
|
|
{
|
|
|
|
hierarchy.add_path(source);
|
|
|
|
}
|
|
|
|
let hierarchy = Rc::try_unwrap(hierarchy).unwrap();
|
2023-07-14 16:46:46 -07:00
|
|
|
let dst = cx.dst.join(&format!("src-files{}.js", cx.shared.resource_suffix));
|
2021-03-31 11:13:51 -04:00
|
|
|
let make_sources = || {
|
|
|
|
let (mut all_sources, _krates) =
|
2022-08-02 13:24:34 -07:00
|
|
|
try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst);
|
2021-03-31 11:13:51 -04:00
|
|
|
all_sources.push(format!(
|
2022-08-02 13:24:34 -07:00
|
|
|
r#""{}":{}"#,
|
2021-10-28 20:55:02 -07:00
|
|
|
&krate.name(cx.tcx()),
|
2022-08-02 13:24:34 -07:00
|
|
|
hierarchy
|
|
|
|
.to_json_string()
|
|
|
|
// All these `replace` calls are because we have to go through JS string for JSON content.
|
|
|
|
.replace('\\', r"\\")
|
|
|
|
.replace('\'', r"\'")
|
|
|
|
// We need to escape double quotes for the JSON.
|
|
|
|
.replace("\\\"", "\\\\\"")
|
2021-03-31 11:13:51 -04:00
|
|
|
));
|
|
|
|
all_sources.sort();
|
2023-07-14 16:46:46 -07:00
|
|
|
let mut v = String::from("var srcIndex = JSON.parse('{\\\n");
|
2022-08-02 13:24:34 -07:00
|
|
|
v.push_str(&all_sources.join(",\\\n"));
|
2023-07-14 16:46:46 -07:00
|
|
|
v.push_str("\\\n}');\ncreateSrcSidebar();\n");
|
2022-08-02 13:24:34 -07:00
|
|
|
Ok(v.into_bytes())
|
2021-03-31 11:13:51 -04:00
|
|
|
};
|
2023-07-14 16:46:46 -07:00
|
|
|
write_invocation_specific("src-files.js", &make_sources)?;
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update the search index and crate list.
|
|
|
|
let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
|
2021-10-28 20:55:02 -07:00
|
|
|
let (mut all_indexes, mut krates) =
|
2021-12-15 14:39:23 +11:00
|
|
|
try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst);
|
2021-02-13 23:17:38 -08:00
|
|
|
all_indexes.push(search_index);
|
2021-10-28 20:55:02 -07:00
|
|
|
krates.push(krate.name(cx.tcx()).to_string());
|
2021-02-13 23:17:38 -08:00
|
|
|
krates.sort();
|
|
|
|
|
|
|
|
// Sort the indexes by crate so the file will be generated identically even
|
|
|
|
// with rustdoc running in parallel.
|
|
|
|
all_indexes.sort();
|
2022-10-24 01:28:55 -07:00
|
|
|
write_invocation_specific("search-index.js", &|| {
|
2021-02-13 23:17:38 -08:00
|
|
|
let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
|
|
|
|
v.push_str(&all_indexes.join(",\\\n"));
|
2022-05-15 21:09:55 -07:00
|
|
|
v.push_str(
|
|
|
|
r#"\
|
|
|
|
}');
|
|
|
|
if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)};
|
|
|
|
if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex};
|
|
|
|
"#,
|
|
|
|
);
|
2021-03-31 11:13:51 -04:00
|
|
|
Ok(v.into_bytes())
|
|
|
|
})?;
|
2021-02-13 23:17:38 -08:00
|
|
|
|
2022-10-24 01:28:55 -07:00
|
|
|
write_invocation_specific("crates.js", &|| {
|
2023-08-14 22:25:32 +02:00
|
|
|
let krates = krates.iter().map(|k| format!("\"{k}\"")).join(",");
|
|
|
|
Ok(format!("window.ALL_CRATES = [{krates}];").into_bytes())
|
2021-03-31 11:13:51 -04:00
|
|
|
})?;
|
2021-02-13 23:17:38 -08:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
2021-04-22 19:38:20 -04:00
|
|
|
crate::markdown::render(&index_page, md_opts, cx.shared.edition())
|
2021-02-13 23:17:38 -08:00
|
|
|
.map_err(|e| Error::new(e, &index_page))?;
|
|
|
|
} else {
|
2022-05-26 20:18:00 +02:00
|
|
|
let shared = Rc::clone(&cx.shared);
|
2021-02-13 23:17:38 -08:00
|
|
|
let dst = cx.dst.join("index.html");
|
|
|
|
let page = layout::Page {
|
|
|
|
title: "Index of crates",
|
2023-09-23 12:59:58 -07:00
|
|
|
css_class: "mod sys",
|
2021-02-13 23:17:38 -08:00
|
|
|
root_path: "./",
|
2022-05-26 20:18:00 +02:00
|
|
|
static_root_path: shared.static_root_path.as_deref(),
|
2021-02-13 23:17:38 -08:00
|
|
|
description: "List of crates",
|
2022-05-26 20:18:00 +02:00
|
|
|
resource_suffix: &shared.resource_suffix,
|
2023-09-19 16:28:18 -07:00
|
|
|
rust_logo: true,
|
2021-02-13 23:17:38 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
let content = format!(
|
2023-01-13 10:09:25 -07:00
|
|
|
"<h1>List of all crates</h1><ul class=\"all-items\">{}</ul>",
|
2021-02-13 23:17:38 -08:00
|
|
|
krates
|
|
|
|
.iter()
|
|
|
|
.map(|s| {
|
|
|
|
format!(
|
2023-08-15 14:27:29 +02:00
|
|
|
"<li><a href=\"{trailing_slash}index.html\">{s}</a></li>",
|
|
|
|
trailing_slash = ensure_trailing_slash(s),
|
2021-02-13 23:17:38 -08:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<String>()
|
|
|
|
);
|
2022-05-26 20:18:00 +02:00
|
|
|
let v = layout::render(&shared.layout, &page, "", content, &shared.style_files);
|
|
|
|
shared.fs.write(dst, v)?;
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the list of all implementors for traits
|
2023-10-05 18:44:40 -07:00
|
|
|
let dst = cx.dst.join("trait.impl");
|
2021-08-22 03:36:37 +00:00
|
|
|
let cache = cx.cache();
|
|
|
|
for (&did, imps) in &cache.implementors {
|
2021-02-13 23:17:38 -08:00
|
|
|
// 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...
|
2022-05-05 17:20:14 -07:00
|
|
|
let (remote_path, remote_item_type) = match cache.exact_paths.get(&did) {
|
|
|
|
Some(p) => match cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) {
|
|
|
|
Some((_, t)) => (p, t),
|
|
|
|
None => continue,
|
|
|
|
},
|
2021-08-22 03:36:37 +00:00
|
|
|
None => match cache.external_paths.get(&did) {
|
2022-05-05 17:20:14 -07:00
|
|
|
Some((p, t)) => (p, t),
|
2021-02-13 23:17:38 -08:00
|
|
|
None => continue,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
struct Implementor {
|
|
|
|
text: String,
|
|
|
|
synthetic: bool,
|
|
|
|
types: Vec<String>,
|
|
|
|
}
|
|
|
|
|
2022-08-05 16:36:47 -07:00
|
|
|
impl Serialize for Implementor {
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
|
|
|
let mut seq = serializer.serialize_seq(None)?;
|
|
|
|
seq.serialize_element(&self.text)?;
|
|
|
|
if self.synthetic {
|
|
|
|
seq.serialize_element(&1)?;
|
|
|
|
seq.serialize_element(&self.types)?;
|
2022-08-04 12:13:16 -07:00
|
|
|
}
|
2022-08-05 16:36:47 -07:00
|
|
|
seq.end()
|
2022-08-04 12:13:16 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 23:17:38 -08:00
|
|
|
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.
|
2022-04-16 14:28:09 +02:00
|
|
|
if imp.impl_item.item_id.krate() == did.krate || !imp.impl_item.item_id.is_local() {
|
2021-02-13 23:17:38 -08:00
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(Implementor {
|
2021-03-17 11:41:01 -07:00
|
|
|
text: imp.inner_impl().print(false, cx).to_string(),
|
2021-11-07 18:26:37 -08:00
|
|
|
synthetic: imp.inner_impl().kind.is_auto(),
|
2021-08-22 03:36:37 +00:00
|
|
|
types: collect_paths_for_type(imp.inner_impl().for_.clone(), cache),
|
2021-02-13 23:17:38 -08:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.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.
|
2021-08-22 03:36:37 +00:00
|
|
|
if implementors.is_empty() && !cache.paths.contains_key(&did) {
|
2021-02-13 23:17:38 -08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let implementors = format!(
|
2022-08-05 16:36:47 -07:00
|
|
|
r#""{}":{}"#,
|
2021-10-28 20:55:02 -07:00
|
|
|
krate.name(cx.tcx()),
|
2022-08-05 16:36:47 -07:00
|
|
|
serde_json::to_string(&implementors).expect("failed serde conversion"),
|
2021-02-13 23:17:38 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
let mut mydst = dst.clone();
|
|
|
|
for part in &remote_path[..remote_path.len() - 1] {
|
2021-12-15 06:18:18 +11:00
|
|
|
mydst.push(part.to_string());
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
|
|
|
cx.shared.ensure_dir(&mydst)?;
|
2023-08-14 22:25:32 +02:00
|
|
|
mydst.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
|
2021-02-13 23:17:38 -08:00
|
|
|
|
|
|
|
let (mut all_implementors, _) =
|
2022-08-04 12:13:16 -07:00
|
|
|
try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst);
|
2021-02-13 23:17:38 -08:00
|
|
|
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();
|
|
|
|
|
2022-08-04 12:13:16 -07:00
|
|
|
let mut v = String::from("(function() {var implementors = {\n");
|
|
|
|
v.push_str(&all_implementors.join(",\n"));
|
|
|
|
v.push_str("\n};");
|
2021-02-13 23:17:38 -08:00
|
|
|
v.push_str(
|
|
|
|
"if (window.register_implementors) {\
|
|
|
|
window.register_implementors(implementors);\
|
|
|
|
} else {\
|
|
|
|
window.pending_implementors = implementors;\
|
|
|
|
}",
|
|
|
|
);
|
|
|
|
v.push_str("})()");
|
2021-08-22 02:25:25 +00:00
|
|
|
cx.shared.fs.write(mydst, v)?;
|
2021-02-13 23:17:38 -08:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|