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