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