1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::fmt::{self, Write as _};
4use std::io;
5use std::path::{Path, PathBuf};
6use std::sync::mpsc::{Receiver, channel};
7
8use askama::Template;
9use rustc_ast::join_path_syms;
10use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
11use rustc_hir::Attribute;
12use rustc_hir::attrs::AttributeKind;
13use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE};
14use rustc_middle::ty::TyCtxt;
15use rustc_session::Session;
16use rustc_span::edition::Edition;
17use rustc_span::{BytePos, FileName, Symbol};
18use tracing::info;
19
20use super::print_item::{full_path, print_item, print_item_path};
21use super::sidebar::{ModuleLike, Sidebar, print_sidebar, sidebar_module_like};
22use super::{AllTypes, LinkFromSrc, StylePath, collect_spans_and_sources, scrape_examples_help};
23use crate::clean::types::ExternalLocation;
24use crate::clean::utils::has_doc_flag;
25use crate::clean::{self, ExternalCrate};
26use crate::config::{ModuleSorting, RenderOptions, ShouldMerge};
27use crate::docfs::{DocFS, PathError};
28use crate::error::Error;
29use crate::formats::FormatRenderer;
30use crate::formats::cache::Cache;
31use crate::formats::item_type::ItemType;
32use crate::html::escape::Escape;
33use crate::html::macro_expansion::ExpandedCode;
34use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary};
35use crate::html::render::span_map::Span;
36use crate::html::render::write_shared::write_shared;
37use crate::html::url_parts_builder::UrlPartsBuilder;
38use crate::html::{layout, sources, static_files};
39use crate::scrape_examples::AllCallLocations;
40use crate::{DOC_RUST_LANG_ORG_VERSION, try_err};
41
42pub(crate) struct Context<'tcx> {
50 pub(crate) current: Vec<Symbol>,
53 pub(crate) dst: PathBuf,
56 pub(super) deref_id_map: RefCell<DefIdMap<String>>,
59 pub(super) id_map: RefCell<IdMap>,
61 pub(crate) shared: SharedContext<'tcx>,
67 pub(crate) types_with_notable_traits: RefCell<FxIndexSet<clean::Type>>,
69 pub(crate) info: ContextInfo,
72}
73
74#[derive(Clone, Copy)]
81pub(crate) struct ContextInfo {
82 pub(super) render_redirect_pages: bool,
86 pub(crate) include_sources: bool,
90 pub(crate) is_inside_inlined_module: bool,
92}
93
94impl ContextInfo {
95 fn new(include_sources: bool) -> Self {
96 Self { render_redirect_pages: false, include_sources, is_inside_inlined_module: false }
97 }
98}
99
100pub(crate) struct SharedContext<'tcx> {
102 pub(crate) tcx: TyCtxt<'tcx>,
103 pub(crate) src_root: PathBuf,
106 pub(crate) layout: layout::Layout,
109 pub(crate) local_sources: FxIndexMap<PathBuf, String>,
111 pub(super) show_type_layout: bool,
113 pub(super) issue_tracker_base_url: Option<String>,
116 created_dirs: RefCell<FxHashSet<PathBuf>>,
119 pub(super) module_sorting: ModuleSorting,
122 pub(crate) style_files: Vec<StylePath>,
124 pub(crate) resource_suffix: String,
127 pub(crate) static_root_path: Option<String>,
130 pub(crate) fs: DocFS,
132 pub(super) codes: ErrorCodes,
133 pub(super) playground: Option<markdown::Playground>,
134 all: RefCell<AllTypes>,
135 errors: Receiver<String>,
138 redirections: Option<RefCell<FxHashMap<String, String>>>,
142
143 pub(crate) span_correspondence_map: FxHashMap<Span, LinkFromSrc>,
146 pub(crate) expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>,
147 pub(crate) cache: Cache,
149 pub(crate) call_locations: AllCallLocations,
150 should_merge: ShouldMerge,
153}
154
155impl SharedContext<'_> {
156 pub(crate) fn ensure_dir(&self, dst: &Path) -> Result<(), Error> {
157 let mut dirs = self.created_dirs.borrow_mut();
158 if !dirs.contains(dst) {
159 try_err!(self.fs.create_dir_all(dst), dst);
160 dirs.insert(dst.to_path_buf());
161 }
162
163 Ok(())
164 }
165
166 pub(crate) fn edition(&self) -> Edition {
167 self.tcx.sess.edition()
168 }
169}
170
171impl<'tcx> Context<'tcx> {
172 pub(crate) fn tcx(&self) -> TyCtxt<'tcx> {
173 self.shared.tcx
174 }
175
176 pub(crate) fn cache(&self) -> &Cache {
177 &self.shared.cache
178 }
179
180 pub(super) fn sess(&self) -> &'tcx Session {
181 self.shared.tcx.sess
182 }
183
184 pub(super) fn derive_id<S: AsRef<str> + ToString>(&self, id: S) -> String {
185 self.id_map.borrow_mut().derive(id)
186 }
187
188 pub(super) fn root_path(&self) -> String {
191 "../".repeat(self.current.len())
192 }
193
194 fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String {
195 let mut render_redirect_pages = self.info.render_redirect_pages;
196 if it.is_stripped()
199 && let Some(def_id) = it.def_id()
200 && def_id.is_local()
201 && (self.info.is_inside_inlined_module
202 || self.shared.cache.inlined_items.contains(&def_id))
203 {
204 render_redirect_pages = true;
207 }
208
209 if !render_redirect_pages {
210 let mut title = String::new();
211 if !is_module {
212 title.push_str(it.name.unwrap().as_str());
213 }
214 let short_title;
215 let short_title = if is_module {
216 let module_name = self.current.last().unwrap();
217 short_title = if it.is_crate() {
218 format!("Crate {module_name}")
219 } else {
220 format!("Module {module_name}")
221 };
222 &short_title[..]
223 } else {
224 it.name.as_ref().unwrap().as_str()
225 };
226 if !it.is_fake_item() {
227 if !is_module {
228 title.push_str(" in ");
229 }
230 title.push_str(&join_path_syms(&self.current));
232 };
233 title.push_str(" - Rust");
234 let tyname = it.type_();
235 let desc = plain_text_summary(&it.doc_value(), &it.link_names(self.cache()));
236 let desc = if !desc.is_empty() {
237 desc
238 } else if it.is_crate() {
239 format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate)
240 } else {
241 format!(
242 "API documentation for the Rust `{name}` {tyname} in crate `{krate}`.",
243 name = it.name.as_ref().unwrap(),
244 krate = self.shared.layout.krate,
245 )
246 };
247
248 let name;
249 let tyname_s = if it.is_crate() {
250 name = format!("{tyname} crate");
251 name.as_str()
252 } else {
253 tyname.as_str()
254 };
255
256 let content = print_item(self, it);
257 let page = layout::Page {
258 css_class: tyname_s,
259 root_path: &self.root_path(),
260 static_root_path: self.shared.static_root_path.as_deref(),
261 title: &title,
262 short_title,
263 description: &desc,
264 resource_suffix: &self.shared.resource_suffix,
265 rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), |d| {
266 d.rust_logo.is_some()
267 }),
268 };
269 layout::render(
270 &self.shared.layout,
271 &page,
272 fmt::from_fn(|f| print_sidebar(self, it, f)),
273 content,
274 &self.shared.style_files,
275 )
276 } else {
277 if let Some(&(ref names, ty)) = self.cache().paths.get(&it.item_id.expect_def_id())
278 && (self.current.len() + 1 != names.len()
279 || self.current.iter().zip(names.iter()).any(|(a, b)| a != b))
280 {
281 let path = fmt::from_fn(|f| {
286 for name in &names[..names.len() - 1] {
287 write!(f, "{name}/")?;
288 }
289 write!(f, "{}", print_item_path(ty, names.last().unwrap().as_str()))
290 });
291 match self.shared.redirections {
292 Some(ref redirections) => {
293 let mut current_path = String::new();
294 for name in &self.current {
295 current_path.push_str(name.as_str());
296 current_path.push('/');
297 }
298 let _ = write!(
299 current_path,
300 "{}",
301 print_item_path(ty, names.last().unwrap().as_str())
302 );
303 redirections.borrow_mut().insert(current_path, path.to_string());
304 }
305 None => {
306 return layout::redirect(&format!("{root}{path}", root = self.root_path()));
307 }
308 }
309 }
310 String::new()
311 }
312 }
313
314 fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<String>> {
316 let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new();
318 let mut inserted: FxHashMap<ItemType, FxHashSet<Symbol>> = FxHashMap::default();
319
320 for item in &m.items {
321 if item.is_stripped() {
322 continue;
323 }
324
325 let short = item.type_();
326 let myname = match item.name {
327 None => continue,
328 Some(s) => s,
329 };
330 if inserted.entry(short).or_default().insert(myname) {
331 let short = short.to_string();
332 let myname = myname.to_string();
333 map.entry(short).or_default().push(myname);
334 }
335 }
336
337 match self.shared.module_sorting {
338 ModuleSorting::Alphabetical => {
339 for items in map.values_mut() {
340 items.sort();
341 }
342 }
343 ModuleSorting::DeclarationOrder => {}
344 }
345 map
346 }
347
348 pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> {
358 self.href_from_span(item.span(self.tcx())?, true)
359 }
360
361 pub(crate) fn href_from_span(&self, span: clean::Span, with_lines: bool) -> Option<String> {
362 let mut root = self.root_path();
363 let mut path: String;
364 let cnum = span.cnum(self.sess());
365
366 let file = match span.filename(self.sess()) {
368 FileName::Real(ref path) => path.local_path_if_available().to_path_buf(),
369 _ => return None,
370 };
371 let file = &file;
372
373 let krate_sym;
374 let (krate, path) = if cnum == LOCAL_CRATE {
375 if let Some(path) = self.shared.local_sources.get(file) {
376 (self.shared.layout.krate.as_str(), path)
377 } else {
378 return None;
379 }
380 } else {
381 let (krate, src_root) = match *self.cache().extern_locations.get(&cnum)? {
382 ExternalLocation::Local => {
383 let e = ExternalCrate { crate_num: cnum };
384 (e.name(self.tcx()), e.src_root(self.tcx()))
385 }
386 ExternalLocation::Remote(ref s) => {
387 root = s.to_string();
388 let e = ExternalCrate { crate_num: cnum };
389 (e.name(self.tcx()), e.src_root(self.tcx()))
390 }
391 ExternalLocation::Unknown => return None,
392 };
393
394 let href = RefCell::new(PathBuf::new());
395 sources::clean_path(
396 &src_root,
397 file,
398 |component| {
399 href.borrow_mut().push(component);
400 },
401 || {
402 href.borrow_mut().pop();
403 },
404 );
405
406 path = href.into_inner().to_string_lossy().into_owned();
407
408 if let Some(c) = path.as_bytes().last()
409 && *c != b'/'
410 {
411 path.push('/');
412 }
413
414 let mut fname = file.file_name().expect("source has no filename").to_os_string();
415 fname.push(".html");
416 path.push_str(&fname.to_string_lossy());
417 krate_sym = krate;
418 (krate_sym.as_str(), &path)
419 };
420
421 let anchor = if with_lines {
422 let loline = span.lo(self.sess()).line;
423 let hiline = span.hi(self.sess()).line;
424 format!(
425 "#{}",
426 if loline == hiline { loline.to_string() } else { format!("{loline}-{hiline}") }
427 )
428 } else {
429 "".to_string()
430 };
431 Some(format!(
432 "{root}src/{krate}/{path}{anchor}",
433 root = Escape(&root),
434 krate = krate,
435 path = path,
436 anchor = anchor
437 ))
438 }
439
440 pub(crate) fn href_from_span_relative(
441 &self,
442 span: clean::Span,
443 relative_to: &str,
444 ) -> Option<String> {
445 self.href_from_span(span, false).map(|s| {
446 let mut url = UrlPartsBuilder::new();
447 let mut dest_href_parts = s.split('/');
448 let mut cur_href_parts = relative_to.split('/');
449 for (cur_href_part, dest_href_part) in (&mut cur_href_parts).zip(&mut dest_href_parts) {
450 if cur_href_part != dest_href_part {
451 url.push(dest_href_part);
452 break;
453 }
454 }
455 for dest_href_part in dest_href_parts {
456 url.push(dest_href_part);
457 }
458 let loline = span.lo(self.sess()).line;
459 let hiline = span.hi(self.sess()).line;
460 format!(
461 "{}{}#{}",
462 "../".repeat(cur_href_parts.count()),
463 url.finish(),
464 if loline == hiline { loline.to_string() } else { format!("{loline}-{hiline}") }
465 )
466 })
467 }
468}
469
470impl<'tcx> Context<'tcx> {
471 pub(crate) fn init(
472 krate: clean::Crate,
473 options: RenderOptions,
474 cache: Cache,
475 tcx: TyCtxt<'tcx>,
476 expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>,
477 ) -> Result<(Self, clean::Crate), Error> {
478 let md_opts = options.clone();
480 let emit_crate = options.should_emit_crate();
481 let RenderOptions {
482 output,
483 external_html,
484 id_map,
485 playground_url,
486 module_sorting,
487 themes: style_files,
488 default_settings,
489 extension_css,
490 resource_suffix,
491 static_root_path,
492 generate_redirect_map,
493 show_type_layout,
494 generate_link_to_definition,
495 call_locations,
496 no_emit_shared,
497 html_no_source,
498 ..
499 } = options;
500
501 let src_root = match krate.src(tcx) {
502 FileName::Real(ref p) => match p.local_path_if_available().parent() {
503 Some(p) => p.to_path_buf(),
504 None => PathBuf::new(),
505 },
506 _ => PathBuf::new(),
507 };
508 let mut playground = None;
510 if let Some(url) = playground_url {
511 playground = Some(markdown::Playground { crate_name: Some(krate.name(tcx)), url });
512 }
513 let krate_version = cache.crate_version.as_deref().unwrap_or_default();
514 let mut layout = layout::Layout {
515 logo: String::new(),
516 favicon: String::new(),
517 external_html,
518 default_settings,
519 krate: krate.name(tcx).to_string(),
520 krate_version: krate_version.to_string(),
521 css_file_extension: extension_css,
522 scrape_examples_extension: !call_locations.is_empty(),
523 };
524 let mut issue_tracker_base_url = None;
525 let mut include_sources = !html_no_source;
526
527 for attr in &krate.module.attrs.other_attrs {
530 let Attribute::Parsed(AttributeKind::Doc(d)) = attr else { continue };
531 if let Some((html_favicon_url, _)) = d.html_favicon_url {
532 layout.favicon = html_favicon_url.to_string();
533 }
534 if let Some((html_logo_url, _)) = d.html_logo_url {
535 layout.logo = html_logo_url.to_string();
536 }
537 if let Some((html_playground_url, _)) = d.html_playground_url {
538 playground = Some(markdown::Playground {
539 crate_name: Some(krate.name(tcx)),
540 url: html_playground_url.to_string(),
541 });
542 }
543 if let Some((s, _)) = d.issue_tracker_base_url {
544 issue_tracker_base_url = Some(s.to_string());
545 }
546 if d.html_no_source.is_some() {
547 include_sources = false;
548 }
549 }
550
551 let (local_sources, matches) = collect_spans_and_sources(
552 tcx,
553 &krate,
554 &src_root,
555 include_sources,
556 generate_link_to_definition,
557 );
558
559 let (sender, receiver) = channel();
560 let scx = SharedContext {
561 tcx,
562 src_root,
563 local_sources,
564 issue_tracker_base_url,
565 layout,
566 created_dirs: Default::default(),
567 module_sorting,
568 style_files,
569 resource_suffix,
570 static_root_path,
571 fs: DocFS::new(sender),
572 codes: ErrorCodes::from(options.unstable_features.is_nightly_build()),
573 playground,
574 all: RefCell::new(AllTypes::new()),
575 errors: receiver,
576 redirections: if generate_redirect_map { Some(Default::default()) } else { None },
577 show_type_layout,
578 span_correspondence_map: matches,
579 cache,
580 call_locations,
581 should_merge: options.should_merge,
582 expanded_codes,
583 };
584
585 let dst = output;
586 scx.ensure_dir(&dst)?;
587
588 let mut cx = Context {
589 current: Vec::new(),
590 dst,
591 id_map: RefCell::new(id_map),
592 deref_id_map: Default::default(),
593 shared: scx,
594 types_with_notable_traits: RefCell::new(FxIndexSet::default()),
595 info: ContextInfo::new(include_sources),
596 };
597
598 if emit_crate {
599 sources::render(&mut cx, &krate)?;
600 }
601
602 if !no_emit_shared {
603 write_shared(&mut cx, &krate, &md_opts, tcx)?;
604 }
605
606 Ok((cx, krate))
607 }
608}
609
610impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
612 fn descr() -> &'static str {
613 "html"
614 }
615
616 const RUN_ON_MODULE: bool = true;
617 type ModuleData = ContextInfo;
618
619 fn save_module_data(&mut self) -> Self::ModuleData {
620 self.deref_id_map.borrow_mut().clear();
621 self.id_map.borrow_mut().clear();
622 self.types_with_notable_traits.borrow_mut().clear();
623 self.info
624 }
625
626 fn restore_module_data(&mut self, info: Self::ModuleData) {
627 self.info = info;
628 }
629
630 fn after_krate(mut self) -> Result<(), Error> {
631 let crate_name = self.tcx().crate_name(LOCAL_CRATE);
632 let final_file = self.dst.join(crate_name.as_str()).join("all.html");
633 let settings_file = self.dst.join("settings.html");
634 let help_file = self.dst.join("help.html");
635 let scrape_examples_help_file = self.dst.join("scrape-examples-help.html");
636
637 let mut root_path = self.dst.to_str().expect("invalid path").to_owned();
638 if !root_path.ends_with('/') {
639 root_path.push('/');
640 }
641 let shared = &self.shared;
642 let mut page = layout::Page {
643 title: "List of all items in this crate",
644 short_title: "All",
645 css_class: "mod sys",
646 root_path: "../",
647 static_root_path: shared.static_root_path.as_deref(),
648 description: "List of all items in this crate",
649 resource_suffix: &shared.resource_suffix,
650 rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), |d| d.rust_logo.is_some()),
651 };
652 let all = shared.all.replace(AllTypes::new());
653 let mut sidebar = String::new();
654
655 let blocks = sidebar_module_like(all.item_sections(), &mut IdMap::new(), ModuleLike::Crate);
657 let bar = Sidebar {
658 title_prefix: "",
659 title: "",
660 is_crate: false,
661 is_mod: false,
662 parent_is_crate: false,
663 blocks: vec![blocks],
664 path: String::new(),
665 };
666
667 bar.render_into(&mut sidebar).unwrap();
668
669 let v = layout::render(&shared.layout, &page, sidebar, all.print(), &shared.style_files);
670 shared.fs.write(final_file, v)?;
671
672 if shared.should_merge.write_rendered_cci {
674 page.title = "Settings";
676 page.description = "Settings of Rustdoc";
677 page.root_path = "./";
678 page.rust_logo = true;
679
680 let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
681 let v = layout::render(
682 &shared.layout,
683 &page,
684 sidebar,
685 fmt::from_fn(|buf| {
686 write!(
687 buf,
688 "<div class=\"main-heading\">\
689 <h1>Rustdoc settings</h1>\
690 <span class=\"out-of-band\">\
691 <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
692 Back\
693 </a>\
694 </span>\
695 </div>\
696 <noscript>\
697 <section>\
698 You need to enable JavaScript be able to update your settings.\
699 </section>\
700 </noscript>\
701 <script defer src=\"{static_root_path}{settings_js}\"></script>",
702 static_root_path = page.get_static_root_path(),
703 settings_js = static_files::STATIC_FILES.settings_js,
704 )?;
705 for file in &shared.style_files {
710 if let Ok(theme) = file.basename() {
711 write!(
712 buf,
713 "<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
714 as=\"style\">",
715 root_path = page.static_root_path.unwrap_or(""),
716 suffix = page.resource_suffix,
717 )?;
718 }
719 }
720 Ok(())
721 }),
722 &shared.style_files,
723 );
724 shared.fs.write(settings_file, v)?;
725
726 page.title = "Help";
728 page.description = "Documentation for Rustdoc";
729 page.root_path = "./";
730 page.rust_logo = true;
731
732 let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
733 let v = layout::render(
734 &shared.layout,
735 &page,
736 sidebar,
737 format_args!(
738 "<div class=\"main-heading\">\
739 <h1>Rustdoc help</h1>\
740 <span class=\"out-of-band\">\
741 <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
742 Back\
743 </a>\
744 </span>\
745 </div>\
746 <noscript>\
747 <section>\
748 <p>You need to enable JavaScript to use keyboard commands or search.</p>\
749 <p>For more information, browse the <a href=\"{DOC_RUST_LANG_ORG_VERSION}/rustdoc/\">rustdoc handbook</a>.</p>\
750 </section>\
751 </noscript>",
752 ),
753 &shared.style_files,
754 );
755 shared.fs.write(help_file, v)?;
756 }
757
758 if shared.layout.scrape_examples_extension && shared.should_merge.write_rendered_cci {
760 page.title = "About scraped examples";
761 page.description = "How the scraped examples feature works in Rustdoc";
762 let v = layout::render(
763 &shared.layout,
764 &page,
765 "",
766 scrape_examples_help(shared),
767 &shared.style_files,
768 );
769 shared.fs.write(scrape_examples_help_file, v)?;
770 }
771
772 if let Some(ref redirections) = shared.redirections
773 && !redirections.borrow().is_empty()
774 {
775 let redirect_map_path = self.dst.join(crate_name.as_str()).join("redirect-map.json");
776 let paths = serde_json::to_string(&*redirections.borrow()).unwrap();
777 shared.ensure_dir(&self.dst.join(crate_name.as_str()))?;
778 shared.fs.write(redirect_map_path, paths)?;
779 }
780
781 self.shared.fs.close();
783 let nb_errors = self.shared.errors.iter().map(|err| self.tcx().dcx().err(err)).count();
784 if nb_errors > 0 { Err(Error::new(io::Error::other("I/O error"), "")) } else { Ok(()) }
785 }
786
787 fn mod_item_in(&mut self, item: &clean::Item) -> Result<(), Error> {
788 if !self.info.render_redirect_pages {
796 self.info.render_redirect_pages = item.is_stripped();
797 }
798 let item_name = item.name.unwrap();
799 self.dst.push(item_name.as_str());
800 self.current.push(item_name);
801
802 info!("Recursing into {}", self.dst.display());
803
804 if !item.is_stripped() {
805 let buf = self.render_item(item, true);
806 if !buf.is_empty() {
808 self.shared.ensure_dir(&self.dst)?;
809 let joint_dst = self.dst.join("index.html");
810 self.shared.fs.write(joint_dst, buf)?;
811 }
812 }
813 if !self.info.is_inside_inlined_module {
814 if let Some(def_id) = item.def_id()
815 && self.cache().inlined_items.contains(&def_id)
816 {
817 self.info.is_inside_inlined_module = true;
818 }
819 } else if !self.cache().document_hidden && item.is_doc_hidden() {
820 self.info.is_inside_inlined_module = false;
822 }
823
824 if !self.info.render_redirect_pages {
826 let (clean::StrippedItem(box clean::ModuleItem(ref module))
827 | clean::ModuleItem(ref module)) = item.kind
828 else {
829 unreachable!()
830 };
831 let items = self.build_sidebar_items(module);
832 let js_dst = self.dst.join(format!("sidebar-items{}.js", self.shared.resource_suffix));
833 let v = format!("window.SIDEBAR_ITEMS = {};", serde_json::to_string(&items).unwrap());
834 self.shared.fs.write(js_dst, v)?;
835 }
836 Ok(())
837 }
838
839 fn mod_item_out(&mut self) -> Result<(), Error> {
840 info!("Recursed; leaving {}", self.dst.display());
841
842 self.dst.pop();
844 self.current.pop();
845 Ok(())
846 }
847
848 fn item(&mut self, item: &clean::Item) -> Result<(), Error> {
849 if !self.info.render_redirect_pages {
857 self.info.render_redirect_pages = item.is_stripped();
858 }
859
860 let buf = self.render_item(item, false);
861 if !buf.is_empty() {
863 let name = item.name.as_ref().unwrap();
864 let item_type = item.type_();
865 let file_name = print_item_path(item_type, name.as_str()).to_string();
866 self.shared.ensure_dir(&self.dst)?;
867 let joint_dst = self.dst.join(&file_name);
868 self.shared.fs.write(joint_dst, buf)?;
869
870 if !self.info.render_redirect_pages {
871 self.shared.all.borrow_mut().append(full_path(self, item), &item_type);
872 }
873 if item_type == ItemType::Macro {
876 let redir_name = format!("{item_type}.{name}!.html");
877 if let Some(ref redirections) = self.shared.redirections {
878 let crate_name = &self.shared.layout.krate;
879 redirections.borrow_mut().insert(
880 format!("{crate_name}/{redir_name}"),
881 format!("{crate_name}/{file_name}"),
882 );
883 } else {
884 let v = layout::redirect(&file_name);
885 let redir_dst = self.dst.join(redir_name);
886 self.shared.fs.write(redir_dst, v)?;
887 }
888 }
889 }
890
891 Ok(())
892 }
893}