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