// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Rustdoc's HTML Rendering module //! //! This modules contains the bulk of the logic necessary for rendering a //! rustdoc `clean::Crate` instance to a set of static HTML pages. This //! rendering process is largely driven by the `format!` syntax extension to //! perform all I/O into files and streams. //! //! The rendering process is largely driven by the `Context` and `Cache` //! structures. The cache is pre-populated by crawling the crate in question, //! and then it is shared among the various rendering threads. The cache is meant //! to be a fairly large structure not implementing `Clone` (because it's shared //! among threads). The context, however, should be a lightweight structure. This //! is cloned per-thread and contains information about what is currently being //! rendered. //! //! In order to speed up rendering (mostly because of markdown rendering), the //! rendering process has been parallelized. This parallelization is only //! exposed through the `crate` method on the context, and then also from the //! fact that the shared cache is stored in TLS (and must be accessed as such). //! //! In addition to rendering the crate itself, this module is also responsible //! for creating the corresponding search index and source file renderings. //! These threads are not parallelized (they haven't been a bottleneck yet), and //! both occur before the crate is rendered. pub use self::ExternalLocation::*; use std::borrow::Cow; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::{BTreeMap, HashSet, VecDeque}; use std::default::Default; use std::error; use std::fmt::{self, Display, Formatter, Write as FmtWrite}; use std::fs::{self, File, OpenOptions}; use std::io::prelude::*; use std::io::{self, BufWriter, BufReader}; use std::iter::repeat; use std::mem; use std::path::{PathBuf, Path, Component}; use std::str; use std::sync::Arc; use externalfiles::ExternalHtml; use serialize::json::{ToJson, Json, as_json}; use syntax::{abi, ast}; use syntax::codemap::FileName; use rustc::hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefId}; use rustc::middle::privacy::AccessLevels; use rustc::middle::stability; use rustc::hir; use rustc::util::nodemap::{FxHashMap, FxHashSet}; use rustc_data_structures::flock; use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability}; use doctree; use fold::DocFolder; use html::escape::Escape; use html::format::{ConstnessSpace}; use html::format::{TyParamBounds, WhereClause, href, AbiSpace}; use html::format::{VisSpace, Method, UnsafetySpace, MutableSpace}; use html::format::fmt_impl_for_trait_page; use html::item_type::ItemType; use html::markdown::{self, Markdown, MarkdownHtml, MarkdownSummaryLine}; use html::{highlight, layout}; /// A pair of name and its optional document. pub type NameDoc = (String, Option); /// Major driving force in all rustdoc rendering. This contains information /// about where in the tree-like hierarchy rendering is occurring and controls /// how the current page is being rendered. /// /// It is intended that this context is a lightweight object which can be fairly /// easily cloned because it is cloned per work-job (about once per item in the /// rustdoc tree). #[derive(Clone)] pub struct Context { /// Current hierarchy of components leading down to what's currently being /// rendered pub current: Vec, /// The current destination folder of where HTML artifacts should be placed. /// This changes as the context descends into the module hierarchy. pub dst: PathBuf, /// A flag, which when `true`, will render pages which redirect to the /// real location of an item. This is used to allow external links to /// publicly reused items to redirect to the right location. pub render_redirect_pages: bool, pub shared: Arc, } pub struct SharedContext { /// The path to the crate root source minus the file name. /// Used for simplifying paths to the highlighted source code files. pub src_root: PathBuf, /// This describes the layout of each page, and is not modified after /// creation of the context (contains info like the favicon and added html). pub layout: layout::Layout, /// This flag indicates whether `[src]` links should be generated or not. If /// the source files are present in the html rendering, then this will be /// `true`. pub include_sources: bool, /// The local file sources we've emitted and their respective url-paths. pub local_sources: FxHashMap, /// All the passes that were run on this crate. pub passes: FxHashSet, /// The base-URL of the issue tracker for when an item has been tagged with /// an issue number. pub issue_tracker_base_url: Option, /// The given user css file which allow to customize the generated /// documentation theme. pub css_file_extension: Option, /// The directories that have already been created in this doc run. Used to reduce the number /// of spurious `create_dir_all` calls. pub created_dirs: RefCell>, /// This flag indicates whether listings of modules (in the side bar and documentation itself) /// should be ordered alphabetically or in order of appearance (in the source code). pub sort_modules_alphabetically: bool, /// Additional themes to be added to the generated docs. pub themes: Vec, /// Suffix to be added on resource files (if suffix is "-v2" then "light.css" becomes /// "light-v2.css"). pub resource_suffix: String, } impl SharedContext { fn ensure_dir(&self, dst: &Path) -> io::Result<()> { let mut dirs = self.created_dirs.borrow_mut(); if !dirs.contains(dst) { fs::create_dir_all(dst)?; dirs.insert(dst.to_path_buf()); } Ok(()) } } impl SharedContext { /// Returns whether the `collapse-docs` pass was run on this crate. pub fn was_collapsed(&self) -> bool { self.passes.contains("collapse-docs") } /// Based on whether the `collapse-docs` pass was run, return either the `doc_value` or the /// `collapsed_doc_value` of the given item. pub fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option> { if self.was_collapsed() { item.collapsed_doc_value().map(|s| s.into()) } else { item.doc_value().map(|s| s.into()) } } } /// Indicates where an external crate can be found. pub enum ExternalLocation { /// Remote URL root of the external crate Remote(String), /// This external crate can be found in the local doc/ folder Local, /// The external crate could not be found. Unknown, } /// Metadata about implementations for a type or trait. #[derive(Clone)] pub struct Impl { pub impl_item: clean::Item, } impl Impl { fn inner_impl(&self) -> &clean::Impl { match self.impl_item.inner { clean::ImplItem(ref impl_) => impl_, _ => panic!("non-impl item found in impl") } } fn trait_did(&self) -> Option { self.inner_impl().trait_.def_id() } } #[derive(Debug)] pub struct Error { file: PathBuf, error: io::Error, } impl error::Error for Error { fn description(&self) -> &str { self.error.description() } } impl Display for Error { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "\"{}\": {}", self.file.display(), self.error) } } impl Error { pub fn new(e: io::Error, file: &Path) -> Error { Error { file: file.to_path_buf(), error: e, } } } macro_rules! try_none { ($e:expr, $file:expr) => ({ use std::io; match $e { Some(e) => e, None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"), $file)) } }) } macro_rules! try_err { ($e:expr, $file:expr) => ({ match $e { Ok(e) => e, Err(e) => return Err(Error::new(e, $file)), } }) } /// This cache is used to store information about the `clean::Crate` being /// rendered in order to provide more useful documentation. This contains /// information like all implementors of a trait, all traits a type implements, /// documentation for all known traits, etc. /// /// This structure purposefully does not implement `Clone` because it's intended /// to be a fairly large and expensive structure to clone. Instead this adheres /// to `Send` so it may be stored in a `Arc` instance and shared among the various /// rendering threads. #[derive(Default)] pub struct Cache { /// Mapping of typaram ids to the name of the type parameter. This is used /// when pretty-printing a type (so pretty printing doesn't have to /// painfully maintain a context like this) pub typarams: FxHashMap, /// Maps a type id to all known implementations for that type. This is only /// recognized for intra-crate `ResolvedPath` types, and is used to print /// out extra documentation on the page of an enum/struct. /// /// The values of the map are a list of implementations and documentation /// found on that implementation. pub impls: FxHashMap>, /// Maintains a mapping of local crate node ids to the fully qualified name /// and "short type description" of that node. This is used when generating /// URLs when a type is being linked to. External paths are not located in /// this map because the `External` type itself has all the information /// necessary. pub paths: FxHashMap, ItemType)>, /// Similar to `paths`, but only holds external paths. This is only used for /// generating explicit hyperlinks to other crates. pub external_paths: FxHashMap, ItemType)>, /// Maps local def ids of exported types to fully qualified paths. /// Unlike 'paths', this mapping ignores any renames that occur /// due to 'use' statements. /// /// This map is used when writing out the special 'implementors' /// javascript file. By using the exact path that the type /// is declared with, we ensure that each path will be identical /// to the path used if the corresponding type is inlined. By /// doing this, we can detect duplicate impls on a trait page, and only display /// the impl for the inlined type. pub exact_paths: FxHashMap>, /// This map contains information about all known traits of this crate. /// Implementations of a crate should inherit the documentation of the /// parent trait if no extra documentation is specified, and default methods /// should show up in documentation about trait implementations. pub traits: FxHashMap, /// When rendering traits, it's often useful to be able to list all /// implementors of the trait, and this mapping is exactly, that: a mapping /// of trait ids to the list of known implementors of the trait pub implementors: FxHashMap>, /// Cache of where external crate documentation can be found. pub extern_locations: FxHashMap, /// Cache of where documentation for primitives can be found. pub primitive_locations: FxHashMap, // Note that external items for which `doc(hidden)` applies to are shown as // non-reachable while local items aren't. This is because we're reusing // the access levels from crateanalysis. pub access_levels: Arc>, /// The version of the crate being documented, if given fron the `--crate-version` flag. pub crate_version: Option, // Private fields only used when initially crawling a crate to build a cache stack: Vec, parent_stack: Vec, parent_is_trait_impl: bool, search_index: Vec, stripped_mod: bool, deref_trait_did: Option, deref_mut_trait_did: Option, owned_box_did: Option, masked_crates: FxHashSet, // In rare case where a structure is defined in one module but implemented // in another, if the implementing module is parsed before defining module, // then the fully qualified name of the structure isn't presented in `paths` // yet when its implementation methods are being indexed. Caches such methods // and their parent id here and indexes them at the end of crate parsing. orphan_impl_items: Vec<(DefId, clean::Item)>, } /// Temporary storage for data obtained during `RustdocVisitor::clean()`. /// Later on moved into `CACHE_KEY`. #[derive(Default)] pub struct RenderInfo { pub inlined: FxHashSet, pub external_paths: ::core::ExternalPaths, pub external_typarams: FxHashMap, pub exact_paths: FxHashMap>, pub deref_trait_did: Option, pub deref_mut_trait_did: Option, pub owned_box_did: Option, } /// Helper struct to render all source code to HTML pages struct SourceCollector<'a> { scx: &'a mut SharedContext, /// Root destination to place all HTML output into dst: PathBuf, } /// Wrapper struct to render the source code of a file. This will do things like /// adding line numbers to the left-hand side. struct Source<'a>(&'a str); // Helper structs for rendering items/sidebars and carrying along contextual // information #[derive(Copy, Clone)] struct Item<'a> { cx: &'a Context, item: &'a clean::Item, } struct Sidebar<'a> { cx: &'a Context, item: &'a clean::Item, } /// Struct representing one entry in the JS search index. These are all emitted /// by hand to a large JS file at the end of cache-creation. struct IndexItem { ty: ItemType, name: String, path: String, desc: String, parent: Option, parent_idx: Option, search_type: Option, } impl ToJson for IndexItem { fn to_json(&self) -> Json { assert_eq!(self.parent.is_some(), self.parent_idx.is_some()); let mut data = Vec::with_capacity(6); data.push((self.ty as usize).to_json()); data.push(self.name.to_json()); data.push(self.path.to_json()); data.push(self.desc.to_json()); data.push(self.parent_idx.to_json()); data.push(self.search_type.to_json()); Json::Array(data) } } /// A type used for the search index. struct Type { name: Option, generics: Option>, } impl ToJson for Type { fn to_json(&self) -> Json { match self.name { Some(ref name) => { let mut data = BTreeMap::new(); data.insert("name".to_owned(), name.to_json()); if let Some(ref generics) = self.generics { data.insert("generics".to_owned(), generics.to_json()); } Json::Object(data) }, None => Json::Null } } } /// Full type of functions/methods in the search index. struct IndexItemFunctionType { inputs: Vec, output: Option } impl ToJson for IndexItemFunctionType { fn to_json(&self) -> Json { // If we couldn't figure out a type, just write `null`. if self.inputs.iter().chain(self.output.iter()).any(|ref i| i.name.is_none()) { Json::Null } else { let mut data = BTreeMap::new(); data.insert("inputs".to_owned(), self.inputs.to_json()); data.insert("output".to_owned(), self.output.to_json()); Json::Object(data) } } } thread_local!(static CACHE_KEY: RefCell> = Default::default()); thread_local!(pub static CURRENT_LOCATION_KEY: RefCell> = RefCell::new(Vec::new())); thread_local!(pub static USED_ID_MAP: RefCell> = RefCell::new(init_ids())); fn init_ids() -> FxHashMap { [ "main", "search", "help", "TOC", "render-detail", "associated-types", "associated-const", "required-methods", "provided-methods", "implementors", "synthetic-implementors", "implementors-list", "synthetic-implementors-list", "methods", "deref-methods", "implementations", ].into_iter().map(|id| (String::from(*id), 1)).collect() } /// This method resets the local table of used ID attributes. This is typically /// used at the beginning of rendering an entire HTML page to reset from the /// previous state (if any). pub fn reset_ids(embedded: bool) { USED_ID_MAP.with(|s| { *s.borrow_mut() = if embedded { init_ids() } else { FxHashMap() }; }); } pub fn derive_id(candidate: String) -> String { USED_ID_MAP.with(|map| { let id = match map.borrow_mut().get_mut(&candidate) { None => candidate, Some(a) => { let id = format!("{}-{}", candidate, *a); *a += 1; id } }; map.borrow_mut().insert(id.clone(), 1); id }) } /// Generates the documentation for `crate` into the directory `dst` pub fn run(mut krate: clean::Crate, external_html: &ExternalHtml, playground_url: Option, dst: PathBuf, resource_suffix: String, passes: FxHashSet, css_file_extension: Option, renderinfo: RenderInfo, sort_modules_alphabetically: bool, themes: Vec) -> Result<(), Error> { let src_root = match krate.src { FileName::Real(ref p) => match p.parent() { Some(p) => p.to_path_buf(), None => PathBuf::new(), }, _ => PathBuf::new(), }; let mut scx = SharedContext { src_root, passes, include_sources: true, local_sources: FxHashMap(), issue_tracker_base_url: None, layout: layout::Layout { logo: "".to_string(), favicon: "".to_string(), external_html: external_html.clone(), krate: krate.name.clone(), }, css_file_extension: css_file_extension.clone(), created_dirs: RefCell::new(FxHashSet()), sort_modules_alphabetically, themes, resource_suffix, }; // If user passed in `--playground-url` arg, we fill in crate name here if let Some(url) = playground_url { markdown::PLAYGROUND.with(|slot| { *slot.borrow_mut() = Some((Some(krate.name.clone()), url)); }); } // Crawl the crate attributes looking for attributes which control how we're // going to emit HTML if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) { for attr in attrs.lists("doc") { let name = attr.name().map(|s| s.as_str()); match (name.as_ref().map(|s| &s[..]), attr.value_str()) { (Some("html_favicon_url"), Some(s)) => { scx.layout.favicon = s.to_string(); } (Some("html_logo_url"), Some(s)) => { scx.layout.logo = s.to_string(); } (Some("html_playground_url"), Some(s)) => { markdown::PLAYGROUND.with(|slot| { let name = krate.name.clone(); *slot.borrow_mut() = Some((Some(name), s.to_string())); }); } (Some("issue_tracker_base_url"), Some(s)) => { scx.issue_tracker_base_url = Some(s.to_string()); } (Some("html_no_source"), None) if attr.is_word() => { scx.include_sources = false; } _ => {} } } } try_err!(fs::create_dir_all(&dst), &dst); krate = render_sources(&dst, &mut scx, krate)?; let cx = Context { current: Vec::new(), dst, render_redirect_pages: false, shared: Arc::new(scx), }; // Crawl the crate to build various caches used for the output let RenderInfo { inlined: _, external_paths, external_typarams, exact_paths, deref_trait_did, deref_mut_trait_did, owned_box_did, } = renderinfo; let external_paths = external_paths.into_iter() .map(|(k, (v, t))| (k, (v, ItemType::from(t)))) .collect(); let mut cache = Cache { impls: FxHashMap(), external_paths, exact_paths, paths: FxHashMap(), implementors: FxHashMap(), stack: Vec::new(), parent_stack: Vec::new(), search_index: Vec::new(), parent_is_trait_impl: false, extern_locations: FxHashMap(), primitive_locations: FxHashMap(), stripped_mod: false, access_levels: krate.access_levels.clone(), crate_version: krate.version.take(), orphan_impl_items: Vec::new(), traits: mem::replace(&mut krate.external_traits, FxHashMap()), deref_trait_did, deref_mut_trait_did, owned_box_did, masked_crates: mem::replace(&mut krate.masked_crates, FxHashSet()), typarams: external_typarams, }; // Cache where all our extern crates are located for &(n, ref e) in &krate.externs { let src_root = match e.src { FileName::Real(ref p) => match p.parent() { Some(p) => p.to_path_buf(), None => PathBuf::new(), }, _ => PathBuf::new(), }; cache.extern_locations.insert(n, (e.name.clone(), src_root, extern_location(e, &cx.dst))); let did = DefId { krate: n, index: CRATE_DEF_INDEX }; cache.external_paths.insert(did, (vec![e.name.to_string()], ItemType::Module)); } // Cache where all known primitives have their documentation located. // // Favor linking to as local extern as possible, so iterate all crates in // reverse topological order. for &(_, ref e) in krate.externs.iter().rev() { for &(def_id, prim, _) in &e.primitives { cache.primitive_locations.insert(prim, def_id); } } for &(def_id, prim, _) in &krate.primitives { cache.primitive_locations.insert(prim, def_id); } cache.stack.push(krate.name.clone()); krate = cache.fold_crate(krate); // Build our search index let index = build_index(&krate, &mut cache); // Freeze the cache now that the index has been built. Put an Arc into TLS // for future parallelization opportunities let cache = Arc::new(cache); CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear()); write_shared(&cx, &krate, &*cache, index)?; // And finally render the whole crate's documentation cx.krate(krate) } /// Build the search index from the collected metadata fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { let mut nodeid_to_pathid = FxHashMap(); let mut crate_items = Vec::with_capacity(cache.search_index.len()); let mut crate_paths = Vec::::new(); let Cache { ref mut search_index, ref orphan_impl_items, ref mut paths, .. } = *cache; // Attach all orphan items to the type's definition if the type // has since been learned. for &(did, ref item) in orphan_impl_items { if let Some(&(ref fqp, _)) = paths.get(&did) { search_index.push(IndexItem { ty: item.type_(), name: item.name.clone().unwrap(), path: fqp[..fqp.len() - 1].join("::"), desc: plain_summary_line(item.doc_value()), parent: Some(did), parent_idx: None, search_type: get_index_search_type(&item), }); } } // Reduce `NodeId` in paths into smaller sequential numbers, // and prune the paths that do not appear in the index. let mut lastpath = String::new(); let mut lastpathid = 0usize; for item in search_index { item.parent_idx = item.parent.map(|nodeid| { if nodeid_to_pathid.contains_key(&nodeid) { *nodeid_to_pathid.get(&nodeid).unwrap() } else { let pathid = lastpathid; nodeid_to_pathid.insert(nodeid, pathid); lastpathid += 1; let &(ref fqp, short) = paths.get(&nodeid).unwrap(); crate_paths.push(((short as usize), fqp.last().unwrap().clone()).to_json()); pathid } }); // Omit the parent path if it is same to that of the prior item. if lastpath == item.path { item.path.clear(); } else { lastpath = item.path.clone(); } crate_items.push(item.to_json()); } let crate_doc = krate.module.as_ref().map(|module| { plain_summary_line(module.doc_value()) }).unwrap_or(String::new()); let mut crate_data = BTreeMap::new(); crate_data.insert("doc".to_owned(), Json::String(crate_doc)); crate_data.insert("items".to_owned(), Json::Array(crate_items)); crate_data.insert("paths".to_owned(), Json::Array(crate_paths)); // Collect the index into a string format!("searchIndex[{}] = {};", as_json(&krate.name), Json::Object(crate_data)) } fn write_shared(cx: &Context, krate: &clean::Crate, cache: &Cache, search_index: String) -> 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 = flock::Lock::panicking_new(&cx.dst.join(".lock"), true, true, true); // 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(cx.dst.join(&format!("rustdoc{}.css", cx.shared.resource_suffix)), include_bytes!("static/rustdoc.css"))?; write(cx.dst.join(&format!("settings{}.css", cx.shared.resource_suffix)), include_bytes!("static/settings.css"))?; // 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: HashSet = HashSet::new(); for entry in &cx.shared.themes { let mut content = Vec::with_capacity(100000); let mut f = try_err!(File::open(&entry), &entry); try_err!(f.read_to_end(&mut content), &entry); let theme = try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry); let extension = try_none!(try_none!(entry.extension(), &entry).to_str(), &entry); write(cx.dst.join(format!("{}{}.{}", theme, cx.shared.resource_suffix, extension)), content.as_slice())?; themes.insert(theme.to_owned()); } write(cx.dst.join(&format!("brush{}.svg", cx.shared.resource_suffix)), include_bytes!("static/brush.svg"))?; write(cx.dst.join(&format!("wheel{}.svg", cx.shared.resource_suffix)), include_bytes!("static/wheel.svg"))?; write(cx.dst.join(&format!("light{}.css", cx.shared.resource_suffix)), include_bytes!("static/themes/light.css"))?; themes.insert("light".to_owned()); write(cx.dst.join(&format!("dark{}.css", cx.shared.resource_suffix)), include_bytes!("static/themes/dark.css"))?; themes.insert("dark".to_owned()); 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. write(cx.dst.join(&format!("theme{}.js", cx.shared.resource_suffix)), format!( r#"var themes = document.getElementById("theme-choices"); var themePicker = document.getElementById("theme-picker"); themePicker.onclick = function() {{ if (themes.style.display === "block") {{ themes.style.display = "none"; themePicker.style.borderBottomRightRadius = "3px"; themePicker.style.borderBottomLeftRadius = "3px"; }} else {{ themes.style.display = "block"; themePicker.style.borderBottomRightRadius = "0"; themePicker.style.borderBottomLeftRadius = "0"; }} }}; [{}].forEach(function(item) {{ var but = document.createElement('button'); but.innerHTML = item; but.onclick = function(el) {{ switchTheme(currentTheme, mainTheme, item); }}; themes.appendChild(but); }});"#, themes.iter() .map(|s| format!("\"{}\"", s)) .collect::>() .join(",")).as_bytes(), )?; write(cx.dst.join(&format!("main{}.js", cx.shared.resource_suffix)), include_bytes!("static/main.js"))?; write(cx.dst.join(&format!("settings{}.js", cx.shared.resource_suffix)), include_bytes!("static/settings.js"))?; { let mut data = format!("var resourcesSuffix = \"{}\";\n", cx.shared.resource_suffix).into_bytes(); data.extend_from_slice(include_bytes!("static/storage.js")); write(cx.dst.join(&format!("storage{}.js", cx.shared.resource_suffix)), &data)?; } if let Some(ref css) = cx.shared.css_file_extension { let out = cx.dst.join(&format!("theme{}.css", cx.shared.resource_suffix)); try_err!(fs::copy(css, out), css); } write(cx.dst.join(&format!("normalize{}.css", cx.shared.resource_suffix)), include_bytes!("static/normalize.css"))?; write(cx.dst.join("FiraSans-Regular.woff"), include_bytes!("static/FiraSans-Regular.woff"))?; write(cx.dst.join("FiraSans-Medium.woff"), include_bytes!("static/FiraSans-Medium.woff"))?; write(cx.dst.join("FiraSans-LICENSE.txt"), include_bytes!("static/FiraSans-LICENSE.txt"))?; write(cx.dst.join("Heuristica-Italic.woff"), include_bytes!("static/Heuristica-Italic.woff"))?; write(cx.dst.join("Heuristica-LICENSE.txt"), include_bytes!("static/Heuristica-LICENSE.txt"))?; write(cx.dst.join("SourceSerifPro-Regular.woff"), include_bytes!("static/SourceSerifPro-Regular.woff"))?; write(cx.dst.join("SourceSerifPro-Bold.woff"), include_bytes!("static/SourceSerifPro-Bold.woff"))?; write(cx.dst.join("SourceSerifPro-LICENSE.txt"), include_bytes!("static/SourceSerifPro-LICENSE.txt"))?; write(cx.dst.join("SourceCodePro-Regular.woff"), include_bytes!("static/SourceCodePro-Regular.woff"))?; write(cx.dst.join("SourceCodePro-Semibold.woff"), include_bytes!("static/SourceCodePro-Semibold.woff"))?; write(cx.dst.join("SourceCodePro-LICENSE.txt"), include_bytes!("static/SourceCodePro-LICENSE.txt"))?; write(cx.dst.join("LICENSE-MIT.txt"), include_bytes!("static/LICENSE-MIT.txt"))?; write(cx.dst.join("LICENSE-APACHE.txt"), include_bytes!("static/LICENSE-APACHE.txt"))?; write(cx.dst.join("COPYRIGHT.txt"), include_bytes!("static/COPYRIGHT.txt"))?; fn collect(path: &Path, krate: &str, key: &str) -> io::Result> { let mut ret = Vec::new(); if path.exists() { for line in BufReader::new(File::open(path)?).lines() { let line = line?; if !line.starts_with(key) { continue; } if line.starts_with(&format!(r#"{}["{}"]"#, key, krate)) { continue; } ret.push(line.to_string()); } } Ok(ret) } // Update the search index let dst = cx.dst.join("search-index.js"); let mut all_indexes = try_err!(collect(&dst, &krate.name, "searchIndex"), &dst); all_indexes.push(search_index); // Sort the indexes by crate so the file will be generated identically even // with rustdoc running in parallel. all_indexes.sort(); let mut w = try_err!(File::create(&dst), &dst); try_err!(writeln!(&mut w, "var searchIndex = {{}};"), &dst); for index in &all_indexes { try_err!(writeln!(&mut w, "{}", *index), &dst); } try_err!(writeln!(&mut w, "initSearch(searchIndex);"), &dst); // Update the list of all implementors for traits let dst = cx.dst.join("implementors"); for (&did, imps) in &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 cache.paths.get(&did) { Some(p) => p, None => match cache.external_paths.get(&did) { Some(p) => p, None => continue, } }; let mut have_impls = false; let mut implementors = format!(r#"implementors["{}"] = ["#, krate.name); for imp in imps { // 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 imp.impl_item.def_id.krate == did.krate { continue } // If the implementation is from another crate then that crate // should add it. if !imp.impl_item.def_id.is_local() { continue } have_impls = true; write!(implementors, "{{text:{},synthetic:{},types:{}}},", as_json(&imp.inner_impl().to_string()), imp.inner_impl().synthetic, as_json(&collect_paths_for_type(imp.inner_impl().for_.clone()))).unwrap(); } implementors.push_str("];"); // 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 !have_impls && !cache.paths.contains_key(&did) { continue; } let mut mydst = dst.clone(); for part in &remote_path[..remote_path.len() - 1] { mydst.push(part); } try_err!(fs::create_dir_all(&mydst), &mydst); mydst.push(&format!("{}.{}.js", remote_item_type.css_class(), remote_path[remote_path.len() - 1])); let mut all_implementors = try_err!(collect(&mydst, &krate.name, "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 f = try_err!(File::create(&mydst), &mydst); try_err!(writeln!(&mut f, "(function() {{var implementors = {{}};"), &mydst); for implementor in &all_implementors { try_err!(writeln!(&mut f, "{}", *implementor), &mydst); } try_err!(writeln!(&mut f, "{}", r" if (window.register_implementors) { window.register_implementors(implementors); } else { window.pending_implementors = implementors; } "), &mydst); try_err!(writeln!(&mut f, r"}})()"), &mydst); } Ok(()) } fn render_sources(dst: &Path, scx: &mut SharedContext, krate: clean::Crate) -> Result { info!("emitting source files"); let dst = dst.join("src").join(&krate.name); try_err!(fs::create_dir_all(&dst), &dst); let mut folder = SourceCollector { dst, scx, }; Ok(folder.fold_crate(krate)) } /// Writes the entire contents of a string to a destination, not attempting to /// catch any errors. fn write(dst: PathBuf, contents: &[u8]) -> Result<(), Error> { Ok(try_err!(fs::write(&dst, contents), &dst)) } /// Takes a path to a source file and cleans the path to it. This canonicalizes /// things like ".." to components which preserve the "top down" hierarchy of a /// static HTML tree. Each component in the cleaned path will be passed as an /// argument to `f`. The very last component of the path (ie the file name) will /// be passed to `f` if `keep_filename` is true, and ignored otherwise. // FIXME (#9639): The closure should deal with &[u8] instead of &str // FIXME (#9639): This is too conservative, rejecting non-UTF-8 paths fn clean_srcpath(src_root: &Path, p: &Path, keep_filename: bool, mut f: F) where F: FnMut(&str), { // make it relative, if possible let p = p.strip_prefix(src_root).unwrap_or(p); let mut iter = p.components().peekable(); while let Some(c) = iter.next() { if !keep_filename && iter.peek().is_none() { break; } match c { Component::ParentDir => f("up"), Component::Normal(c) => f(c.to_str().unwrap()), _ => continue, } } } /// Attempts to find where an external crate is located, given that we're /// rendering in to the specified source destination. fn extern_location(e: &clean::ExternalCrate, dst: &Path) -> ExternalLocation { // See if there's documentation generated into the local directory let local_location = dst.join(&e.name); if local_location.is_dir() { return Local; } // Failing that, see if there's an attribute specifying where to find this // external crate e.attrs.lists("doc") .filter(|a| a.check_name("html_root_url")) .filter_map(|a| a.value_str()) .map(|url| { let mut url = url.to_string(); if !url.ends_with("/") { url.push('/') } Remote(url) }).next().unwrap_or(Unknown) // Well, at least we tried. } impl<'a> DocFolder for SourceCollector<'a> { fn fold_item(&mut self, item: clean::Item) -> Option { // If we're including source files, and we haven't seen this file yet, // then we need to render it out to the filesystem. if self.scx.include_sources // skip all invalid or macro spans && item.source.filename.is_real() // skip non-local items && item.def_id.is_local() { // If it turns out that we couldn't read this file, then we probably // can't read any of the files (generating html output from json or // something like that), so just don't include sources for the // entire crate. The other option is maintaining this mapping on a // per-file basis, but that's probably not worth it... self.scx .include_sources = match self.emit_source(&item.source.filename) { Ok(()) => true, Err(e) => { println!("warning: source code was requested to be rendered, \ but processing `{}` had an error: {}", item.source.filename, e); println!(" skipping rendering of source code"); false } }; } self.fold_item_recur(item) } } impl<'a> SourceCollector<'a> { /// Renders the given filename into its corresponding HTML source file. fn emit_source(&mut self, filename: &FileName) -> io::Result<()> { let p = match *filename { FileName::Real(ref file) => file, _ => return Ok(()), }; if self.scx.local_sources.contains_key(&**p) { // We've already emitted this source return Ok(()); } let contents = fs::read_to_string(&p)?; // Remove the utf-8 BOM if any let contents = if contents.starts_with("\u{feff}") { &contents[3..] } else { &contents[..] }; // Create the intermediate directories let mut cur = self.dst.clone(); let mut root_path = String::from("../../"); let mut href = String::new(); clean_srcpath(&self.scx.src_root, &p, false, |component| { cur.push(component); fs::create_dir_all(&cur).unwrap(); root_path.push_str("../"); href.push_str(component); href.push('/'); }); let mut fname = p.file_name() .expect("source has no filename") .to_os_string(); fname.push(".html"); cur.push(&fname); href.push_str(&fname.to_string_lossy()); let mut w = BufWriter::new(File::create(&cur)?); let title = format!("{} -- source", cur.file_name().unwrap() .to_string_lossy()); let desc = format!("Source to the Rust file `{}`.", filename); let page = layout::Page { title: &title, css_class: "source", root_path: &root_path, description: &desc, keywords: BASIC_KEYWORDS, resource_suffix: &self.scx.resource_suffix, }; layout::render(&mut w, &self.scx.layout, &page, &(""), &Source(contents), self.scx.css_file_extension.is_some(), &self.scx.themes)?; w.flush()?; self.scx.local_sources.insert(p.clone(), href); Ok(()) } } impl DocFolder for Cache { fn fold_item(&mut self, item: clean::Item) -> Option { // If this is a stripped module, // we don't want it or its children in the search index. let orig_stripped_mod = match item.inner { clean::StrippedItem(box clean::ModuleItem(..)) => { mem::replace(&mut self.stripped_mod, true) } _ => self.stripped_mod, }; // If the impl is from a masked crate or references something from a // masked crate then remove it completely. if let clean::ImplItem(ref i) = item.inner { if self.masked_crates.contains(&item.def_id.krate) || i.trait_.def_id().map_or(false, |d| self.masked_crates.contains(&d.krate)) || i.for_.def_id().map_or(false, |d| self.masked_crates.contains(&d.krate)) { return None; } } // Register any generics to their corresponding string. This is used // when pretty-printing types. if let Some(generics) = item.inner.generics() { self.generics(generics); } // Propagate a trait method's documentation to all implementors of the // trait. if let clean::TraitItem(ref t) = item.inner { self.traits.entry(item.def_id).or_insert_with(|| t.clone()); } // Collect all the implementors of traits. if let clean::ImplItem(ref i) = item.inner { if let Some(did) = i.trait_.def_id() { self.implementors.entry(did).or_insert(vec![]).push(Impl { impl_item: item.clone(), }); } } // Index this method for searching later on. if let Some(ref s) = item.name { let (parent, is_inherent_impl_item) = match item.inner { clean::StrippedItem(..) => ((None, None), false), clean::AssociatedConstItem(..) | clean::TypedefItem(_, true) if self.parent_is_trait_impl => { // skip associated items in trait impls ((None, None), false) } clean::AssociatedTypeItem(..) | clean::TyMethodItem(..) | clean::StructFieldItem(..) | clean::VariantItem(..) => { ((Some(*self.parent_stack.last().unwrap()), Some(&self.stack[..self.stack.len() - 1])), false) } clean::MethodItem(..) | clean::AssociatedConstItem(..) => { if self.parent_stack.is_empty() { ((None, None), false) } else { let last = self.parent_stack.last().unwrap(); let did = *last; let path = match self.paths.get(&did) { // The current stack not necessarily has correlation // for where the type was defined. On the other // hand, `paths` always has the right // information if present. Some(&(ref fqp, ItemType::Trait)) | Some(&(ref fqp, ItemType::Struct)) | Some(&(ref fqp, ItemType::Union)) | Some(&(ref fqp, ItemType::Enum)) => Some(&fqp[..fqp.len() - 1]), Some(..) => Some(&*self.stack), None => None }; ((Some(*last), path), true) } } _ => ((None, Some(&*self.stack)), false) }; match parent { (parent, Some(path)) if is_inherent_impl_item || (!self.stripped_mod) => { debug_assert!(!item.is_stripped()); // A crate has a module at its root, containing all items, // which should not be indexed. The crate-item itself is // inserted later on when serializing the search-index. if item.def_id.index != CRATE_DEF_INDEX { self.search_index.push(IndexItem { ty: item.type_(), name: s.to_string(), path: path.join("::").to_string(), desc: plain_summary_line(item.doc_value()), parent, parent_idx: None, search_type: get_index_search_type(&item), }); } } (Some(parent), None) if is_inherent_impl_item => { // We have a parent, but we don't know where they're // defined yet. Wait for later to index this item. self.orphan_impl_items.push((parent, item.clone())); } _ => {} } } // Keep track of the fully qualified path for this item. let pushed = match item.name { Some(ref n) if !n.is_empty() => { self.stack.push(n.to_string()); true } _ => false, }; match item.inner { clean::StructItem(..) | clean::EnumItem(..) | clean::TypedefItem(..) | clean::TraitItem(..) | clean::FunctionItem(..) | clean::ModuleItem(..) | clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) | clean::ConstantItem(..) | clean::StaticItem(..) | clean::UnionItem(..) | clean::ForeignTypeItem | clean::MacroItem(..) if !self.stripped_mod => { // Re-exported items mean that the same id can show up twice // in the rustdoc ast that we're looking at. We know, // however, that a re-exported item doesn't show up in the // `public_items` map, so we can skip inserting into the // paths map if there was already an entry present and we're // not a public item. if !self.paths.contains_key(&item.def_id) || self.access_levels.is_public(item.def_id) { self.paths.insert(item.def_id, (self.stack.clone(), item.type_())); } } // Link variants to their parent enum because pages aren't emitted // for each variant. clean::VariantItem(..) if !self.stripped_mod => { let mut stack = self.stack.clone(); stack.pop(); self.paths.insert(item.def_id, (stack, ItemType::Enum)); } clean::PrimitiveItem(..) if item.visibility.is_some() => { self.paths.insert(item.def_id, (self.stack.clone(), item.type_())); } _ => {} } // Maintain the parent stack let orig_parent_is_trait_impl = self.parent_is_trait_impl; let parent_pushed = match item.inner { clean::TraitItem(..) | clean::EnumItem(..) | clean::ForeignTypeItem | clean::StructItem(..) | clean::UnionItem(..) => { self.parent_stack.push(item.def_id); self.parent_is_trait_impl = false; true } clean::ImplItem(ref i) => { self.parent_is_trait_impl = i.trait_.is_some(); match i.for_ { clean::ResolvedPath{ did, .. } => { self.parent_stack.push(did); true } ref t => { let prim_did = t.primitive_type().and_then(|t| { self.primitive_locations.get(&t).cloned() }); match prim_did { Some(did) => { self.parent_stack.push(did); true } None => false, } } } } _ => false }; // Once we've recursively found all the generics, hoard off all the // implementations elsewhere. let ret = self.fold_item_recur(item).and_then(|item| { if let clean::Item { inner: clean::ImplItem(_), .. } = item { // Figure out the id of this impl. This may map to a // primitive rather than always to a struct/enum. // Note: matching twice to restrict the lifetime of the `i` borrow. let mut dids = FxHashSet(); if let clean::Item { inner: clean::ImplItem(ref i), .. } = item { match i.for_ { clean::ResolvedPath { did, .. } | clean::BorrowedRef { type_: box clean::ResolvedPath { did, .. }, .. } => { dids.insert(did); } ref t => { let did = t.primitive_type().and_then(|t| { self.primitive_locations.get(&t).cloned() }); if let Some(did) = did { dids.insert(did); } } } if let Some(generics) = i.trait_.as_ref().and_then(|t| t.generics()) { for bound in generics { if let Some(did) = bound.def_id() { dids.insert(did); } } } } else { unreachable!() }; for did in dids { self.impls.entry(did).or_insert(vec![]).push(Impl { impl_item: item.clone(), }); } None } else { Some(item) } }); if pushed { self.stack.pop().unwrap(); } if parent_pushed { self.parent_stack.pop().unwrap(); } self.stripped_mod = orig_stripped_mod; self.parent_is_trait_impl = orig_parent_is_trait_impl; ret } } impl<'a> Cache { fn generics(&mut self, generics: &clean::Generics) { for param in &generics.params { if let clean::GenericParam::Type(ref typ) = *param { self.typarams.insert(typ.did, typ.name.clone()); } } } } #[derive(Debug, Eq, PartialEq, Hash)] struct ItemEntry { url: String, name: String, } impl ItemEntry { fn new(mut url: String, name: String) -> ItemEntry { while url.starts_with('/') { url.remove(0); } ItemEntry { url, name, } } } impl fmt::Display for ItemEntry { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.url, Escape(&self.name)) } } impl PartialOrd for ItemEntry { fn partial_cmp(&self, other: &ItemEntry) -> Option<::std::cmp::Ordering> { Some(self.cmp(other)) } } impl Ord for ItemEntry { fn cmp(&self, other: &ItemEntry) -> ::std::cmp::Ordering { self.name.cmp(&other.name) } } #[derive(Debug)] struct AllTypes { structs: HashSet, enums: HashSet, unions: HashSet, primitives: HashSet, traits: HashSet, macros: HashSet, functions: HashSet, typedefs: HashSet, statics: HashSet, constants: HashSet, } impl AllTypes { fn new() -> AllTypes { AllTypes { structs: HashSet::with_capacity(100), enums: HashSet::with_capacity(100), unions: HashSet::with_capacity(100), primitives: HashSet::with_capacity(26), traits: HashSet::with_capacity(100), macros: HashSet::with_capacity(100), functions: HashSet::with_capacity(100), typedefs: HashSet::with_capacity(100), statics: HashSet::with_capacity(100), constants: HashSet::with_capacity(100), } } fn append(&mut self, item_name: String, item_type: &ItemType) { let mut url: Vec<_> = item_name.split("::").skip(1).collect(); if let Some(name) = url.pop() { let new_url = format!("{}/{}.{}.html", url.join("/"), item_type, name); url.push(name); let name = url.join("::"); match *item_type { ItemType::Struct => self.structs.insert(ItemEntry::new(new_url, name)), ItemType::Enum => self.enums.insert(ItemEntry::new(new_url, name)), ItemType::Union => self.unions.insert(ItemEntry::new(new_url, name)), ItemType::Primitive => self.primitives.insert(ItemEntry::new(new_url, name)), ItemType::Trait => self.traits.insert(ItemEntry::new(new_url, name)), ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)), ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)), ItemType::Typedef => self.typedefs.insert(ItemEntry::new(new_url, name)), ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)), ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)), _ => true, }; } } } fn print_entries(f: &mut fmt::Formatter, e: &HashSet, title: &str, class: &str) -> fmt::Result { if !e.is_empty() { let mut e: Vec<&ItemEntry> = e.iter().collect(); e.sort(); write!(f, "

{}

    {}
", title, Escape(title), class, e.iter().map(|s| format!("
  • {}
  • ", s)).collect::())?; } Ok(()) } impl fmt::Display for AllTypes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "

    \ List of all items\ \ \ \ []\ \

    ")?; print_entries(f, &self.structs, "Structs", "structs")?; print_entries(f, &self.enums, "Enums", "enums")?; print_entries(f, &self.unions, "Unions", "unions")?; print_entries(f, &self.primitives, "Primitives", "primitives")?; print_entries(f, &self.traits, "Traits", "traits")?; print_entries(f, &self.macros, "Macros", "macros")?; print_entries(f, &self.functions, "Functions", "functions")?; print_entries(f, &self.typedefs, "Typedefs", "typedefs")?; print_entries(f, &self.statics, "Statics", "statics")?; print_entries(f, &self.constants, "Constants", "constants") } } #[derive(Debug)] struct Settings<'a> { // (id, explanation, default value) settings: Vec<(&'static str, &'static str, bool)>, root_path: &'a str, suffix: &'a str, } impl<'a> Settings<'a> { pub fn new(root_path: &'a str, suffix: &'a str) -> Settings<'a> { Settings { settings: vec![ ("item-declarations", "Auto-hide item declarations.", true), ("item-attributes", "Auto-hide item attributes.", true), ], root_path, suffix, } } } impl<'a> fmt::Display for Settings<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "

    \ Rustdoc settings\

    \
    {}
    \ ", self.settings.iter() .map(|(id, text, enabled)| { format!("
    \ \
    {}
    \
    ", id, if *enabled { " checked" } else { "" }, text) }) .collect::(), self.root_path, self.suffix) } } impl Context { /// String representation of how to get back to the root path of the 'doc/' /// folder in terms of a relative URL. fn root_path(&self) -> String { repeat("../").take(self.current.len()).collect::() } /// Recurse in the directory structure and change the "root path" to make /// sure it always points to the top (relatively). fn recurse(&mut self, s: String, f: F) -> T where F: FnOnce(&mut Context) -> T, { if s.is_empty() { panic!("Unexpected empty destination: {:?}", self.current); } let prev = self.dst.clone(); self.dst.push(&s); self.current.push(s); info!("Recursing into {}", self.dst.display()); let ret = f(self); info!("Recursed; leaving {}", self.dst.display()); // Go back to where we were at self.dst = prev; self.current.pop().unwrap(); ret } /// Main method for rendering a crate. /// /// This currently isn't parallelized, but it'd be pretty easy to add /// parallelization to this function. fn krate(self, mut krate: clean::Crate) -> Result<(), Error> { let mut item = match krate.module.take() { Some(i) => i, None => return Ok(()), }; let final_file = self.dst.join(&krate.name) .join("all.html"); let settings_file = self.dst.join("settings.html"); let crate_name = krate.name.clone(); item.name = Some(krate.name); let mut all = AllTypes::new(); { // Render the crate documentation let mut work = vec![(self.clone(), item)]; while let Some((mut cx, item)) = work.pop() { cx.item(item, &mut all, |cx, item| { work.push((cx.clone(), item)) })? } } let mut w = BufWriter::new(try_err!(File::create(&final_file), &final_file)); let mut root_path = self.dst.to_str().expect("invalid path").to_owned(); if !root_path.ends_with('/') { root_path.push('/'); } let mut page = layout::Page { title: "List of all items in this crate", css_class: "mod", root_path: "../", description: "List of all items in this crate", keywords: BASIC_KEYWORDS, resource_suffix: &self.shared.resource_suffix, }; let sidebar = if let Some(ref version) = cache().crate_version { format!("

    Crate {}

    \
    \

    Version {}

    \
    \

    Back to index

    ", crate_name, version) } else { String::new() }; try_err!(layout::render(&mut w, &self.shared.layout, &page, &sidebar, &all, self.shared.css_file_extension.is_some(), &self.shared.themes), &final_file); // If the file already exists, no need to generate it again... if !settings_file.is_file() { let settings = Settings::new("./", &self.shared.resource_suffix); page.title = "Rustdoc settings"; page.description = "Settings of Rustdoc"; page.root_path = "./"; let mut w = BufWriter::new(try_err!(File::create(&settings_file), &settings_file)); let mut themes = self.shared.themes.clone(); let sidebar = "

    Settings