From fd14e386123f96f27f9d2cf12f64a6e060b6a079 Mon Sep 17 00:00:00 2001 From: Nicholas-Baron Date: Sat, 13 Feb 2021 23:17:38 -0800 Subject: [PATCH] Moved `write_shared` to its own file --- src/librustdoc/html/render/context.rs | 1 + src/librustdoc/html/render/mod.rs | 541 +------------------- src/librustdoc/html/render/write_shared.rs | 542 +++++++++++++++++++++ 3 files changed, 549 insertions(+), 535 deletions(-) create mode 100644 src/librustdoc/html/render/write_shared.rs 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()) + } +}