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