]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/format.rs
Doc says to avoid mixing allocator instead of forbiding it
[rust.git] / src / librustdoc / html / format.rs
1 // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
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 //! HTML formatting module
12 //!
13 //! This module contains a large number of `fmt::Show` implementations for
14 //! various types in `rustdoc::clean`. These implementations all currently
15 //! assume that HTML output is desired, although it may be possible to redesign
16 //! them in the future to instead emit any format desired.
17
18 use std::fmt;
19 use std::string::String;
20
21 use syntax::ast;
22 use syntax::ast_util;
23
24 use clean;
25 use stability_summary::ModuleSummary;
26 use html::item_type;
27 use html::item_type::ItemType;
28 use html::render;
29 use html::render::{cache_key, current_location_key};
30
31 /// Helper to render an optional visibility with a space after it (if the
32 /// visibility is preset)
33 pub struct VisSpace(pub Option<ast::Visibility>);
34 /// Similarly to VisSpace, this structure is used to render a function style with a
35 /// space after it.
36 pub struct FnStyleSpace(pub ast::FnStyle);
37 /// Wrapper struct for properly emitting a method declaration.
38 pub struct Method<'a>(pub &'a clean::SelfTy, pub &'a clean::FnDecl);
39 /// Similar to VisSpace, but used for mutability
40 pub struct MutableSpace(pub clean::Mutability);
41 /// Similar to VisSpace, but used for mutability
42 pub struct RawMutableSpace(pub clean::Mutability);
43 /// Wrapper struct for properly emitting the stability level.
44 pub struct Stability<'a>(pub &'a Option<clean::Stability>);
45 /// Wrapper struct for emitting the stability level concisely.
46 pub struct ConciseStability<'a>(pub &'a Option<clean::Stability>);
47
48 impl VisSpace {
49     pub fn get(&self) -> Option<ast::Visibility> {
50         let VisSpace(v) = *self; v
51     }
52 }
53
54 impl FnStyleSpace {
55     pub fn get(&self) -> ast::FnStyle {
56         let FnStyleSpace(v) = *self; v
57     }
58 }
59
60 impl fmt::Show for clean::Generics {
61     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62         if self.lifetimes.len() == 0 && self.type_params.len() == 0 { return Ok(()) }
63         try!(f.write("&lt;".as_bytes()));
64
65         for (i, life) in self.lifetimes.iter().enumerate() {
66             if i > 0 {
67                 try!(f.write(", ".as_bytes()));
68             }
69             try!(write!(f, "{}", *life));
70         }
71
72         if self.type_params.len() > 0 {
73             if self.lifetimes.len() > 0 {
74                 try!(f.write(", ".as_bytes()));
75             }
76
77             for (i, tp) in self.type_params.iter().enumerate() {
78                 if i > 0 {
79                     try!(f.write(", ".as_bytes()))
80                 }
81                 try!(f.write(tp.name.as_bytes()));
82
83                 if tp.bounds.len() > 0 {
84                     try!(f.write(": ".as_bytes()));
85                     for (i, bound) in tp.bounds.iter().enumerate() {
86                         if i > 0 {
87                             try!(f.write(" + ".as_bytes()));
88                         }
89                         try!(write!(f, "{}", *bound));
90                     }
91                 }
92
93                 match tp.default {
94                     Some(ref ty) => { try!(write!(f, " = {}", ty)); },
95                     None => {}
96                 };
97             }
98         }
99         try!(f.write("&gt;".as_bytes()));
100         Ok(())
101     }
102 }
103
104 impl fmt::Show for clean::Lifetime {
105     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106         try!(f.write(self.get_ref().as_bytes()));
107         Ok(())
108     }
109 }
110
111 impl fmt::Show for clean::TyParamBound {
112     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113         match *self {
114             clean::RegionBound => {
115                 f.write("'static".as_bytes())
116             }
117             clean::TraitBound(ref ty) => {
118                 write!(f, "{}", *ty)
119             }
120         }
121     }
122 }
123
124 impl fmt::Show for clean::Path {
125     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126         if self.global {
127             try!(f.write("::".as_bytes()))
128         }
129
130         for (i, seg) in self.segments.iter().enumerate() {
131             if i > 0 {
132                 try!(f.write("::".as_bytes()))
133             }
134             try!(f.write(seg.name.as_bytes()));
135
136             if seg.lifetimes.len() > 0 || seg.types.len() > 0 {
137                 try!(f.write("&lt;".as_bytes()));
138                 let mut comma = false;
139                 for lifetime in seg.lifetimes.iter() {
140                     if comma {
141                         try!(f.write(", ".as_bytes()));
142                     }
143                     comma = true;
144                     try!(write!(f, "{}", *lifetime));
145                 }
146                 for ty in seg.types.iter() {
147                     if comma {
148                         try!(f.write(", ".as_bytes()));
149                     }
150                     comma = true;
151                     try!(write!(f, "{}", *ty));
152                 }
153                 try!(f.write("&gt;".as_bytes()));
154             }
155         }
156         Ok(())
157     }
158 }
159
160 /// Used when rendering a `ResolvedPath` structure. This invokes the `path`
161 /// rendering function with the necessary arguments for linking to a local path.
162 fn resolved_path(w: &mut fmt::Formatter, did: ast::DefId, p: &clean::Path,
163                  print_all: bool) -> fmt::Result {
164     path(w, p, print_all,
165         |cache, loc| {
166             if ast_util::is_local(did) || cache.inlined.contains(&did) {
167                 Some(("../".repeat(loc.len())).to_string())
168             } else {
169                 match cache.extern_locations[did.krate] {
170                     render::Remote(ref s) => Some(s.to_string()),
171                     render::Local => {
172                         Some(("../".repeat(loc.len())).to_string())
173                     }
174                     render::Unknown => None,
175                 }
176             }
177         },
178         |cache| {
179             match cache.paths.find(&did) {
180                 None => None,
181                 Some(&(ref fqp, shortty)) => Some((fqp.clone(), shortty))
182             }
183         })
184 }
185
186 fn path(w: &mut fmt::Formatter, path: &clean::Path, print_all: bool,
187         root: |&render::Cache, &[String]| -> Option<String>,
188         info: |&render::Cache| -> Option<(Vec<String> , ItemType)>)
189     -> fmt::Result
190 {
191     // The generics will get written to both the title and link
192     let mut generics = String::new();
193     let last = path.segments.last().unwrap();
194     if last.lifetimes.len() > 0 || last.types.len() > 0 {
195         let mut counter = 0u;
196         generics.push_str("&lt;");
197         for lifetime in last.lifetimes.iter() {
198             if counter > 0 { generics.push_str(", "); }
199             counter += 1;
200             generics.push_str(format!("{}", *lifetime).as_slice());
201         }
202         for ty in last.types.iter() {
203             if counter > 0 { generics.push_str(", "); }
204             counter += 1;
205             generics.push_str(format!("{}", *ty).as_slice());
206         }
207         generics.push_str("&gt;");
208     }
209
210     let loc = current_location_key.get().unwrap();
211     let cache = cache_key.get().unwrap();
212     let abs_root = root(&**cache, loc.as_slice());
213     let rel_root = match path.segments[0].name.as_slice() {
214         "self" => Some("./".to_string()),
215         _ => None,
216     };
217
218     if print_all {
219         let amt = path.segments.len() - 1;
220         match rel_root {
221             Some(root) => {
222                 let mut root = String::from_str(root.as_slice());
223                 for seg in path.segments.slice_to(amt).iter() {
224                     if "super" == seg.name.as_slice() ||
225                             "self" == seg.name.as_slice() {
226                         try!(write!(w, "{}::", seg.name));
227                     } else {
228                         root.push_str(seg.name.as_slice());
229                         root.push_str("/");
230                         try!(write!(w, "<a class='mod'
231                                             href='{}index.html'>{}</a>::",
232                                       root.as_slice(),
233                                       seg.name));
234                     }
235                 }
236             }
237             None => {
238                 for seg in path.segments.slice_to(amt).iter() {
239                     try!(write!(w, "{}::", seg.name));
240                 }
241             }
242         }
243     }
244
245     match info(&**cache) {
246         // This is a documented path, link to it!
247         Some((ref fqp, shortty)) if abs_root.is_some() => {
248             let mut url = String::from_str(abs_root.unwrap().as_slice());
249             let to_link = fqp.slice_to(fqp.len() - 1);
250             for component in to_link.iter() {
251                 url.push_str(component.as_slice());
252                 url.push_str("/");
253             }
254             match shortty {
255                 item_type::Module => {
256                     url.push_str(fqp.last().unwrap().as_slice());
257                     url.push_str("/index.html");
258                 }
259                 _ => {
260                     url.push_str(shortty.to_static_str());
261                     url.push_str(".");
262                     url.push_str(fqp.last().unwrap().as_slice());
263                     url.push_str(".html");
264                 }
265             }
266
267             try!(write!(w, "<a class='{}' href='{}' title='{}'>{}</a>",
268                           shortty, url, fqp.connect("::"), last.name));
269         }
270
271         _ => {
272             try!(write!(w, "{}", last.name));
273         }
274     }
275     try!(write!(w, "{}", generics.as_slice()));
276     Ok(())
277 }
278
279 fn primitive_link(f: &mut fmt::Formatter,
280                   prim: clean::Primitive,
281                   name: &str) -> fmt::Result {
282     let m = cache_key.get().unwrap();
283     let mut needs_termination = false;
284     match m.primitive_locations.find(&prim) {
285         Some(&ast::LOCAL_CRATE) => {
286             let loc = current_location_key.get().unwrap();
287             let len = if loc.len() == 0 {0} else {loc.len() - 1};
288             try!(write!(f, "<a href='{}primitive.{}.html'>",
289                         "../".repeat(len),
290                         prim.to_url_str()));
291             needs_termination = true;
292         }
293         Some(&cnum) => {
294             let path = &m.paths[ast::DefId {
295                 krate: cnum,
296                 node: ast::CRATE_NODE_ID,
297             }];
298             let loc = match m.extern_locations[cnum] {
299                 render::Remote(ref s) => Some(s.to_string()),
300                 render::Local => {
301                     let loc = current_location_key.get().unwrap();
302                     Some("../".repeat(loc.len()))
303                 }
304                 render::Unknown => None,
305             };
306             match loc {
307                 Some(root) => {
308                     try!(write!(f, "<a href='{}{}/primitive.{}.html'>",
309                                 root,
310                                 path.ref0().as_slice().head().unwrap(),
311                                 prim.to_url_str()));
312                     needs_termination = true;
313                 }
314                 None => {}
315             }
316         }
317         None => {}
318     }
319     try!(write!(f, "{}", name));
320     if needs_termination {
321         try!(write!(f, "</a>"));
322     }
323     Ok(())
324 }
325
326 /// Helper to render type parameters
327 fn tybounds(w: &mut fmt::Formatter,
328             typarams: &Option<Vec<clean::TyParamBound> >) -> fmt::Result {
329     match *typarams {
330         Some(ref params) => {
331             for param in params.iter() {
332                 try!(write!(w, " + "));
333                 try!(write!(w, "{}", *param));
334             }
335             Ok(())
336         }
337         None => Ok(())
338     }
339 }
340
341 impl fmt::Show for clean::Type {
342     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
343         match *self {
344             clean::TyParamBinder(id) => {
345                 let m = cache_key.get().unwrap();
346                 f.write(m.typarams[ast_util::local_def(id)].as_bytes())
347             }
348             clean::Generic(did) => {
349                 let m = cache_key.get().unwrap();
350                 f.write(m.typarams[did].as_bytes())
351             }
352             clean::ResolvedPath{ did, ref typarams, ref path } => {
353                 try!(resolved_path(f, did, path, false));
354                 tybounds(f, typarams)
355             }
356             clean::Self(..) => f.write("Self".as_bytes()),
357             clean::Primitive(prim) => primitive_link(f, prim, prim.to_string()),
358             clean::Closure(ref decl) => {
359                 write!(f, "{style}{lifetimes}|{args}|{bounds}{arrow}",
360                        style = FnStyleSpace(decl.fn_style),
361                        lifetimes = if decl.lifetimes.len() == 0 {
362                            "".to_string()
363                        } else {
364                            format!("&lt;{:#}&gt;", decl.lifetimes)
365                        },
366                        args = decl.decl.inputs,
367                        arrow = match decl.decl.output {
368                            clean::Primitive(clean::Unit) => "".to_string(),
369                            _ => format!(" -&gt; {}", decl.decl.output),
370                        },
371                        bounds = {
372                            let mut ret = String::new();
373                            for bound in decl.bounds.iter() {
374                                 match *bound {
375                                     clean::RegionBound => {}
376                                     clean::TraitBound(ref t) => {
377                                         if ret.len() == 0 {
378                                             ret.push_str(": ");
379                                         } else {
380                                             ret.push_str(" + ");
381                                         }
382                                         ret.push_str(format!("{}",
383                                                              *t).as_slice());
384                                     }
385                                 }
386                            }
387                            ret
388                        })
389             }
390             clean::Proc(ref decl) => {
391                 write!(f, "{style}{lifetimes}proc({args}){bounds}{arrow}",
392                        style = FnStyleSpace(decl.fn_style),
393                        lifetimes = if decl.lifetimes.len() == 0 {
394                            "".to_string()
395                        } else {
396                            format!("&lt;{:#}&gt;", decl.lifetimes)
397                        },
398                        args = decl.decl.inputs,
399                        bounds = if decl.bounds.len() == 0 {
400                            "".to_string()
401                        } else {
402                            let mut m = decl.bounds
403                                            .iter()
404                                            .map(|s| s.to_string());
405                            format!(
406                                ": {}",
407                                m.collect::<Vec<String>>().connect(" + "))
408                        },
409                        arrow = match decl.decl.output {
410                            clean::Primitive(clean::Unit) => "".to_string(),
411                            _ => format!(" -&gt; {}", decl.decl.output)
412                        })
413             }
414             clean::BareFunction(ref decl) => {
415                 write!(f, "{}{}fn{}{}",
416                        FnStyleSpace(decl.fn_style),
417                        match decl.abi.as_slice() {
418                            "" => " extern ".to_string(),
419                            "\"Rust\"" => "".to_string(),
420                            s => format!(" extern {} ", s)
421                        },
422                        decl.generics,
423                        decl.decl)
424             }
425             clean::Tuple(ref typs) => {
426                 primitive_link(f, clean::PrimitiveTuple,
427                                match typs.as_slice() {
428                                     [ref one] => format!("({},)", one),
429                                     many => format!("({:#})", many)
430                                }.as_slice())
431             }
432             clean::Vector(ref t) => {
433                 primitive_link(f, clean::Slice, format!("[{}]", **t).as_slice())
434             }
435             clean::FixedVector(ref t, ref s) => {
436                 primitive_link(f, clean::Slice,
437                                format!("[{}, ..{}]", **t, *s).as_slice())
438             }
439             clean::Bottom => f.write("!".as_bytes()),
440             clean::RawPointer(m, ref t) => {
441                 write!(f, "*{}{}", RawMutableSpace(m), **t)
442             }
443             clean::BorrowedRef{ lifetime: ref l, mutability, type_: ref ty} => {
444                 let lt = match *l {
445                     Some(ref l) => format!("{} ", *l),
446                     _ => "".to_string(),
447                 };
448                 write!(f, "&amp;{}{}{}", lt, MutableSpace(mutability), **ty)
449             }
450             clean::Unique(..) | clean::Managed(..) => {
451                 fail!("should have been cleaned")
452             }
453         }
454     }
455 }
456
457 impl fmt::Show for clean::Arguments {
458     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
459         for (i, input) in self.values.iter().enumerate() {
460             if i > 0 { try!(write!(f, ", ")); }
461             if input.name.len() > 0 {
462                 try!(write!(f, "{}: ", input.name));
463             }
464             try!(write!(f, "{}", input.type_));
465         }
466         Ok(())
467     }
468 }
469
470 impl fmt::Show for clean::FnDecl {
471     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
472         write!(f, "({args}){arrow}",
473                args = self.inputs,
474                arrow = match self.output {
475                    clean::Primitive(clean::Unit) => "".to_string(),
476                    _ => format!(" -&gt; {}", self.output),
477                })
478     }
479 }
480
481 impl<'a> fmt::Show for Method<'a> {
482     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
483         let Method(selfty, d) = *self;
484         let mut args = String::new();
485         match *selfty {
486             clean::SelfStatic => {},
487             clean::SelfValue => args.push_str("self"),
488             clean::SelfBorrowed(Some(ref lt), mtbl) => {
489                 args.push_str(format!("&amp;{} {}self", *lt,
490                                       MutableSpace(mtbl)).as_slice());
491             }
492             clean::SelfBorrowed(None, mtbl) => {
493                 args.push_str(format!("&amp;{}self",
494                                       MutableSpace(mtbl)).as_slice());
495             }
496             clean::SelfExplicit(ref typ) => {
497                 args.push_str(format!("self: {}", *typ).as_slice());
498             }
499         }
500         for (i, input) in d.inputs.values.iter().enumerate() {
501             if i > 0 || args.len() > 0 { args.push_str(", "); }
502             if input.name.len() > 0 {
503                 args.push_str(format!("{}: ", input.name).as_slice());
504             }
505             args.push_str(format!("{}", input.type_).as_slice());
506         }
507         write!(f, "({args}){arrow}",
508                args = args,
509                arrow = match d.output {
510                    clean::Primitive(clean::Unit) => "".to_string(),
511                    _ => format!(" -&gt; {}", d.output),
512                })
513     }
514 }
515
516 impl fmt::Show for VisSpace {
517     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
518         match self.get() {
519             Some(ast::Public) => write!(f, "pub "),
520             Some(ast::Inherited) | None => Ok(())
521         }
522     }
523 }
524
525 impl fmt::Show for FnStyleSpace {
526     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
527         match self.get() {
528             ast::UnsafeFn => write!(f, "unsafe "),
529             ast::NormalFn => Ok(())
530         }
531     }
532 }
533
534 impl fmt::Show for clean::ViewPath {
535     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
536         match *self {
537             clean::SimpleImport(ref name, ref src) => {
538                 if *name == src.path.segments.last().unwrap().name {
539                     write!(f, "use {};", *src)
540                 } else {
541                     write!(f, "use {} as {};", *src, *name)
542                 }
543             }
544             clean::GlobImport(ref src) => {
545                 write!(f, "use {}::*;", *src)
546             }
547             clean::ImportList(ref src, ref names) => {
548                 try!(write!(f, "use {}::{{", *src));
549                 for (i, n) in names.iter().enumerate() {
550                     if i > 0 {
551                         try!(write!(f, ", "));
552                     }
553                     try!(write!(f, "{}", *n));
554                 }
555                 write!(f, "}};")
556             }
557         }
558     }
559 }
560
561 impl fmt::Show for clean::ImportSource {
562     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
563         match self.did {
564             Some(did) => resolved_path(f, did, &self.path, true),
565             _ => {
566                 for (i, seg) in self.path.segments.iter().enumerate() {
567                     if i > 0 {
568                         try!(write!(f, "::"))
569                     }
570                     try!(write!(f, "{}", seg.name));
571                 }
572                 Ok(())
573             }
574         }
575     }
576 }
577
578 impl fmt::Show for clean::ViewListIdent {
579     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
580         match self.source {
581             Some(did) => {
582                 let path = clean::Path {
583                     global: false,
584                     segments: vec!(clean::PathSegment {
585                         name: self.name.clone(),
586                         lifetimes: Vec::new(),
587                         types: Vec::new(),
588                     })
589                 };
590                 resolved_path(f, did, &path, false)
591             }
592             _ => write!(f, "{}", self.name),
593         }
594     }
595 }
596
597 impl fmt::Show for MutableSpace {
598     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
599         match *self {
600             MutableSpace(clean::Immutable) => Ok(()),
601             MutableSpace(clean::Mutable) => write!(f, "mut "),
602         }
603     }
604 }
605
606 impl fmt::Show for RawMutableSpace {
607     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
608         match *self {
609             RawMutableSpace(clean::Immutable) => write!(f, "const "),
610             RawMutableSpace(clean::Mutable) => write!(f, "mut "),
611         }
612     }
613 }
614
615 impl<'a> fmt::Show for Stability<'a> {
616     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
617         let Stability(stab) = *self;
618         match *stab {
619             Some(ref stability) => {
620                 write!(f, "<a class='stability {lvl}' title='{reason}'>{lvl}</a>",
621                        lvl = stability.level.to_string(),
622                        reason = stability.text)
623             }
624             None => Ok(())
625         }
626     }
627 }
628
629 impl<'a> fmt::Show for ConciseStability<'a> {
630     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
631         let ConciseStability(stab) = *self;
632         match *stab {
633             Some(ref stability) => {
634                 write!(f, "<a class='stability {lvl}' title='{lvl}{colon}{reason}'></a>",
635                        lvl = stability.level.to_string(),
636                        colon = if stability.text.len() > 0 { ": " } else { "" },
637                        reason = stability.text)
638             }
639             None => {
640                 write!(f, "<a class='stability Unmarked' title='No stability level'></a>")
641             }
642         }
643     }
644 }
645
646 impl fmt::Show for ModuleSummary {
647     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
648         fn fmt_inner<'a>(f: &mut fmt::Formatter,
649                          context: &mut Vec<&'a str>,
650                          m: &'a ModuleSummary)
651                      -> fmt::Result {
652             let cnt = m.counts;
653             let tot = cnt.total();
654             if tot == 0 { return Ok(()) }
655
656             context.push(m.name.as_slice());
657             let path = context.connect("::");
658
659             try!(write!(f, "<tr>"));
660             try!(write!(f, "<td><a href='{}'>{}</a></td>",
661                         Vec::from_slice(context.slice_from(1))
662                             .append_one("index.html").connect("/"),
663                         path));
664             try!(write!(f, "<td class='summary-column'>"));
665             try!(write!(f, "<span class='summary Stable' \
666                             style='width: {:.4}%; display: inline-block'>&nbsp</span>",
667                         (100 * cnt.stable) as f64/tot as f64));
668             try!(write!(f, "<span class='summary Unstable' \
669                             style='width: {:.4}%; display: inline-block'>&nbsp</span>",
670                         (100 * cnt.unstable) as f64/tot as f64));
671             try!(write!(f, "<span class='summary Experimental' \
672                             style='width: {:.4}%; display: inline-block'>&nbsp</span>",
673                         (100 * cnt.experimental) as f64/tot as f64));
674             try!(write!(f, "<span class='summary Deprecated' \
675                             style='width: {:.4}%; display: inline-block'>&nbsp</span>",
676                         (100 * cnt.deprecated) as f64/tot as f64));
677             try!(write!(f, "<span class='summary Unmarked' \
678                             style='width: {:.4}%; display: inline-block'>&nbsp</span>",
679                         (100 * cnt.unmarked) as f64/tot as f64));
680             try!(write!(f, "</td></tr>"));
681
682             for submodule in m.submodules.iter() {
683                 try!(fmt_inner(f, context, submodule));
684             }
685             context.pop();
686             Ok(())
687         }
688
689         let mut context = Vec::new();
690
691         let tot = self.counts.total();
692         let (stable, unstable, experimental, deprecated, unmarked) = if tot == 0 {
693             (0, 0, 0, 0, 0)
694         } else {
695             ((100 * self.counts.stable)/tot,
696              (100 * self.counts.unstable)/tot,
697              (100 * self.counts.experimental)/tot,
698              (100 * self.counts.deprecated)/tot,
699              (100 * self.counts.unmarked)/tot)
700         };
701
702         try!(write!(f,
703 r"<h1 class='fqn'>Stability dashboard: crate <a class='mod' href='index.html'>{name}</a></h1>
704 This dashboard summarizes the stability levels for all of the public modules of
705 the crate, according to the total number of items at each level in the module and
706 its children (percentages total for {name}):
707 <blockquote>
708 <a class='stability Stable'></a> stable ({}%),<br/>
709 <a class='stability Unstable'></a> unstable ({}%),<br/>
710 <a class='stability Experimental'></a> experimental ({}%),<br/>
711 <a class='stability Deprecated'></a> deprecated ({}%),<br/>
712 <a class='stability Unmarked'></a> unmarked ({}%)
713 </blockquote>
714 The counts do not include methods or trait
715 implementations that are visible only through a re-exported type.",
716 stable, unstable, experimental, deprecated, unmarked,
717 name=self.name));
718         try!(write!(f, "<table>"))
719         try!(fmt_inner(f, &mut context, self));
720         write!(f, "</table>")
721     }
722 }