1 use std::cell::RefCell; 2 use std::collections::BTreeMap; 3 use std::io; 4 use std::path::{Path, PathBuf}; 5 use std::rc::Rc; 6 use std::sync::mpsc::{channel, Receiver}; 7 8 use rustc_data_structures::fx::{FxHashMap, FxHashSet}; 9 use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE}; 10 use rustc_middle::ty::TyCtxt; 11 use rustc_session::Session; 12 use rustc_span::edition::Edition; 13 use rustc_span::source_map::FileName; 14 use rustc_span::{sym, Symbol}; 15 16 use super::print_item::{full_path, item_path, print_item}; 17 use super::search_index::build_index; 18 use super::write_shared::write_shared; 19 use super::{ 20 collect_spans_and_sources, scrape_examples_help, 21 sidebar::print_sidebar, 22 sidebar::{sidebar_module_like, Sidebar}, 23 AllTypes, LinkFromSrc, StylePath, 24 }; 25 use crate::clean::{self, types::ExternalLocation, ExternalCrate}; 26 use crate::config::{ModuleSorting, RenderOptions}; 27 use crate::docfs::{DocFS, PathError}; 28 use crate::error::Error; 29 use crate::formats::cache::Cache; 30 use crate::formats::item_type::ItemType; 31 use crate::formats::FormatRenderer; 32 use crate::html::escape::Escape; 33 use crate::html::format::{join_with_double_colon, Buffer}; 34 use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; 35 use crate::html::url_parts_builder::UrlPartsBuilder; 36 use crate::html::{layout, sources, static_files}; 37 use crate::scrape_examples::AllCallLocations; 38 use crate::try_err; 39 use askama::Template; 40 41 /// Major driving force in all rustdoc rendering. This contains information 42 /// about where in the tree-like hierarchy rendering is occurring and controls 43 /// how the current page is being rendered. 44 /// 45 /// It is intended that this context is a lightweight object which can be fairly 46 /// easily cloned because it is cloned per work-job (about once per item in the 47 /// rustdoc tree). 48 pub(crate) struct Context<'tcx> { 49 /// Current hierarchy of components leading down to what's currently being 50 /// rendered 51 pub(crate) current: Vec<Symbol>, 52 /// The current destination folder of where HTML artifacts should be placed. 53 /// This changes as the context descends into the module hierarchy. 54 pub(crate) dst: PathBuf, 55 /// A flag, which when `true`, will render pages which redirect to the 56 /// real location of an item. This is used to allow external links to 57 /// publicly reused items to redirect to the right location. 58 pub(super) render_redirect_pages: bool, 59 /// Tracks section IDs for `Deref` targets so they match in both the main 60 /// body and the sidebar. 61 pub(super) deref_id_map: DefIdMap<String>, 62 /// The map used to ensure all generated 'id=' attributes are unique. 63 pub(super) id_map: IdMap, 64 /// Shared mutable state. 65 /// 66 /// Issue for improving the situation: [#82381][] 67 /// 68 /// [#82381]: https://github.com/rust-lang/rust/issues/82381 69 pub(crate) shared: Rc<SharedContext<'tcx>>, 70 /// This flag indicates whether source links should be generated or not. If 71 /// the source files are present in the html rendering, then this will be 72 /// `true`. 73 pub(crate) include_sources: bool, 74 /// Collection of all types with notable traits referenced in the current module. 75 pub(crate) types_with_notable_traits: FxHashSet<clean::Type>, 76 /// Field used during rendering, to know if we're inside an inlined item. 77 pub(crate) is_inside_inlined_module: bool, 78 } 79 80 // `Context` is cloned a lot, so we don't want the size to grow unexpectedly. 81 #[cfg(all(not(windows), target_arch = "x86_64", target_pointer_width = "64"))] 82 rustc_data_structures::static_assert_size!(Context<'_>, 160); 83 84 /// Shared mutable state used in [`Context`] and elsewhere. 85 pub(crate) struct SharedContext<'tcx> { 86 pub(crate) tcx: TyCtxt<'tcx>, 87 /// The path to the crate root source minus the file name. 88 /// Used for simplifying paths to the highlighted source code files. 89 pub(crate) src_root: PathBuf, 90 /// This describes the layout of each page, and is not modified after 91 /// creation of the context (contains info like the favicon and added html). 92 pub(crate) layout: layout::Layout, 93 /// The local file sources we've emitted and their respective url-paths. 94 pub(crate) local_sources: FxHashMap<PathBuf, String>, 95 /// Show the memory layout of types in the docs. 96 pub(super) show_type_layout: bool, 97 /// The base-URL of the issue tracker for when an item has been tagged with 98 /// an issue number. 99 pub(super) issue_tracker_base_url: Option<String>, 100 /// The directories that have already been created in this doc run. Used to reduce the number 101 /// of spurious `create_dir_all` calls. 102 created_dirs: RefCell<FxHashSet<PathBuf>>, 103 /// This flag indicates whether listings of modules (in the side bar and documentation itself) 104 /// should be ordered alphabetically or in order of appearance (in the source code). 105 pub(super) module_sorting: ModuleSorting, 106 /// Additional CSS files to be added to the generated docs. 107 pub(crate) style_files: Vec<StylePath>, 108 /// Suffix to be added on resource files (if suffix is "-v2" then "light.css" becomes 109 /// "light-v2.css"). 110 pub(crate) resource_suffix: String, 111 /// Optional path string to be used to load static files on output pages. If not set, uses 112 /// combinations of `../` to reach the documentation root. 113 pub(crate) static_root_path: Option<String>, 114 /// The fs handle we are working with. 115 pub(crate) fs: DocFS, 116 pub(super) codes: ErrorCodes, 117 pub(super) playground: Option<markdown::Playground>, 118 all: RefCell<AllTypes>, 119 /// Storage for the errors produced while generating documentation so they 120 /// can be printed together at the end. 121 errors: Receiver<String>, 122 /// `None` by default, depends on the `generate-redirect-map` option flag. If this field is set 123 /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of 124 /// the crate. 125 redirections: Option<RefCell<FxHashMap<String, String>>>, 126 127 /// Correspondence map used to link types used in the source code pages to allow to click on 128 /// links to jump to the type's definition. 129 pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>, 130 /// The [`Cache`] used during rendering. 131 pub(crate) cache: Cache, 132 133 pub(crate) call_locations: AllCallLocations, 134 } 135 136 impl SharedContext<'_> { ensure_dir(&self, dst: &Path) -> Result<(), Error>137 pub(crate) fn ensure_dir(&self, dst: &Path) -> Result<(), Error> { 138 let mut dirs = self.created_dirs.borrow_mut(); 139 if !dirs.contains(dst) { 140 try_err!(self.fs.create_dir_all(dst), dst); 141 dirs.insert(dst.to_path_buf()); 142 } 143 144 Ok(()) 145 } 146 edition(&self) -> Edition147 pub(crate) fn edition(&self) -> Edition { 148 self.tcx.sess.edition() 149 } 150 } 151 152 impl<'tcx> Context<'tcx> { tcx(&self) -> TyCtxt<'tcx>153 pub(crate) fn tcx(&self) -> TyCtxt<'tcx> { 154 self.shared.tcx 155 } 156 cache(&self) -> &Cache157 pub(crate) fn cache(&self) -> &Cache { 158 &self.shared.cache 159 } 160 sess(&self) -> &'tcx Session161 pub(super) fn sess(&self) -> &'tcx Session { 162 self.shared.tcx.sess 163 } 164 derive_id(&mut self, id: String) -> String165 pub(super) fn derive_id(&mut self, id: String) -> String { 166 self.id_map.derive(id) 167 } 168 169 /// String representation of how to get back to the root path of the 'doc/' 170 /// folder in terms of a relative URL. root_path(&self) -> String171 pub(super) fn root_path(&self) -> String { 172 "../".repeat(self.current.len()) 173 } 174 render_item(&mut self, it: &clean::Item, is_module: bool) -> String175 fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String { 176 let mut render_redirect_pages = self.render_redirect_pages; 177 // If the item is stripped but inlined, links won't point to the item so no need to generate 178 // a file for it. 179 if it.is_stripped() && 180 let Some(def_id) = it.def_id() && 181 def_id.is_local() 182 { 183 if self.is_inside_inlined_module || self.shared.cache.inlined_items.contains(&def_id) { 184 // For now we're forced to generate a redirect page for stripped items until 185 // `record_extern_fqn` correctly points to external items. 186 render_redirect_pages = true; 187 } 188 } 189 let mut title = String::new(); 190 if !is_module { 191 title.push_str(it.name.unwrap().as_str()); 192 } 193 if !it.is_primitive() && !it.is_keyword() { 194 if !is_module { 195 title.push_str(" in "); 196 } 197 // No need to include the namespace for primitive types and keywords 198 title.push_str(&join_with_double_colon(&self.current)); 199 }; 200 title.push_str(" - Rust"); 201 let tyname = it.type_(); 202 let desc = plain_text_summary(&it.doc_value(), &it.link_names(&self.cache())); 203 let desc = if !desc.is_empty() { 204 desc 205 } else if it.is_crate() { 206 format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) 207 } else { 208 format!( 209 "API documentation for the Rust `{}` {} in crate `{}`.", 210 it.name.as_ref().unwrap(), 211 tyname, 212 self.shared.layout.krate 213 ) 214 }; 215 let name; 216 let tyname_s = if it.is_crate() { 217 name = format!("{} crate", tyname); 218 name.as_str() 219 } else { 220 tyname.as_str() 221 }; 222 223 if !render_redirect_pages { 224 let clone_shared = Rc::clone(&self.shared); 225 let page = layout::Page { 226 css_class: tyname_s, 227 root_path: &self.root_path(), 228 static_root_path: clone_shared.static_root_path.as_deref(), 229 title: &title, 230 description: &desc, 231 resource_suffix: &clone_shared.resource_suffix, 232 }; 233 let mut page_buffer = Buffer::html(); 234 print_item(self, it, &mut page_buffer, &page); 235 layout::render( 236 &clone_shared.layout, 237 &page, 238 |buf: &mut _| print_sidebar(self, it, buf), 239 move |buf: &mut Buffer| buf.push_buffer(page_buffer), 240 &clone_shared.style_files, 241 ) 242 } else { 243 if let Some(&(ref names, ty)) = self.cache().paths.get(&it.item_id.expect_def_id()) { 244 if self.current.len() + 1 != names.len() 245 || self.current.iter().zip(names.iter()).any(|(a, b)| a != b) 246 { 247 // We checked that the redirection isn't pointing to the current file, 248 // preventing an infinite redirection loop in the generated 249 // documentation. 250 251 let mut path = String::new(); 252 for name in &names[..names.len() - 1] { 253 path.push_str(name.as_str()); 254 path.push('/'); 255 } 256 path.push_str(&item_path(ty, names.last().unwrap().as_str())); 257 match self.shared.redirections { 258 Some(ref redirections) => { 259 let mut current_path = String::new(); 260 for name in &self.current { 261 current_path.push_str(name.as_str()); 262 current_path.push('/'); 263 } 264 current_path.push_str(&item_path(ty, names.last().unwrap().as_str())); 265 redirections.borrow_mut().insert(current_path, path); 266 } 267 None => return layout::redirect(&format!("{}{}", self.root_path(), path)), 268 } 269 } 270 } 271 String::new() 272 } 273 } 274 275 /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<String>>276 fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<String>> { 277 // BTreeMap instead of HashMap to get a sorted output 278 let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); 279 let mut inserted: FxHashMap<ItemType, FxHashSet<Symbol>> = FxHashMap::default(); 280 281 for item in &m.items { 282 if item.is_stripped() { 283 continue; 284 } 285 286 let short = item.type_(); 287 let myname = match item.name { 288 None => continue, 289 Some(s) => s, 290 }; 291 if inserted.entry(short).or_default().insert(myname) { 292 let short = short.to_string(); 293 let myname = myname.to_string(); 294 map.entry(short).or_default().push(myname); 295 } 296 } 297 298 match self.shared.module_sorting { 299 ModuleSorting::Alphabetical => { 300 for items in map.values_mut() { 301 items.sort(); 302 } 303 } 304 ModuleSorting::DeclarationOrder => {} 305 } 306 map 307 } 308 309 /// Generates a url appropriate for an `href` attribute back to the source of 310 /// this item. 311 /// 312 /// The url generated, when clicked, will redirect the browser back to the 313 /// original source code. 314 /// 315 /// If `None` is returned, then a source link couldn't be generated. This 316 /// may happen, for example, with externally inlined items where the source 317 /// of their crate documentation isn't known. src_href(&self, item: &clean::Item) -> Option<String>318 pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> { 319 self.href_from_span(item.span(self.tcx())?, true) 320 } 321 href_from_span(&self, span: clean::Span, with_lines: bool) -> Option<String>322 pub(crate) fn href_from_span(&self, span: clean::Span, with_lines: bool) -> Option<String> { 323 let mut root = self.root_path(); 324 let mut path: String; 325 let cnum = span.cnum(self.sess()); 326 327 // We can safely ignore synthetic `SourceFile`s. 328 let file = match span.filename(self.sess()) { 329 FileName::Real(ref path) => path.local_path_if_available().to_path_buf(), 330 _ => return None, 331 }; 332 let file = &file; 333 334 let krate_sym; 335 let (krate, path) = if cnum == LOCAL_CRATE { 336 if let Some(path) = self.shared.local_sources.get(file) { 337 (self.shared.layout.krate.as_str(), path) 338 } else { 339 return None; 340 } 341 } else { 342 let (krate, src_root) = match *self.cache().extern_locations.get(&cnum)? { 343 ExternalLocation::Local => { 344 let e = ExternalCrate { crate_num: cnum }; 345 (e.name(self.tcx()), e.src_root(self.tcx())) 346 } 347 ExternalLocation::Remote(ref s) => { 348 root = s.to_string(); 349 let e = ExternalCrate { crate_num: cnum }; 350 (e.name(self.tcx()), e.src_root(self.tcx())) 351 } 352 ExternalLocation::Unknown => return None, 353 }; 354 355 let href = RefCell::new(PathBuf::new()); 356 sources::clean_path( 357 &src_root, 358 file, 359 |component| { 360 href.borrow_mut().push(component); 361 }, 362 || { 363 href.borrow_mut().pop(); 364 }, 365 ); 366 367 path = href.into_inner().to_string_lossy().into_owned(); 368 369 if let Some(c) = path.as_bytes().last() && *c != b'/' { 370 path.push('/'); 371 } 372 373 let mut fname = file.file_name().expect("source has no filename").to_os_string(); 374 fname.push(".html"); 375 path.push_str(&fname.to_string_lossy()); 376 krate_sym = krate; 377 (krate_sym.as_str(), &path) 378 }; 379 380 let anchor = if with_lines { 381 let loline = span.lo(self.sess()).line; 382 let hiline = span.hi(self.sess()).line; 383 format!( 384 "#{}", 385 if loline == hiline { 386 loline.to_string() 387 } else { 388 format!("{}-{}", loline, hiline) 389 } 390 ) 391 } else { 392 "".to_string() 393 }; 394 Some(format!( 395 "{root}src/{krate}/{path}{anchor}", 396 root = Escape(&root), 397 krate = krate, 398 path = path, 399 anchor = anchor 400 )) 401 } 402 href_from_span_relative( &self, span: clean::Span, relative_to: &str, ) -> Option<String>403 pub(crate) fn href_from_span_relative( 404 &self, 405 span: clean::Span, 406 relative_to: &str, 407 ) -> Option<String> { 408 self.href_from_span(span, false).map(|s| { 409 let mut url = UrlPartsBuilder::new(); 410 let mut dest_href_parts = s.split('/'); 411 let mut cur_href_parts = relative_to.split('/'); 412 for (cur_href_part, dest_href_part) in (&mut cur_href_parts).zip(&mut dest_href_parts) { 413 if cur_href_part != dest_href_part { 414 url.push(dest_href_part); 415 break; 416 } 417 } 418 for dest_href_part in dest_href_parts { 419 url.push(dest_href_part); 420 } 421 let loline = span.lo(self.sess()).line; 422 let hiline = span.hi(self.sess()).line; 423 format!( 424 "{}{}#{}", 425 "../".repeat(cur_href_parts.count()), 426 url.finish(), 427 if loline == hiline { loline.to_string() } else { format!("{loline}-{hiline}") } 428 ) 429 }) 430 } 431 } 432 433 /// Generates the documentation for `crate` into the directory `dst` 434 impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { descr() -> &'static str435 fn descr() -> &'static str { 436 "html" 437 } 438 439 const RUN_ON_MODULE: bool = true; 440 init( krate: clean::Crate, options: RenderOptions, cache: Cache, tcx: TyCtxt<'tcx>, ) -> Result<(Self, clean::Crate), Error>441 fn init( 442 krate: clean::Crate, 443 options: RenderOptions, 444 cache: Cache, 445 tcx: TyCtxt<'tcx>, 446 ) -> Result<(Self, clean::Crate), Error> { 447 // need to save a copy of the options for rendering the index page 448 let md_opts = options.clone(); 449 let emit_crate = options.should_emit_crate(); 450 let RenderOptions { 451 output, 452 external_html, 453 id_map, 454 playground_url, 455 module_sorting, 456 themes: style_files, 457 default_settings, 458 extension_css, 459 resource_suffix, 460 static_root_path, 461 generate_redirect_map, 462 show_type_layout, 463 generate_link_to_definition, 464 call_locations, 465 no_emit_shared, 466 .. 467 } = options; 468 469 let src_root = match krate.src(tcx) { 470 FileName::Real(ref p) => match p.local_path_if_available().parent() { 471 Some(p) => p.to_path_buf(), 472 None => PathBuf::new(), 473 }, 474 _ => PathBuf::new(), 475 }; 476 // If user passed in `--playground-url` arg, we fill in crate name here 477 let mut playground = None; 478 if let Some(url) = playground_url { 479 playground = Some(markdown::Playground { crate_name: Some(krate.name(tcx)), url }); 480 } 481 let mut layout = layout::Layout { 482 logo: String::new(), 483 favicon: String::new(), 484 external_html, 485 default_settings, 486 krate: krate.name(tcx).to_string(), 487 css_file_extension: extension_css, 488 scrape_examples_extension: !call_locations.is_empty(), 489 }; 490 let mut issue_tracker_base_url = None; 491 let mut include_sources = true; 492 493 // Crawl the crate attributes looking for attributes which control how we're 494 // going to emit HTML 495 for attr in krate.module.attrs.lists(sym::doc) { 496 match (attr.name_or_empty(), attr.value_str()) { 497 (sym::html_favicon_url, Some(s)) => { 498 layout.favicon = s.to_string(); 499 } 500 (sym::html_logo_url, Some(s)) => { 501 layout.logo = s.to_string(); 502 } 503 (sym::html_playground_url, Some(s)) => { 504 playground = Some(markdown::Playground { 505 crate_name: Some(krate.name(tcx)), 506 url: s.to_string(), 507 }); 508 } 509 (sym::issue_tracker_base_url, Some(s)) => { 510 issue_tracker_base_url = Some(s.to_string()); 511 } 512 (sym::html_no_source, None) if attr.is_word() => { 513 include_sources = false; 514 } 515 _ => {} 516 } 517 } 518 519 let (local_sources, matches) = collect_spans_and_sources( 520 tcx, 521 &krate, 522 &src_root, 523 include_sources, 524 generate_link_to_definition, 525 ); 526 527 let (sender, receiver) = channel(); 528 let scx = SharedContext { 529 tcx, 530 src_root, 531 local_sources, 532 issue_tracker_base_url, 533 layout, 534 created_dirs: Default::default(), 535 module_sorting, 536 style_files, 537 resource_suffix, 538 static_root_path, 539 fs: DocFS::new(sender), 540 codes: ErrorCodes::from(options.unstable_features.is_nightly_build()), 541 playground, 542 all: RefCell::new(AllTypes::new()), 543 errors: receiver, 544 redirections: if generate_redirect_map { Some(Default::default()) } else { None }, 545 show_type_layout, 546 span_correspondence_map: matches, 547 cache, 548 call_locations, 549 }; 550 551 let dst = output; 552 scx.ensure_dir(&dst)?; 553 554 let mut cx = Context { 555 current: Vec::new(), 556 dst, 557 render_redirect_pages: false, 558 id_map, 559 deref_id_map: Default::default(), 560 shared: Rc::new(scx), 561 include_sources, 562 types_with_notable_traits: FxHashSet::default(), 563 is_inside_inlined_module: false, 564 }; 565 566 if emit_crate { 567 sources::render(&mut cx, &krate)?; 568 } 569 570 if !no_emit_shared { 571 // Build our search index 572 let index = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx); 573 574 // Write shared runs within a flock; disable thread dispatching of IO temporarily. 575 Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); 576 write_shared(&mut cx, &krate, index, &md_opts)?; 577 Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); 578 } 579 580 Ok((cx, krate)) 581 } 582 make_child_renderer(&self) -> Self583 fn make_child_renderer(&self) -> Self { 584 Self { 585 current: self.current.clone(), 586 dst: self.dst.clone(), 587 render_redirect_pages: self.render_redirect_pages, 588 deref_id_map: Default::default(), 589 id_map: IdMap::new(), 590 shared: Rc::clone(&self.shared), 591 include_sources: self.include_sources, 592 types_with_notable_traits: FxHashSet::default(), 593 is_inside_inlined_module: self.is_inside_inlined_module, 594 } 595 } 596 after_krate(&mut self) -> Result<(), Error>597 fn after_krate(&mut self) -> Result<(), Error> { 598 let crate_name = self.tcx().crate_name(LOCAL_CRATE); 599 let final_file = self.dst.join(crate_name.as_str()).join("all.html"); 600 let settings_file = self.dst.join("settings.html"); 601 let help_file = self.dst.join("help.html"); 602 let scrape_examples_help_file = self.dst.join("scrape-examples-help.html"); 603 604 let mut root_path = self.dst.to_str().expect("invalid path").to_owned(); 605 if !root_path.ends_with('/') { 606 root_path.push('/'); 607 } 608 let shared = Rc::clone(&self.shared); 609 let mut page = layout::Page { 610 title: "List of all items in this crate", 611 css_class: "mod", 612 root_path: "../", 613 static_root_path: shared.static_root_path.as_deref(), 614 description: "List of all items in this crate", 615 resource_suffix: &shared.resource_suffix, 616 }; 617 let all = shared.all.replace(AllTypes::new()); 618 let mut sidebar = Buffer::html(); 619 620 let blocks = sidebar_module_like(all.item_sections()); 621 let bar = Sidebar { 622 title_prefix: "Crate ", 623 title: crate_name.as_str(), 624 is_crate: false, 625 version: "", 626 blocks: vec![blocks], 627 path: String::new(), 628 }; 629 630 bar.render_into(&mut sidebar).unwrap(); 631 632 let v = layout::render( 633 &shared.layout, 634 &page, 635 sidebar.into_inner(), 636 |buf: &mut Buffer| all.print(buf), 637 &shared.style_files, 638 ); 639 shared.fs.write(final_file, v)?; 640 641 // Generating settings page. 642 page.title = "Rustdoc settings"; 643 page.description = "Settings of Rustdoc"; 644 page.root_path = "./"; 645 646 let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>"; 647 let v = layout::render( 648 &shared.layout, 649 &page, 650 sidebar, 651 |buf: &mut Buffer| { 652 write!( 653 buf, 654 "<div class=\"main-heading\">\ 655 <h1>Rustdoc settings</h1>\ 656 <span class=\"out-of-band\">\ 657 <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\ 658 Back\ 659 </a>\ 660 </span>\ 661 </div>\ 662 <noscript>\ 663 <section>\ 664 You need to enable JavaScript be able to update your settings.\ 665 </section>\ 666 </noscript>\ 667 <link rel=\"stylesheet\" \ 668 href=\"{static_root_path}{settings_css}\">\ 669 <script defer src=\"{static_root_path}{settings_js}\"></script>\ 670 <link rel=\"preload\" href=\"{static_root_path}{theme_light_css}\" \ 671 as=\"style\">\ 672 <link rel=\"preload\" href=\"{static_root_path}{theme_dark_css}\" \ 673 as=\"style\">\ 674 <link rel=\"preload\" href=\"{static_root_path}{theme_ayu_css}\" \ 675 as=\"style\">", 676 static_root_path = page.get_static_root_path(), 677 settings_css = static_files::STATIC_FILES.settings_css, 678 settings_js = static_files::STATIC_FILES.settings_js, 679 theme_light_css = static_files::STATIC_FILES.theme_light_css, 680 theme_dark_css = static_files::STATIC_FILES.theme_dark_css, 681 theme_ayu_css = static_files::STATIC_FILES.theme_ayu_css, 682 ); 683 // Pre-load all theme CSS files, so that switching feels seamless. 684 // 685 // When loading settings.html as a popover, the equivalent HTML is 686 // generated in main.js. 687 for file in &shared.style_files { 688 if let Ok(theme) = file.basename() { 689 write!( 690 buf, 691 "<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \ 692 as=\"style\">", 693 root_path = page.static_root_path.unwrap_or(""), 694 suffix = page.resource_suffix, 695 ); 696 } 697 } 698 }, 699 &shared.style_files, 700 ); 701 shared.fs.write(settings_file, v)?; 702 703 // Generating help page. 704 page.title = "Rustdoc help"; 705 page.description = "Documentation for Rustdoc"; 706 page.root_path = "./"; 707 708 let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>"; 709 let v = layout::render( 710 &shared.layout, 711 &page, 712 sidebar, 713 |buf: &mut Buffer| { 714 write!( 715 buf, 716 "<div class=\"main-heading\">\ 717 <h1>Rustdoc help</h1>\ 718 <span class=\"out-of-band\">\ 719 <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\ 720 Back\ 721 </a>\ 722 </span>\ 723 </div>\ 724 <noscript>\ 725 <section>\ 726 <p>You need to enable JavaScript to use keyboard commands or search.</p>\ 727 <p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\ 728 </section>\ 729 </noscript>", 730 ) 731 }, 732 &shared.style_files, 733 ); 734 shared.fs.write(help_file, v)?; 735 736 if shared.layout.scrape_examples_extension { 737 page.title = "About scraped examples"; 738 page.description = "How the scraped examples feature works in Rustdoc"; 739 let v = layout::render( 740 &shared.layout, 741 &page, 742 "", 743 scrape_examples_help(&*shared), 744 &shared.style_files, 745 ); 746 shared.fs.write(scrape_examples_help_file, v)?; 747 } 748 749 if let Some(ref redirections) = shared.redirections && !redirections.borrow().is_empty() { 750 let redirect_map_path = 751 self.dst.join(crate_name.as_str()).join("redirect-map.json"); 752 let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); 753 shared.ensure_dir(&self.dst.join(crate_name.as_str()))?; 754 shared.fs.write(redirect_map_path, paths)?; 755 } 756 757 // No need for it anymore. 758 drop(shared); 759 760 // Flush pending errors. 761 Rc::get_mut(&mut self.shared).unwrap().fs.close(); 762 let nb_errors = 763 self.shared.errors.iter().map(|err| self.tcx().sess.struct_err(err).emit()).count(); 764 if nb_errors > 0 { 765 Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) 766 } else { 767 Ok(()) 768 } 769 } 770 mod_item_in(&mut self, item: &clean::Item) -> Result<(), Error>771 fn mod_item_in(&mut self, item: &clean::Item) -> Result<(), Error> { 772 // Stripped modules survive the rustdoc passes (i.e., `strip-private`) 773 // if they contain impls for public types. These modules can also 774 // contain items such as publicly re-exported structures. 775 // 776 // External crates will provide links to these structures, so 777 // these modules are recursed into, but not rendered normally 778 // (a flag on the context). 779 if !self.render_redirect_pages { 780 self.render_redirect_pages = item.is_stripped(); 781 } 782 let item_name = item.name.unwrap(); 783 self.dst.push(&*item_name.as_str()); 784 self.current.push(item_name); 785 786 info!("Recursing into {}", self.dst.display()); 787 788 if !item.is_stripped() { 789 let buf = self.render_item(item, true); 790 // buf will be empty if the module is stripped and there is no redirect for it 791 if !buf.is_empty() { 792 self.shared.ensure_dir(&self.dst)?; 793 let joint_dst = self.dst.join("index.html"); 794 self.shared.fs.write(joint_dst, buf)?; 795 } 796 } 797 if !self.is_inside_inlined_module { 798 if let Some(def_id) = item.def_id() && self.cache().inlined_items.contains(&def_id) { 799 self.is_inside_inlined_module = true; 800 } 801 } else if item.is_doc_hidden() { 802 // We're not inside an inlined module anymore since this one cannot be re-exported. 803 self.is_inside_inlined_module = false; 804 } 805 806 // Render sidebar-items.js used throughout this module. 807 if !self.render_redirect_pages { 808 let (clean::StrippedItem(box clean::ModuleItem(ref module)) | clean::ModuleItem(ref module)) = *item.kind 809 else { unreachable!() }; 810 let items = self.build_sidebar_items(module); 811 let js_dst = self.dst.join(&format!("sidebar-items{}.js", self.shared.resource_suffix)); 812 let v = format!("window.SIDEBAR_ITEMS = {};", serde_json::to_string(&items).unwrap()); 813 self.shared.fs.write(js_dst, v)?; 814 } 815 Ok(()) 816 } 817 mod_item_out(&mut self) -> Result<(), Error>818 fn mod_item_out(&mut self) -> Result<(), Error> { 819 info!("Recursed; leaving {}", self.dst.display()); 820 821 // Go back to where we were at 822 self.dst.pop(); 823 self.current.pop(); 824 Ok(()) 825 } 826 item(&mut self, item: clean::Item) -> Result<(), Error>827 fn item(&mut self, item: clean::Item) -> Result<(), Error> { 828 // Stripped modules survive the rustdoc passes (i.e., `strip-private`) 829 // if they contain impls for public types. These modules can also 830 // contain items such as publicly re-exported structures. 831 // 832 // External crates will provide links to these structures, so 833 // these modules are recursed into, but not rendered normally 834 // (a flag on the context). 835 if !self.render_redirect_pages { 836 self.render_redirect_pages = item.is_stripped(); 837 } 838 839 let buf = self.render_item(&item, false); 840 // buf will be empty if the item is stripped and there is no redirect for it 841 if !buf.is_empty() { 842 let name = item.name.as_ref().unwrap(); 843 let item_type = item.type_(); 844 let file_name = &item_path(item_type, name.as_str()); 845 self.shared.ensure_dir(&self.dst)?; 846 let joint_dst = self.dst.join(file_name); 847 self.shared.fs.write(joint_dst, buf)?; 848 849 if !self.render_redirect_pages { 850 self.shared.all.borrow_mut().append(full_path(self, &item), &item_type); 851 } 852 // If the item is a macro, redirect from the old macro URL (with !) 853 // to the new one (without). 854 if item_type == ItemType::Macro { 855 let redir_name = format!("{}.{}!.html", item_type, name); 856 if let Some(ref redirections) = self.shared.redirections { 857 let crate_name = &self.shared.layout.krate; 858 redirections.borrow_mut().insert( 859 format!("{}/{}", crate_name, redir_name), 860 format!("{}/{}", crate_name, file_name), 861 ); 862 } else { 863 let v = layout::redirect(file_name); 864 let redir_dst = self.dst.join(redir_name); 865 self.shared.fs.write(redir_dst, v)?; 866 } 867 } 868 } 869 870 Ok(()) 871 } 872 cache(&self) -> &Cache873 fn cache(&self) -> &Cache { 874 &self.shared.cache 875 } 876 } 877