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