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