]>
Commit | Line | Data |
---|---|---|
c1a9b12d | 1 | // Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT |
1a4d82fc JJ |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! Rustdoc's HTML Rendering module | |
12 | //! | |
13 | //! This modules contains the bulk of the logic necessary for rendering a | |
14 | //! rustdoc `clean::Crate` instance to a set of static HTML pages. This | |
15 | //! rendering process is largely driven by the `format!` syntax extension to | |
16 | //! perform all I/O into files and streams. | |
17 | //! | |
18 | //! The rendering process is largely driven by the `Context` and `Cache` | |
19 | //! structures. The cache is pre-populated by crawling the crate in question, | |
bd371182 | 20 | //! and then it is shared among the various rendering threads. The cache is meant |
1a4d82fc | 21 | //! to be a fairly large structure not implementing `Clone` (because it's shared |
bd371182 AL |
22 | //! among threads). The context, however, should be a lightweight structure. This |
23 | //! is cloned per-thread and contains information about what is currently being | |
1a4d82fc JJ |
24 | //! rendered. |
25 | //! | |
26 | //! In order to speed up rendering (mostly because of markdown rendering), the | |
27 | //! rendering process has been parallelized. This parallelization is only | |
28 | //! exposed through the `crate` method on the context, and then also from the | |
29 | //! fact that the shared cache is stored in TLS (and must be accessed as such). | |
30 | //! | |
31 | //! In addition to rendering the crate itself, this module is also responsible | |
32 | //! for creating the corresponding search index and source file renderings. | |
bd371182 | 33 | //! These threads are not parallelized (they haven't been a bottleneck yet), and |
1a4d82fc JJ |
34 | //! both occur before the crate is rendered. |
35 | pub use self::ExternalLocation::*; | |
36 | ||
c1a9b12d | 37 | use std::ascii::AsciiExt; |
1a4d82fc | 38 | use std::cell::RefCell; |
85aaf69f | 39 | use std::cmp::Ordering; |
d9579d0f | 40 | use std::collections::{BTreeMap, HashMap, HashSet}; |
1a4d82fc JJ |
41 | use std::default::Default; |
42 | use std::fmt; | |
c34b1796 AL |
43 | use std::fs::{self, File}; |
44 | use std::io::prelude::*; | |
45 | use std::io::{self, BufWriter, BufReader}; | |
1a4d82fc | 46 | use std::iter::repeat; |
9346a6ac | 47 | use std::mem; |
c34b1796 | 48 | use std::path::{PathBuf, Path}; |
1a4d82fc JJ |
49 | use std::str; |
50 | use std::sync::Arc; | |
51 | ||
52 | use externalfiles::ExternalHtml; | |
53 | ||
62682a34 SL |
54 | use serialize::json::{self, ToJson}; |
55 | use syntax::{abi, ast, ast_util, attr}; | |
1a4d82fc JJ |
56 | use rustc::util::nodemap::NodeSet; |
57 | ||
62682a34 | 58 | use clean::{self, SelfTy}; |
1a4d82fc JJ |
59 | use doctree; |
60 | use fold::DocFolder; | |
d9579d0f | 61 | use html::escape::Escape; |
62682a34 | 62 | use html::format::{ConstnessSpace}; |
d9579d0f AL |
63 | use html::format::{TyParamBounds, WhereClause, href, AbiSpace}; |
64 | use html::format::{VisSpace, Method, UnsafetySpace, MutableSpace}; | |
1a4d82fc | 65 | use html::item_type::ItemType; |
62682a34 SL |
66 | use html::markdown::{self, Markdown}; |
67 | use html::{highlight, layout}; | |
1a4d82fc | 68 | |
85aaf69f | 69 | /// A pair of name and its optional document. |
c34b1796 | 70 | pub type NameDoc = (String, Option<String>); |
85aaf69f | 71 | |
1a4d82fc JJ |
72 | /// Major driving force in all rustdoc rendering. This contains information |
73 | /// about where in the tree-like hierarchy rendering is occurring and controls | |
74 | /// how the current page is being rendered. | |
75 | /// | |
76 | /// It is intended that this context is a lightweight object which can be fairly | |
77 | /// easily cloned because it is cloned per work-job (about once per item in the | |
78 | /// rustdoc tree). | |
79 | #[derive(Clone)] | |
80 | pub struct Context { | |
81 | /// Current hierarchy of components leading down to what's currently being | |
82 | /// rendered | |
83 | pub current: Vec<String>, | |
84 | /// String representation of how to get back to the root path of the 'doc/' | |
85 | /// folder in terms of a relative URL. | |
86 | pub root_path: String, | |
87 | /// The path to the crate root source minus the file name. | |
88 | /// Used for simplifying paths to the highlighted source code files. | |
c34b1796 | 89 | pub src_root: PathBuf, |
1a4d82fc JJ |
90 | /// The current destination folder of where HTML artifacts should be placed. |
91 | /// This changes as the context descends into the module hierarchy. | |
c34b1796 | 92 | pub dst: PathBuf, |
1a4d82fc JJ |
93 | /// This describes the layout of each page, and is not modified after |
94 | /// creation of the context (contains info like the favicon and added html). | |
95 | pub layout: layout::Layout, | |
1a4d82fc JJ |
96 | /// This flag indicates whether [src] links should be generated or not. If |
97 | /// the source files are present in the html rendering, then this will be | |
98 | /// `true`. | |
99 | pub include_sources: bool, | |
100 | /// A flag, which when turned off, will render pages which redirect to the | |
101 | /// real location of an item. This is used to allow external links to | |
102 | /// publicly reused items to redirect to the right location. | |
103 | pub render_redirect_pages: bool, | |
104 | /// All the passes that were run on this crate. | |
105 | pub passes: HashSet<String>, | |
106 | } | |
107 | ||
108 | /// Indicates where an external crate can be found. | |
109 | pub enum ExternalLocation { | |
110 | /// Remote URL root of the external crate | |
111 | Remote(String), | |
112 | /// This external crate can be found in the local doc/ folder | |
113 | Local, | |
114 | /// The external crate could not be found. | |
115 | Unknown, | |
116 | } | |
117 | ||
118 | /// Metadata about an implementor of a trait. | |
119 | pub struct Implementor { | |
120 | pub def_id: ast::DefId, | |
1a4d82fc | 121 | pub stability: Option<clean::Stability>, |
c1a9b12d | 122 | pub impl_: clean::Impl, |
1a4d82fc JJ |
123 | } |
124 | ||
125 | /// Metadata about implementations for a type. | |
126 | #[derive(Clone)] | |
127 | pub struct Impl { | |
128 | pub impl_: clean::Impl, | |
129 | pub dox: Option<String>, | |
130 | pub stability: Option<clean::Stability>, | |
131 | } | |
132 | ||
9346a6ac AL |
133 | impl Impl { |
134 | fn trait_did(&self) -> Option<ast::DefId> { | |
135 | self.impl_.trait_.as_ref().and_then(|tr| { | |
136 | if let clean::ResolvedPath { did, .. } = *tr {Some(did)} else {None} | |
137 | }) | |
138 | } | |
139 | } | |
140 | ||
1a4d82fc JJ |
141 | /// This cache is used to store information about the `clean::Crate` being |
142 | /// rendered in order to provide more useful documentation. This contains | |
143 | /// information like all implementors of a trait, all traits a type implements, | |
144 | /// documentation for all known traits, etc. | |
145 | /// | |
146 | /// This structure purposefully does not implement `Clone` because it's intended | |
147 | /// to be a fairly large and expensive structure to clone. Instead this adheres | |
148 | /// to `Send` so it may be stored in a `Arc` instance and shared among the various | |
bd371182 | 149 | /// rendering threads. |
1a4d82fc JJ |
150 | #[derive(Default)] |
151 | pub struct Cache { | |
152 | /// Mapping of typaram ids to the name of the type parameter. This is used | |
153 | /// when pretty-printing a type (so pretty printing doesn't have to | |
154 | /// painfully maintain a context like this) | |
155 | pub typarams: HashMap<ast::DefId, String>, | |
156 | ||
157 | /// Maps a type id to all known implementations for that type. This is only | |
158 | /// recognized for intra-crate `ResolvedPath` types, and is used to print | |
159 | /// out extra documentation on the page of an enum/struct. | |
160 | /// | |
161 | /// The values of the map are a list of implementations and documentation | |
162 | /// found on that implementation. | |
163 | pub impls: HashMap<ast::DefId, Vec<Impl>>, | |
164 | ||
165 | /// Maintains a mapping of local crate node ids to the fully qualified name | |
166 | /// and "short type description" of that node. This is used when generating | |
167 | /// URLs when a type is being linked to. External paths are not located in | |
168 | /// this map because the `External` type itself has all the information | |
169 | /// necessary. | |
170 | pub paths: HashMap<ast::DefId, (Vec<String>, ItemType)>, | |
171 | ||
172 | /// Similar to `paths`, but only holds external paths. This is only used for | |
173 | /// generating explicit hyperlinks to other crates. | |
174 | pub external_paths: HashMap<ast::DefId, Vec<String>>, | |
175 | ||
176 | /// This map contains information about all known traits of this crate. | |
177 | /// Implementations of a crate should inherit the documentation of the | |
178 | /// parent trait if no extra documentation is specified, and default methods | |
179 | /// should show up in documentation about trait implementations. | |
180 | pub traits: HashMap<ast::DefId, clean::Trait>, | |
181 | ||
182 | /// When rendering traits, it's often useful to be able to list all | |
183 | /// implementors of the trait, and this mapping is exactly, that: a mapping | |
184 | /// of trait ids to the list of known implementors of the trait | |
185 | pub implementors: HashMap<ast::DefId, Vec<Implementor>>, | |
186 | ||
187 | /// Cache of where external crate documentation can be found. | |
d9579d0f | 188 | pub extern_locations: HashMap<ast::CrateNum, (String, ExternalLocation)>, |
1a4d82fc JJ |
189 | |
190 | /// Cache of where documentation for primitives can be found. | |
191 | pub primitive_locations: HashMap<clean::PrimitiveType, ast::CrateNum>, | |
192 | ||
193 | /// Set of definitions which have been inlined from external crates. | |
194 | pub inlined: HashSet<ast::DefId>, | |
195 | ||
196 | // Private fields only used when initially crawling a crate to build a cache | |
197 | ||
198 | stack: Vec<String>, | |
199 | parent_stack: Vec<ast::DefId>, | |
200 | search_index: Vec<IndexItem>, | |
201 | privmod: bool, | |
202 | remove_priv: bool, | |
203 | public_items: NodeSet, | |
d9579d0f | 204 | deref_trait_did: Option<ast::DefId>, |
1a4d82fc JJ |
205 | |
206 | // In rare case where a structure is defined in one module but implemented | |
207 | // in another, if the implementing module is parsed before defining module, | |
208 | // then the fully qualified name of the structure isn't presented in `paths` | |
209 | // yet when its implementation methods are being indexed. Caches such methods | |
210 | // and their parent id here and indexes them at the end of crate parsing. | |
211 | orphan_methods: Vec<(ast::NodeId, clean::Item)>, | |
212 | } | |
213 | ||
214 | /// Helper struct to render all source code to HTML pages | |
215 | struct SourceCollector<'a> { | |
216 | cx: &'a mut Context, | |
217 | ||
218 | /// Processed source-file paths | |
219 | seen: HashSet<String>, | |
220 | /// Root destination to place all HTML output into | |
c34b1796 | 221 | dst: PathBuf, |
1a4d82fc JJ |
222 | } |
223 | ||
224 | /// Wrapper struct to render the source code of a file. This will do things like | |
225 | /// adding line numbers to the left-hand side. | |
226 | struct Source<'a>(&'a str); | |
227 | ||
228 | // Helper structs for rendering items/sidebars and carrying along contextual | |
229 | // information | |
230 | ||
c34b1796 | 231 | #[derive(Copy, Clone)] |
1a4d82fc JJ |
232 | struct Item<'a> { |
233 | cx: &'a Context, | |
234 | item: &'a clean::Item, | |
235 | } | |
236 | ||
237 | struct Sidebar<'a> { cx: &'a Context, item: &'a clean::Item, } | |
238 | ||
239 | /// Struct representing one entry in the JS search index. These are all emitted | |
240 | /// by hand to a large JS file at the end of cache-creation. | |
241 | struct IndexItem { | |
242 | ty: ItemType, | |
243 | name: String, | |
244 | path: String, | |
245 | desc: String, | |
246 | parent: Option<ast::DefId>, | |
c34b1796 AL |
247 | search_type: Option<IndexItemFunctionType>, |
248 | } | |
249 | ||
250 | /// A type used for the search index. | |
251 | struct Type { | |
252 | name: Option<String>, | |
253 | } | |
254 | ||
255 | impl fmt::Display for Type { | |
256 | /// Formats type as {name: $name}. | |
257 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
258 | // Wrapping struct fmt should never call us when self.name is None, | |
259 | // but just to be safe we write `null` in that case. | |
260 | match self.name { | |
261 | Some(ref n) => write!(f, "{{\"name\":\"{}\"}}", n), | |
262 | None => write!(f, "null") | |
263 | } | |
264 | } | |
265 | } | |
266 | ||
267 | /// Full type of functions/methods in the search index. | |
268 | struct IndexItemFunctionType { | |
269 | inputs: Vec<Type>, | |
270 | output: Option<Type> | |
271 | } | |
272 | ||
273 | impl fmt::Display for IndexItemFunctionType { | |
274 | /// Formats a full fn type as a JSON {inputs: [Type], outputs: Type/null}. | |
275 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
276 | // If we couldn't figure out a type, just write `null`. | |
277 | if self.inputs.iter().any(|ref i| i.name.is_none()) || | |
278 | (self.output.is_some() && self.output.as_ref().unwrap().name.is_none()) { | |
279 | return write!(f, "null") | |
280 | } | |
281 | ||
9346a6ac AL |
282 | let inputs: Vec<String> = self.inputs.iter().map(|ref t| { |
283 | format!("{}", t) | |
284 | }).collect(); | |
c1a9b12d | 285 | try!(write!(f, "{{\"inputs\":[{}],\"output\":", inputs.join(","))); |
c34b1796 AL |
286 | |
287 | match self.output { | |
288 | Some(ref t) => try!(write!(f, "{}", t)), | |
289 | None => try!(write!(f, "null")) | |
290 | }; | |
291 | ||
292 | Ok(try!(write!(f, "}}"))) | |
293 | } | |
1a4d82fc JJ |
294 | } |
295 | ||
296 | // TLS keys used to carry information around during rendering. | |
297 | ||
298 | thread_local!(static CACHE_KEY: RefCell<Arc<Cache>> = Default::default()); | |
299 | thread_local!(pub static CURRENT_LOCATION_KEY: RefCell<Vec<String>> = | |
300 | RefCell::new(Vec::new())); | |
301 | ||
302 | /// Generates the documentation for `crate` into the directory `dst` | |
303 | pub fn run(mut krate: clean::Crate, | |
304 | external_html: &ExternalHtml, | |
c34b1796 AL |
305 | dst: PathBuf, |
306 | passes: HashSet<String>) -> io::Result<()> { | |
307 | let src_root = match krate.src.parent() { | |
308 | Some(p) => p.to_path_buf(), | |
309 | None => PathBuf::new(), | |
310 | }; | |
1a4d82fc JJ |
311 | let mut cx = Context { |
312 | dst: dst, | |
c34b1796 | 313 | src_root: src_root, |
1a4d82fc JJ |
314 | passes: passes, |
315 | current: Vec::new(), | |
316 | root_path: String::new(), | |
1a4d82fc JJ |
317 | layout: layout::Layout { |
318 | logo: "".to_string(), | |
319 | favicon: "".to_string(), | |
320 | external_html: external_html.clone(), | |
321 | krate: krate.name.clone(), | |
322 | playground_url: "".to_string(), | |
323 | }, | |
324 | include_sources: true, | |
325 | render_redirect_pages: false, | |
326 | }; | |
327 | ||
328 | try!(mkdir(&cx.dst)); | |
329 | ||
330 | // Crawl the crate attributes looking for attributes which control how we're | |
331 | // going to emit HTML | |
332 | let default: &[_] = &[]; | |
333 | match krate.module.as_ref().map(|m| m.doc_list().unwrap_or(default)) { | |
334 | Some(attrs) => { | |
85aaf69f | 335 | for attr in attrs { |
1a4d82fc JJ |
336 | match *attr { |
337 | clean::NameValue(ref x, ref s) | |
338 | if "html_favicon_url" == *x => { | |
339 | cx.layout.favicon = s.to_string(); | |
340 | } | |
341 | clean::NameValue(ref x, ref s) | |
342 | if "html_logo_url" == *x => { | |
343 | cx.layout.logo = s.to_string(); | |
344 | } | |
345 | clean::NameValue(ref x, ref s) | |
346 | if "html_playground_url" == *x => { | |
347 | cx.layout.playground_url = s.to_string(); | |
348 | markdown::PLAYGROUND_KRATE.with(|slot| { | |
349 | if slot.borrow().is_none() { | |
350 | let name = krate.name.clone(); | |
351 | *slot.borrow_mut() = Some(Some(name)); | |
352 | } | |
353 | }); | |
354 | } | |
355 | clean::Word(ref x) | |
356 | if "html_no_source" == *x => { | |
357 | cx.include_sources = false; | |
358 | } | |
359 | _ => {} | |
360 | } | |
361 | } | |
362 | } | |
363 | None => {} | |
364 | } | |
365 | ||
366 | // Crawl the crate to build various caches used for the output | |
367 | let analysis = ::ANALYSISKEY.with(|a| a.clone()); | |
368 | let analysis = analysis.borrow(); | |
369 | let public_items = analysis.as_ref().map(|a| a.public_items.clone()); | |
85aaf69f | 370 | let public_items = public_items.unwrap_or(NodeSet()); |
1a4d82fc JJ |
371 | let paths: HashMap<ast::DefId, (Vec<String>, ItemType)> = |
372 | analysis.as_ref().map(|a| { | |
373 | let paths = a.external_paths.borrow_mut().take().unwrap(); | |
374 | paths.into_iter().map(|(k, (v, t))| (k, (v, ItemType::from_type_kind(t)))).collect() | |
375 | }).unwrap_or(HashMap::new()); | |
376 | let mut cache = Cache { | |
377 | impls: HashMap::new(), | |
378 | external_paths: paths.iter().map(|(&k, v)| (k, v.0.clone())) | |
379 | .collect(), | |
380 | paths: paths, | |
381 | implementors: HashMap::new(), | |
382 | stack: Vec::new(), | |
383 | parent_stack: Vec::new(), | |
384 | search_index: Vec::new(), | |
385 | extern_locations: HashMap::new(), | |
386 | primitive_locations: HashMap::new(), | |
387 | remove_priv: cx.passes.contains("strip-private"), | |
388 | privmod: false, | |
389 | public_items: public_items, | |
390 | orphan_methods: Vec::new(), | |
9346a6ac | 391 | traits: mem::replace(&mut krate.external_traits, HashMap::new()), |
d9579d0f | 392 | deref_trait_did: analysis.as_ref().and_then(|a| a.deref_trait_did), |
1a4d82fc JJ |
393 | typarams: analysis.as_ref().map(|a| { |
394 | a.external_typarams.borrow_mut().take().unwrap() | |
395 | }).unwrap_or(HashMap::new()), | |
396 | inlined: analysis.as_ref().map(|a| { | |
397 | a.inlined.borrow_mut().take().unwrap() | |
398 | }).unwrap_or(HashSet::new()), | |
399 | }; | |
1a4d82fc JJ |
400 | |
401 | // Cache where all our extern crates are located | |
85aaf69f | 402 | for &(n, ref e) in &krate.externs { |
d9579d0f AL |
403 | cache.extern_locations.insert(n, (e.name.clone(), |
404 | extern_location(e, &cx.dst))); | |
1a4d82fc JJ |
405 | let did = ast::DefId { krate: n, node: ast::CRATE_NODE_ID }; |
406 | cache.paths.insert(did, (vec![e.name.to_string()], ItemType::Module)); | |
407 | } | |
408 | ||
409 | // Cache where all known primitives have their documentation located. | |
410 | // | |
411 | // Favor linking to as local extern as possible, so iterate all crates in | |
412 | // reverse topological order. | |
413 | for &(n, ref e) in krate.externs.iter().rev() { | |
85aaf69f | 414 | for &prim in &e.primitives { |
1a4d82fc JJ |
415 | cache.primitive_locations.insert(prim, n); |
416 | } | |
417 | } | |
85aaf69f | 418 | for &prim in &krate.primitives { |
1a4d82fc JJ |
419 | cache.primitive_locations.insert(prim, ast::LOCAL_CRATE); |
420 | } | |
421 | ||
d9579d0f AL |
422 | cache.stack.push(krate.name.clone()); |
423 | krate = cache.fold_crate(krate); | |
424 | ||
1a4d82fc JJ |
425 | // Build our search index |
426 | let index = try!(build_index(&krate, &mut cache)); | |
427 | ||
428 | // Freeze the cache now that the index has been built. Put an Arc into TLS | |
429 | // for future parallelization opportunities | |
430 | let cache = Arc::new(cache); | |
431 | CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); | |
432 | CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear()); | |
433 | ||
434 | try!(write_shared(&cx, &krate, &*cache, index)); | |
435 | let krate = try!(render_sources(&mut cx, krate)); | |
436 | ||
1a4d82fc | 437 | // And finally render the whole crate's documentation |
d9579d0f | 438 | cx.krate(krate) |
1a4d82fc JJ |
439 | } |
440 | ||
c34b1796 | 441 | fn build_index(krate: &clean::Crate, cache: &mut Cache) -> io::Result<String> { |
1a4d82fc JJ |
442 | // Build the search index from the collected metadata |
443 | let mut nodeid_to_pathid = HashMap::new(); | |
444 | let mut pathid_to_nodeid = Vec::new(); | |
445 | { | |
446 | let Cache { ref mut search_index, | |
447 | ref orphan_methods, | |
448 | ref mut paths, .. } = *cache; | |
449 | ||
450 | // Attach all orphan methods to the type's definition if the type | |
451 | // has since been learned. | |
85aaf69f | 452 | for &(pid, ref item) in orphan_methods { |
1a4d82fc JJ |
453 | let did = ast_util::local_def(pid); |
454 | match paths.get(&did) { | |
455 | Some(&(ref fqp, _)) => { | |
62682a34 SL |
456 | // Needed to determine `self` type. |
457 | let parent_basename = Some(fqp[fqp.len() - 1].clone()); | |
1a4d82fc JJ |
458 | search_index.push(IndexItem { |
459 | ty: shortty(item), | |
460 | name: item.name.clone().unwrap(), | |
c1a9b12d | 461 | path: fqp[..fqp.len() - 1].join("::"), |
c34b1796 | 462 | desc: shorter(item.doc_value()), |
1a4d82fc | 463 | parent: Some(did), |
62682a34 | 464 | search_type: get_index_search_type(&item, parent_basename), |
1a4d82fc JJ |
465 | }); |
466 | }, | |
467 | None => {} | |
468 | } | |
469 | }; | |
470 | ||
471 | // Reduce `NodeId` in paths into smaller sequential numbers, | |
472 | // and prune the paths that do not appear in the index. | |
62682a34 | 473 | for item in search_index.iter() { |
1a4d82fc JJ |
474 | match item.parent { |
475 | Some(nodeid) => { | |
476 | if !nodeid_to_pathid.contains_key(&nodeid) { | |
477 | let pathid = pathid_to_nodeid.len(); | |
478 | nodeid_to_pathid.insert(nodeid, pathid); | |
479 | pathid_to_nodeid.push(nodeid); | |
480 | } | |
481 | } | |
482 | None => {} | |
483 | } | |
484 | } | |
485 | assert_eq!(nodeid_to_pathid.len(), pathid_to_nodeid.len()); | |
486 | } | |
487 | ||
488 | // Collect the index into a string | |
c34b1796 | 489 | let mut w = io::Cursor::new(Vec::new()); |
1a4d82fc JJ |
490 | try!(write!(&mut w, r#"searchIndex['{}'] = {{"items":["#, krate.name)); |
491 | ||
492 | let mut lastpath = "".to_string(); | |
493 | for (i, item) in cache.search_index.iter().enumerate() { | |
494 | // Omit the path if it is same to that of the prior item. | |
495 | let path; | |
496 | if lastpath == item.path { | |
497 | path = ""; | |
498 | } else { | |
499 | lastpath = item.path.to_string(); | |
85aaf69f | 500 | path = &item.path; |
1a4d82fc JJ |
501 | }; |
502 | ||
503 | if i > 0 { | |
504 | try!(write!(&mut w, ",")); | |
505 | } | |
506 | try!(write!(&mut w, r#"[{},"{}","{}",{}"#, | |
c34b1796 | 507 | item.ty as usize, item.name, path, |
1a4d82fc JJ |
508 | item.desc.to_json().to_string())); |
509 | match item.parent { | |
510 | Some(nodeid) => { | |
511 | let pathid = *nodeid_to_pathid.get(&nodeid).unwrap(); | |
512 | try!(write!(&mut w, ",{}", pathid)); | |
513 | } | |
c34b1796 AL |
514 | None => try!(write!(&mut w, ",null")) |
515 | } | |
516 | match item.search_type { | |
517 | Some(ref t) => try!(write!(&mut w, ",{}", t)), | |
518 | None => try!(write!(&mut w, ",null")) | |
1a4d82fc JJ |
519 | } |
520 | try!(write!(&mut w, "]")); | |
521 | } | |
522 | ||
523 | try!(write!(&mut w, r#"],"paths":["#)); | |
524 | ||
525 | for (i, &did) in pathid_to_nodeid.iter().enumerate() { | |
526 | let &(ref fqp, short) = cache.paths.get(&did).unwrap(); | |
527 | if i > 0 { | |
528 | try!(write!(&mut w, ",")); | |
529 | } | |
530 | try!(write!(&mut w, r#"[{},"{}"]"#, | |
c34b1796 | 531 | short as usize, *fqp.last().unwrap())); |
1a4d82fc JJ |
532 | } |
533 | ||
534 | try!(write!(&mut w, "]}};")); | |
535 | ||
c34b1796 | 536 | Ok(String::from_utf8(w.into_inner()).unwrap()) |
1a4d82fc JJ |
537 | } |
538 | ||
539 | fn write_shared(cx: &Context, | |
540 | krate: &clean::Crate, | |
541 | cache: &Cache, | |
c34b1796 | 542 | search_index: String) -> io::Result<()> { |
1a4d82fc JJ |
543 | // Write out the shared files. Note that these are shared among all rustdoc |
544 | // docs placed in the output directory, so this needs to be a synchronized | |
545 | // operation with respect to all other rustdocs running around. | |
546 | try!(mkdir(&cx.dst)); | |
547 | let _lock = ::flock::Lock::new(&cx.dst.join(".lock")); | |
548 | ||
549 | // Add all the static files. These may already exist, but we just | |
550 | // overwrite them anyway to make sure that they're fresh and up-to-date. | |
551 | try!(write(cx.dst.join("jquery.js"), | |
c1a9b12d | 552 | include_bytes!("static/jquery-2.1.4.min.js"))); |
1a4d82fc JJ |
553 | try!(write(cx.dst.join("main.js"), include_bytes!("static/main.js"))); |
554 | try!(write(cx.dst.join("playpen.js"), include_bytes!("static/playpen.js"))); | |
555 | try!(write(cx.dst.join("main.css"), include_bytes!("static/main.css"))); | |
556 | try!(write(cx.dst.join("normalize.css"), | |
557 | include_bytes!("static/normalize.css"))); | |
558 | try!(write(cx.dst.join("FiraSans-Regular.woff"), | |
559 | include_bytes!("static/FiraSans-Regular.woff"))); | |
560 | try!(write(cx.dst.join("FiraSans-Medium.woff"), | |
561 | include_bytes!("static/FiraSans-Medium.woff"))); | |
562 | try!(write(cx.dst.join("Heuristica-Italic.woff"), | |
563 | include_bytes!("static/Heuristica-Italic.woff"))); | |
564 | try!(write(cx.dst.join("SourceSerifPro-Regular.woff"), | |
565 | include_bytes!("static/SourceSerifPro-Regular.woff"))); | |
566 | try!(write(cx.dst.join("SourceSerifPro-Bold.woff"), | |
567 | include_bytes!("static/SourceSerifPro-Bold.woff"))); | |
568 | try!(write(cx.dst.join("SourceCodePro-Regular.woff"), | |
569 | include_bytes!("static/SourceCodePro-Regular.woff"))); | |
570 | try!(write(cx.dst.join("SourceCodePro-Semibold.woff"), | |
571 | include_bytes!("static/SourceCodePro-Semibold.woff"))); | |
572 | ||
573 | fn collect(path: &Path, krate: &str, | |
c34b1796 | 574 | key: &str) -> io::Result<Vec<String>> { |
1a4d82fc JJ |
575 | let mut ret = Vec::new(); |
576 | if path.exists() { | |
c34b1796 | 577 | for line in BufReader::new(try!(File::open(path))).lines() { |
1a4d82fc JJ |
578 | let line = try!(line); |
579 | if !line.starts_with(key) { | |
580 | continue | |
581 | } | |
85aaf69f | 582 | if line.starts_with(&format!("{}['{}']", key, krate)) { |
1a4d82fc JJ |
583 | continue |
584 | } | |
585 | ret.push(line.to_string()); | |
586 | } | |
587 | } | |
588 | return Ok(ret); | |
589 | } | |
590 | ||
591 | // Update the search index | |
592 | let dst = cx.dst.join("search-index.js"); | |
85aaf69f | 593 | let all_indexes = try!(collect(&dst, &krate.name, "searchIndex")); |
1a4d82fc JJ |
594 | let mut w = try!(File::create(&dst)); |
595 | try!(writeln!(&mut w, "var searchIndex = {{}};")); | |
596 | try!(writeln!(&mut w, "{}", search_index)); | |
85aaf69f | 597 | for index in &all_indexes { |
1a4d82fc JJ |
598 | try!(writeln!(&mut w, "{}", *index)); |
599 | } | |
600 | try!(writeln!(&mut w, "initSearch(searchIndex);")); | |
601 | ||
602 | // Update the list of all implementors for traits | |
603 | let dst = cx.dst.join("implementors"); | |
604 | try!(mkdir(&dst)); | |
85aaf69f | 605 | for (&did, imps) in &cache.implementors { |
1a4d82fc JJ |
606 | // Private modules can leak through to this phase of rustdoc, which |
607 | // could contain implementations for otherwise private types. In some | |
608 | // rare cases we could find an implementation for an item which wasn't | |
609 | // indexed, so we just skip this step in that case. | |
610 | // | |
611 | // FIXME: this is a vague explanation for why this can't be a `get`, in | |
612 | // theory it should be... | |
613 | let &(ref remote_path, remote_item_type) = match cache.paths.get(&did) { | |
614 | Some(p) => p, | |
615 | None => continue, | |
616 | }; | |
617 | ||
618 | let mut mydst = dst.clone(); | |
85aaf69f SL |
619 | for part in &remote_path[..remote_path.len() - 1] { |
620 | mydst.push(part); | |
1a4d82fc JJ |
621 | try!(mkdir(&mydst)); |
622 | } | |
c34b1796 AL |
623 | mydst.push(&format!("{}.{}.js", |
624 | remote_item_type.to_static_str(), | |
625 | remote_path[remote_path.len() - 1])); | |
85aaf69f | 626 | let all_implementors = try!(collect(&mydst, &krate.name, |
1a4d82fc JJ |
627 | "implementors")); |
628 | ||
c34b1796 AL |
629 | try!(mkdir(mydst.parent().unwrap())); |
630 | let mut f = BufWriter::new(try!(File::create(&mydst))); | |
1a4d82fc JJ |
631 | try!(writeln!(&mut f, "(function() {{var implementors = {{}};")); |
632 | ||
85aaf69f | 633 | for implementor in &all_implementors { |
1a4d82fc JJ |
634 | try!(write!(&mut f, "{}", *implementor)); |
635 | } | |
636 | ||
637 | try!(write!(&mut f, r"implementors['{}'] = [", krate.name)); | |
85aaf69f | 638 | for imp in imps { |
1a4d82fc JJ |
639 | // If the trait and implementation are in the same crate, then |
640 | // there's no need to emit information about it (there's inlining | |
641 | // going on). If they're in different crates then the crate defining | |
642 | // the trait will be interested in our implementation. | |
643 | if imp.def_id.krate == did.krate { continue } | |
c1a9b12d | 644 | try!(write!(&mut f, r#""{}","#, imp.impl_)); |
1a4d82fc JJ |
645 | } |
646 | try!(writeln!(&mut f, r"];")); | |
647 | try!(writeln!(&mut f, "{}", r" | |
648 | if (window.register_implementors) { | |
649 | window.register_implementors(implementors); | |
650 | } else { | |
651 | window.pending_implementors = implementors; | |
652 | } | |
653 | ")); | |
654 | try!(writeln!(&mut f, r"}})()")); | |
655 | } | |
656 | Ok(()) | |
657 | } | |
658 | ||
659 | fn render_sources(cx: &mut Context, | |
c34b1796 | 660 | krate: clean::Crate) -> io::Result<clean::Crate> { |
1a4d82fc JJ |
661 | info!("emitting source files"); |
662 | let dst = cx.dst.join("src"); | |
663 | try!(mkdir(&dst)); | |
85aaf69f | 664 | let dst = dst.join(&krate.name); |
1a4d82fc JJ |
665 | try!(mkdir(&dst)); |
666 | let mut folder = SourceCollector { | |
667 | dst: dst, | |
668 | seen: HashSet::new(), | |
669 | cx: cx, | |
670 | }; | |
671 | // skip all invalid spans | |
672 | folder.seen.insert("".to_string()); | |
673 | Ok(folder.fold_crate(krate)) | |
674 | } | |
675 | ||
676 | /// Writes the entire contents of a string to a destination, not attempting to | |
677 | /// catch any errors. | |
c34b1796 AL |
678 | fn write(dst: PathBuf, contents: &[u8]) -> io::Result<()> { |
679 | try!(File::create(&dst)).write_all(contents) | |
1a4d82fc JJ |
680 | } |
681 | ||
bd371182 | 682 | /// Makes a directory on the filesystem, failing the thread if an error occurs and |
1a4d82fc | 683 | /// skipping if the directory already exists. |
c34b1796 | 684 | fn mkdir(path: &Path) -> io::Result<()> { |
1a4d82fc | 685 | if !path.exists() { |
c34b1796 | 686 | fs::create_dir(path) |
1a4d82fc JJ |
687 | } else { |
688 | Ok(()) | |
689 | } | |
690 | } | |
691 | ||
692 | /// Returns a documentation-level item type from the item. | |
693 | fn shortty(item: &clean::Item) -> ItemType { | |
694 | ItemType::from_item(item) | |
695 | } | |
696 | ||
697 | /// Takes a path to a source file and cleans the path to it. This canonicalizes | |
698 | /// things like ".." to components which preserve the "top down" hierarchy of a | |
c34b1796 AL |
699 | /// static HTML tree. Each component in the cleaned path will be passed as an |
700 | /// argument to `f`. The very last component of the path (ie the file name) will | |
701 | /// be passed to `f` if `keep_filename` is true, and ignored otherwise. | |
1a4d82fc JJ |
702 | // FIXME (#9639): The closure should deal with &[u8] instead of &str |
703 | // FIXME (#9639): This is too conservative, rejecting non-UTF-8 paths | |
c34b1796 | 704 | fn clean_srcpath<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F) where |
1a4d82fc JJ |
705 | F: FnMut(&str), |
706 | { | |
1a4d82fc | 707 | // make it relative, if possible |
c34b1796 | 708 | let p = p.relative_from(src_root).unwrap_or(p); |
1a4d82fc | 709 | |
c34b1796 AL |
710 | let mut iter = p.iter().map(|x| x.to_str().unwrap()).peekable(); |
711 | while let Some(c) = iter.next() { | |
712 | if !keep_filename && iter.peek().is_none() { | |
713 | break; | |
714 | } | |
715 | ||
716 | if ".." == c { | |
717 | f("up"); | |
718 | } else { | |
719 | f(c) | |
1a4d82fc JJ |
720 | } |
721 | } | |
722 | } | |
723 | ||
724 | /// Attempts to find where an external crate is located, given that we're | |
725 | /// rendering in to the specified source destination. | |
726 | fn extern_location(e: &clean::ExternalCrate, dst: &Path) -> ExternalLocation { | |
727 | // See if there's documentation generated into the local directory | |
85aaf69f | 728 | let local_location = dst.join(&e.name); |
1a4d82fc JJ |
729 | if local_location.is_dir() { |
730 | return Local; | |
731 | } | |
732 | ||
733 | // Failing that, see if there's an attribute specifying where to find this | |
734 | // external crate | |
85aaf69f | 735 | for attr in &e.attrs { |
1a4d82fc JJ |
736 | match *attr { |
737 | clean::List(ref x, ref list) if "doc" == *x => { | |
85aaf69f | 738 | for attr in list { |
1a4d82fc JJ |
739 | match *attr { |
740 | clean::NameValue(ref x, ref s) | |
741 | if "html_root_url" == *x => { | |
742 | if s.ends_with("/") { | |
743 | return Remote(s.to_string()); | |
744 | } | |
745 | return Remote(format!("{}/", s)); | |
746 | } | |
747 | _ => {} | |
748 | } | |
749 | } | |
750 | } | |
751 | _ => {} | |
752 | } | |
753 | } | |
754 | ||
755 | // Well, at least we tried. | |
756 | return Unknown; | |
757 | } | |
758 | ||
759 | impl<'a> DocFolder for SourceCollector<'a> { | |
760 | fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | |
761 | // If we're including source files, and we haven't seen this file yet, | |
762 | // then we need to render it out to the filesystem | |
763 | if self.cx.include_sources && !self.seen.contains(&item.source.filename) { | |
764 | ||
765 | // If it turns out that we couldn't read this file, then we probably | |
766 | // can't read any of the files (generating html output from json or | |
767 | // something like that), so just don't include sources for the | |
768 | // entire crate. The other option is maintaining this mapping on a | |
769 | // per-file basis, but that's probably not worth it... | |
770 | self.cx | |
85aaf69f | 771 | .include_sources = match self.emit_source(&item.source .filename) { |
1a4d82fc JJ |
772 | Ok(()) => true, |
773 | Err(e) => { | |
774 | println!("warning: source code was requested to be rendered, \ | |
775 | but processing `{}` had an error: {}", | |
776 | item.source.filename, e); | |
777 | println!(" skipping rendering of source code"); | |
778 | false | |
779 | } | |
780 | }; | |
781 | self.seen.insert(item.source.filename.clone()); | |
782 | } | |
783 | ||
784 | self.fold_item_recur(item) | |
785 | } | |
786 | } | |
787 | ||
788 | impl<'a> SourceCollector<'a> { | |
789 | /// Renders the given filename into its corresponding HTML source file. | |
c34b1796 AL |
790 | fn emit_source(&mut self, filename: &str) -> io::Result<()> { |
791 | let p = PathBuf::from(filename); | |
1a4d82fc JJ |
792 | |
793 | // If we couldn't open this file, then just returns because it | |
794 | // probably means that it's some standard library macro thing and we | |
795 | // can't have the source to it anyway. | |
c34b1796 AL |
796 | let mut contents = Vec::new(); |
797 | match File::open(&p).and_then(|mut f| f.read_to_end(&mut contents)) { | |
1a4d82fc JJ |
798 | Ok(r) => r, |
799 | // macros from other libraries get special filenames which we can | |
800 | // safely ignore | |
801 | Err(..) if filename.starts_with("<") && | |
802 | filename.ends_with("macros>") => return Ok(()), | |
803 | Err(e) => return Err(e) | |
804 | }; | |
85aaf69f | 805 | let contents = str::from_utf8(&contents).unwrap(); |
1a4d82fc JJ |
806 | |
807 | // Remove the utf-8 BOM if any | |
808 | let contents = if contents.starts_with("\u{feff}") { | |
85aaf69f | 809 | &contents[3..] |
1a4d82fc JJ |
810 | } else { |
811 | contents | |
812 | }; | |
813 | ||
814 | // Create the intermediate directories | |
815 | let mut cur = self.dst.clone(); | |
62682a34 | 816 | let mut root_path = String::from("../../"); |
c34b1796 | 817 | clean_srcpath(&self.cx.src_root, &p, false, |component| { |
1a4d82fc JJ |
818 | cur.push(component); |
819 | mkdir(&cur).unwrap(); | |
820 | root_path.push_str("../"); | |
821 | }); | |
822 | ||
c34b1796 AL |
823 | let mut fname = p.file_name().expect("source has no filename") |
824 | .to_os_string(); | |
825 | fname.push(".html"); | |
826 | cur.push(&fname[..]); | |
827 | let mut w = BufWriter::new(try!(File::create(&cur))); | |
1a4d82fc | 828 | |
c34b1796 AL |
829 | let title = format!("{} -- source", cur.file_name().unwrap() |
830 | .to_string_lossy()); | |
1a4d82fc JJ |
831 | let desc = format!("Source to the Rust file `{}`.", filename); |
832 | let page = layout::Page { | |
85aaf69f | 833 | title: &title, |
1a4d82fc | 834 | ty: "source", |
85aaf69f SL |
835 | root_path: &root_path, |
836 | description: &desc, | |
1a4d82fc JJ |
837 | keywords: get_basic_keywords(), |
838 | }; | |
c34b1796 | 839 | try!(layout::render(&mut w, &self.cx.layout, |
1a4d82fc JJ |
840 | &page, &(""), &Source(contents))); |
841 | try!(w.flush()); | |
842 | return Ok(()); | |
843 | } | |
844 | } | |
845 | ||
846 | impl DocFolder for Cache { | |
847 | fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | |
848 | // If this is a private module, we don't want it in the search index. | |
849 | let orig_privmod = match item.inner { | |
850 | clean::ModuleItem(..) => { | |
851 | let prev = self.privmod; | |
852 | self.privmod = prev || (self.remove_priv && item.visibility != Some(ast::Public)); | |
853 | prev | |
854 | } | |
855 | _ => self.privmod, | |
856 | }; | |
857 | ||
858 | // Register any generics to their corresponding string. This is used | |
859 | // when pretty-printing types | |
860 | match item.inner { | |
861 | clean::StructItem(ref s) => self.generics(&s.generics), | |
862 | clean::EnumItem(ref e) => self.generics(&e.generics), | |
863 | clean::FunctionItem(ref f) => self.generics(&f.generics), | |
62682a34 | 864 | clean::TypedefItem(ref t, _) => self.generics(&t.generics), |
1a4d82fc JJ |
865 | clean::TraitItem(ref t) => self.generics(&t.generics), |
866 | clean::ImplItem(ref i) => self.generics(&i.generics), | |
867 | clean::TyMethodItem(ref i) => self.generics(&i.generics), | |
868 | clean::MethodItem(ref i) => self.generics(&i.generics), | |
869 | clean::ForeignFunctionItem(ref f) => self.generics(&f.generics), | |
870 | _ => {} | |
871 | } | |
872 | ||
873 | // Propagate a trait methods' documentation to all implementors of the | |
874 | // trait | |
875 | if let clean::TraitItem(ref t) = item.inner { | |
876 | self.traits.insert(item.def_id, t.clone()); | |
877 | } | |
878 | ||
879 | // Collect all the implementors of traits. | |
880 | if let clean::ImplItem(ref i) = item.inner { | |
881 | match i.trait_ { | |
882 | Some(clean::ResolvedPath{ did, .. }) => { | |
c34b1796 | 883 | self.implementors.entry(did).or_insert(vec![]).push(Implementor { |
1a4d82fc | 884 | def_id: item.def_id, |
1a4d82fc | 885 | stability: item.stability.clone(), |
c1a9b12d | 886 | impl_: i.clone(), |
1a4d82fc JJ |
887 | }); |
888 | } | |
889 | Some(..) | None => {} | |
890 | } | |
891 | } | |
892 | ||
893 | // Index this method for searching later on | |
894 | if let Some(ref s) = item.name { | |
895 | let (parent, is_method) = match item.inner { | |
d9579d0f AL |
896 | clean::AssociatedTypeItem(..) | |
897 | clean::AssociatedConstItem(..) | | |
1a4d82fc JJ |
898 | clean::TyMethodItem(..) | |
899 | clean::StructFieldItem(..) | | |
900 | clean::VariantItem(..) => { | |
901 | ((Some(*self.parent_stack.last().unwrap()), | |
85aaf69f | 902 | Some(&self.stack[..self.stack.len() - 1])), |
1a4d82fc JJ |
903 | false) |
904 | } | |
905 | clean::MethodItem(..) => { | |
9346a6ac | 906 | if self.parent_stack.is_empty() { |
1a4d82fc JJ |
907 | ((None, None), false) |
908 | } else { | |
909 | let last = self.parent_stack.last().unwrap(); | |
910 | let did = *last; | |
911 | let path = match self.paths.get(&did) { | |
912 | Some(&(_, ItemType::Trait)) => | |
85aaf69f | 913 | Some(&self.stack[..self.stack.len() - 1]), |
9346a6ac AL |
914 | // The current stack not necessarily has correlation |
915 | // for where the type was defined. On the other | |
916 | // hand, `paths` always has the right | |
917 | // information if present. | |
1a4d82fc JJ |
918 | Some(&(ref fqp, ItemType::Struct)) | |
919 | Some(&(ref fqp, ItemType::Enum)) => | |
85aaf69f SL |
920 | Some(&fqp[..fqp.len() - 1]), |
921 | Some(..) => Some(&*self.stack), | |
1a4d82fc JJ |
922 | None => None |
923 | }; | |
924 | ((Some(*last), path), true) | |
925 | } | |
926 | } | |
62682a34 SL |
927 | clean::TypedefItem(_, true) => { |
928 | // skip associated types in impls | |
929 | ((None, None), false) | |
930 | } | |
85aaf69f | 931 | _ => ((None, Some(&*self.stack)), false) |
1a4d82fc JJ |
932 | }; |
933 | let hidden_field = match item.inner { | |
934 | clean::StructFieldItem(clean::HiddenStructField) => true, | |
935 | _ => false | |
936 | }; | |
937 | ||
938 | match parent { | |
939 | (parent, Some(path)) if is_method || (!self.privmod && !hidden_field) => { | |
c34b1796 AL |
940 | // Needed to determine `self` type. |
941 | let parent_basename = self.parent_stack.first().and_then(|parent| { | |
942 | match self.paths.get(parent) { | |
943 | Some(&(ref fqp, _)) => Some(fqp[fqp.len() - 1].clone()), | |
944 | _ => None | |
945 | } | |
946 | }); | |
947 | ||
1a4d82fc JJ |
948 | self.search_index.push(IndexItem { |
949 | ty: shortty(&item), | |
950 | name: s.to_string(), | |
c1a9b12d | 951 | path: path.join("::").to_string(), |
c34b1796 | 952 | desc: shorter(item.doc_value()), |
1a4d82fc | 953 | parent: parent, |
c34b1796 | 954 | search_type: get_index_search_type(&item, parent_basename), |
1a4d82fc JJ |
955 | }); |
956 | } | |
957 | (Some(parent), None) if is_method || (!self.privmod && !hidden_field)=> { | |
958 | if ast_util::is_local(parent) { | |
959 | // We have a parent, but we don't know where they're | |
960 | // defined yet. Wait for later to index this item. | |
961 | self.orphan_methods.push((parent.node, item.clone())) | |
962 | } | |
963 | } | |
964 | _ => {} | |
965 | } | |
966 | } | |
967 | ||
968 | // Keep track of the fully qualified path for this item. | |
969 | let pushed = if item.name.is_some() { | |
970 | let n = item.name.as_ref().unwrap(); | |
9346a6ac | 971 | if !n.is_empty() { |
1a4d82fc JJ |
972 | self.stack.push(n.to_string()); |
973 | true | |
974 | } else { false } | |
975 | } else { false }; | |
976 | match item.inner { | |
977 | clean::StructItem(..) | clean::EnumItem(..) | | |
978 | clean::TypedefItem(..) | clean::TraitItem(..) | | |
979 | clean::FunctionItem(..) | clean::ModuleItem(..) | | |
980 | clean::ForeignFunctionItem(..) if !self.privmod => { | |
981 | // Reexported items mean that the same id can show up twice | |
982 | // in the rustdoc ast that we're looking at. We know, | |
983 | // however, that a reexported item doesn't show up in the | |
984 | // `public_items` map, so we can skip inserting into the | |
985 | // paths map if there was already an entry present and we're | |
986 | // not a public item. | |
987 | let id = item.def_id.node; | |
988 | if !self.paths.contains_key(&item.def_id) || | |
989 | !ast_util::is_local(item.def_id) || | |
990 | self.public_items.contains(&id) { | |
991 | self.paths.insert(item.def_id, | |
992 | (self.stack.clone(), shortty(&item))); | |
993 | } | |
994 | } | |
995 | // link variants to their parent enum because pages aren't emitted | |
996 | // for each variant | |
997 | clean::VariantItem(..) if !self.privmod => { | |
998 | let mut stack = self.stack.clone(); | |
999 | stack.pop(); | |
1000 | self.paths.insert(item.def_id, (stack, ItemType::Enum)); | |
1001 | } | |
1002 | ||
1003 | clean::PrimitiveItem(..) if item.visibility.is_some() => { | |
1004 | self.paths.insert(item.def_id, (self.stack.clone(), | |
1005 | shortty(&item))); | |
1006 | } | |
1007 | ||
1008 | _ => {} | |
1009 | } | |
1010 | ||
1011 | // Maintain the parent stack | |
1012 | let parent_pushed = match item.inner { | |
1013 | clean::TraitItem(..) | clean::EnumItem(..) | clean::StructItem(..) => { | |
1014 | self.parent_stack.push(item.def_id); | |
1015 | true | |
1016 | } | |
1017 | clean::ImplItem(ref i) => { | |
1018 | match i.for_ { | |
1019 | clean::ResolvedPath{ did, .. } => { | |
1020 | self.parent_stack.push(did); | |
1021 | true | |
1022 | } | |
9346a6ac AL |
1023 | ref t => { |
1024 | match t.primitive_type() { | |
1025 | Some(prim) => { | |
1026 | let did = ast_util::local_def(prim.to_node_id()); | |
1027 | self.parent_stack.push(did); | |
1028 | true | |
1029 | } | |
1030 | _ => false, | |
1031 | } | |
1032 | } | |
1a4d82fc JJ |
1033 | } |
1034 | } | |
1035 | _ => false | |
1036 | }; | |
1037 | ||
1038 | // Once we've recursively found all the generics, then hoard off all the | |
1039 | // implementations elsewhere | |
1040 | let ret = match self.fold_item_recur(item) { | |
1041 | Some(item) => { | |
1042 | match item { | |
1043 | clean::Item{ attrs, inner: clean::ImplItem(i), .. } => { | |
1a4d82fc JJ |
1044 | // extract relevant documentation for this impl |
1045 | let dox = match attrs.into_iter().find(|a| { | |
1046 | match *a { | |
1047 | clean::NameValue(ref x, _) | |
1048 | if "doc" == *x => { | |
1049 | true | |
1050 | } | |
1051 | _ => false | |
1052 | } | |
1053 | }) { | |
1054 | Some(clean::NameValue(_, dox)) => Some(dox), | |
1055 | Some(..) | None => None, | |
1056 | }; | |
1057 | ||
1058 | // Figure out the id of this impl. This may map to a | |
1059 | // primitive rather than always to a struct/enum. | |
1060 | let did = match i.for_ { | |
9346a6ac AL |
1061 | clean::ResolvedPath { did, .. } | |
1062 | clean::BorrowedRef { | |
1063 | type_: box clean::ResolvedPath { did, .. }, .. | |
1064 | } => { | |
1065 | Some(did) | |
1a4d82fc JJ |
1066 | } |
1067 | ||
9346a6ac | 1068 | ref t => { |
d9579d0f AL |
1069 | t.primitive_type().and_then(|t| { |
1070 | self.primitive_locations.get(&t).map(|n| { | |
1071 | let id = t.to_node_id(); | |
1072 | ast::DefId { krate: *n, node: id } | |
1073 | }) | |
9346a6ac | 1074 | }) |
1a4d82fc | 1075 | } |
1a4d82fc JJ |
1076 | }; |
1077 | ||
1078 | if let Some(did) = did { | |
c34b1796 | 1079 | self.impls.entry(did).or_insert(vec![]).push(Impl { |
1a4d82fc JJ |
1080 | impl_: i, |
1081 | dox: dox, | |
1082 | stability: item.stability.clone(), | |
1083 | }); | |
1084 | } | |
1085 | ||
1086 | None | |
1087 | } | |
1088 | ||
1089 | i => Some(i), | |
1090 | } | |
1091 | } | |
1092 | i => i, | |
1093 | }; | |
1094 | ||
1095 | if pushed { self.stack.pop().unwrap(); } | |
1096 | if parent_pushed { self.parent_stack.pop().unwrap(); } | |
1097 | self.privmod = orig_privmod; | |
1098 | return ret; | |
1099 | } | |
1100 | } | |
1101 | ||
1102 | impl<'a> Cache { | |
1103 | fn generics(&mut self, generics: &clean::Generics) { | |
85aaf69f | 1104 | for typ in &generics.type_params { |
1a4d82fc JJ |
1105 | self.typarams.insert(typ.did, typ.name.clone()); |
1106 | } | |
1107 | } | |
1108 | } | |
1109 | ||
1110 | impl Context { | |
1111 | /// Recurse in the directory structure and change the "root path" to make | |
1112 | /// sure it always points to the top (relatively) | |
1113 | fn recurse<T, F>(&mut self, s: String, f: F) -> T where | |
1114 | F: FnOnce(&mut Context) -> T, | |
1115 | { | |
9346a6ac | 1116 | if s.is_empty() { |
1a4d82fc JJ |
1117 | panic!("Unexpected empty destination: {:?}", self.current); |
1118 | } | |
1119 | let prev = self.dst.clone(); | |
85aaf69f | 1120 | self.dst.push(&s); |
1a4d82fc JJ |
1121 | self.root_path.push_str("../"); |
1122 | self.current.push(s); | |
1123 | ||
1124 | info!("Recursing into {}", self.dst.display()); | |
1125 | ||
1126 | mkdir(&self.dst).unwrap(); | |
1127 | let ret = f(self); | |
1128 | ||
1129 | info!("Recursed; leaving {}", self.dst.display()); | |
1130 | ||
1131 | // Go back to where we were at | |
1132 | self.dst = prev; | |
1133 | let len = self.root_path.len(); | |
1134 | self.root_path.truncate(len - 3); | |
1135 | self.current.pop().unwrap(); | |
1136 | ||
1137 | return ret; | |
1138 | } | |
1139 | ||
1140 | /// Main method for rendering a crate. | |
1141 | /// | |
1142 | /// This currently isn't parallelized, but it'd be pretty easy to add | |
1143 | /// parallelization to this function. | |
d9579d0f | 1144 | fn krate(self, mut krate: clean::Crate) -> io::Result<()> { |
1a4d82fc JJ |
1145 | let mut item = match krate.module.take() { |
1146 | Some(i) => i, | |
1147 | None => return Ok(()) | |
1148 | }; | |
1149 | item.name = Some(krate.name); | |
1150 | ||
1a4d82fc JJ |
1151 | // render the crate documentation |
1152 | let mut work = vec!((self, item)); | |
1153 | loop { | |
1154 | match work.pop() { | |
1155 | Some((mut cx, item)) => try!(cx.item(item, |cx, item| { | |
1156 | work.push((cx.clone(), item)); | |
1157 | })), | |
1158 | None => break, | |
1159 | } | |
1160 | } | |
1161 | ||
1162 | Ok(()) | |
1163 | } | |
1164 | ||
1165 | /// Non-parallelized version of rendering an item. This will take the input | |
1166 | /// item, render its contents, and then invoke the specified closure with | |
1167 | /// all sub-items which need to be rendered. | |
1168 | /// | |
1169 | /// The rendering driver uses this closure to queue up more work. | |
c34b1796 | 1170 | fn item<F>(&mut self, item: clean::Item, mut f: F) -> io::Result<()> where |
1a4d82fc JJ |
1171 | F: FnMut(&mut Context, clean::Item), |
1172 | { | |
c34b1796 AL |
1173 | fn render(w: File, cx: &Context, it: &clean::Item, |
1174 | pushname: bool) -> io::Result<()> { | |
1a4d82fc JJ |
1175 | // A little unfortunate that this is done like this, but it sure |
1176 | // does make formatting *a lot* nicer. | |
1177 | CURRENT_LOCATION_KEY.with(|slot| { | |
1178 | *slot.borrow_mut() = cx.current.clone(); | |
1179 | }); | |
1180 | ||
c1a9b12d | 1181 | let mut title = cx.current.join("::"); |
1a4d82fc | 1182 | if pushname { |
9346a6ac | 1183 | if !title.is_empty() { |
1a4d82fc JJ |
1184 | title.push_str("::"); |
1185 | } | |
85aaf69f | 1186 | title.push_str(it.name.as_ref().unwrap()); |
1a4d82fc JJ |
1187 | } |
1188 | title.push_str(" - Rust"); | |
1189 | let tyname = shortty(it).to_static_str(); | |
1190 | let is_crate = match it.inner { | |
1191 | clean::ModuleItem(clean::Module { items: _, is_crate: true }) => true, | |
1192 | _ => false | |
1193 | }; | |
1194 | let desc = if is_crate { | |
1195 | format!("API documentation for the Rust `{}` crate.", | |
1196 | cx.layout.krate) | |
1197 | } else { | |
1198 | format!("API documentation for the Rust `{}` {} in crate `{}`.", | |
1199 | it.name.as_ref().unwrap(), tyname, cx.layout.krate) | |
1200 | }; | |
1201 | let keywords = make_item_keywords(it); | |
1202 | let page = layout::Page { | |
1203 | ty: tyname, | |
85aaf69f SL |
1204 | root_path: &cx.root_path, |
1205 | title: &title, | |
1206 | description: &desc, | |
1207 | keywords: &keywords, | |
1a4d82fc JJ |
1208 | }; |
1209 | ||
1210 | markdown::reset_headers(); | |
1211 | ||
1212 | // We have a huge number of calls to write, so try to alleviate some | |
1213 | // of the pain by using a buffered writer instead of invoking the | |
1214 | // write syscall all the time. | |
c34b1796 | 1215 | let mut writer = BufWriter::new(w); |
1a4d82fc JJ |
1216 | if !cx.render_redirect_pages { |
1217 | try!(layout::render(&mut writer, &cx.layout, &page, | |
1218 | &Sidebar{ cx: cx, item: it }, | |
1219 | &Item{ cx: cx, item: it })); | |
1220 | } else { | |
1221 | let mut url = repeat("../").take(cx.current.len()) | |
1222 | .collect::<String>(); | |
1223 | match cache().paths.get(&it.def_id) { | |
1224 | Some(&(ref names, _)) => { | |
85aaf69f SL |
1225 | for name in &names[..names.len() - 1] { |
1226 | url.push_str(name); | |
1a4d82fc JJ |
1227 | url.push_str("/"); |
1228 | } | |
85aaf69f SL |
1229 | url.push_str(&item_path(it)); |
1230 | try!(layout::redirect(&mut writer, &url)); | |
1a4d82fc JJ |
1231 | } |
1232 | None => {} | |
1233 | } | |
1234 | } | |
1235 | writer.flush() | |
1236 | } | |
1237 | ||
1238 | // Private modules may survive the strip-private pass if they | |
1239 | // contain impls for public types. These modules can also | |
1240 | // contain items such as publicly reexported structures. | |
1241 | // | |
1242 | // External crates will provide links to these structures, so | |
1243 | // these modules are recursed into, but not rendered normally (a | |
1244 | // flag on the context). | |
1245 | if !self.render_redirect_pages { | |
1246 | self.render_redirect_pages = self.ignore_private_item(&item); | |
1247 | } | |
1248 | ||
1249 | match item.inner { | |
1250 | // modules are special because they add a namespace. We also need to | |
1251 | // recurse into the items of the module as well. | |
1252 | clean::ModuleItem(..) => { | |
1253 | let name = item.name.as_ref().unwrap().to_string(); | |
1254 | let mut item = Some(item); | |
1255 | self.recurse(name, |this| { | |
1256 | let item = item.take().unwrap(); | |
1257 | let dst = this.dst.join("index.html"); | |
1258 | let dst = try!(File::create(&dst)); | |
1259 | try!(render(dst, this, &item, false)); | |
1260 | ||
1261 | let m = match item.inner { | |
1262 | clean::ModuleItem(m) => m, | |
1263 | _ => unreachable!() | |
1264 | }; | |
c34b1796 AL |
1265 | |
1266 | // render sidebar-items.js used throughout this module | |
1267 | { | |
1268 | let items = this.build_sidebar_items(&m); | |
1269 | let js_dst = this.dst.join("sidebar-items.js"); | |
1270 | let mut js_out = BufWriter::new(try!(File::create(&js_dst))); | |
1271 | try!(write!(&mut js_out, "initSidebarItems({});", | |
1272 | json::as_json(&items))); | |
1273 | } | |
1274 | ||
85aaf69f | 1275 | for item in m.items { |
1a4d82fc JJ |
1276 | f(this,item); |
1277 | } | |
1278 | Ok(()) | |
1279 | }) | |
1280 | } | |
1281 | ||
1282 | // Things which don't have names (like impls) don't get special | |
1283 | // pages dedicated to them. | |
1284 | _ if item.name.is_some() => { | |
c34b1796 | 1285 | let dst = self.dst.join(&item_path(&item)); |
1a4d82fc JJ |
1286 | let dst = try!(File::create(&dst)); |
1287 | render(dst, self, &item, true) | |
1288 | } | |
1289 | ||
1290 | _ => Ok(()) | |
1291 | } | |
1292 | } | |
1293 | ||
d9579d0f AL |
1294 | fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> { |
1295 | // BTreeMap instead of HashMap to get a sorted output | |
1296 | let mut map = BTreeMap::new(); | |
85aaf69f | 1297 | for item in &m.items { |
1a4d82fc JJ |
1298 | if self.ignore_private_item(item) { continue } |
1299 | ||
1a4d82fc JJ |
1300 | let short = shortty(item).to_static_str(); |
1301 | let myname = match item.name { | |
1302 | None => continue, | |
1303 | Some(ref s) => s.to_string(), | |
1304 | }; | |
1305 | let short = short.to_string(); | |
c34b1796 AL |
1306 | map.entry(short).or_insert(vec![]) |
1307 | .push((myname, Some(plain_summary_line(item.doc_value())))); | |
1a4d82fc JJ |
1308 | } |
1309 | ||
85aaf69f | 1310 | for (_, items) in &mut map { |
1a4d82fc JJ |
1311 | items.sort(); |
1312 | } | |
1313 | return map; | |
1314 | } | |
1315 | ||
1316 | fn ignore_private_item(&self, it: &clean::Item) -> bool { | |
1317 | match it.inner { | |
1318 | clean::ModuleItem(ref m) => { | |
9346a6ac AL |
1319 | (m.items.is_empty() && |
1320 | it.doc_value().is_none() && | |
1321 | it.visibility != Some(ast::Public)) || | |
1a4d82fc JJ |
1322 | (self.passes.contains("strip-private") && it.visibility != Some(ast::Public)) |
1323 | } | |
1324 | clean::PrimitiveItem(..) => it.visibility != Some(ast::Public), | |
1325 | _ => false, | |
1326 | } | |
1327 | } | |
1328 | } | |
1329 | ||
1330 | impl<'a> Item<'a> { | |
1331 | fn ismodule(&self) -> bool { | |
1332 | match self.item.inner { | |
1333 | clean::ModuleItem(..) => true, _ => false | |
1334 | } | |
1335 | } | |
1336 | ||
1337 | /// Generate a url appropriate for an `href` attribute back to the source of | |
1338 | /// this item. | |
1339 | /// | |
1340 | /// The url generated, when clicked, will redirect the browser back to the | |
1341 | /// original source code. | |
1342 | /// | |
1343 | /// If `None` is returned, then a source link couldn't be generated. This | |
1344 | /// may happen, for example, with externally inlined items where the source | |
1345 | /// of their crate documentation isn't known. | |
1346 | fn href(&self, cx: &Context) -> Option<String> { | |
d9579d0f AL |
1347 | let href = if self.item.source.loline == self.item.source.hiline { |
1348 | format!("{}", self.item.source.loline) | |
1349 | } else { | |
1350 | format!("{}-{}", self.item.source.loline, self.item.source.hiline) | |
1351 | }; | |
1352 | ||
1353 | // First check to see if this is an imported macro source. In this case | |
1354 | // we need to handle it specially as cross-crate inlined macros have... | |
1355 | // odd locations! | |
1356 | let imported_macro_from = match self.item.inner { | |
1357 | clean::MacroItem(ref m) => m.imported_from.as_ref(), | |
1358 | _ => None, | |
1359 | }; | |
1360 | if let Some(krate) = imported_macro_from { | |
1361 | let cache = cache(); | |
1362 | let root = cache.extern_locations.values().find(|&&(ref n, _)| { | |
1363 | *krate == *n | |
1364 | }).map(|l| &l.1); | |
1365 | let root = match root { | |
1366 | Some(&Remote(ref s)) => s.to_string(), | |
1367 | Some(&Local) => self.cx.root_path.clone(), | |
1368 | None | Some(&Unknown) => return None, | |
1369 | }; | |
1370 | Some(format!("{root}/{krate}/macro.{name}.html?gotomacrosrc=1", | |
1371 | root = root, | |
1372 | krate = krate, | |
1373 | name = self.item.name.as_ref().unwrap())) | |
1374 | ||
1a4d82fc JJ |
1375 | // If this item is part of the local crate, then we're guaranteed to |
1376 | // know the span, so we plow forward and generate a proper url. The url | |
1377 | // has anchors for the line numbers that we're linking to. | |
d9579d0f | 1378 | } else if ast_util::is_local(self.item.def_id) { |
1a4d82fc | 1379 | let mut path = Vec::new(); |
c34b1796 AL |
1380 | clean_srcpath(&cx.src_root, Path::new(&self.item.source.filename), |
1381 | true, |component| { | |
1a4d82fc JJ |
1382 | path.push(component.to_string()); |
1383 | }); | |
1a4d82fc JJ |
1384 | Some(format!("{root}src/{krate}/{path}.html#{href}", |
1385 | root = self.cx.root_path, | |
1386 | krate = self.cx.layout.krate, | |
c1a9b12d | 1387 | path = path.join("/"), |
1a4d82fc JJ |
1388 | href = href)) |
1389 | ||
1390 | // If this item is not part of the local crate, then things get a little | |
1391 | // trickier. We don't actually know the span of the external item, but | |
1392 | // we know that the documentation on the other end knows the span! | |
1393 | // | |
1394 | // In this case, we generate a link to the *documentation* for this type | |
1395 | // in the original crate. There's an extra URL parameter which says that | |
1396 | // we want to go somewhere else, and the JS on the destination page will | |
1397 | // pick it up and instantly redirect the browser to the source code. | |
1398 | // | |
1399 | // If we don't know where the external documentation for this crate is | |
1400 | // located, then we return `None`. | |
1401 | } else { | |
1402 | let cache = cache(); | |
c34b1796 AL |
1403 | let path = &cache.external_paths[&self.item.def_id]; |
1404 | let root = match cache.extern_locations[&self.item.def_id.krate] { | |
d9579d0f AL |
1405 | (_, Remote(ref s)) => s.to_string(), |
1406 | (_, Local) => self.cx.root_path.clone(), | |
1407 | (_, Unknown) => return None, | |
1a4d82fc JJ |
1408 | }; |
1409 | Some(format!("{root}{path}/{file}?gotosrc={goto}", | |
1410 | root = root, | |
c1a9b12d | 1411 | path = path[..path.len() - 1].join("/"), |
1a4d82fc JJ |
1412 | file = item_path(self.item), |
1413 | goto = self.item.def_id.node)) | |
1414 | } | |
1415 | } | |
1416 | } | |
1417 | ||
1418 | ||
85aaf69f | 1419 | impl<'a> fmt::Display for Item<'a> { |
1a4d82fc JJ |
1420 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
1421 | // Write the breadcrumb trail header for the top | |
1422 | try!(write!(fmt, "\n<h1 class='fqn'><span class='in-band'>")); | |
1423 | match self.item.inner { | |
1424 | clean::ModuleItem(ref m) => if m.is_crate { | |
1425 | try!(write!(fmt, "Crate ")); | |
1426 | } else { | |
1427 | try!(write!(fmt, "Module ")); | |
1428 | }, | |
1429 | clean::FunctionItem(..) => try!(write!(fmt, "Function ")), | |
1430 | clean::TraitItem(..) => try!(write!(fmt, "Trait ")), | |
1431 | clean::StructItem(..) => try!(write!(fmt, "Struct ")), | |
1432 | clean::EnumItem(..) => try!(write!(fmt, "Enum ")), | |
1433 | clean::PrimitiveItem(..) => try!(write!(fmt, "Primitive Type ")), | |
1434 | _ => {} | |
1435 | } | |
1436 | let is_primitive = match self.item.inner { | |
1437 | clean::PrimitiveItem(..) => true, | |
1438 | _ => false, | |
1439 | }; | |
1440 | if !is_primitive { | |
85aaf69f | 1441 | let cur = &self.cx.current; |
1a4d82fc JJ |
1442 | let amt = if self.ismodule() { cur.len() - 1 } else { cur.len() }; |
1443 | for (i, component) in cur.iter().enumerate().take(amt) { | |
1444 | try!(write!(fmt, "<a href='{}index.html'>{}</a>::<wbr>", | |
1445 | repeat("../").take(cur.len() - i - 1) | |
1446 | .collect::<String>(), | |
85aaf69f | 1447 | component)); |
1a4d82fc JJ |
1448 | } |
1449 | } | |
1450 | try!(write!(fmt, "<a class='{}' href=''>{}</a>", | |
85aaf69f | 1451 | shortty(self.item), self.item.name.as_ref().unwrap())); |
1a4d82fc | 1452 | |
1a4d82fc | 1453 | try!(write!(fmt, "</span>")); // in-band |
1a4d82fc | 1454 | try!(write!(fmt, "<span class='out-of-band'>")); |
1a4d82fc JJ |
1455 | try!(write!(fmt, |
1456 | r##"<span id='render-detail'> | |
d9579d0f AL |
1457 | <a id="toggle-all-docs" href="javascript:void(0)" title="collapse all docs"> |
1458 | [<span class='inner'>−</span>] | |
1459 | </a> | |
1a4d82fc JJ |
1460 | </span>"##)); |
1461 | ||
1462 | // Write `src` tag | |
1463 | // | |
1464 | // When this item is part of a `pub use` in a downstream crate, the | |
1465 | // [src] link in the downstream documentation will actually come back to | |
1466 | // this page, and this link will be auto-clicked. The `id` attribute is | |
1467 | // used to find the link to auto-click. | |
1468 | if self.cx.include_sources && !is_primitive { | |
1469 | match self.href(self.cx) { | |
1470 | Some(l) => { | |
d9579d0f AL |
1471 | try!(write!(fmt, "<a id='src-{}' class='srclink' \ |
1472 | href='{}' title='{}'>[src]</a>", | |
1473 | self.item.def_id.node, l, "goto source code")); | |
1a4d82fc JJ |
1474 | } |
1475 | None => {} | |
1476 | } | |
1477 | } | |
1478 | ||
1479 | try!(write!(fmt, "</span>")); // out-of-band | |
1480 | ||
1481 | try!(write!(fmt, "</h1>\n")); | |
1482 | ||
1483 | match self.item.inner { | |
1484 | clean::ModuleItem(ref m) => { | |
85aaf69f | 1485 | item_module(fmt, self.cx, self.item, &m.items) |
1a4d82fc JJ |
1486 | } |
1487 | clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => | |
1488 | item_function(fmt, self.item, f), | |
1489 | clean::TraitItem(ref t) => item_trait(fmt, self.cx, self.item, t), | |
1490 | clean::StructItem(ref s) => item_struct(fmt, self.item, s), | |
1491 | clean::EnumItem(ref e) => item_enum(fmt, self.item, e), | |
62682a34 | 1492 | clean::TypedefItem(ref t, _) => item_typedef(fmt, self.item, t), |
1a4d82fc JJ |
1493 | clean::MacroItem(ref m) => item_macro(fmt, self.item, m), |
1494 | clean::PrimitiveItem(ref p) => item_primitive(fmt, self.item, p), | |
1495 | clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => | |
1496 | item_static(fmt, self.item, i), | |
1497 | clean::ConstantItem(ref c) => item_constant(fmt, self.item, c), | |
1498 | _ => Ok(()) | |
1499 | } | |
1500 | } | |
1501 | } | |
1502 | ||
1503 | fn item_path(item: &clean::Item) -> String { | |
1504 | match item.inner { | |
1505 | clean::ModuleItem(..) => { | |
1506 | format!("{}/index.html", item.name.as_ref().unwrap()) | |
1507 | } | |
1508 | _ => { | |
1509 | format!("{}.{}.html", | |
1510 | shortty(item).to_static_str(), | |
1511 | *item.name.as_ref().unwrap()) | |
1512 | } | |
1513 | } | |
1514 | } | |
1515 | ||
1516 | fn full_path(cx: &Context, item: &clean::Item) -> String { | |
c1a9b12d | 1517 | let mut s = cx.current.join("::"); |
1a4d82fc | 1518 | s.push_str("::"); |
85aaf69f | 1519 | s.push_str(item.name.as_ref().unwrap()); |
1a4d82fc JJ |
1520 | return s |
1521 | } | |
1522 | ||
c34b1796 | 1523 | fn shorter<'a>(s: Option<&'a str>) -> String { |
1a4d82fc | 1524 | match s { |
c34b1796 AL |
1525 | Some(s) => s.lines().take_while(|line|{ |
1526 | (*line).chars().any(|chr|{ | |
1527 | !chr.is_whitespace() | |
1528 | }) | |
c1a9b12d | 1529 | }).collect::<Vec<_>>().join("\n"), |
c34b1796 | 1530 | None => "".to_string() |
1a4d82fc JJ |
1531 | } |
1532 | } | |
1533 | ||
85aaf69f | 1534 | #[inline] |
c34b1796 AL |
1535 | fn plain_summary_line(s: Option<&str>) -> String { |
1536 | let line = shorter(s).replace("\n", " "); | |
1537 | markdown::plain_summary_line(&line[..]) | |
85aaf69f SL |
1538 | } |
1539 | ||
1a4d82fc | 1540 | fn document(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result { |
d9579d0f AL |
1541 | if let Some(s) = short_stability(item, true) { |
1542 | try!(write!(w, "<div class='stability'>{}</div>", s)); | |
1543 | } | |
1544 | if let Some(s) = item.doc_value() { | |
1545 | try!(write!(w, "<div class='docblock'>{}</div>", Markdown(s))); | |
1a4d82fc JJ |
1546 | } |
1547 | Ok(()) | |
1548 | } | |
1549 | ||
1550 | fn item_module(w: &mut fmt::Formatter, cx: &Context, | |
1551 | item: &clean::Item, items: &[clean::Item]) -> fmt::Result { | |
1552 | try!(document(w, item)); | |
1553 | ||
85aaf69f | 1554 | let mut indices = (0..items.len()).filter(|i| { |
1a4d82fc | 1555 | !cx.ignore_private_item(&items[*i]) |
c34b1796 | 1556 | }).collect::<Vec<usize>>(); |
1a4d82fc JJ |
1557 | |
1558 | // the order of item types in the listing | |
1559 | fn reorder(ty: ItemType) -> u8 { | |
1560 | match ty { | |
85aaf69f SL |
1561 | ItemType::ExternCrate => 0, |
1562 | ItemType::Import => 1, | |
1563 | ItemType::Primitive => 2, | |
1564 | ItemType::Module => 3, | |
1565 | ItemType::Macro => 4, | |
1566 | ItemType::Struct => 5, | |
1567 | ItemType::Enum => 6, | |
1568 | ItemType::Constant => 7, | |
1569 | ItemType::Static => 8, | |
1570 | ItemType::Trait => 9, | |
1571 | ItemType::Function => 10, | |
1572 | ItemType::Typedef => 12, | |
1573 | _ => 13 + ty as u8, | |
1a4d82fc JJ |
1574 | } |
1575 | } | |
1576 | ||
c34b1796 | 1577 | fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: usize, idx2: usize) -> Ordering { |
1a4d82fc JJ |
1578 | let ty1 = shortty(i1); |
1579 | let ty2 = shortty(i2); | |
d9579d0f AL |
1580 | if ty1 != ty2 { |
1581 | return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)) | |
1582 | } | |
1583 | let s1 = i1.stability.as_ref().map(|s| s.level); | |
1584 | let s2 = i2.stability.as_ref().map(|s| s.level); | |
1585 | match (s1, s2) { | |
1586 | (Some(attr::Unstable), Some(attr::Stable)) => return Ordering::Greater, | |
1587 | (Some(attr::Stable), Some(attr::Unstable)) => return Ordering::Less, | |
1588 | _ => {} | |
1a4d82fc | 1589 | } |
d9579d0f | 1590 | i1.name.cmp(&i2.name) |
1a4d82fc JJ |
1591 | } |
1592 | ||
1593 | indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2)); | |
1594 | ||
1595 | debug!("{:?}", indices); | |
1596 | let mut curty = None; | |
85aaf69f | 1597 | for &idx in &indices { |
1a4d82fc JJ |
1598 | let myitem = &items[idx]; |
1599 | ||
1600 | let myty = Some(shortty(myitem)); | |
85aaf69f SL |
1601 | if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) { |
1602 | // Put `extern crate` and `use` re-exports in the same section. | |
1603 | curty = myty; | |
1604 | } else if myty != curty { | |
1a4d82fc JJ |
1605 | if curty.is_some() { |
1606 | try!(write!(w, "</table>")); | |
1607 | } | |
1608 | curty = myty; | |
1609 | let (short, name) = match myty.unwrap() { | |
85aaf69f SL |
1610 | ItemType::ExternCrate | |
1611 | ItemType::Import => ("reexports", "Reexports"), | |
1a4d82fc JJ |
1612 | ItemType::Module => ("modules", "Modules"), |
1613 | ItemType::Struct => ("structs", "Structs"), | |
1614 | ItemType::Enum => ("enums", "Enums"), | |
1615 | ItemType::Function => ("functions", "Functions"), | |
1616 | ItemType::Typedef => ("types", "Type Definitions"), | |
1617 | ItemType::Static => ("statics", "Statics"), | |
1618 | ItemType::Constant => ("constants", "Constants"), | |
1619 | ItemType::Trait => ("traits", "Traits"), | |
1620 | ItemType::Impl => ("impls", "Implementations"), | |
1a4d82fc JJ |
1621 | ItemType::TyMethod => ("tymethods", "Type Methods"), |
1622 | ItemType::Method => ("methods", "Methods"), | |
1623 | ItemType::StructField => ("fields", "Struct Fields"), | |
1624 | ItemType::Variant => ("variants", "Variants"), | |
1625 | ItemType::Macro => ("macros", "Macros"), | |
1626 | ItemType::Primitive => ("primitives", "Primitive Types"), | |
1627 | ItemType::AssociatedType => ("associated-types", "Associated Types"), | |
d9579d0f | 1628 | ItemType::AssociatedConst => ("associated-consts", "Associated Constants"), |
1a4d82fc JJ |
1629 | }; |
1630 | try!(write!(w, | |
1631 | "<h2 id='{id}' class='section-header'>\ | |
1632 | <a href=\"#{id}\">{name}</a></h2>\n<table>", | |
1633 | id = short, name = name)); | |
1634 | } | |
1635 | ||
1636 | match myitem.inner { | |
85aaf69f SL |
1637 | clean::ExternCrateItem(ref name, ref src) => { |
1638 | match *src { | |
1639 | Some(ref src) => { | |
d9579d0f | 1640 | try!(write!(w, "<tr><td><code>{}extern crate {} as {};", |
85aaf69f SL |
1641 | VisSpace(myitem.visibility), |
1642 | src, | |
1643 | name)) | |
1a4d82fc | 1644 | } |
85aaf69f SL |
1645 | None => { |
1646 | try!(write!(w, "<tr><td><code>{}extern crate {};", | |
1647 | VisSpace(myitem.visibility), name)) | |
1a4d82fc JJ |
1648 | } |
1649 | } | |
85aaf69f SL |
1650 | try!(write!(w, "</code></td></tr>")); |
1651 | } | |
1a4d82fc | 1652 | |
85aaf69f SL |
1653 | clean::ImportItem(ref import) => { |
1654 | try!(write!(w, "<tr><td><code>{}{}</code></td></tr>", | |
1655 | VisSpace(myitem.visibility), *import)); | |
1a4d82fc JJ |
1656 | } |
1657 | ||
1658 | _ => { | |
1659 | if myitem.name.is_none() { continue } | |
d9579d0f AL |
1660 | let stab_docs = if let Some(s) = short_stability(myitem, false) { |
1661 | format!("[{}]", s) | |
1662 | } else { | |
1663 | String::new() | |
1664 | }; | |
1a4d82fc | 1665 | try!(write!(w, " |
d9579d0f AL |
1666 | <tr class='{stab} module-item'> |
1667 | <td><a class='{class}' href='{href}' | |
1668 | title='{title}'>{name}</a></td> | |
1669 | <td class='docblock short'> | |
1670 | {stab_docs} {docs} | |
1671 | </td> | |
1a4d82fc JJ |
1672 | </tr> |
1673 | ", | |
d9579d0f AL |
1674 | name = *myitem.name.as_ref().unwrap(), |
1675 | stab_docs = stab_docs, | |
1676 | docs = Markdown(&shorter(myitem.doc_value())), | |
1a4d82fc | 1677 | class = shortty(myitem), |
d9579d0f | 1678 | stab = myitem.stability_class(), |
1a4d82fc | 1679 | href = item_path(myitem), |
d9579d0f | 1680 | title = full_path(cx, myitem))); |
1a4d82fc JJ |
1681 | } |
1682 | } | |
1683 | } | |
1684 | ||
1685 | write!(w, "</table>") | |
1686 | } | |
1687 | ||
d9579d0f AL |
1688 | fn short_stability(item: &clean::Item, show_reason: bool) -> Option<String> { |
1689 | item.stability.as_ref().and_then(|stab| { | |
1690 | let reason = if show_reason && !stab.reason.is_empty() { | |
1691 | format!(": {}", stab.reason) | |
1692 | } else { | |
1693 | String::new() | |
1694 | }; | |
1695 | let text = if !stab.deprecated_since.is_empty() { | |
1696 | let since = if show_reason { | |
1697 | format!(" since {}", Escape(&stab.deprecated_since)) | |
1698 | } else { | |
1699 | String::new() | |
1700 | }; | |
1701 | format!("Deprecated{}{}", since, Markdown(&reason)) | |
1702 | } else if stab.level == attr::Unstable { | |
1703 | format!("Unstable{}", Markdown(&reason)) | |
1704 | } else { | |
1705 | return None | |
1706 | }; | |
1707 | Some(format!("<em class='stab {}'>{}</em>", | |
1708 | item.stability_class(), text)) | |
1709 | }) | |
1710 | } | |
1711 | ||
1a4d82fc JJ |
1712 | struct Initializer<'a>(&'a str); |
1713 | ||
85aaf69f | 1714 | impl<'a> fmt::Display for Initializer<'a> { |
1a4d82fc JJ |
1715 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1716 | let Initializer(s) = *self; | |
9346a6ac | 1717 | if s.is_empty() { return Ok(()); } |
1a4d82fc | 1718 | try!(write!(f, "<code> = </code>")); |
85aaf69f | 1719 | write!(f, "<code>{}</code>", s) |
1a4d82fc JJ |
1720 | } |
1721 | } | |
1722 | ||
1723 | fn item_constant(w: &mut fmt::Formatter, it: &clean::Item, | |
1724 | c: &clean::Constant) -> fmt::Result { | |
1725 | try!(write!(w, "<pre class='rust const'>{vis}const \ | |
1726 | {name}: {typ}{init}</pre>", | |
1727 | vis = VisSpace(it.visibility), | |
85aaf69f | 1728 | name = it.name.as_ref().unwrap(), |
1a4d82fc | 1729 | typ = c.type_, |
85aaf69f | 1730 | init = Initializer(&c.expr))); |
1a4d82fc JJ |
1731 | document(w, it) |
1732 | } | |
1733 | ||
1734 | fn item_static(w: &mut fmt::Formatter, it: &clean::Item, | |
1735 | s: &clean::Static) -> fmt::Result { | |
1736 | try!(write!(w, "<pre class='rust static'>{vis}static {mutability}\ | |
1737 | {name}: {typ}{init}</pre>", | |
1738 | vis = VisSpace(it.visibility), | |
1739 | mutability = MutableSpace(s.mutability), | |
85aaf69f | 1740 | name = it.name.as_ref().unwrap(), |
1a4d82fc | 1741 | typ = s.type_, |
85aaf69f | 1742 | init = Initializer(&s.expr))); |
1a4d82fc JJ |
1743 | document(w, it) |
1744 | } | |
1745 | ||
1746 | fn item_function(w: &mut fmt::Formatter, it: &clean::Item, | |
1747 | f: &clean::Function) -> fmt::Result { | |
62682a34 | 1748 | try!(write!(w, "<pre class='rust fn'>{vis}{unsafety}{abi}{constness}fn \ |
1a4d82fc JJ |
1749 | {name}{generics}{decl}{where_clause}</pre>", |
1750 | vis = VisSpace(it.visibility), | |
1751 | unsafety = UnsafetySpace(f.unsafety), | |
9346a6ac | 1752 | abi = AbiSpace(f.abi), |
62682a34 | 1753 | constness = ConstnessSpace(f.constness), |
85aaf69f | 1754 | name = it.name.as_ref().unwrap(), |
1a4d82fc JJ |
1755 | generics = f.generics, |
1756 | where_clause = WhereClause(&f.generics), | |
1757 | decl = f.decl)); | |
1758 | document(w, it) | |
1759 | } | |
1760 | ||
1761 | fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, | |
1762 | t: &clean::Trait) -> fmt::Result { | |
1763 | let mut bounds = String::new(); | |
9346a6ac AL |
1764 | if !t.bounds.is_empty() { |
1765 | if !bounds.is_empty() { | |
1a4d82fc JJ |
1766 | bounds.push(' '); |
1767 | } | |
1768 | bounds.push_str(": "); | |
1769 | for (i, p) in t.bounds.iter().enumerate() { | |
1770 | if i > 0 { bounds.push_str(" + "); } | |
85aaf69f | 1771 | bounds.push_str(&format!("{}", *p)); |
1a4d82fc JJ |
1772 | } |
1773 | } | |
1774 | ||
1775 | // Output the trait definition | |
1776 | try!(write!(w, "<pre class='rust trait'>{}{}trait {}{}{}{} ", | |
1777 | VisSpace(it.visibility), | |
1778 | UnsafetySpace(t.unsafety), | |
85aaf69f | 1779 | it.name.as_ref().unwrap(), |
1a4d82fc JJ |
1780 | t.generics, |
1781 | bounds, | |
1782 | WhereClause(&t.generics))); | |
1783 | ||
c34b1796 AL |
1784 | let types = t.items.iter().filter(|m| { |
1785 | match m.inner { clean::AssociatedTypeItem(..) => true, _ => false } | |
1786 | }).collect::<Vec<_>>(); | |
d9579d0f AL |
1787 | let consts = t.items.iter().filter(|m| { |
1788 | match m.inner { clean::AssociatedConstItem(..) => true, _ => false } | |
1789 | }).collect::<Vec<_>>(); | |
c34b1796 AL |
1790 | let required = t.items.iter().filter(|m| { |
1791 | match m.inner { clean::TyMethodItem(_) => true, _ => false } | |
1792 | }).collect::<Vec<_>>(); | |
1793 | let provided = t.items.iter().filter(|m| { | |
1794 | match m.inner { clean::MethodItem(_) => true, _ => false } | |
1795 | }).collect::<Vec<_>>(); | |
1a4d82fc | 1796 | |
9346a6ac | 1797 | if t.items.is_empty() { |
1a4d82fc JJ |
1798 | try!(write!(w, "{{ }}")); |
1799 | } else { | |
1800 | try!(write!(w, "{{\n")); | |
85aaf69f | 1801 | for t in &types { |
1a4d82fc | 1802 | try!(write!(w, " ")); |
d9579d0f AL |
1803 | try!(render_assoc_item(w, t, AssocItemLink::Anchor)); |
1804 | try!(write!(w, ";\n")); | |
1805 | } | |
1806 | if !types.is_empty() && !consts.is_empty() { | |
1807 | try!(w.write_str("\n")); | |
1808 | } | |
1809 | for t in &consts { | |
1810 | try!(write!(w, " ")); | |
1811 | try!(render_assoc_item(w, t, AssocItemLink::Anchor)); | |
1a4d82fc JJ |
1812 | try!(write!(w, ";\n")); |
1813 | } | |
d9579d0f | 1814 | if !consts.is_empty() && !required.is_empty() { |
1a4d82fc JJ |
1815 | try!(w.write_str("\n")); |
1816 | } | |
85aaf69f | 1817 | for m in &required { |
1a4d82fc | 1818 | try!(write!(w, " ")); |
d9579d0f | 1819 | try!(render_assoc_item(w, m, AssocItemLink::Anchor)); |
1a4d82fc JJ |
1820 | try!(write!(w, ";\n")); |
1821 | } | |
9346a6ac | 1822 | if !required.is_empty() && !provided.is_empty() { |
1a4d82fc JJ |
1823 | try!(w.write_str("\n")); |
1824 | } | |
85aaf69f | 1825 | for m in &provided { |
1a4d82fc | 1826 | try!(write!(w, " ")); |
d9579d0f | 1827 | try!(render_assoc_item(w, m, AssocItemLink::Anchor)); |
1a4d82fc JJ |
1828 | try!(write!(w, " {{ ... }}\n")); |
1829 | } | |
1830 | try!(write!(w, "}}")); | |
1831 | } | |
1832 | try!(write!(w, "</pre>")); | |
1833 | ||
1834 | // Trait documentation | |
1835 | try!(document(w, it)); | |
1836 | ||
c34b1796 | 1837 | fn trait_item(w: &mut fmt::Formatter, m: &clean::Item) |
1a4d82fc | 1838 | -> fmt::Result { |
d9579d0f AL |
1839 | try!(write!(w, "<h3 id='{ty}.{name}' class='method stab {stab}'><code>", |
1840 | ty = shortty(m), | |
1841 | name = *m.name.as_ref().unwrap(), | |
1842 | stab = m.stability_class())); | |
1843 | try!(render_assoc_item(w, m, AssocItemLink::Anchor)); | |
1a4d82fc | 1844 | try!(write!(w, "</code></h3>")); |
c34b1796 | 1845 | try!(document(w, m)); |
1a4d82fc JJ |
1846 | Ok(()) |
1847 | } | |
1848 | ||
9346a6ac | 1849 | if !types.is_empty() { |
1a4d82fc JJ |
1850 | try!(write!(w, " |
1851 | <h2 id='associated-types'>Associated Types</h2> | |
1852 | <div class='methods'> | |
1853 | ")); | |
85aaf69f | 1854 | for t in &types { |
1a4d82fc JJ |
1855 | try!(trait_item(w, *t)); |
1856 | } | |
1857 | try!(write!(w, "</div>")); | |
1858 | } | |
1859 | ||
d9579d0f AL |
1860 | if !consts.is_empty() { |
1861 | try!(write!(w, " | |
1862 | <h2 id='associated-const'>Associated Constants</h2> | |
1863 | <div class='methods'> | |
1864 | ")); | |
1865 | for t in &consts { | |
1866 | try!(trait_item(w, *t)); | |
1867 | } | |
1868 | try!(write!(w, "</div>")); | |
1869 | } | |
1870 | ||
1a4d82fc | 1871 | // Output the documentation for each function individually |
9346a6ac | 1872 | if !required.is_empty() { |
1a4d82fc JJ |
1873 | try!(write!(w, " |
1874 | <h2 id='required-methods'>Required Methods</h2> | |
1875 | <div class='methods'> | |
1876 | ")); | |
85aaf69f | 1877 | for m in &required { |
1a4d82fc JJ |
1878 | try!(trait_item(w, *m)); |
1879 | } | |
1880 | try!(write!(w, "</div>")); | |
1881 | } | |
9346a6ac | 1882 | if !provided.is_empty() { |
1a4d82fc JJ |
1883 | try!(write!(w, " |
1884 | <h2 id='provided-methods'>Provided Methods</h2> | |
1885 | <div class='methods'> | |
1886 | ")); | |
85aaf69f | 1887 | for m in &provided { |
1a4d82fc JJ |
1888 | try!(trait_item(w, *m)); |
1889 | } | |
1890 | try!(write!(w, "</div>")); | |
1891 | } | |
1892 | ||
9346a6ac | 1893 | // If there are methods directly on this trait object, render them here. |
d9579d0f | 1894 | try!(render_assoc_items(w, it.def_id, AssocItemRender::All)); |
9346a6ac | 1895 | |
1a4d82fc JJ |
1896 | let cache = cache(); |
1897 | try!(write!(w, " | |
1898 | <h2 id='implementors'>Implementors</h2> | |
1899 | <ul class='item-list' id='implementors-list'> | |
1900 | ")); | |
1901 | match cache.implementors.get(&it.def_id) { | |
1902 | Some(implementors) => { | |
85aaf69f | 1903 | for i in implementors { |
c1a9b12d | 1904 | try!(writeln!(w, "<li><code>{}</code></li>", i.impl_)); |
1a4d82fc JJ |
1905 | } |
1906 | } | |
1907 | None => {} | |
1908 | } | |
1909 | try!(write!(w, "</ul>")); | |
1910 | try!(write!(w, r#"<script type="text/javascript" async | |
1911 | src="{root_path}/implementors/{path}/{ty}.{name}.js"> | |
1912 | </script>"#, | |
c1a9b12d | 1913 | root_path = vec![".."; cx.current.len()].join("/"), |
1a4d82fc | 1914 | path = if ast_util::is_local(it.def_id) { |
c1a9b12d | 1915 | cx.current.join("/") |
1a4d82fc | 1916 | } else { |
c34b1796 | 1917 | let path = &cache.external_paths[&it.def_id]; |
c1a9b12d | 1918 | path[..path.len() - 1].join("/") |
1a4d82fc JJ |
1919 | }, |
1920 | ty = shortty(it).to_static_str(), | |
1921 | name = *it.name.as_ref().unwrap())); | |
1922 | Ok(()) | |
1923 | } | |
1924 | ||
d9579d0f AL |
1925 | fn assoc_const(w: &mut fmt::Formatter, it: &clean::Item, |
1926 | ty: &clean::Type, default: Option<&String>) | |
1927 | -> fmt::Result { | |
1928 | try!(write!(w, "const {}", it.name.as_ref().unwrap())); | |
1929 | try!(write!(w, ": {}", ty)); | |
1930 | if let Some(default) = default { | |
1931 | try!(write!(w, " = {}", default)); | |
1932 | } | |
1933 | Ok(()) | |
1934 | } | |
1935 | ||
1a4d82fc | 1936 | fn assoc_type(w: &mut fmt::Formatter, it: &clean::Item, |
c34b1796 AL |
1937 | bounds: &Vec<clean::TyParamBound>, |
1938 | default: &Option<clean::Type>) | |
1939 | -> fmt::Result { | |
1a4d82fc | 1940 | try!(write!(w, "type {}", it.name.as_ref().unwrap())); |
9346a6ac | 1941 | if !bounds.is_empty() { |
c34b1796 | 1942 | try!(write!(w, ": {}", TyParamBounds(bounds))) |
1a4d82fc | 1943 | } |
c34b1796 | 1944 | if let Some(ref default) = *default { |
1a4d82fc JJ |
1945 | try!(write!(w, " = {}", default)); |
1946 | } | |
1947 | Ok(()) | |
1948 | } | |
1949 | ||
d9579d0f AL |
1950 | fn render_assoc_item(w: &mut fmt::Formatter, meth: &clean::Item, |
1951 | link: AssocItemLink) -> fmt::Result { | |
62682a34 SL |
1952 | fn method(w: &mut fmt::Formatter, |
1953 | it: &clean::Item, | |
1954 | unsafety: ast::Unsafety, | |
1955 | constness: ast::Constness, | |
1956 | abi: abi::Abi, | |
1957 | g: &clean::Generics, | |
1958 | selfty: &clean::SelfTy, | |
1959 | d: &clean::FnDecl, | |
1960 | link: AssocItemLink) | |
1961 | -> fmt::Result { | |
85aaf69f SL |
1962 | use syntax::abi::Abi; |
1963 | ||
9346a6ac AL |
1964 | let name = it.name.as_ref().unwrap(); |
1965 | let anchor = format!("#{}.{}", shortty(it), name); | |
1966 | let href = match link { | |
d9579d0f AL |
1967 | AssocItemLink::Anchor => anchor, |
1968 | AssocItemLink::GotoSource(did) => { | |
9346a6ac AL |
1969 | href(did).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor) |
1970 | } | |
1971 | }; | |
62682a34 | 1972 | write!(w, "{}{}{}fn <a href='{href}' class='fnname'>{name}</a>\ |
1a4d82fc | 1973 | {generics}{decl}{where_clause}", |
62682a34 SL |
1974 | UnsafetySpace(unsafety), |
1975 | ConstnessSpace(constness), | |
85aaf69f SL |
1976 | match abi { |
1977 | Abi::Rust => String::new(), | |
1978 | a => format!("extern {} ", a.to_string()) | |
1979 | }, | |
9346a6ac AL |
1980 | href = href, |
1981 | name = name, | |
1a4d82fc JJ |
1982 | generics = *g, |
1983 | decl = Method(selfty, d), | |
1984 | where_clause = WhereClause(g)) | |
1985 | } | |
1986 | match meth.inner { | |
1987 | clean::TyMethodItem(ref m) => { | |
62682a34 SL |
1988 | method(w, meth, m.unsafety, ast::Constness::NotConst, |
1989 | m.abi, &m.generics, &m.self_, &m.decl, link) | |
1a4d82fc JJ |
1990 | } |
1991 | clean::MethodItem(ref m) => { | |
62682a34 SL |
1992 | method(w, meth, m.unsafety, m.constness, |
1993 | m.abi, &m.generics, &m.self_, &m.decl, | |
9346a6ac | 1994 | link) |
1a4d82fc | 1995 | } |
d9579d0f AL |
1996 | clean::AssociatedConstItem(ref ty, ref default) => { |
1997 | assoc_const(w, meth, ty, default.as_ref()) | |
1998 | } | |
c34b1796 AL |
1999 | clean::AssociatedTypeItem(ref bounds, ref default) => { |
2000 | assoc_type(w, meth, bounds, default) | |
1a4d82fc | 2001 | } |
d9579d0f | 2002 | _ => panic!("render_assoc_item called on non-associated-item") |
1a4d82fc JJ |
2003 | } |
2004 | } | |
2005 | ||
2006 | fn item_struct(w: &mut fmt::Formatter, it: &clean::Item, | |
2007 | s: &clean::Struct) -> fmt::Result { | |
2008 | try!(write!(w, "<pre class='rust struct'>")); | |
85aaf69f | 2009 | try!(render_attributes(w, it)); |
1a4d82fc JJ |
2010 | try!(render_struct(w, |
2011 | it, | |
2012 | Some(&s.generics), | |
2013 | s.struct_type, | |
85aaf69f | 2014 | &s.fields, |
1a4d82fc JJ |
2015 | "", |
2016 | true)); | |
2017 | try!(write!(w, "</pre>")); | |
2018 | ||
2019 | try!(document(w, it)); | |
2020 | let mut fields = s.fields.iter().filter(|f| { | |
2021 | match f.inner { | |
2022 | clean::StructFieldItem(clean::HiddenStructField) => false, | |
2023 | clean::StructFieldItem(clean::TypedStructField(..)) => true, | |
2024 | _ => false, | |
2025 | } | |
2026 | }).peekable(); | |
2027 | if let doctree::Plain = s.struct_type { | |
2028 | if fields.peek().is_some() { | |
2029 | try!(write!(w, "<h2 class='fields'>Fields</h2>\n<table>")); | |
2030 | for field in fields { | |
d9579d0f AL |
2031 | try!(write!(w, "<tr class='stab {stab}'> |
2032 | <td id='structfield.{name}'>\ | |
2033 | <code>{name}</code></td><td>", | |
2034 | stab = field.stability_class(), | |
85aaf69f | 2035 | name = field.name.as_ref().unwrap())); |
1a4d82fc JJ |
2036 | try!(document(w, field)); |
2037 | try!(write!(w, "</td></tr>")); | |
2038 | } | |
2039 | try!(write!(w, "</table>")); | |
2040 | } | |
2041 | } | |
d9579d0f | 2042 | render_assoc_items(w, it.def_id, AssocItemRender::All) |
1a4d82fc JJ |
2043 | } |
2044 | ||
2045 | fn item_enum(w: &mut fmt::Formatter, it: &clean::Item, | |
2046 | e: &clean::Enum) -> fmt::Result { | |
85aaf69f SL |
2047 | try!(write!(w, "<pre class='rust enum'>")); |
2048 | try!(render_attributes(w, it)); | |
2049 | try!(write!(w, "{}enum {}{}{}", | |
1a4d82fc | 2050 | VisSpace(it.visibility), |
85aaf69f | 2051 | it.name.as_ref().unwrap(), |
1a4d82fc JJ |
2052 | e.generics, |
2053 | WhereClause(&e.generics))); | |
9346a6ac | 2054 | if e.variants.is_empty() && !e.variants_stripped { |
1a4d82fc JJ |
2055 | try!(write!(w, " {{}}")); |
2056 | } else { | |
2057 | try!(write!(w, " {{\n")); | |
85aaf69f | 2058 | for v in &e.variants { |
1a4d82fc | 2059 | try!(write!(w, " ")); |
85aaf69f | 2060 | let name = v.name.as_ref().unwrap(); |
1a4d82fc JJ |
2061 | match v.inner { |
2062 | clean::VariantItem(ref var) => { | |
2063 | match var.kind { | |
2064 | clean::CLikeVariant => try!(write!(w, "{}", name)), | |
2065 | clean::TupleVariant(ref tys) => { | |
2066 | try!(write!(w, "{}(", name)); | |
2067 | for (i, ty) in tys.iter().enumerate() { | |
2068 | if i > 0 { | |
2069 | try!(write!(w, ", ")) | |
2070 | } | |
2071 | try!(write!(w, "{}", *ty)); | |
2072 | } | |
2073 | try!(write!(w, ")")); | |
2074 | } | |
2075 | clean::StructVariant(ref s) => { | |
2076 | try!(render_struct(w, | |
2077 | v, | |
2078 | None, | |
2079 | s.struct_type, | |
85aaf69f | 2080 | &s.fields, |
1a4d82fc JJ |
2081 | " ", |
2082 | false)); | |
2083 | } | |
2084 | } | |
2085 | } | |
2086 | _ => unreachable!() | |
2087 | } | |
2088 | try!(write!(w, ",\n")); | |
2089 | } | |
2090 | ||
2091 | if e.variants_stripped { | |
2092 | try!(write!(w, " // some variants omitted\n")); | |
2093 | } | |
2094 | try!(write!(w, "}}")); | |
2095 | } | |
2096 | try!(write!(w, "</pre>")); | |
2097 | ||
2098 | try!(document(w, it)); | |
9346a6ac | 2099 | if !e.variants.is_empty() { |
1a4d82fc | 2100 | try!(write!(w, "<h2 class='variants'>Variants</h2>\n<table>")); |
85aaf69f | 2101 | for variant in &e.variants { |
d9579d0f | 2102 | try!(write!(w, "<tr><td id='variant.{name}'><code>{name}</code></td><td>", |
85aaf69f | 2103 | name = variant.name.as_ref().unwrap())); |
1a4d82fc JJ |
2104 | try!(document(w, variant)); |
2105 | match variant.inner { | |
2106 | clean::VariantItem(ref var) => { | |
2107 | match var.kind { | |
2108 | clean::StructVariant(ref s) => { | |
85aaf69f | 2109 | let fields = s.fields.iter().filter(|f| { |
1a4d82fc JJ |
2110 | match f.inner { |
2111 | clean::StructFieldItem(ref t) => match *t { | |
2112 | clean::HiddenStructField => false, | |
2113 | clean::TypedStructField(..) => true, | |
2114 | }, | |
2115 | _ => false, | |
2116 | } | |
2117 | }); | |
2118 | try!(write!(w, "<h3 class='fields'>Fields</h3>\n | |
2119 | <table>")); | |
2120 | for field in fields { | |
2121 | try!(write!(w, "<tr><td \ | |
2122 | id='variant.{v}.field.{f}'>\ | |
2123 | <code>{f}</code></td><td>", | |
85aaf69f SL |
2124 | v = variant.name.as_ref().unwrap(), |
2125 | f = field.name.as_ref().unwrap())); | |
1a4d82fc JJ |
2126 | try!(document(w, field)); |
2127 | try!(write!(w, "</td></tr>")); | |
2128 | } | |
2129 | try!(write!(w, "</table>")); | |
2130 | } | |
2131 | _ => () | |
2132 | } | |
2133 | } | |
2134 | _ => () | |
2135 | } | |
2136 | try!(write!(w, "</td></tr>")); | |
2137 | } | |
2138 | try!(write!(w, "</table>")); | |
2139 | ||
2140 | } | |
d9579d0f | 2141 | try!(render_assoc_items(w, it.def_id, AssocItemRender::All)); |
1a4d82fc JJ |
2142 | Ok(()) |
2143 | } | |
2144 | ||
85aaf69f SL |
2145 | fn render_attributes(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result { |
2146 | for attr in &it.attrs { | |
2147 | match *attr { | |
2148 | clean::Word(ref s) if *s == "must_use" => { | |
2149 | try!(write!(w, "#[{}]\n", s)); | |
2150 | } | |
2151 | clean::NameValue(ref k, ref v) if *k == "must_use" => { | |
2152 | try!(write!(w, "#[{} = \"{}\"]\n", k, v)); | |
2153 | } | |
2154 | _ => () | |
2155 | } | |
2156 | } | |
2157 | Ok(()) | |
2158 | } | |
2159 | ||
1a4d82fc JJ |
2160 | fn render_struct(w: &mut fmt::Formatter, it: &clean::Item, |
2161 | g: Option<&clean::Generics>, | |
2162 | ty: doctree::StructType, | |
2163 | fields: &[clean::Item], | |
2164 | tab: &str, | |
2165 | structhead: bool) -> fmt::Result { | |
2166 | try!(write!(w, "{}{}{}", | |
2167 | VisSpace(it.visibility), | |
2168 | if structhead {"struct "} else {""}, | |
85aaf69f | 2169 | it.name.as_ref().unwrap())); |
1a4d82fc JJ |
2170 | match g { |
2171 | Some(g) => try!(write!(w, "{}{}", *g, WhereClause(g))), | |
2172 | None => {} | |
2173 | } | |
2174 | match ty { | |
2175 | doctree::Plain => { | |
2176 | try!(write!(w, " {{\n{}", tab)); | |
2177 | let mut fields_stripped = false; | |
85aaf69f | 2178 | for field in fields { |
1a4d82fc JJ |
2179 | match field.inner { |
2180 | clean::StructFieldItem(clean::HiddenStructField) => { | |
2181 | fields_stripped = true; | |
2182 | } | |
2183 | clean::StructFieldItem(clean::TypedStructField(ref ty)) => { | |
2184 | try!(write!(w, " {}{}: {},\n{}", | |
2185 | VisSpace(field.visibility), | |
85aaf69f | 2186 | field.name.as_ref().unwrap(), |
1a4d82fc JJ |
2187 | *ty, |
2188 | tab)); | |
2189 | } | |
2190 | _ => unreachable!(), | |
2191 | }; | |
2192 | } | |
2193 | ||
2194 | if fields_stripped { | |
2195 | try!(write!(w, " // some fields omitted\n{}", tab)); | |
2196 | } | |
2197 | try!(write!(w, "}}")); | |
2198 | } | |
2199 | doctree::Tuple | doctree::Newtype => { | |
2200 | try!(write!(w, "(")); | |
2201 | for (i, field) in fields.iter().enumerate() { | |
2202 | if i > 0 { | |
2203 | try!(write!(w, ", ")); | |
2204 | } | |
2205 | match field.inner { | |
2206 | clean::StructFieldItem(clean::HiddenStructField) => { | |
2207 | try!(write!(w, "_")) | |
2208 | } | |
2209 | clean::StructFieldItem(clean::TypedStructField(ref ty)) => { | |
2210 | try!(write!(w, "{}{}", VisSpace(field.visibility), *ty)) | |
2211 | } | |
2212 | _ => unreachable!() | |
2213 | } | |
2214 | } | |
2215 | try!(write!(w, ");")); | |
2216 | } | |
2217 | doctree::Unit => { | |
2218 | try!(write!(w, ";")); | |
2219 | } | |
2220 | } | |
2221 | Ok(()) | |
2222 | } | |
2223 | ||
9346a6ac | 2224 | #[derive(Copy, Clone)] |
d9579d0f | 2225 | enum AssocItemLink { |
9346a6ac AL |
2226 | Anchor, |
2227 | GotoSource(ast::DefId), | |
2228 | } | |
2229 | ||
d9579d0f AL |
2230 | enum AssocItemRender<'a> { |
2231 | All, | |
2232 | DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type }, | |
2233 | } | |
2234 | ||
2235 | fn render_assoc_items(w: &mut fmt::Formatter, | |
2236 | it: ast::DefId, | |
2237 | what: AssocItemRender) -> fmt::Result { | |
2238 | let c = cache(); | |
2239 | let v = match c.impls.get(&it) { | |
2240 | Some(v) => v, | |
9346a6ac AL |
2241 | None => return Ok(()), |
2242 | }; | |
d9579d0f AL |
2243 | let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| { |
2244 | i.impl_.trait_.is_none() | |
2245 | }); | |
9346a6ac | 2246 | if !non_trait.is_empty() { |
d9579d0f AL |
2247 | let render_header = match what { |
2248 | AssocItemRender::All => { | |
2249 | try!(write!(w, "<h2 id='methods'>Methods</h2>")); | |
2250 | true | |
2251 | } | |
2252 | AssocItemRender::DerefFor { trait_, type_ } => { | |
2253 | try!(write!(w, "<h2 id='deref-methods'>Methods from \ | |
2254 | {}<Target={}></h2>", trait_, type_)); | |
2255 | false | |
2256 | } | |
2257 | }; | |
9346a6ac | 2258 | for i in &non_trait { |
d9579d0f | 2259 | try!(render_impl(w, i, AssocItemLink::Anchor, render_header)); |
9346a6ac AL |
2260 | } |
2261 | } | |
d9579d0f AL |
2262 | if let AssocItemRender::DerefFor { .. } = what { |
2263 | return Ok(()) | |
2264 | } | |
9346a6ac | 2265 | if !traits.is_empty() { |
d9579d0f AL |
2266 | let deref_impl = traits.iter().find(|t| { |
2267 | match *t.impl_.trait_.as_ref().unwrap() { | |
2268 | clean::ResolvedPath { did, .. } => { | |
2269 | Some(did) == c.deref_trait_did | |
2270 | } | |
2271 | _ => false | |
2272 | } | |
2273 | }); | |
2274 | if let Some(impl_) = deref_impl { | |
2275 | try!(render_deref_methods(w, impl_)); | |
2276 | } | |
9346a6ac AL |
2277 | try!(write!(w, "<h2 id='implementations'>Trait \ |
2278 | Implementations</h2>")); | |
62682a34 | 2279 | let (derived, manual): (Vec<_>, Vec<&Impl>) = traits.iter().partition(|i| { |
d9579d0f AL |
2280 | i.impl_.derived |
2281 | }); | |
9346a6ac AL |
2282 | for i in &manual { |
2283 | let did = i.trait_did().unwrap(); | |
d9579d0f | 2284 | try!(render_impl(w, i, AssocItemLink::GotoSource(did), true)); |
9346a6ac AL |
2285 | } |
2286 | if !derived.is_empty() { | |
2287 | try!(write!(w, "<h3 id='derived_implementations'>\ | |
2288 | Derived Implementations \ | |
2289 | </h3>")); | |
2290 | for i in &derived { | |
2291 | let did = i.trait_did().unwrap(); | |
d9579d0f | 2292 | try!(render_impl(w, i, AssocItemLink::GotoSource(did), true)); |
1a4d82fc JJ |
2293 | } |
2294 | } | |
1a4d82fc JJ |
2295 | } |
2296 | Ok(()) | |
2297 | } | |
2298 | ||
d9579d0f AL |
2299 | fn render_deref_methods(w: &mut fmt::Formatter, impl_: &Impl) -> fmt::Result { |
2300 | let deref_type = impl_.impl_.trait_.as_ref().unwrap(); | |
2301 | let target = impl_.impl_.items.iter().filter_map(|item| { | |
2302 | match item.inner { | |
62682a34 | 2303 | clean::TypedefItem(ref t, true) => Some(&t.type_), |
d9579d0f AL |
2304 | _ => None, |
2305 | } | |
62682a34 | 2306 | }).next().expect("Expected associated type binding"); |
d9579d0f AL |
2307 | let what = AssocItemRender::DerefFor { trait_: deref_type, type_: target }; |
2308 | match *target { | |
2309 | clean::ResolvedPath { did, .. } => render_assoc_items(w, did, what), | |
2310 | _ => { | |
2311 | if let Some(prim) = target.primitive_type() { | |
2312 | if let Some(c) = cache().primitive_locations.get(&prim) { | |
2313 | let did = ast::DefId { krate: *c, node: prim.to_node_id() }; | |
2314 | try!(render_assoc_items(w, did, what)); | |
2315 | } | |
2316 | } | |
2317 | Ok(()) | |
2318 | } | |
1a4d82fc | 2319 | } |
d9579d0f AL |
2320 | } |
2321 | ||
62682a34 SL |
2322 | // Render_header is false when we are rendering a `Deref` impl and true |
2323 | // otherwise. If render_header is false, we will avoid rendering static | |
2324 | // methods, since they are not accessible for the type implementing `Deref` | |
d9579d0f AL |
2325 | fn render_impl(w: &mut fmt::Formatter, i: &Impl, link: AssocItemLink, |
2326 | render_header: bool) -> fmt::Result { | |
2327 | if render_header { | |
c1a9b12d | 2328 | try!(write!(w, "<h3 class='impl'><code>{}</code></h3>", i.impl_)); |
d9579d0f AL |
2329 | if let Some(ref dox) = i.dox { |
2330 | try!(write!(w, "<div class='docblock'>{}</div>", Markdown(dox))); | |
2331 | } | |
1a4d82fc JJ |
2332 | } |
2333 | ||
9346a6ac | 2334 | fn doctraititem(w: &mut fmt::Formatter, item: &clean::Item, |
62682a34 | 2335 | link: AssocItemLink, render_static: bool) -> fmt::Result { |
1a4d82fc JJ |
2336 | match item.inner { |
2337 | clean::MethodItem(..) | clean::TyMethodItem(..) => { | |
62682a34 SL |
2338 | // Only render when the method is not static or we allow static methods |
2339 | if !is_static_method(item) || render_static { | |
2340 | try!(write!(w, "<h4 id='method.{}' class='{}'><code>", | |
2341 | *item.name.as_ref().unwrap(), | |
2342 | shortty(item))); | |
d9579d0f | 2343 | try!(render_assoc_item(w, item, link)); |
62682a34 SL |
2344 | try!(write!(w, "</code></h4>\n")); |
2345 | } | |
1a4d82fc | 2346 | } |
62682a34 | 2347 | clean::TypedefItem(ref tydef, _) => { |
1a4d82fc | 2348 | let name = item.name.as_ref().unwrap(); |
d9579d0f | 2349 | try!(write!(w, "<h4 id='assoc_type.{}' class='{}'><code>", |
1a4d82fc | 2350 | *name, |
d9579d0f | 2351 | shortty(item))); |
1a4d82fc JJ |
2352 | try!(write!(w, "type {} = {}", name, tydef.type_)); |
2353 | try!(write!(w, "</code></h4>\n")); | |
2354 | } | |
d9579d0f AL |
2355 | clean::AssociatedConstItem(ref ty, ref default) => { |
2356 | let name = item.name.as_ref().unwrap(); | |
2357 | try!(write!(w, "<h4 id='assoc_const.{}' class='{}'><code>", | |
2358 | *name, shortty(item))); | |
2359 | try!(assoc_const(w, item, ty, default.as_ref())); | |
2360 | try!(write!(w, "</code></h4>\n")); | |
2361 | } | |
2362 | clean::ConstantItem(ref c) => { | |
2363 | let name = item.name.as_ref().unwrap(); | |
2364 | try!(write!(w, "<h4 id='assoc_const.{}' class='{}'><code>", | |
2365 | *name, shortty(item))); | |
2366 | try!(assoc_const(w, item, &c.type_, Some(&c.expr))); | |
2367 | try!(write!(w, "</code></h4>\n")); | |
2368 | } | |
c34b1796 | 2369 | clean::AssociatedTypeItem(ref bounds, ref default) => { |
1a4d82fc | 2370 | let name = item.name.as_ref().unwrap(); |
d9579d0f | 2371 | try!(write!(w, "<h4 id='assoc_type.{}' class='{}'><code>", |
1a4d82fc | 2372 | *name, |
d9579d0f | 2373 | shortty(item))); |
c34b1796 | 2374 | try!(assoc_type(w, item, bounds, default)); |
1a4d82fc JJ |
2375 | try!(write!(w, "</code></h4>\n")); |
2376 | } | |
2377 | _ => panic!("can't make docs for trait item with name {:?}", item.name) | |
2378 | } | |
62682a34 SL |
2379 | |
2380 | return if let AssocItemLink::Anchor = link { | |
2381 | if is_static_method(item) && !render_static { | |
2382 | Ok(()) | |
2383 | } else { | |
2384 | document(w, item) | |
2385 | } | |
d9579d0f AL |
2386 | } else { |
2387 | Ok(()) | |
62682a34 SL |
2388 | }; |
2389 | ||
2390 | fn is_static_method(item: &clean::Item) -> bool { | |
2391 | match item.inner { | |
2392 | clean::MethodItem(ref method) => method.self_ == SelfTy::SelfStatic, | |
2393 | clean::TyMethodItem(ref method) => method.self_ == SelfTy::SelfStatic, | |
2394 | _ => false | |
2395 | } | |
1a4d82fc JJ |
2396 | } |
2397 | } | |
2398 | ||
2399 | try!(write!(w, "<div class='impl-items'>")); | |
62682a34 SL |
2400 | for trait_item in &i.impl_.items { |
2401 | try!(doctraititem(w, trait_item, link, render_header)); | |
1a4d82fc JJ |
2402 | } |
2403 | ||
d9579d0f AL |
2404 | fn render_default_items(w: &mut fmt::Formatter, |
2405 | did: ast::DefId, | |
2406 | t: &clean::Trait, | |
62682a34 SL |
2407 | i: &clean::Impl, |
2408 | render_static: bool) -> fmt::Result { | |
85aaf69f | 2409 | for trait_item in &t.items { |
c34b1796 | 2410 | let n = trait_item.name.clone(); |
1a4d82fc JJ |
2411 | match i.items.iter().find(|m| { m.name == n }) { |
2412 | Some(..) => continue, | |
2413 | None => {} | |
2414 | } | |
2415 | ||
62682a34 | 2416 | try!(doctraititem(w, trait_item, AssocItemLink::GotoSource(did), render_static)); |
1a4d82fc JJ |
2417 | } |
2418 | Ok(()) | |
2419 | } | |
2420 | ||
2421 | // If we've implemented a trait, then also emit documentation for all | |
2422 | // default methods which weren't overridden in the implementation block. | |
2423 | // FIXME: this also needs to be done for associated types, whenever defaults | |
2424 | // for them work. | |
9346a6ac AL |
2425 | if let Some(clean::ResolvedPath { did, .. }) = i.impl_.trait_ { |
2426 | if let Some(t) = cache().traits.get(&did) { | |
62682a34 SL |
2427 | try!(render_default_items(w, did, t, &i.impl_, render_header)); |
2428 | ||
1a4d82fc | 2429 | } |
1a4d82fc JJ |
2430 | } |
2431 | try!(write!(w, "</div>")); | |
2432 | Ok(()) | |
2433 | } | |
2434 | ||
2435 | fn item_typedef(w: &mut fmt::Formatter, it: &clean::Item, | |
2436 | t: &clean::Typedef) -> fmt::Result { | |
62682a34 | 2437 | try!(write!(w, "<pre class='rust typedef'>type {}{}{where_clause} = {type_};</pre>", |
85aaf69f | 2438 | it.name.as_ref().unwrap(), |
1a4d82fc | 2439 | t.generics, |
62682a34 SL |
2440 | where_clause = WhereClause(&t.generics), |
2441 | type_ = t.type_)); | |
1a4d82fc JJ |
2442 | |
2443 | document(w, it) | |
2444 | } | |
2445 | ||
85aaf69f | 2446 | impl<'a> fmt::Display for Sidebar<'a> { |
1a4d82fc JJ |
2447 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
2448 | let cx = self.cx; | |
2449 | let it = self.item; | |
c34b1796 AL |
2450 | let parentlen = cx.current.len() - if it.is_mod() {1} else {0}; |
2451 | ||
2452 | // the sidebar is designed to display sibling functions, modules and | |
2453 | // other miscellaneous informations. since there are lots of sibling | |
2454 | // items (and that causes quadratic growth in large modules), | |
2455 | // we refactor common parts into a shared JavaScript file per module. | |
2456 | // still, we don't move everything into JS because we want to preserve | |
2457 | // as much HTML as possible in order to allow non-JS-enabled browsers | |
2458 | // to navigate the documentation (though slightly inefficiently). | |
2459 | ||
1a4d82fc | 2460 | try!(write!(fmt, "<p class='location'>")); |
c34b1796 | 2461 | for (i, name) in cx.current.iter().take(parentlen).enumerate() { |
1a4d82fc JJ |
2462 | if i > 0 { |
2463 | try!(write!(fmt, "::<wbr>")); | |
2464 | } | |
2465 | try!(write!(fmt, "<a href='{}index.html'>{}</a>", | |
85aaf69f | 2466 | &cx.root_path[..(cx.current.len() - i - 1) * 3], |
1a4d82fc JJ |
2467 | *name)); |
2468 | } | |
2469 | try!(write!(fmt, "</p>")); | |
2470 | ||
c34b1796 AL |
2471 | // sidebar refers to the enclosing module, not this module |
2472 | let relpath = if shortty(it) == ItemType::Module { "../" } else { "" }; | |
2473 | try!(write!(fmt, | |
2474 | "<script>window.sidebarCurrent = {{\ | |
2475 | name: '{name}', \ | |
2476 | ty: '{ty}', \ | |
2477 | relpath: '{path}'\ | |
2478 | }};</script>", | |
2479 | name = it.name.as_ref().map(|x| &x[..]).unwrap_or(""), | |
2480 | ty = shortty(it).to_static_str(), | |
2481 | path = relpath)); | |
2482 | if parentlen == 0 { | |
2483 | // there is no sidebar-items.js beyond the crate root path | |
2484 | // FIXME maybe dynamic crate loading can be merged here | |
2485 | } else { | |
2486 | try!(write!(fmt, "<script defer src=\"{path}sidebar-items.js\"></script>", | |
2487 | path = relpath)); | |
1a4d82fc JJ |
2488 | } |
2489 | ||
1a4d82fc JJ |
2490 | Ok(()) |
2491 | } | |
2492 | } | |
2493 | ||
85aaf69f | 2494 | impl<'a> fmt::Display for Source<'a> { |
1a4d82fc JJ |
2495 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
2496 | let Source(s) = *self; | |
2497 | let lines = s.lines().count(); | |
2498 | let mut cols = 0; | |
2499 | let mut tmp = lines; | |
2500 | while tmp > 0 { | |
2501 | cols += 1; | |
2502 | tmp /= 10; | |
2503 | } | |
2504 | try!(write!(fmt, "<pre class=\"line-numbers\">")); | |
85aaf69f | 2505 | for i in 1..lines + 1 { |
1a4d82fc JJ |
2506 | try!(write!(fmt, "<span id=\"{0}\">{0:1$}</span>\n", i, cols)); |
2507 | } | |
2508 | try!(write!(fmt, "</pre>")); | |
85aaf69f | 2509 | try!(write!(fmt, "{}", highlight::highlight(s, None, None))); |
1a4d82fc JJ |
2510 | Ok(()) |
2511 | } | |
2512 | } | |
2513 | ||
2514 | fn item_macro(w: &mut fmt::Formatter, it: &clean::Item, | |
2515 | t: &clean::Macro) -> fmt::Result { | |
85aaf69f | 2516 | try!(w.write_str(&highlight::highlight(&t.source, |
1a4d82fc | 2517 | Some("macro"), |
85aaf69f | 2518 | None))); |
1a4d82fc JJ |
2519 | document(w, it) |
2520 | } | |
2521 | ||
2522 | fn item_primitive(w: &mut fmt::Formatter, | |
2523 | it: &clean::Item, | |
2524 | _p: &clean::PrimitiveType) -> fmt::Result { | |
2525 | try!(document(w, it)); | |
d9579d0f | 2526 | render_assoc_items(w, it.def_id, AssocItemRender::All) |
1a4d82fc JJ |
2527 | } |
2528 | ||
2529 | fn get_basic_keywords() -> &'static str { | |
2530 | "rust, rustlang, rust-lang" | |
2531 | } | |
2532 | ||
2533 | fn make_item_keywords(it: &clean::Item) -> String { | |
2534 | format!("{}, {}", get_basic_keywords(), it.name.as_ref().unwrap()) | |
2535 | } | |
2536 | ||
c34b1796 AL |
2537 | fn get_index_search_type(item: &clean::Item, |
2538 | parent: Option<String>) -> Option<IndexItemFunctionType> { | |
2539 | let decl = match item.inner { | |
2540 | clean::FunctionItem(ref f) => &f.decl, | |
2541 | clean::MethodItem(ref m) => &m.decl, | |
2542 | clean::TyMethodItem(ref m) => &m.decl, | |
2543 | _ => return None | |
2544 | }; | |
2545 | ||
2546 | let mut inputs = Vec::new(); | |
2547 | ||
2548 | // Consider `self` an argument as well. | |
2549 | if let Some(name) = parent { | |
c1a9b12d | 2550 | inputs.push(Type { name: Some(name.to_ascii_lowercase()) }); |
c34b1796 AL |
2551 | } |
2552 | ||
2553 | inputs.extend(&mut decl.inputs.values.iter().map(|arg| { | |
2554 | get_index_type(&arg.type_) | |
2555 | })); | |
2556 | ||
2557 | let output = match decl.output { | |
2558 | clean::FunctionRetTy::Return(ref return_type) => Some(get_index_type(return_type)), | |
2559 | _ => None | |
2560 | }; | |
2561 | ||
2562 | Some(IndexItemFunctionType { inputs: inputs, output: output }) | |
2563 | } | |
2564 | ||
2565 | fn get_index_type(clean_type: &clean::Type) -> Type { | |
c1a9b12d | 2566 | Type { name: get_index_type_name(clean_type).map(|s| s.to_ascii_lowercase()) } |
c34b1796 AL |
2567 | } |
2568 | ||
2569 | fn get_index_type_name(clean_type: &clean::Type) -> Option<String> { | |
2570 | match *clean_type { | |
2571 | clean::ResolvedPath { ref path, .. } => { | |
2572 | let segments = &path.segments; | |
2573 | Some(segments[segments.len() - 1].name.clone()) | |
2574 | }, | |
2575 | clean::Generic(ref s) => Some(s.clone()), | |
2576 | clean::Primitive(ref p) => Some(format!("{:?}", p)), | |
2577 | clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_), | |
2578 | // FIXME: add all from clean::Type. | |
2579 | _ => None | |
2580 | } | |
2581 | } | |
2582 | ||
1a4d82fc JJ |
2583 | pub fn cache() -> Arc<Cache> { |
2584 | CACHE_KEY.with(|c| c.borrow().clone()) | |
2585 | } |