]>
Commit | Line | Data |
---|---|---|
6a06907d XL |
1 | use std::cell::RefCell; |
2 | use std::collections::BTreeMap; | |
3 | use std::io; | |
4 | use std::path::PathBuf; | |
5 | use std::rc::Rc; | |
6 | use std::sync::mpsc::channel; | |
7 | ||
36d6ef2b | 8 | use rustc_hir::def_id::{LOCAL_CRATE}; |
6a06907d XL |
9 | use rustc_middle::ty::TyCtxt; |
10 | use rustc_session::Session; | |
11 | use rustc_span::edition::Edition; | |
12 | use rustc_span::source_map::FileName; | |
13 | use rustc_span::symbol::sym; | |
14 | ||
15 | use super::cache::{build_index, ExternalLocation}; | |
16 | use super::print_item::{full_path, item_path, print_item}; | |
17 | use super::write_shared::write_shared; | |
18 | use super::{ | |
19 | print_sidebar, settings, AllTypes, NameDoc, SharedContext, StylePath, BASIC_KEYWORDS, | |
20 | CURRENT_DEPTH, INITIAL_IDS, | |
21 | }; | |
22 | ||
23 | use crate::clean::{self, AttributesExt}; | |
24 | use crate::config::RenderOptions; | |
25 | use crate::docfs::{DocFS, PathError}; | |
26 | use crate::error::Error; | |
27 | use crate::formats::cache::Cache; | |
28 | use crate::formats::item_type::ItemType; | |
29 | use crate::formats::FormatRenderer; | |
30 | use crate::html::escape::Escape; | |
31 | use crate::html::format::Buffer; | |
32 | use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap}; | |
33 | use crate::html::{layout, sources}; | |
34 | ||
35 | /// Major driving force in all rustdoc rendering. This contains information | |
36 | /// about where in the tree-like hierarchy rendering is occurring and controls | |
37 | /// how the current page is being rendered. | |
38 | /// | |
39 | /// It is intended that this context is a lightweight object which can be fairly | |
40 | /// easily cloned because it is cloned per work-job (about once per item in the | |
41 | /// rustdoc tree). | |
42 | crate struct Context<'tcx> { | |
43 | /// Current hierarchy of components leading down to what's currently being | |
44 | /// rendered | |
45 | pub(super) current: Vec<String>, | |
46 | /// The current destination folder of where HTML artifacts should be placed. | |
47 | /// This changes as the context descends into the module hierarchy. | |
48 | pub(super) dst: PathBuf, | |
49 | /// A flag, which when `true`, will render pages which redirect to the | |
50 | /// real location of an item. This is used to allow external links to | |
51 | /// publicly reused items to redirect to the right location. | |
52 | pub(super) render_redirect_pages: bool, | |
53 | /// The map used to ensure all generated 'id=' attributes are unique. | |
54 | pub(super) id_map: RefCell<IdMap>, | |
6a06907d XL |
55 | /// Shared mutable state. |
56 | /// | |
57 | /// Issue for improving the situation: [#82381][] | |
58 | /// | |
59 | /// [#82381]: https://github.com/rust-lang/rust/issues/82381 | |
60 | pub(super) shared: Rc<SharedContext<'tcx>>, | |
61 | /// The [`Cache`] used during rendering. | |
62 | /// | |
63 | /// Ideally the cache would be in [`SharedContext`], but it's mutated | |
64 | /// between when the `SharedContext` is created and when `Context` | |
65 | /// is created, so more refactoring would be needed. | |
66 | /// | |
67 | /// It's immutable once in `Context`, so it's not as bad that it's not in | |
68 | /// `SharedContext`. | |
69 | // FIXME: move `cache` to `SharedContext` | |
70 | pub(super) cache: Rc<Cache>, | |
71 | } | |
72 | ||
73 | // `Context` is cloned a lot, so we don't want the size to grow unexpectedly. | |
74 | #[cfg(target_arch = "x86_64")] | |
36d6ef2b | 75 | rustc_data_structures::static_assert_size!(Context<'_>, 112); |
6a06907d XL |
76 | |
77 | impl<'tcx> Context<'tcx> { | |
78 | pub(super) fn path(&self, filename: &str) -> PathBuf { | |
79 | // We use splitn vs Path::extension here because we might get a filename | |
80 | // like `style.min.css` and we want to process that into | |
81 | // `style-suffix.min.css`. Path::extension would just return `css` | |
82 | // which would result in `style.min-suffix.css` which isn't what we | |
83 | // want. | |
84 | let (base, ext) = filename.split_once('.').unwrap(); | |
85 | let filename = format!("{}{}.{}", base, self.shared.resource_suffix, ext); | |
86 | self.dst.join(&filename) | |
87 | } | |
88 | ||
89 | pub(super) fn tcx(&self) -> TyCtxt<'tcx> { | |
90 | self.shared.tcx | |
91 | } | |
92 | ||
93 | fn sess(&self) -> &'tcx Session { | |
94 | &self.shared.tcx.sess | |
95 | } | |
96 | ||
97 | pub(super) fn derive_id(&self, id: String) -> String { | |
98 | let mut map = self.id_map.borrow_mut(); | |
99 | map.derive(id) | |
100 | } | |
101 | ||
102 | /// String representation of how to get back to the root path of the 'doc/' | |
103 | /// folder in terms of a relative URL. | |
104 | pub(super) fn root_path(&self) -> String { | |
105 | "../".repeat(self.current.len()) | |
106 | } | |
107 | ||
108 | fn render_item(&self, it: &clean::Item, pushname: bool) -> String { | |
109 | // A little unfortunate that this is done like this, but it sure | |
110 | // does make formatting *a lot* nicer. | |
111 | CURRENT_DEPTH.with(|slot| { | |
112 | slot.set(self.current.len()); | |
113 | }); | |
114 | ||
115 | let mut title = if it.is_primitive() || it.is_keyword() { | |
116 | // No need to include the namespace for primitive types and keywords | |
117 | String::new() | |
118 | } else { | |
119 | self.current.join("::") | |
120 | }; | |
121 | if pushname { | |
122 | if !title.is_empty() { | |
123 | title.push_str("::"); | |
124 | } | |
125 | title.push_str(&it.name.unwrap().as_str()); | |
126 | } | |
127 | title.push_str(" - Rust"); | |
128 | let tyname = it.type_(); | |
129 | let desc = it.doc_value().as_ref().map(|doc| plain_text_summary(&doc)); | |
130 | let desc = if let Some(desc) = desc { | |
131 | desc | |
132 | } else if it.is_crate() { | |
133 | format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) | |
134 | } else { | |
135 | format!( | |
136 | "API documentation for the Rust `{}` {} in crate `{}`.", | |
137 | it.name.as_ref().unwrap(), | |
138 | tyname, | |
139 | self.shared.layout.krate | |
140 | ) | |
141 | }; | |
142 | let keywords = make_item_keywords(it); | |
143 | let page = layout::Page { | |
144 | css_class: tyname.as_str(), | |
145 | root_path: &self.root_path(), | |
146 | static_root_path: self.shared.static_root_path.as_deref(), | |
147 | title: &title, | |
148 | description: &desc, | |
149 | keywords: &keywords, | |
150 | resource_suffix: &self.shared.resource_suffix, | |
151 | extra_scripts: &[], | |
152 | static_extra_scripts: &[], | |
153 | }; | |
154 | ||
155 | if !self.render_redirect_pages { | |
156 | layout::render( | |
157 | &self.shared.layout, | |
158 | &page, | |
159 | |buf: &mut _| print_sidebar(self, it, buf), | |
160 | |buf: &mut _| print_item(self, it, buf), | |
161 | &self.shared.style_files, | |
162 | ) | |
163 | } else { | |
164 | if let Some(&(ref names, ty)) = self.cache.paths.get(&it.def_id) { | |
165 | let mut path = String::new(); | |
166 | for name in &names[..names.len() - 1] { | |
167 | path.push_str(name); | |
168 | path.push('/'); | |
169 | } | |
170 | path.push_str(&item_path(ty, names.last().unwrap())); | |
171 | match self.shared.redirections { | |
172 | Some(ref redirections) => { | |
173 | let mut current_path = String::new(); | |
174 | for name in &self.current { | |
175 | current_path.push_str(name); | |
176 | current_path.push('/'); | |
177 | } | |
178 | current_path.push_str(&item_path(ty, names.last().unwrap())); | |
179 | redirections.borrow_mut().insert(current_path, path); | |
180 | } | |
181 | None => return layout::redirect(&format!("{}{}", self.root_path(), path)), | |
182 | } | |
183 | } | |
184 | String::new() | |
185 | } | |
186 | } | |
187 | ||
188 | /// Construct a map of items shown in the sidebar to a plain-text summary of their docs. | |
189 | fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> { | |
190 | // BTreeMap instead of HashMap to get a sorted output | |
191 | let mut map: BTreeMap<_, Vec<_>> = BTreeMap::new(); | |
192 | for item in &m.items { | |
193 | if item.is_stripped() { | |
194 | continue; | |
195 | } | |
196 | ||
197 | let short = item.type_(); | |
198 | let myname = match item.name { | |
199 | None => continue, | |
200 | Some(ref s) => s.to_string(), | |
201 | }; | |
202 | let short = short.to_string(); | |
203 | map.entry(short).or_default().push(( | |
204 | myname, | |
205 | Some(item.doc_value().map_or_else(String::new, |s| plain_text_summary(&s))), | |
206 | )); | |
207 | } | |
208 | ||
209 | if self.shared.sort_modules_alphabetically { | |
210 | for items in map.values_mut() { | |
211 | items.sort(); | |
212 | } | |
213 | } | |
214 | map | |
215 | } | |
216 | ||
217 | /// Generates a url appropriate for an `href` attribute back to the source of | |
218 | /// this item. | |
219 | /// | |
220 | /// The url generated, when clicked, will redirect the browser back to the | |
221 | /// original source code. | |
222 | /// | |
223 | /// If `None` is returned, then a source link couldn't be generated. This | |
224 | /// may happen, for example, with externally inlined items where the source | |
225 | /// of their crate documentation isn't known. | |
226 | pub(super) fn src_href(&self, item: &clean::Item) -> Option<String> { | |
227 | if item.source.is_dummy() { | |
228 | return None; | |
229 | } | |
230 | let mut root = self.root_path(); | |
231 | let mut path = String::new(); | |
232 | let cnum = item.source.cnum(self.sess()); | |
233 | ||
234 | // We can safely ignore synthetic `SourceFile`s. | |
235 | let file = match item.source.filename(self.sess()) { | |
236 | FileName::Real(ref path) => path.local_path().to_path_buf(), | |
237 | _ => return None, | |
238 | }; | |
239 | let file = &file; | |
240 | ||
241 | let symbol; | |
242 | let (krate, path) = if cnum == LOCAL_CRATE { | |
243 | if let Some(path) = self.shared.local_sources.get(file) { | |
244 | (self.shared.layout.krate.as_str(), path) | |
245 | } else { | |
246 | return None; | |
247 | } | |
248 | } else { | |
249 | let (krate, src_root) = match *self.cache.extern_locations.get(&cnum)? { | |
250 | (name, ref src, ExternalLocation::Local) => (name, src), | |
251 | (name, ref src, ExternalLocation::Remote(ref s)) => { | |
252 | root = s.to_string(); | |
253 | (name, src) | |
254 | } | |
255 | (_, _, ExternalLocation::Unknown) => return None, | |
256 | }; | |
257 | ||
258 | sources::clean_path(&src_root, file, false, |component| { | |
259 | path.push_str(&component.to_string_lossy()); | |
260 | path.push('/'); | |
261 | }); | |
262 | let mut fname = file.file_name().expect("source has no filename").to_os_string(); | |
263 | fname.push(".html"); | |
264 | path.push_str(&fname.to_string_lossy()); | |
265 | symbol = krate.as_str(); | |
266 | (&*symbol, &path) | |
267 | }; | |
268 | ||
269 | let loline = item.source.lo(self.sess()).line; | |
270 | let hiline = item.source.hi(self.sess()).line; | |
271 | let lines = | |
272 | if loline == hiline { loline.to_string() } else { format!("{}-{}", loline, hiline) }; | |
273 | Some(format!( | |
274 | "{root}src/{krate}/{path}#{lines}", | |
275 | root = Escape(&root), | |
276 | krate = krate, | |
277 | path = path, | |
278 | lines = lines | |
279 | )) | |
280 | } | |
281 | } | |
282 | ||
283 | /// Generates the documentation for `crate` into the directory `dst` | |
284 | impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { | |
285 | fn descr() -> &'static str { | |
286 | "html" | |
287 | } | |
288 | ||
289 | fn init( | |
290 | mut krate: clean::Crate, | |
291 | options: RenderOptions, | |
292 | edition: Edition, | |
293 | mut cache: Cache, | |
294 | tcx: TyCtxt<'tcx>, | |
295 | ) -> Result<(Self, clean::Crate), Error> { | |
296 | // need to save a copy of the options for rendering the index page | |
297 | let md_opts = options.clone(); | |
298 | let RenderOptions { | |
299 | output, | |
300 | external_html, | |
301 | id_map, | |
302 | playground_url, | |
303 | sort_modules_alphabetically, | |
304 | themes: style_files, | |
305 | default_settings, | |
306 | extension_css, | |
307 | resource_suffix, | |
308 | static_root_path, | |
309 | generate_search_filter, | |
310 | unstable_features, | |
311 | generate_redirect_map, | |
312 | .. | |
313 | } = options; | |
314 | ||
315 | let src_root = match krate.src { | |
316 | FileName::Real(ref p) => match p.local_path().parent() { | |
317 | Some(p) => p.to_path_buf(), | |
318 | None => PathBuf::new(), | |
319 | }, | |
320 | _ => PathBuf::new(), | |
321 | }; | |
322 | // If user passed in `--playground-url` arg, we fill in crate name here | |
323 | let mut playground = None; | |
324 | if let Some(url) = playground_url { | |
325 | playground = | |
326 | Some(markdown::Playground { crate_name: Some(krate.name.to_string()), url }); | |
327 | } | |
328 | let mut layout = layout::Layout { | |
329 | logo: String::new(), | |
330 | favicon: String::new(), | |
331 | external_html, | |
332 | default_settings, | |
333 | krate: krate.name.to_string(), | |
334 | css_file_extension: extension_css, | |
335 | generate_search_filter, | |
336 | }; | |
337 | let mut issue_tracker_base_url = None; | |
338 | let mut include_sources = true; | |
339 | ||
340 | // Crawl the crate attributes looking for attributes which control how we're | |
341 | // going to emit HTML | |
342 | if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) { | |
343 | for attr in attrs.lists(sym::doc) { | |
344 | match (attr.name_or_empty(), attr.value_str()) { | |
345 | (sym::html_favicon_url, Some(s)) => { | |
346 | layout.favicon = s.to_string(); | |
347 | } | |
348 | (sym::html_logo_url, Some(s)) => { | |
349 | layout.logo = s.to_string(); | |
350 | } | |
351 | (sym::html_playground_url, Some(s)) => { | |
352 | playground = Some(markdown::Playground { | |
353 | crate_name: Some(krate.name.to_string()), | |
354 | url: s.to_string(), | |
355 | }); | |
356 | } | |
357 | (sym::issue_tracker_base_url, Some(s)) => { | |
358 | issue_tracker_base_url = Some(s.to_string()); | |
359 | } | |
360 | (sym::html_no_source, None) if attr.is_word() => { | |
361 | include_sources = false; | |
362 | } | |
363 | _ => {} | |
364 | } | |
365 | } | |
366 | } | |
367 | let (sender, receiver) = channel(); | |
368 | let mut scx = SharedContext { | |
369 | tcx, | |
370 | collapsed: krate.collapsed, | |
371 | src_root, | |
372 | include_sources, | |
373 | local_sources: Default::default(), | |
374 | issue_tracker_base_url, | |
375 | layout, | |
376 | created_dirs: Default::default(), | |
377 | sort_modules_alphabetically, | |
378 | style_files, | |
379 | resource_suffix, | |
380 | static_root_path, | |
381 | fs: DocFS::new(sender), | |
382 | edition, | |
383 | codes: ErrorCodes::from(unstable_features.is_nightly_build()), | |
384 | playground, | |
385 | all: RefCell::new(AllTypes::new()), | |
386 | errors: receiver, | |
387 | redirections: if generate_redirect_map { Some(Default::default()) } else { None }, | |
388 | }; | |
389 | ||
390 | // Add the default themes to the `Vec` of stylepaths | |
391 | // | |
392 | // Note that these must be added before `sources::render` is called | |
393 | // so that the resulting source pages are styled | |
394 | // | |
395 | // `light.css` is not disabled because it is the stylesheet that stays loaded | |
396 | // by the browser as the theme stylesheet. The theme system (hackily) works by | |
397 | // changing the href to this stylesheet. All other themes are disabled to | |
398 | // prevent rule conflicts | |
399 | scx.style_files.push(StylePath { path: PathBuf::from("light.css"), disabled: false }); | |
400 | scx.style_files.push(StylePath { path: PathBuf::from("dark.css"), disabled: true }); | |
401 | scx.style_files.push(StylePath { path: PathBuf::from("ayu.css"), disabled: true }); | |
402 | ||
403 | let dst = output; | |
404 | scx.ensure_dir(&dst)?; | |
405 | krate = sources::render(&dst, &mut scx, krate)?; | |
406 | ||
407 | // Build our search index | |
408 | let index = build_index(&krate, &mut cache, tcx); | |
409 | ||
410 | let mut cx = Context { | |
411 | current: Vec::new(), | |
412 | dst, | |
413 | render_redirect_pages: false, | |
414 | id_map: RefCell::new(id_map), | |
6a06907d XL |
415 | shared: Rc::new(scx), |
416 | cache: Rc::new(cache), | |
417 | }; | |
418 | ||
419 | CURRENT_DEPTH.with(|s| s.set(0)); | |
420 | ||
421 | // Write shared runs within a flock; disable thread dispatching of IO temporarily. | |
422 | Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true); | |
423 | write_shared(&cx, &krate, index, &md_opts)?; | |
424 | Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false); | |
425 | Ok((cx, krate)) | |
426 | } | |
427 | ||
428 | fn make_child_renderer(&self) -> Self { | |
429 | let mut id_map = IdMap::new(); | |
430 | id_map.populate(&INITIAL_IDS); | |
431 | ||
432 | Self { | |
433 | current: self.current.clone(), | |
434 | dst: self.dst.clone(), | |
435 | render_redirect_pages: self.render_redirect_pages, | |
436 | id_map: RefCell::new(id_map), | |
6a06907d XL |
437 | shared: Rc::clone(&self.shared), |
438 | cache: Rc::clone(&self.cache), | |
439 | } | |
440 | } | |
441 | ||
442 | fn after_krate( | |
443 | &mut self, | |
444 | krate: &clean::Crate, | |
445 | diag: &rustc_errors::Handler, | |
446 | ) -> Result<(), Error> { | |
447 | let final_file = self.dst.join(&*krate.name.as_str()).join("all.html"); | |
448 | let settings_file = self.dst.join("settings.html"); | |
449 | let crate_name = krate.name; | |
450 | ||
451 | let mut root_path = self.dst.to_str().expect("invalid path").to_owned(); | |
452 | if !root_path.ends_with('/') { | |
453 | root_path.push('/'); | |
454 | } | |
455 | let mut page = layout::Page { | |
456 | title: "List of all items in this crate", | |
457 | css_class: "mod", | |
458 | root_path: "../", | |
459 | static_root_path: self.shared.static_root_path.as_deref(), | |
460 | description: "List of all items in this crate", | |
461 | keywords: BASIC_KEYWORDS, | |
462 | resource_suffix: &self.shared.resource_suffix, | |
463 | extra_scripts: &[], | |
464 | static_extra_scripts: &[], | |
465 | }; | |
466 | let sidebar = if let Some(ref version) = self.cache.crate_version { | |
467 | format!( | |
468 | "<p class=\"location\">Crate {}</p>\ | |
469 | <div class=\"block version\">\ | |
470 | <p>Version {}</p>\ | |
471 | </div>\ | |
472 | <a id=\"all-types\" href=\"index.html\"><p>Back to index</p></a>", | |
473 | crate_name, | |
474 | Escape(version), | |
475 | ) | |
476 | } else { | |
477 | String::new() | |
478 | }; | |
479 | let all = self.shared.all.replace(AllTypes::new()); | |
480 | let v = layout::render( | |
481 | &self.shared.layout, | |
482 | &page, | |
483 | sidebar, | |
484 | |buf: &mut Buffer| all.print(buf), | |
485 | &self.shared.style_files, | |
486 | ); | |
487 | self.shared.fs.write(&final_file, v.as_bytes())?; | |
488 | ||
489 | // Generating settings page. | |
490 | page.title = "Rustdoc settings"; | |
491 | page.description = "Settings of Rustdoc"; | |
492 | page.root_path = "./"; | |
493 | ||
494 | let mut style_files = self.shared.style_files.clone(); | |
495 | let sidebar = "<p class=\"location\">Settings</p><div class=\"sidebar-elems\"></div>"; | |
496 | style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false }); | |
497 | let v = layout::render( | |
498 | &self.shared.layout, | |
499 | &page, | |
500 | sidebar, | |
501 | settings( | |
502 | self.shared.static_root_path.as_deref().unwrap_or("./"), | |
503 | &self.shared.resource_suffix, | |
504 | &self.shared.style_files, | |
505 | )?, | |
506 | &style_files, | |
507 | ); | |
508 | self.shared.fs.write(&settings_file, v.as_bytes())?; | |
509 | if let Some(ref redirections) = self.shared.redirections { | |
510 | if !redirections.borrow().is_empty() { | |
511 | let redirect_map_path = | |
512 | self.dst.join(&*krate.name.as_str()).join("redirect-map.json"); | |
513 | let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); | |
514 | self.shared.ensure_dir(&self.dst.join(&*krate.name.as_str()))?; | |
515 | self.shared.fs.write(&redirect_map_path, paths.as_bytes())?; | |
516 | } | |
517 | } | |
518 | ||
519 | // Flush pending errors. | |
520 | Rc::get_mut(&mut self.shared).unwrap().fs.close(); | |
521 | let nb_errors = self.shared.errors.iter().map(|err| diag.struct_err(&err).emit()).count(); | |
522 | if nb_errors > 0 { | |
523 | Err(Error::new(io::Error::new(io::ErrorKind::Other, "I/O error"), "")) | |
524 | } else { | |
525 | Ok(()) | |
526 | } | |
527 | } | |
528 | ||
529 | fn mod_item_in(&mut self, item: &clean::Item, item_name: &str) -> Result<(), Error> { | |
530 | // Stripped modules survive the rustdoc passes (i.e., `strip-private`) | |
531 | // if they contain impls for public types. These modules can also | |
532 | // contain items such as publicly re-exported structures. | |
533 | // | |
534 | // External crates will provide links to these structures, so | |
535 | // these modules are recursed into, but not rendered normally | |
536 | // (a flag on the context). | |
537 | if !self.render_redirect_pages { | |
538 | self.render_redirect_pages = item.is_stripped(); | |
539 | } | |
540 | let scx = &self.shared; | |
541 | self.dst.push(item_name); | |
542 | self.current.push(item_name.to_owned()); | |
543 | ||
544 | info!("Recursing into {}", self.dst.display()); | |
545 | ||
546 | let buf = self.render_item(item, false); | |
547 | // buf will be empty if the module is stripped and there is no redirect for it | |
548 | if !buf.is_empty() { | |
549 | self.shared.ensure_dir(&self.dst)?; | |
550 | let joint_dst = self.dst.join("index.html"); | |
551 | scx.fs.write(&joint_dst, buf.as_bytes())?; | |
552 | } | |
553 | ||
554 | // Render sidebar-items.js used throughout this module. | |
555 | if !self.render_redirect_pages { | |
556 | let module = match *item.kind { | |
557 | clean::StrippedItem(box clean::ModuleItem(ref m)) | clean::ModuleItem(ref m) => m, | |
558 | _ => unreachable!(), | |
559 | }; | |
560 | let items = self.build_sidebar_items(module); | |
561 | let js_dst = self.dst.join("sidebar-items.js"); | |
562 | let v = format!("initSidebarItems({});", serde_json::to_string(&items).unwrap()); | |
563 | scx.fs.write(&js_dst, &v)?; | |
564 | } | |
565 | Ok(()) | |
566 | } | |
567 | ||
568 | fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> { | |
569 | info!("Recursed; leaving {}", self.dst.display()); | |
570 | ||
571 | // Go back to where we were at | |
572 | self.dst.pop(); | |
573 | self.current.pop(); | |
574 | Ok(()) | |
575 | } | |
576 | ||
577 | fn item(&mut self, item: clean::Item) -> Result<(), Error> { | |
578 | // Stripped modules survive the rustdoc passes (i.e., `strip-private`) | |
579 | // if they contain impls for public types. These modules can also | |
580 | // contain items such as publicly re-exported structures. | |
581 | // | |
582 | // External crates will provide links to these structures, so | |
583 | // these modules are recursed into, but not rendered normally | |
584 | // (a flag on the context). | |
585 | if !self.render_redirect_pages { | |
586 | self.render_redirect_pages = item.is_stripped(); | |
587 | } | |
588 | ||
589 | let buf = self.render_item(&item, true); | |
590 | // buf will be empty if the item is stripped and there is no redirect for it | |
591 | if !buf.is_empty() { | |
592 | let name = item.name.as_ref().unwrap(); | |
593 | let item_type = item.type_(); | |
594 | let file_name = &item_path(item_type, &name.as_str()); | |
595 | self.shared.ensure_dir(&self.dst)?; | |
596 | let joint_dst = self.dst.join(file_name); | |
597 | self.shared.fs.write(&joint_dst, buf.as_bytes())?; | |
598 | ||
599 | if !self.render_redirect_pages { | |
600 | self.shared.all.borrow_mut().append(full_path(self, &item), &item_type); | |
601 | } | |
602 | // If the item is a macro, redirect from the old macro URL (with !) | |
603 | // to the new one (without). | |
604 | if item_type == ItemType::Macro { | |
605 | let redir_name = format!("{}.{}!.html", item_type, name); | |
606 | if let Some(ref redirections) = self.shared.redirections { | |
607 | let crate_name = &self.shared.layout.krate; | |
608 | redirections.borrow_mut().insert( | |
609 | format!("{}/{}", crate_name, redir_name), | |
610 | format!("{}/{}", crate_name, file_name), | |
611 | ); | |
612 | } else { | |
613 | let v = layout::redirect(file_name); | |
614 | let redir_dst = self.dst.join(redir_name); | |
615 | self.shared.fs.write(&redir_dst, v.as_bytes())?; | |
616 | } | |
617 | } | |
618 | } | |
619 | Ok(()) | |
620 | } | |
621 | ||
622 | fn cache(&self) -> &Cache { | |
623 | &self.cache | |
624 | } | |
625 | } | |
626 | ||
627 | fn make_item_keywords(it: &clean::Item) -> String { | |
628 | format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap()) | |
629 | } |