about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicholas-Baron <nicholas.baron.ten@gmail.com>2021-02-13 23:17:38 -0800
committerNicholas-Baron <nicholas.baron.ten@gmail.com>2021-03-04 10:24:56 -0800
commitfd14e386123f96f27f9d2cf12f64a6e060b6a079 (patch)
treec63f300456bc2f37f74389de7fb6cc3ee056cea1
parent14983b9812e67a283587b651c0a14e1d4e1ab723 (diff)
downloadrust-fd14e386123f96f27f9d2cf12f64a6e060b6a079.tar.gz
rust-fd14e386123f96f27f9d2cf12f64a6e060b6a079.zip
Moved `write_shared` to its own file
-rw-r--r--src/librustdoc/html/render/context.rs1
-rw-r--r--src/librustdoc/html/render/mod.rs541
-rw-r--r--src/librustdoc/html/render/write_shared.rs542
3 files changed, 549 insertions, 535 deletions
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<String>);
@@ -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<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
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<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())
+    }
+}