diff --git a/src/librustdoc/ascii/format.rs b/src/librustdoc/ascii/format.rs new file mode 100644 index 0000000000000..908ebfdf3532b --- /dev/null +++ b/src/librustdoc/ascii/format.rs @@ -0,0 +1,1292 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Ascii formatting module +//! +//! This module contains a large number of `fmt::Display` implementations for +//! various types in `rustdoc::clean`. These implementations all currently +//! assume that HTML output is desired, although it may be possible to redesign +//! them in the future to instead emit any format desired. + +extern crate term; + +use std::fmt; +use std::iter::repeat; + +use syntax::abi::Abi; +use syntax::ast; +use syntax::ast_util; + +use clean; +use ascii::item_type::ItemType; +use ascii::render; +use ascii::render::{cache, CURRENT_LOCATION_KEY}; + +const RED : &'static str = "\x1b[31m"; +const GREEN : &'static str = "\x1b[32m"; +const BROWN : &'static str = "\x1b[33m"; +const BLUE : &'static str = "\x1b[34m"; +const MAGENTA : &'static str = "\x1b[35m"; +const CYAN : &'static str = "\x1b[36m"; +const WHITE : &'static str = "\x1b[37m"; +const NORMAL : &'static str = "\x1b[0m"; + +pub trait AsciiDisplay { + fn get_display(&self) -> String; +} + +/// Helper to render an optional visibility with a space after it (if the +/// visibility is preset) +#[derive(Copy, Clone)] +pub struct VisSpace(pub Option); +/// Similarly to VisSpace, this structure is used to render a function style with a +/// space after it. +#[derive(Copy, Clone)] +pub struct UnsafetySpace(pub ast::Unsafety); +/// Similarly to VisSpace, this structure is used to render a function constness +/// with a space after it. +#[derive(Copy, Clone)] +pub struct ConstnessSpace(pub ast::Constness); +/// Wrapper struct for properly emitting a method declaration. +pub struct Method<'a>(pub &'a clean::SelfTy, pub &'a clean::FnDecl); +/// Similar to VisSpace, but used for mutability +#[derive(Copy, Clone)] +pub struct MutableSpace(pub clean::Mutability); +/// Similar to VisSpace, but used for mutability +#[derive(Copy, Clone)] +pub struct RawMutableSpace(pub clean::Mutability); +/// Wrapper struct for emitting a where clause from Generics. +pub struct WhereClause<'a>(pub &'a clean::Generics); +/// Wrapper struct for emitting type parameter bounds. +pub struct TyParamBounds<'a>(pub &'a [clean::TyParamBound]); +/// Wrapper struct for emitting a comma-separated list of items +pub struct CommaSep<'a, T: 'a>(pub &'a [T]); +pub struct AbiSpace(pub Abi); + +pub fn convert_color(ty_: &str) { + let known = vec!("u8", "u16", "u32", "u64"); +} + +impl VisSpace { + pub fn get(&self) -> Option { + let VisSpace(v) = *self; v + } +} + +impl UnsafetySpace { + pub fn get(&self) -> ast::Unsafety { + let UnsafetySpace(v) = *self; v + } +} + +impl ConstnessSpace { + pub fn get(&self) -> ast::Constness { + let ConstnessSpace(v) = *self; v + } +} + +impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, item) in self.0.iter().enumerate() { + if i != 0 { try!(write!(f, ", ")); } + try!(write!(f, "{}", item)); + } + Ok(()) + } +} + +impl<'a> fmt::Display for TyParamBounds<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let &TyParamBounds(bounds) = self; + for (i, bound) in bounds.iter().enumerate() { + if i > 0 { + try!(f.write_str(" + ")); + } + try!(write!(f, "{}", *bound)); + } + Ok(()) + } +} + +/*impl fmt::Display for clean::Generics { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.lifetimes.is_empty() && self.type_params.is_empty() { return Ok(()) } + try!(f.write_str("<")); + + for (i, life) in self.lifetimes.iter().enumerate() { + if i > 0 { + try!(f.write_str(", ")); + } + try!(write!(f, "{}", *life)); + } + + if !self.type_params.is_empty() { + if !self.lifetimes.is_empty() { + try!(f.write_str(", ")); + } + for (i, tp) in self.type_params.iter().enumerate() { + if i > 0 { + try!(f.write_str(", ")) + } + try!(f.write_str(&tp.name)); + + if !tp.bounds.is_empty() { + try!(write!(f, ": {}", TyParamBounds(&tp.bounds))); + } + + match tp.default { + Some(ref ty) => { try!(write!(f, " = {}", ty)); }, + None => {} + }; + } + } + try!(f.write_str(">")); + Ok(()) + } +}*/ + +impl AsciiDisplay for clean::Generics { + fn get_display(&self) -> String { + if self.lifetimes.is_empty() && self.type_params.is_empty() { return String::new() } + let mut res = "<".to_owned(); + + for (i, life) in self.lifetimes.iter().enumerate() { + if i > 0 { + res.push_str(", "); + } + res.push_str(&format!("{}", *life)); + } + + if !self.type_params.is_empty() { + if !self.lifetimes.is_empty() { + res.push_str(", "); + } + for (i, tp) in self.type_params.iter().enumerate() { + if i > 0 { + res.push_str(", "); + } + res.push_str(&tp.name); + + if !tp.bounds.is_empty() { + res.push_str(&format!(": {}", TyParamBounds(&tp.bounds))); + } + + match tp.default { + Some(ref ty) => { res.push_str(&format!(" = {}", ty.get_display())); }, + None => {} + }; + } + } + res.push_str(">"); + res + } +} + +/*impl<'a> fmt::Display for WhereClause<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let &WhereClause(gens) = self; + if gens.where_predicates.is_empty() { + return Ok(()); + } + try!(f.write_str(&format!(" {}where{} ", WHITE, NORMAL))); + for (i, pred) in gens.where_predicates.iter().enumerate() { + if i > 0 { + try!(f.write_str(", ")); + } + match pred { + &clean::WherePredicate::BoundPredicate { ref ty, ref bounds } => { + let bounds = bounds; + try!(write!(f, "{}: {}", ty, TyParamBounds(bounds))); + } + &clean::WherePredicate::RegionPredicate { ref lifetime, + ref bounds } => { + try!(write!(f, "{}: ", lifetime)); + for (i, lifetime) in bounds.iter().enumerate() { + if i > 0 { + try!(f.write_str(" + ")); + } + + try!(write!(f, "{}", lifetime)); + } + } + &clean::WherePredicate::EqPredicate { ref lhs, ref rhs } => { + try!(write!(f, "{} == {}", lhs, rhs)); + } + } + } + Ok(()) + } +}*/ + +impl<'a> AsciiDisplay for WhereClause<'a> { + fn get_display(&self) -> String { + let &WhereClause(gens) = self; + let mut res = String::new(); + if gens.where_predicates.is_empty() { + return String::new(); + } + //try!(f.write_str(&format!(" {}where{} ", WHITE, NORMAL))); + res.push_str(&format!(" {}where{} ", WHITE, NORMAL)); + for (i, pred) in gens.where_predicates.iter().enumerate() { + if i > 0 { + //try!(f.write_str(", ")); + res.push_str(", "); + } + match pred { + &clean::WherePredicate::BoundPredicate { ref ty, ref bounds } => { + let bounds = bounds; + //try!(write!(f, "{}: {}", ty, TyParamBounds(bounds))); + res.push_str(&format!("{}: {}", ty.get_display(), + TyParamBounds(bounds))); + } + &clean::WherePredicate::RegionPredicate { ref lifetime, + ref bounds } => { + //try!(write!(f, "{}: ", lifetime)); + res.push_str(&format!("{}: ", lifetime)); + for (i, lifetime) in bounds.iter().enumerate() { + if i > 0 { + //try!(f.write_str(" + ")); + res.push_str(" + "); + } + + //try!(write!(f, "{}", lifetime)); + res.push_str(&format!("{}", lifetime)); + } + } + &clean::WherePredicate::EqPredicate { ref lhs, ref rhs } => { + //try!(write!(f, "{} == {}", lhs, rhs)); + res.push_str(&format!("{} == {}", lhs.get_display(), rhs.get_display())); + } + } + } + res + } +} + +/*impl fmt::Display for clean::Lifetime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(f.write_str(self.get_ref())); + Ok(()) + } +}*/ + +impl AsciiDisplay for clean::Lifetime { + fn get_display(&self) -> String { + self.get_ref().to_owned() + } +} + +/*impl fmt::Display for clean::PolyTrait { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if !self.lifetimes.is_empty() { + try!(f.write_str("for<")); + for (i, lt) in self.lifetimes.iter().enumerate() { + if i > 0 { + try!(f.write_str(", ")); + } + try!(write!(f, "{}", lt)); + } + try!(f.write_str("> ")); + } + write!(f, "{}", self.trait_) + } +}*/ + +impl AsciiDisplay for clean::PolyTrait { + fn get_display(&self) -> String { + let mut res = String::new(); + + if !self.lifetimes.is_empty() { + res.push_str("for>"); + for (i, lt) in self.lifetimes.iter().enumerate() { + if i > 0 { + res.push_str(", "); + } + res.push_str(&format!("{}", lt)); + } + res.push_str("> "); + } + res.push_str(&format!("{}", self.trait_.get_display())); + res + } +} + +/*impl fmt::Display for clean::TyParamBound { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + clean::RegionBound(ref lt) => { + write!(f, "{}", *lt) + } + clean::TraitBound(ref ty, modifier) => { + let modifier_str = match modifier { + ast::TraitBoundModifier::None => "", + ast::TraitBoundModifier::Maybe => "?", + }; + write!(f, "{}{}", modifier_str, *ty) + } + } + } +}*/ + +impl AsciiDisplay for clean::TyParamBound { + fn get_display(&self) -> String { + match *self { + clean::RegionBound(ref lt) => { + format!("{}", *lt) + } + clean::TraitBound(ref ty, modifier) => { + let modifier_str = match modifier { + ast::TraitBoundModifier::None => "", + ast::TraitBoundModifier::Maybe => "?", + }; + format!("{}{}", modifier_str, (*ty).get_display()) + } + } + } +} + +/*impl fmt::Display for clean::PathParameters { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + clean::PathParameters::AngleBracketed { + ref lifetimes, ref types, ref bindings + } => { + if !lifetimes.is_empty() || !types.is_empty() || !bindings.is_empty() { + try!(f.write_str("<")); + let mut comma = false; + for lifetime in lifetimes { + if comma { + try!(f.write_str(", ")); + } + comma = true; + try!(write!(f, "{}", *lifetime)); + } + for ty in types { + if comma { + try!(f.write_str(", ")); + } + comma = true; + try!(write!(f, "{}", *ty)); + } + for binding in bindings { + if comma { + try!(f.write_str(", ")); + } + comma = true; + try!(write!(f, "{}", *binding)); + } + try!(f.write_str(">")); + } + } + clean::PathParameters::Parenthesized { ref inputs, ref output } => { + try!(f.write_str("(")); + let mut comma = false; + for ty in inputs { + if comma { + try!(f.write_str(", ")); + } + comma = true; + try!(write!(f, "{}", *ty)); + } + try!(f.write_str(")")); + if let Some(ref ty) = *output { + try!(f.write_str(" -> ")); + try!(write!(f, "{}", ty)); + } + } + } + Ok(()) + } +}*/ + +impl AsciiDisplay for clean::PathParameters { + fn get_display(&self) -> String { + let mut res = String::new(); + + match *self { + clean::PathParameters::AngleBracketed { + ref lifetimes, ref types, ref bindings + } => { + if !lifetimes.is_empty() || !types.is_empty() || !bindings.is_empty() { + res.push_str("<"); + let mut comma = false; + for lifetime in lifetimes { + if comma { + res.push_str(", "); + } + comma = true; + res.push_str(&format!("{}", *lifetime)); + } + for ty in types { + if comma { + res.push_str(", "); + } + comma = true; + res.push_str(&format!("{}", (*ty).get_display())); + } + for binding in bindings { + if comma { + res.push_str(", "); + } + comma = true; + res.push_str(&format!("{}", *binding)); + } + res.push_str(">"); + } + } + clean::PathParameters::Parenthesized { ref inputs, ref output } => { + res.push_str("("); + let mut comma = false; + for ty in inputs { + if comma { + res.push_str(", "); + } + comma = true; + res.push_str(&format!("{}", (*ty).get_display())); + } + res.push_str(")"); + if let Some(ref ty) = *output { + res.push_str(" -> "); + res.push_str(&format!("{}", (*ty).get_display())); + } + } + } + res + } +} + +/*impl fmt::Display for clean::PathSegment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(f.write_str(&self.name)); + write!(f, "{}", self.params) + } +}*/ + +impl AsciiDisplay for clean::PathSegment { + fn get_display(&self) -> String { + let mut res = String::new(); + + res.push_str(&self.name); + res.push_str(&format!("{}", self.params.get_display())); + res + } +} + +/*impl fmt::Display for clean::Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.global { + try!(f.write_str("::")) + } + + for (i, seg) in self.segments.iter().enumerate() { + if i > 0 { + try!(f.write_str("::")) + } + try!(write!(f, "{}", seg)); + } + Ok(()) + } +}*/ + +impl AsciiDisplay for clean::Path { + fn get_display(&self) -> String { + let mut res = String::new(); + + if self.global { + //try!(f.write_str("::")) + res.push_str("::"); + } + + for (i, seg) in self.segments.iter().enumerate() { + if i > 0 { + //try!(f.write_str("::")) + res.push_str("::"); + } + //try!(write!(f, "{}", seg)); + res.push_str(&format!("{}", seg)); + } + res + } +} + +pub fn href(did: ast::DefId) -> Option<(String, ItemType, Vec)> { + let cache = cache(); + let loc = CURRENT_LOCATION_KEY.with(|l| l.borrow().clone()); + let &(ref fqp, shortty) = match cache.paths.get(&did) { + Some(p) => p, + None => return None, + }; + let mut url = if ast_util::is_local(did) || cache.inlined.contains(&did) { + repeat("../").take(loc.len()).collect::() + } else { + match cache.extern_locations[&did.krate] { + (_, render::Remote(ref s)) => s.to_string(), + (_, render::Local) => repeat("../").take(loc.len()).collect(), + (_, render::Unknown) => return None, + } + }; + for component in &fqp[..fqp.len() - 1] { + url.push_str(component); + url.push_str("/"); + } + match shortty { + ItemType::Module => { + url.push_str(fqp.last().unwrap()); + url.push_str("/index.html"); + } + _ => { + url.push_str(shortty.to_static_str()); + url.push_str("."); + url.push_str(fqp.last().unwrap()); + url.push_str(".html"); + } + } + Some((url, shortty, fqp.to_vec())) +} + +/// Used when rendering a `ResolvedPath` structure. This invokes the `path` +/// rendering function with the necessary arguments for linking to a local path. +fn resolved_path(did: ast::DefId, path: &clean::Path, + print_all: bool) -> String { + let mut w = String::new(); + let last = path.segments.last().unwrap(); + let rel_root = match &*path.segments[0].name { + "self" => Some("./".to_string()), + _ => None, + }; + + if print_all { + let amt = path.segments.len() - 1; + match rel_root { + Some(root) => { + let mut root = String::from_str(&root); + for seg in &path.segments[..amt] { + if "super" == seg.name || "self" == seg.name { + //try!(write!(w, "{}::", seg.name)); + w.push_str(&format!("{}::", seg.name)); + } else { + /*root.push_str(&seg.name); + root.push_str("/"); + try!(write!(w, "{}::", + root, + seg.name));*/ + w.push_str(&format!("{}::", seg.name)); + } + } + } + None => { + for seg in &path.segments[..amt] { + //try!(write!(w, "{}::", seg.name)); + w.push_str(&format!("{}::", seg.name)); + } + } + } + } + + match href(did) { + Some((url, shortty, fqp)) => { + /*try!(write!(w, "{}", + shortty, url, fqp.connect("::"), last.name));*/ + w.push_str(&format!("{}", last.name)); + } + _ => /*try!(write!(w, "{}", last.name))*/w.push_str(&format!("{}", last.name)), + } + //try!(write!(w, "{}", last.params)); + w.push_str(&format!("{}", last.params.get_display())); + w +} + +fn primitive_link(prim: clean::PrimitiveType, + name: &str) -> String { + let m = cache(); + let mut f = String::new(); + let mut needs_termination = false; + match m.primitive_locations.get(&prim) { + Some(&ast::LOCAL_CRATE) => { + let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len()); + let len = if len == 0 {0} else {len - 1}; + /*try!(write!(f, "{}{}{}", + GREEN, prim.to_url_str(), NORMAL));*/ + f.push_str(&format!("{}{}{}", + GREEN, prim.to_url_str(), NORMAL)); + needs_termination = true; + } + Some(&cnum) => { + let path = &m.paths[&ast::DefId { + krate: cnum, + node: ast::CRATE_NODE_ID, + }]; + let loc = match m.extern_locations[&cnum] { + (_, render::Remote(ref s)) => Some(s.to_string()), + (_, render::Local) => { + let len = CURRENT_LOCATION_KEY.with(|s| s.borrow().len()); + Some(repeat("../").take(len).collect::()) + } + (_, render::Unknown) => None, + }; + match loc { + Some(root) => { + /*try!(write!(f, "{}{}{}", + GREEN, + prim.to_url_str(), + NORMAL));*/ + f.push_str(&format!("{}{}{}", + GREEN, + prim.to_url_str(), + NORMAL)); + needs_termination = true; + } + None => {} + } + } + None => {} + } + //try!(write!(f, "{}", name)); + f.push_str(name); + /*if needs_termination { + try!(write!(f, "")); + }*/ + f +} + +/// Helper to render type parameters +fn tybounds(typarams: &Option >) -> String { + match *typarams { + Some(ref params) => { + let mut res = String::new(); + + for param in params { + /*try!(write!(w, " + ")); + try!(write!(w, "{}", *param));*/ + res.push_str(&format!(" + {}", *param)); + } + res + } + None => String::new() + } +} + +/*impl fmt::Display for clean::Type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + clean::Generic(ref name) => { + f.write_str(name) + } + clean::ResolvedPath{ did, ref typarams, ref path, is_generic } => { + // Paths like T::Output and Self::Output should be rendered with all segments + try!(resolved_path(f, did, path, is_generic)); + tybounds(f, typarams) + } + clean::Infer => write!(f, "_"), + clean::Primitive(prim) => primitive_link(f, prim, prim.to_string()), + clean::BareFunction(ref decl) => { + write!(f, "{}{}{}fn{}{}{}", + BLUE, + UnsafetySpace(decl.unsafety), + match &*decl.abi { + "" => " extern ".to_string(), + "\"Rust\"" => "".to_string(), + s => format!(" extern {} ", s) + }, + decl.generics, + decl.decl, + NORMAL) + } + clean::Tuple(ref typs) => { + primitive_link(f, clean::PrimitiveTuple, + &*match &**typs { + [ref one] => format!("({},)", one), + many => format!("({})", CommaSep(&many)), + }) + } + clean::Vector(ref t) => { + primitive_link(f, clean::Slice, &format!("[{}]", **t)) + } + clean::FixedVector(ref t, ref s) => { + primitive_link(f, clean::PrimitiveType::Array, + &format!("[{}; {}]", **t, *s)) + } + clean::Bottom => f.write_str("!"), + clean::RawPointer(m, ref t) => { + primitive_link(f, clean::PrimitiveType::PrimitiveRawPointer, + &format!("*{}{}", RawMutableSpace(m), **t)) + } + clean::BorrowedRef{ lifetime: ref l, mutability, type_: ref ty} => { + let lt = match *l { + Some(ref l) => format!("{} ", *l), + _ => "".to_string(), + }; + let m = MutableSpace(mutability); + match **ty { + clean::Vector(ref bt) => { // BorrowedRef{ ... Vector(T) } is &[T] + match **bt { + clean::Generic(_) => + primitive_link(f, clean::Slice, + &format!("&{}{}[{}]", lt, m, **bt)), + _ => { + try!(primitive_link(f, clean::Slice, + &format!("&{}{}[", lt, m))); + try!(write!(f, "{}", **bt)); + primitive_link(f, clean::Slice, "]") + } + } + } + _ => { + write!(f, "&{}{}{}", lt, m, **ty) + } + } + } + clean::PolyTraitRef(ref bounds) => { + for (i, bound) in bounds.iter().enumerate() { + if i != 0 { + try!(write!(f, " + ")); + } + try!(write!(f, "{}", *bound)); + } + Ok(()) + } + // It's pretty unsightly to look at `::C` in output, and + // we've got hyperlinking on our side, so try to avoid longer + // notation as much as possible by making `C` a hyperlink to trait + // `B` to disambiguate. + // + // FIXME: this is still a lossy conversion and there should probably + // be a better way of representing this in general? Most of + // the ugliness comes from inlining across crates where + // everything comes in as a fully resolved QPath (hard to + // look at). + clean::QPath { + ref name, + ref self_type, + trait_: box clean::ResolvedPath { did, ref typarams, .. }, + } => { + try!(write!(f, "{}::", self_type)); + let path = clean::Path::singleton(name.clone()); + try!(resolved_path(f, did, &path, false)); + + // FIXME: `typarams` are not rendered, and this seems bad? + drop(typarams); + Ok(()) + } + clean::QPath { ref name, ref self_type, ref trait_ } => { + write!(f, "<{} as {}>::{}", self_type, trait_, name) + } + clean::Unique(..) => { + panic!("should have been cleaned") + } + } + } +}*/ + +impl AsciiDisplay for clean::Type { + fn get_display(&self) -> String { + match *self { + clean::Generic(ref name) => { + //f.write_str(name) + name.to_owned() + } + clean::ResolvedPath{ did, ref typarams, ref path, is_generic } => { + // Paths like T::Output and Self::Output should be rendered with all segments + let mut res = resolved_path(did, path, is_generic); + res.push_str(&tybounds(typarams)); + res + } + clean::Infer => /*write!(f, "_")*/"_".to_owned(), + clean::Primitive(prim) => primitive_link(prim, prim.to_string()), + clean::BareFunction(ref decl) => { + /*write!(f, "{}{}{}fn{}{}{}", + BLUE, + UnsafetySpace(decl.unsafety), + match &*decl.abi { + "" => " extern ".to_string(), + "\"Rust\"" => "".to_string(), + s => format!(" extern {} ", s) + }, + decl.generics, + decl.decl, + NORMAL)*/ + format!("{}{}{}fn{}{}{}", + BLUE, + UnsafetySpace(decl.unsafety).get_display(), + match &*decl.abi { + "" => " extern ".to_string(), + "\"Rust\"" => "".to_string(), + s => format!(" extern {} ", s) + }, + decl.generics.get_display(), + decl.decl, + NORMAL) + } + clean::Tuple(ref typs) => { + primitive_link(clean::PrimitiveTuple, + &*match &**typs { + [ref one] => format!("({},)", one.get_display()), + many => format!("({})", CommaSep(&many)), + }) + } + clean::Vector(ref t) => { + primitive_link(clean::Slice, &format!("[{}]", (**t).get_display())) + } + clean::FixedVector(ref t, ref s) => { + primitive_link(clean::PrimitiveType::Array, + &format!("[{}; {}]", (**t).get_display(), *s)) + } + clean::Bottom => /*f.write_str("!")*/"!".to_owned(), + clean::RawPointer(m, ref t) => { + primitive_link(clean::PrimitiveType::PrimitiveRawPointer, + &format!("*{}{}", RawMutableSpace(m).get_display(), (**t).get_display())) + } + clean::BorrowedRef{ lifetime: ref l, mutability, type_: ref ty} => { + let lt = match *l { + Some(ref l) => format!("{} ", *l), + _ => "".to_string(), + }; + let m = MutableSpace(mutability); + match **ty { + clean::Vector(ref bt) => { // BorrowedRef{ ... Vector(T) } is &[T] + match **bt { + clean::Generic(_) => + primitive_link(clean::Slice, + &format!("&{}{}[{}]", lt, m.get_display(), (**bt).get_display())), + _ => { + let mut res = primitive_link(clean::Slice, + &format!("&{}{}[", lt, m.get_display())); + //try!(write!(f, "{}", **bt)); + res.push_str(&format!("{}", (**bt).get_display())); + res.push_str(&primitive_link(clean::Slice, "]")); + res + } + } + } + _ => { + /*write!(f, */format!("&{}{}{}", lt, m.get_display(), (**ty).get_display()) + } + } + } + clean::PolyTraitRef(ref bounds) => { + let mut res = String::new(); + + for (i, bound) in bounds.iter().enumerate() { + if i != 0 { + //try!(write!(f, " + ")); + res.push_str(" + "); + } + //try!(write!(f, "{}", *bound)); + res.push_str(&format!("{}", *bound)); + } + res + } + // It's pretty unsightly to look at `::C` in output, and + // we've got hyperlinking on our side, so try to avoid longer + // notation as much as possible by making `C` a hyperlink to trait + // `B` to disambiguate. + // + // FIXME: this is still a lossy conversion and there should probably + // be a better way of representing this in general? Most of + // the ugliness comes from inlining across crates where + // everything comes in as a fully resolved QPath (hard to + // look at). + clean::QPath { + ref name, + ref self_type, + trait_: box clean::ResolvedPath { did, ref typarams, .. }, + } => { + //try!(write!(f, "{}::", self_type)); + let mut res = format!("{}::", self_type.get_display()); + + let path = clean::Path::singleton(name.clone()); + res.push_str(&resolved_path(did, &path, false)); + + // FIXME: `typarams` are not rendered, and this seems bad? + drop(typarams); + res + } + clean::QPath { ref name, ref self_type, ref trait_ } => { + //write!(f, "<{} as {}>::{}", self_type, trait_, name) + format!("<{} as {}>::{}", self_type.get_display(), trait_.get_display(), name) + } + clean::Unique(..) => { + panic!("should have been cleaned") + } + } + } +} + +/*impl<'a> fmt::Display for Method<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Method(selfty, d) = *self; + let mut args = String::new(); + match *selfty { + clean::SelfStatic => {}, + clean::SelfValue => args.push_str("self"), + clean::SelfBorrowed(Some(ref lt), mtbl) => { + args.push_str(&format!("&{} {}self", *lt, MutableSpace(mtbl))); + } + clean::SelfBorrowed(None, mtbl) => { + args.push_str(&format!("&{}self", MutableSpace(mtbl))); + } + clean::SelfExplicit(ref typ) => { + args.push_str(&format!("self: {}", *typ)); + } + } + for (i, input) in d.inputs.values.iter().enumerate() { + if i > 0 || !args.is_empty() { args.push_str(", "); } + if !input.name.is_empty() { + args.push_str(&format!("{}: ", input.name)); + } + args.push_str(&format!("{}", input.type_)); + } + write!(f, "({args}){arrow}", args = args, arrow = d.output) + } +}*/ + +impl<'a> AsciiDisplay for Method<'a> { + fn get_display(&self) -> String { + let Method(selfty, d) = *self; + let mut args = String::new(); + match *selfty { + clean::SelfStatic => {}, + clean::SelfValue => args.push_str("self"), + clean::SelfBorrowed(Some(ref lt), mtbl) => { + args.push_str(&format!("&{} {}self", *lt, MutableSpace(mtbl).get_display())); + } + clean::SelfBorrowed(None, mtbl) => { + args.push_str(&format!("&{}self", MutableSpace(mtbl).get_display())); + } + clean::SelfExplicit(ref typ) => { + args.push_str(&format!("self: {}", (*typ).get_display())); + } + } + for (i, input) in d.inputs.values.iter().enumerate() { + if i > 0 || !args.is_empty() { args.push_str(", "); } + if !input.name.is_empty() { + args.push_str(&format!("{}: ", input.name)); + } + args.push_str(&format!("{}", input.type_.get_display())); + } + /*write!(f, */format!("({args}){arrow}", args = args, arrow = d.output.get_display()) + } +} + +/*impl fmt::Display for clean::Arguments { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, input) in self.values.iter().enumerate() { + if i > 0 { try!(write!(f, ", ")); } + if !input.name.is_empty() { + try!(write!(f, "{}: ", input.name)); + } + try!(write!(f, "{}", input.type_)); + } + Ok(()) + } +}*/ + +impl AsciiDisplay for clean::Arguments { + fn get_display(&self) -> String { + let mut res = String::new(); + + for (i, input) in self.values.iter().enumerate() { + if i > 0 { + //try!(write!(f, ", ")); + res.push_str(", "); + } + if !input.name.is_empty() { + //try!(write!(f, "{}: ", input.name)); + res.push_str(&format!("{}: ", input.name)); + } + //try!(write!(f, "{}", input.type_)); + res.push_str(&format!("{} ", input.type_.get_display())); + } + res + } +} + +/*impl fmt::Display for MutableSpace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + MutableSpace(clean::Immutable) => Ok(()), + MutableSpace(clean::Mutable) => write!(f, "mut "), + } + } +}*/ + +impl AsciiDisplay for MutableSpace { + fn get_display(&self) -> String { + match *self { + MutableSpace(clean::Immutable) => String::new(), + MutableSpace(clean::Mutable) => /*write!(f, "mut ")*/"mut ".to_owned(), + } + } +} + +/*impl fmt::Display for clean::FunctionRetTy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + clean::Return(clean::Tuple(ref tys)) if tys.is_empty() => Ok(()), + clean::Return(ref ty) => write!(f, " -> {}", ty), + clean::DefaultReturn => Ok(()), + clean::NoReturn => write!(f, " -> !") + } + } +}*/ + +impl AsciiDisplay for clean::FunctionRetTy { + fn get_display(&self) -> String { + match *self { + clean::Return(clean::Tuple(ref tys)) if tys.is_empty() => String::new(), + clean::Return(ref ty) => /*write!(f, */format!(" -> {}", ty.get_display()), + clean::DefaultReturn => String::new(), + clean::NoReturn => /*write!(f, " -> !")*/" - > !".to_owned() + } + } +} + +/*impl fmt::Display for clean::FnDecl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({args}){arrow}", args = self.inputs, arrow = self.output) + } +}*/ + +impl AsciiDisplay for clean::FnDecl { + fn get_display(&self) -> String { + //write!(f, "({args}){arrow}", args = self.inputs, arrow = self.output) + format!("({args}){arrow}", args = self.inputs, arrow = self.output.get_display()) + } +} + +/*impl fmt::Display for VisSpace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.get() { + Some(ast::Public) => write!(f, "pub "), + Some(ast::Inherited) | None => Ok(()) + } + } +}*/ + +impl AsciiDisplay for VisSpace { + fn get_display(&self) -> String { + match self.get() { + Some(ast::Public) => /*write!(f, "pub ")*/"pub ".to_owned(), + Some(ast::Inherited) | None => String::new() + } + } +} + +/*impl fmt::Display for UnsafetySpace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.get() { + ast::Unsafety::Unsafe => write!(f, "unsafe "), + ast::Unsafety::Normal => Ok(()) + } + } +}*/ + +impl AsciiDisplay for UnsafetySpace { + fn get_display(&self) -> String { + match self.get() { + ast::Unsafety::Unsafe => /*write!(f, "unsafe ")*/"unsafe ".to_owned(), + ast::Unsafety::Normal => String::new() + } + } +} + +/*impl fmt::Display for ConstnessSpace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.get() { + ast::Constness::Const => write!(f, "const "), + ast::Constness::NotConst => Ok(()) + } + } +}*/ + +impl AsciiDisplay for ConstnessSpace { + fn get_display(&self) -> String { + match self.get() { + ast::Constness::Const => /*write!(f, "const ")*/"const ".to_owned(), + ast::Constness::NotConst => String::new() + } + } +} + +/*impl fmt::Display for clean::Import { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + clean::SimpleImport(ref name, ref src) => { + if *name == src.path.segments.last().unwrap().name { + write!(f, "use {};", *src) + } else { + write!(f, "use {} as {};", *src, *name) + } + } + clean::GlobImport(ref src) => { + write!(f, "use {}::*;", *src) + } + clean::ImportList(ref src, ref names) => { + try!(write!(f, "use {}::{{", *src)); + for (i, n) in names.iter().enumerate() { + if i > 0 { + try!(write!(f, ", ")); + } + try!(write!(f, "{}", *n)); + } + write!(f, "}};") + } + } + } +}*/ + +impl AsciiDisplay for clean::Import { + fn get_display(&self) -> String { + match *self { + clean::SimpleImport(ref name, ref src) => { + if *name == src.path.segments.last().unwrap().name { + //write!(f, "use {};", *src) + format!("use {}", *src) + } else { + //write!(f, "use {} as {};", *src, *name) + format!("use {} as {};", *src, *name) + } + } + clean::GlobImport(ref src) => { + //write!(f, "use {}::*;", *src) + format!("use {}::*;", *src) + } + clean::ImportList(ref src, ref names) => { + //try!(write!(f, "use {}::{{", *src)); + let mut res = format!("use {}::{{", *src); + + for (i, n) in names.iter().enumerate() { + if i > 0 { + //try!(write!(f, ", ")); + res.push_str(", "); + } + //try!(write!(f, "{}", *n)); + res.push_str(&format!("{}", *n)); + } + //write!(f, "}};") + res.push_str("};"); + res + } + } + } +} + +/*impl fmt::Display for clean::ImportSource { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.did { + Some(did) => resolved_path(f, did, &self.path, true), + _ => { + for (i, seg) in self.path.segments.iter().enumerate() { + if i > 0 { + try!(write!(f, "::")) + } + try!(write!(f, "{}", seg.name)); + } + Ok(()) + } + } + } +}*/ + +impl AsciiDisplay for clean::ImportSource { + fn get_display(&self) -> String { + match self.did { + Some(did) => resolved_path(did, &self.path, true), + _ => { + let mut res = String::new(); + + for (i, seg) in self.path.segments.iter().enumerate() { + if i > 0 { + //try!(write!(f, "::")) + res.push_str("::"); + } + //try!(write!(f, "{}", seg.name)); + res.push_str(&format!("{}", seg.name)); + } + res + } + } + } +} + +/*impl fmt::Display for clean::ViewListIdent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.source { + Some(did) => { + let path = clean::Path::singleton(self.name.clone()); + resolved_path(f, did, &path, false) + } + _ => write!(f, "{}", self.name), + } + } +}*/ + +impl AsciiDisplay for clean::ViewListIdent { + fn get_display(&self) -> String { + match self.source { + Some(did) => { + let path = clean::Path::singleton(self.name.clone()); + resolved_path(did, &path, false) + } + _ => /*write!(f, */format!("{}", self.name), + } + } +} + +/*impl fmt::Display for clean::TypeBinding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}={}", self.name, self.ty) + } +}*/ + +impl AsciiDisplay for clean::TypeBinding { + fn get_display(&self) -> String { + /*write!(f, */format!("{}={}", self.name, self.ty.get_display()) + } +} + +/*impl fmt::Display for RawMutableSpace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + RawMutableSpace(clean::Immutable) => write!(f, "const "), + RawMutableSpace(clean::Mutable) => write!(f, "mut "), + } + } +}*/ + +impl AsciiDisplay for RawMutableSpace { + fn get_display(&self) -> String { + match *self { + RawMutableSpace(clean::Immutable) => /*write!(f, "const ")*/"const ".to_owned(), + RawMutableSpace(clean::Mutable) => /*write!(f, "mut ")*/"mut ".to_owned(), + } + } +} + +/*impl fmt::Display for AbiSpace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Abi::Rust => Ok(()), + Abi::C => write!(f, "extern "), + abi => write!(f, "extern {} ", abi), + } + } +}*/ + +impl AsciiDisplay for AbiSpace { + fn get_display(&self) -> String { + match self.0 { + Abi::Rust => String::new(), + Abi::C => /*write!(f, "extern ")*/"extern ".to_owned(), + abi => /*write!(f, */format!("extern {} ", abi), + } + } +} \ No newline at end of file diff --git a/src/librustdoc/ascii/highlight.rs b/src/librustdoc/ascii/highlight.rs new file mode 100644 index 0000000000000..3264f21eb82df --- /dev/null +++ b/src/librustdoc/ascii/highlight.rs @@ -0,0 +1,197 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Basic html highlighting functionality +//! +//! This module uses libsyntax's lexer to provide token-based highlighting for +//! the HTML documentation generated by rustdoc. + +use std::io; +use std::io::prelude::*; +use syntax::parse::lexer; +use syntax::parse::token; +use syntax::parse; + +const RED : &'static str = "\x1b[31m"; +const GREEN : &'static str = "\x1b[32m"; +const BROWN : &'static str = "\x1b[33m"; +const BLUE : &'static str = "\x1b[34m"; +const MAGENTA : &'static str = "\x1b[35m"; +const CYAN : &'static str = "\x1b[36m"; +const WHITE : &'static str = "\x1b[37m"; +const NORMAL : &'static str = "\x1b[0m"; + +/// Highlights some source code, returning the HTML output. +pub fn highlight(src: &str, class: Option<&str>, id: Option<&str>) -> String { + debug!("highlighting: ================\n{}\n==============", src); + let sess = parse::ParseSess::new(); + let fm = sess.codemap().new_filemap("".to_string(), src.to_string()); + + let mut out = Vec::new(); + doit(&sess, + lexer::StringReader::new(&sess.span_diagnostic, fm), + class, + id, + &mut out).unwrap(); + String::from_utf8_lossy(&out[..]).into_owned() +} + +/// Exhausts the `lexer` writing the output into `out`. +/// +/// The general structure for this method is to iterate over each token, +/// possibly giving it an HTML span with a class specifying what flavor of token +/// it's used. All source code emission is done as slices from the source map, +/// not from the tokens themselves, in order to stay true to the original +/// source. +fn doit(sess: &parse::ParseSess, mut lexer: lexer::StringReader, + class: Option<&str>, id: Option<&str>, + out: &mut Write) -> io::Result<()> { + use syntax::parse::lexer::Reader; + + //try!(write!(out, "
 try!(write!(out, "id='{}' ", id)),
+        None => {}
+    }*/
+    //try!(write!(out, "class='rust {}'>\n", class.unwrap_or("")));
+    let mut is_attribute = false;
+    let mut is_macro = false;
+    let mut is_macro_nonterminal = false;
+    loop {
+        let next = lexer.next_token();
+
+        let snip = |sp| sess.codemap().span_to_snippet(sp).unwrap();
+
+        if next.tok == token::Eof { break }
+
+        let klass = match next.tok {
+            token::Whitespace => {
+                try!(write!(out, "{}", &snip(next.sp)));
+                continue
+            },
+            token::Comment => {
+                try!(write!(out, "{}{}{}",
+                            GREEN, &snip(next.sp), NORMAL));
+                continue
+            },
+            token::Shebang(s) => {
+                try!(write!(out, "{}", s.as_str()));
+                continue
+            },
+            // If this '&' token is directly adjacent to another token, assume
+            // that it's the address-of operator instead of the and-operator.
+            // This allows us to give all pointers their own class (`Box` and
+            // `@` are below).
+            token::BinOp(token::And) if lexer.peek().sp.lo == next.sp.hi => RED,//"kw-2",
+            token::At | token::Tilde => RED,//"kw-2",
+
+            // consider this as part of a macro invocation if there was a
+            // leading identifier
+            token::Not if is_macro => { is_macro = false; BLUE/*"macro"*/ }
+
+            // operators
+            token::Eq | token::Lt | token::Le | token::EqEq | token::Ne | token::Ge | token::Gt |
+                token::AndAnd | token::OrOr | token::Not | token::BinOp(..) | token::RArrow |
+                token::BinOpEq(..) | token::FatArrow => BLUE,//"op",
+
+            // miscellaneous, no highlighting
+            token::Dot | token::DotDot | token::DotDotDot | token::Comma | token::Semi |
+                token::Colon | token::ModSep | token::LArrow | token::OpenDelim(_) |
+                token::CloseDelim(token::Brace) | token::CloseDelim(token::Paren) |
+                token::Question => "",
+            token::Dollar => {
+                if lexer.peek().tok.is_ident() {
+                    is_macro_nonterminal = true;
+                    "macro-nonterminal"
+                } else {
+                    ""
+                }
+            }
+
+            // This is the start of an attribute. We're going to want to
+            // continue highlighting it as an attribute until the ending ']' is
+            // seen, so skip out early. Down below we terminate the attribute
+            // span when we see the ']'.
+            token::Pound => {
+                is_attribute = true;
+                try!(write!(out, "\x1b[32m#"/*r"#"*/));
+                continue
+            }
+            token::CloseDelim(token::Bracket) => {
+                if is_attribute {
+                    is_attribute = false;
+                    try!(write!(out, "]\x1b[0m"/*"["*/));
+                    continue
+                } else {
+                    ""
+                }
+            }
+
+            token::Literal(lit, _suf) => {
+                match lit {
+                    // text literals
+                    token::Byte(..) | token::Char(..) |
+                        token::Binary(..) | token::BinaryRaw(..) |
+                        token::Str_(..) | token::StrRaw(..) => GREEN,//"string",
+
+                    // number literals
+                    token::Integer(..) | token::Float(..) => RED,//"number",
+                }
+            }
+
+            // keywords are also included in the identifier set
+            token::Ident(ident, _is_mod_sep) => {
+                match &token::get_ident(ident)[..] {
+                    "ref" | "mut" => RED,//"kw-2",
+
+                    "self" => "self",
+                    "false" | "true" => GREEN,//"boolval",
+
+                    "Option" | "Result" => "prelude-ty",
+                    "Some" | "None" | "Ok" | "Err" => BROWN,//"prelude-val",
+
+                    _ if next.tok.is_any_keyword() => CYAN,//"kw",
+                    _ => {
+                        if is_macro_nonterminal {
+                            is_macro_nonterminal = false;
+                            MAGENTA//"macro-nonterminal"
+                        } else if lexer.peek().tok == token::Not {
+                            is_macro = true;
+                            MAGENTA//"macro"
+                        } else {
+                            MAGENTA//"ident"
+                        }
+                    }
+                }
+            }
+
+            // Special macro vars are like keywords
+            token::SpecialVarNt(_) => RED,//"kw-2",
+
+            token::Lifetime(..) => BLUE,//"lifetime",
+            token::DocComment(..) => BLUE,//"doccomment",
+            token::Underscore | token::Eof | token::Interpolated(..) |
+                token::MatchNt(..) | token::SubstNt(..) => "",
+        };
+
+        // as mentioned above, use the original source code instead of
+        // stringifying this token
+        let snip = sess.codemap().span_to_snippet(next.sp).unwrap();
+        if klass == "" {
+            try!(write!(out, "{}", &snip));
+        } else {
+            try!(write!(out, "{}{}{}", klass,
+                          &snip, NORMAL));
+        }
+    }
+
+    //write!(out, "
\n") + write!(out, "\n") +} diff --git a/src/librustdoc/ascii/item_type.rs b/src/librustdoc/ascii/item_type.rs new file mode 100644 index 0000000000000..d2010a60e6a0e --- /dev/null +++ b/src/librustdoc/ascii/item_type.rs @@ -0,0 +1,116 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Item types. + +use std::fmt; +use clean; + +/// Item type. Corresponds to `clean::ItemEnum` variants. +/// +/// The search index uses item types encoded as smaller numbers which equal to +/// discriminants. JavaScript then is used to decode them into the original value. +/// Consequently, every change to this type should be synchronized to +/// the `itemTypes` mapping table in `static/main.js`. +#[derive(Copy, PartialEq, Clone, Debug)] +pub enum ItemType { + Module = 0, + ExternCrate = 1, + Import = 2, + Struct = 3, + Enum = 4, + Function = 5, + Typedef = 6, + Static = 7, + Trait = 8, + Impl = 9, + TyMethod = 10, + Method = 11, + StructField = 12, + Variant = 13, + Macro = 14, + Primitive = 15, + AssociatedType = 16, + Constant = 17, + AssociatedConst = 18, +} + +impl ItemType { + pub fn from_item(item: &clean::Item) -> ItemType { + match item.inner { + clean::ModuleItem(..) => ItemType::Module, + clean::ExternCrateItem(..) => ItemType::ExternCrate, + clean::ImportItem(..) => ItemType::Import, + clean::StructItem(..) => ItemType::Struct, + clean::EnumItem(..) => ItemType::Enum, + clean::FunctionItem(..) => ItemType::Function, + clean::TypedefItem(..) => ItemType::Typedef, + clean::StaticItem(..) => ItemType::Static, + clean::ConstantItem(..) => ItemType::Constant, + clean::TraitItem(..) => ItemType::Trait, + clean::ImplItem(..) => ItemType::Impl, + clean::TyMethodItem(..) => ItemType::TyMethod, + clean::MethodItem(..) => ItemType::Method, + clean::StructFieldItem(..) => ItemType::StructField, + clean::VariantItem(..) => ItemType::Variant, + clean::ForeignFunctionItem(..) => ItemType::Function, // no ForeignFunction + clean::ForeignStaticItem(..) => ItemType::Static, // no ForeignStatic + clean::MacroItem(..) => ItemType::Macro, + clean::PrimitiveItem(..) => ItemType::Primitive, + clean::AssociatedConstItem(..) => ItemType::AssociatedConst, + clean::AssociatedTypeItem(..) => ItemType::AssociatedType, + clean::DefaultImplItem(..) => ItemType::Impl, + } + } + + pub fn from_type_kind(kind: clean::TypeKind) -> ItemType { + match kind { + clean::TypeStruct => ItemType::Struct, + clean::TypeEnum => ItemType::Enum, + clean::TypeFunction => ItemType::Function, + clean::TypeTrait => ItemType::Trait, + clean::TypeModule => ItemType::Module, + clean::TypeStatic => ItemType::Static, + clean::TypeConst => ItemType::Constant, + clean::TypeVariant => ItemType::Variant, + clean::TypeTypedef => ItemType::Typedef, + } + } + + pub fn to_static_str(&self) -> &'static str { + match *self { + ItemType::Module => "mod", + ItemType::ExternCrate => "externcrate", + ItemType::Import => "import", + ItemType::Struct => "struct", + ItemType::Enum => "enum", + ItemType::Function => "fn", + ItemType::Typedef => "type", + ItemType::Static => "static", + ItemType::Trait => "trait", + ItemType::Impl => "impl", + ItemType::TyMethod => "tymethod", + ItemType::Method => "method", + ItemType::StructField => "structfield", + ItemType::Variant => "variant", + ItemType::Macro => "macro", + ItemType::Primitive => "primitive", + ItemType::AssociatedType => "associatedtype", + ItemType::Constant => "constant", + ItemType::AssociatedConst => "associatedconstant", + } + } +} + +impl fmt::Display for ItemType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_static_str().fmt(f) + } +} diff --git a/src/librustdoc/ascii/layout.rs b/src/librustdoc/ascii/layout.rs new file mode 100644 index 0000000000000..632a6926228c2 --- /dev/null +++ b/src/librustdoc/ascii/layout.rs @@ -0,0 +1,57 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt; +use std::io::prelude::*; +use std::io; + +use externalfiles::ExternalHtml; + +#[derive(Clone)] +pub struct Layout { + pub logo: String, + pub favicon: String, + pub external_html: ExternalHtml, + pub krate: String, + pub playground_url: String, +} + +pub struct Page<'a> { + pub title: &'a str, + pub ty: &'a str, + pub root_path: &'a str, + pub description: &'a str, + pub keywords: &'a str +} + +pub fn render( + dst: &mut io::Write, layout: &Layout, page: &Page, sidebar: &S, t: &T) + -> io::Result<()> +{ + write!(dst, "{}", *t) +} + +pub fn redirect(dst: &mut io::Write, url: &str) -> io::Result<()> { + // + +"##, + url = url, + )*/ + Ok(()) +} diff --git a/src/librustdoc/ascii/mod.rs b/src/librustdoc/ascii/mod.rs new file mode 100644 index 0000000000000..48529434631e7 --- /dev/null +++ b/src/librustdoc/ascii/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub mod highlight; +pub mod item_type; +pub mod format; +pub mod layout; +pub mod render; +pub mod toc; \ No newline at end of file diff --git a/src/librustdoc/ascii/render.rs b/src/librustdoc/ascii/render.rs new file mode 100644 index 0000000000000..0ea374348fdb0 --- /dev/null +++ b/src/librustdoc/ascii/render.rs @@ -0,0 +1,2710 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Rustdoc's HTML Rendering module +//! +//! This modules contains the bulk of the logic necessary for rendering a +//! rustdoc `clean::Crate` instance to a set of static HTML pages. This +//! rendering process is largely driven by the `format!` syntax extension to +//! perform all I/O into files and streams. +//! +//! The rendering process is largely driven by the `Context` and `Cache` +//! structures. The cache is pre-populated by crawling the crate in question, +//! and then it is shared among the various rendering threads. The cache is meant +//! to be a fairly large structure not implementing `Clone` (because it's shared +//! among threads). The context, however, should be a lightweight structure. This +//! is cloned per-thread and contains information about what is currently being +//! rendered. +//! +//! In order to speed up rendering (mostly because of markdown rendering), the +//! rendering process has been parallelized. This parallelization is only +//! exposed through the `crate` method on the context, and then also from the +//! fact that the shared cache is stored in TLS (and must be accessed as such). +//! +//! In addition to rendering the crate itself, this module is also responsible +//! for creating the corresponding search index and source file renderings. +//! These threads are not parallelized (they haven't been a bottleneck yet), and +//! both occur before the crate is rendered. +pub use self::ExternalLocation::*; + +use std::ascii::OwnedAsciiExt; +use std::cell::RefCell; +use std::cmp::Ordering; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::default::Default; +use std::fmt; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::io::{self, BufWriter, BufReader}; +use std::iter::repeat; +use std::mem; +use std::path::{PathBuf, Path}; +use std::str; +use std::sync::Arc; + +use externalfiles::ExternalHtml; + +use serialize::json::{self, ToJson}; +use syntax::{abi, ast, ast_util, attr}; +use rustc::util::nodemap::NodeSet; + +use clean::{self, SelfTy}; +use doctree; +use fold::DocFolder; +use ascii::format::{ConstnessSpace}; +use ascii::format::{TyParamBounds, WhereClause, href, AbiSpace}; +use ascii::format::{VisSpace, Method, UnsafetySpace, MutableSpace, AsciiDisplay}; +use ascii::item_type::ItemType; +use html::markdown::{self, Markdown}; +use ascii::{highlight, layout}; + +const RED : &'static str = "\x1b[31m"; +const GREEN : &'static str = "\x1b[32m"; +const BROWN : &'static str = "\x1b[33m"; +const BLUE : &'static str = "\x1b[34m"; +const MAGENTA : &'static str = "\x1b[35m"; +const CYAN : &'static str = "\x1b[36m"; +const WHITE : &'static str = "\x1b[37m"; +const NORMAL : &'static str = "\x1b[0m"; + +/// A pair of name and its optional document. +pub type NameDoc = (String, Option); + +/// Major driving force in all rustdoc rendering. This contains information +/// about where in the tree-like hierarchy rendering is occurring and controls +/// how the current page is being rendered. +/// +/// It is intended that this context is a lightweight object which can be fairly +/// easily cloned because it is cloned per work-job (about once per item in the +/// rustdoc tree). +#[derive(Clone)] +pub struct Context { + /// Current hierarchy of components leading down to what's currently being + /// rendered + pub current: Vec, + /// String representation of how to get back to the root path of the 'doc/' + /// folder in terms of a relative URL. + pub root_path: String, + /// The path to the crate root source minus the file name. + /// Used for simplifying paths to the highlighted source code files. + pub src_root: PathBuf, + /// The current destination folder of where HTML artifacts should be placed. + /// This changes as the context descends into the module hierarchy. + pub dst: PathBuf, + /// This describes the layout of each page, and is not modified after + /// creation of the context (contains info like the favicon and added html). + pub layout: layout::Layout, + /// This flag indicates whether [src] links should be generated or not. If + /// the source files are present in the html rendering, then this will be + /// `true`. + pub include_sources: bool, + /// A flag, which when turned off, will render pages which redirect to the + /// real location of an item. This is used to allow external links to + /// publicly reused items to redirect to the right location. + pub render_redirect_pages: bool, + /// All the passes that were run on this crate. + pub passes: HashSet, +} + +/// Indicates where an external crate can be found. +pub enum ExternalLocation { + /// Remote URL root of the external crate + Remote(String), + /// This external crate can be found in the local doc/ folder + Local, + /// The external crate could not be found. + Unknown, +} + +/// Metadata about an implementor of a trait. +pub struct Implementor { + pub def_id: ast::DefId, + pub generics: clean::Generics, + pub trait_: clean::Type, + pub for_: clean::Type, + pub stability: Option, + pub polarity: Option, +} + +/// Metadata about implementations for a type. +#[derive(Clone)] +pub struct Impl { + pub impl_: clean::Impl, + pub dox: Option, + pub stability: Option, +} + +impl Impl { + fn trait_did(&self) -> Option { + self.impl_.trait_.as_ref().and_then(|tr| { + if let clean::ResolvedPath { did, .. } = *tr {Some(did)} else {None} + }) + } +} + +/// This cache is used to store information about the `clean::Crate` being +/// rendered in order to provide more useful documentation. This contains +/// information like all implementors of a trait, all traits a type implements, +/// documentation for all known traits, etc. +/// +/// This structure purposefully does not implement `Clone` because it's intended +/// to be a fairly large and expensive structure to clone. Instead this adheres +/// to `Send` so it may be stored in a `Arc` instance and shared among the various +/// rendering threads. +#[derive(Default)] +pub struct Cache { + /// Mapping of typaram ids to the name of the type parameter. This is used + /// when pretty-printing a type (so pretty printing doesn't have to + /// painfully maintain a context like this) + pub typarams: HashMap, + + /// Maps a type id to all known implementations for that type. This is only + /// recognized for intra-crate `ResolvedPath` types, and is used to print + /// out extra documentation on the page of an enum/struct. + /// + /// The values of the map are a list of implementations and documentation + /// found on that implementation. + pub impls: HashMap>, + + /// Maintains a mapping of local crate node ids to the fully qualified name + /// and "short type description" of that node. This is used when generating + /// URLs when a type is being linked to. External paths are not located in + /// this map because the `External` type itself has all the information + /// necessary. + pub paths: HashMap, ItemType)>, + + /// Similar to `paths`, but only holds external paths. This is only used for + /// generating explicit hyperlinks to other crates. + pub external_paths: HashMap>, + + /// This map contains information about all known traits of this crate. + /// Implementations of a crate should inherit the documentation of the + /// parent trait if no extra documentation is specified, and default methods + /// should show up in documentation about trait implementations. + pub traits: HashMap, + + /// When rendering traits, it's often useful to be able to list all + /// implementors of the trait, and this mapping is exactly, that: a mapping + /// of trait ids to the list of known implementors of the trait + pub implementors: HashMap>, + + /// Cache of where external crate documentation can be found. + pub extern_locations: HashMap, + + /// Cache of where documentation for primitives can be found. + pub primitive_locations: HashMap, + + /// Set of definitions which have been inlined from external crates. + pub inlined: HashSet, + + // Private fields only used when initially crawling a crate to build a cache + + stack: Vec, + parent_stack: Vec, + search_index: Vec, + privmod: bool, + remove_priv: bool, + public_items: NodeSet, + deref_trait_did: Option, + + // In rare case where a structure is defined in one module but implemented + // in another, if the implementing module is parsed before defining module, + // then the fully qualified name of the structure isn't presented in `paths` + // yet when its implementation methods are being indexed. Caches such methods + // and their parent id here and indexes them at the end of crate parsing. + orphan_methods: Vec<(ast::NodeId, clean::Item)>, +} + +/// Helper struct to render all source code to HTML pages +struct SourceCollector<'a> { + cx: &'a mut Context, + + /// Processed source-file paths + seen: HashSet, + /// Root destination to place all HTML output into + dst: PathBuf, +} + +/// Wrapper struct to render the source code of a file. This will do things like +/// adding line numbers to the left-hand side. +struct Source<'a>(&'a str); + +// Helper structs for rendering items/sidebars and carrying along contextual +// information + +#[derive(Copy, Clone)] +struct Item<'a> { + cx: &'a Context, + item: &'a clean::Item, +} + +struct Sidebar<'a> { cx: &'a Context, item: &'a clean::Item, } + +/// Struct representing one entry in the JS search index. These are all emitted +/// by hand to a large JS file at the end of cache-creation. +struct IndexItem { + ty: ItemType, + name: String, + path: String, + desc: String, + parent: Option, + search_type: Option, +} + +/// A type used for the search index. +struct Type { + name: Option, +} + +impl fmt::Display for Type { + /// Formats type as {name: $name}. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Wrapping struct fmt should never call us when self.name is None, + // but just to be safe we write `null` in that case. + match self.name { + Some(ref n) => write!(f, "{{\"name\":\"{}\"}}", n), + None => write!(f, "null") + } + } +} + +/// Full type of functions/methods in the search index. +struct IndexItemFunctionType { + inputs: Vec, + output: Option +} + +impl fmt::Display for IndexItemFunctionType { + /// Formats a full fn type as a JSON {inputs: [Type], outputs: Type/null}. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // If we couldn't figure out a type, just write `null`. + if self.inputs.iter().any(|ref i| i.name.is_none()) || + (self.output.is_some() && self.output.as_ref().unwrap().name.is_none()) { + return write!(f, "null") + } + + let inputs: Vec = self.inputs.iter().map(|ref t| { + format!("{}", t) + }).collect(); + try!(write!(f, "{{\"inputs\":[{}],\"output\":", inputs.connect(","))); + + match self.output { + Some(ref t) => try!(write!(f, "{}", t)), + None => try!(write!(f, "null")) + }; + + Ok(try!(write!(f, "}}"))) + } +} + +// TLS keys used to carry information around during rendering. + +thread_local!(static CACHE_KEY: RefCell> = Default::default()); +thread_local!(pub static CURRENT_LOCATION_KEY: RefCell> = + RefCell::new(Vec::new())); + +/// Generates the documentation for `crate` into the directory `dst` +pub fn run(mut krate: clean::Crate, + external_html: &ExternalHtml, + dst: PathBuf, + passes: HashSet) -> io::Result<()> { + let src_root = match krate.src.parent() { + Some(p) => p.to_path_buf(), + None => PathBuf::new(), + }; + let mut cx = Context { + dst: dst, + src_root: src_root, + passes: passes, + current: Vec::new(), + root_path: String::new(), + layout: layout::Layout { + logo: "".to_string(), + favicon: "".to_string(), + external_html: external_html.clone(), + krate: krate.name.clone(), + playground_url: "".to_string(), + }, + include_sources: true, + render_redirect_pages: false, + }; + + try!(mkdir(&cx.dst)); + + // Crawl the crate attributes looking for attributes which control how we're + // going to emit HTML + let default: &[_] = &[]; + match krate.module.as_ref().map(|m| m.doc_list().unwrap_or(default)) { + Some(attrs) => { + for attr in attrs { + match *attr { + clean::NameValue(ref x, ref s) + if "html_favicon_url" == *x => { + cx.layout.favicon = s.to_string(); + } + clean::NameValue(ref x, ref s) + if "html_logo_url" == *x => { + cx.layout.logo = s.to_string(); + } + clean::NameValue(ref x, ref s) + if "html_playground_url" == *x => { + cx.layout.playground_url = s.to_string(); + markdown::PLAYGROUND_KRATE.with(|slot| { + if slot.borrow().is_none() { + let name = krate.name.clone(); + *slot.borrow_mut() = Some(Some(name)); + } + }); + } + clean::Word(ref x) + if "html_no_source" == *x => { + cx.include_sources = false; + } + _ => {} + } + } + } + None => {} + } + + // Crawl the crate to build various caches used for the output + let analysis = ::ANALYSISKEY.with(|a| a.clone()); + let analysis = analysis.borrow(); + let public_items = analysis.as_ref().map(|a| a.public_items.clone()); + let public_items = public_items.unwrap_or(NodeSet()); + let paths: HashMap, ItemType)> = + analysis.as_ref().map(|a| { + let paths = a.external_paths.borrow_mut().take().unwrap(); + paths.into_iter().map(|(k, (v, t))| (k, (v, ItemType::from_type_kind(t)))).collect() + }).unwrap_or(HashMap::new()); + let mut cache = Cache { + impls: HashMap::new(), + external_paths: paths.iter().map(|(&k, v)| (k, v.0.clone())) + .collect(), + paths: paths, + implementors: HashMap::new(), + stack: Vec::new(), + parent_stack: Vec::new(), + search_index: Vec::new(), + extern_locations: HashMap::new(), + primitive_locations: HashMap::new(), + remove_priv: cx.passes.contains("strip-private"), + privmod: false, + public_items: public_items, + orphan_methods: Vec::new(), + traits: mem::replace(&mut krate.external_traits, HashMap::new()), + deref_trait_did: analysis.as_ref().and_then(|a| a.deref_trait_did), + typarams: analysis.as_ref().map(|a| { + a.external_typarams.borrow_mut().take().unwrap() + }).unwrap_or(HashMap::new()), + inlined: analysis.as_ref().map(|a| { + a.inlined.borrow_mut().take().unwrap() + }).unwrap_or(HashSet::new()), + }; + + // Cache where all our extern crates are located + for &(n, ref e) in &krate.externs { + cache.extern_locations.insert(n, (e.name.clone(), + extern_location(e, &cx.dst))); + let did = ast::DefId { krate: n, node: ast::CRATE_NODE_ID }; + cache.paths.insert(did, (vec![e.name.to_string()], ItemType::Module)); + } + + // Cache where all known primitives have their documentation located. + // + // Favor linking to as local extern as possible, so iterate all crates in + // reverse topological order. + for &(n, ref e) in krate.externs.iter().rev() { + for &prim in &e.primitives { + cache.primitive_locations.insert(prim, n); + } + } + for &prim in &krate.primitives { + cache.primitive_locations.insert(prim, ast::LOCAL_CRATE); + } + + cache.stack.push(krate.name.clone()); + krate = cache.fold_crate(krate); + + // Build our search index + let index = try!(build_index(&krate, &mut cache)); + + // Freeze the cache now that the index has been built. Put an Arc into TLS + // for future parallelization opportunities + let cache = Arc::new(cache); + CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); + CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear()); + + try!(write_shared(&cx, &krate, &*cache, index)); + let krate = try!(render_sources(&mut cx, krate)); + + // And finally render the whole crate's documentation + cx.krate(krate) +} + +fn build_index(krate: &clean::Crate, cache: &mut Cache) -> io::Result { + // Build the search index from the collected metadata + let mut nodeid_to_pathid = HashMap::new(); + let mut pathid_to_nodeid = Vec::new(); + { + let Cache { ref mut search_index, + ref orphan_methods, + ref mut paths, .. } = *cache; + + // Attach all orphan methods to the type's definition if the type + // has since been learned. + for &(pid, ref item) in orphan_methods { + let did = ast_util::local_def(pid); + match paths.get(&did) { + Some(&(ref fqp, _)) => { + search_index.push(IndexItem { + ty: shortty(item), + name: item.name.clone().unwrap(), + path: fqp[..fqp.len() - 1].connect("::"), + desc: shorter(item.doc_value()), + parent: Some(did), + search_type: None, + }); + }, + None => {} + } + }; + + // Reduce `NodeId` in paths into smaller sequential numbers, + // and prune the paths that do not appear in the index. + for item in &*search_index { + match item.parent { + Some(nodeid) => { + if !nodeid_to_pathid.contains_key(&nodeid) { + let pathid = pathid_to_nodeid.len(); + nodeid_to_pathid.insert(nodeid, pathid); + pathid_to_nodeid.push(nodeid); + } + } + None => {} + } + } + assert_eq!(nodeid_to_pathid.len(), pathid_to_nodeid.len()); + } + + // Collect the index into a string + let mut w = io::Cursor::new(Vec::new()); + try!(write!(&mut w, r#"searchIndex['{}'] = {{"items":["#, krate.name)); + + let mut lastpath = "".to_string(); + for (i, item) in cache.search_index.iter().enumerate() { + // Omit the path if it is same to that of the prior item. + let path; + if lastpath == item.path { + path = ""; + } else { + lastpath = item.path.to_string(); + path = &item.path; + }; + + if i > 0 { + try!(write!(&mut w, ",")); + } + try!(write!(&mut w, r#"[{},"{}","{}",{}"#, + item.ty as usize, item.name, path, + item.desc.to_json().to_string())); + match item.parent { + Some(nodeid) => { + let pathid = *nodeid_to_pathid.get(&nodeid).unwrap(); + try!(write!(&mut w, ",{}", pathid)); + } + None => try!(write!(&mut w, ",null")) + } + match item.search_type { + Some(ref t) => try!(write!(&mut w, ",{}", t)), + None => try!(write!(&mut w, ",null")) + } + try!(write!(&mut w, "]")); + } + + try!(write!(&mut w, r#"],"paths":["#)); + + for (i, &did) in pathid_to_nodeid.iter().enumerate() { + let &(ref fqp, short) = cache.paths.get(&did).unwrap(); + if i > 0 { + try!(write!(&mut w, ",")); + } + try!(write!(&mut w, r#"[{},"{}"]"#, + short as usize, *fqp.last().unwrap())); + } + + try!(write!(&mut w, "]}};")); + + Ok(String::from_utf8(w.into_inner()).unwrap()) +} + +fn write_shared(cx: &Context, + krate: &clean::Crate, + cache: &Cache, + search_index: String) -> io::Result<()> { + // Write out the shared files. Note that these are shared among all rustdoc + // docs placed in the output directory, so this needs to be a synchronized + // operation with respect to all other rustdocs running around. + try!(mkdir(&cx.dst)); + let _lock = ::flock::Lock::new(&cx.dst.join(".lock")); + + // Add all the static files. These may already exist, but we just + // overwrite them anyway to make sure that they're fresh and up-to-date. + /*try!(write(cx.dst.join("jquery.js"), + include_bytes!("static/jquery-2.1.0.min.js"))); + try!(write(cx.dst.join("main.js"), include_bytes!("static/main.js"))); + try!(write(cx.dst.join("playpen.js"), include_bytes!("static/playpen.js"))); + try!(write(cx.dst.join("main.css"), include_bytes!("static/main.css"))); + try!(write(cx.dst.join("normalize.css"), + include_bytes!("static/normalize.css"))); + try!(write(cx.dst.join("FiraSans-Regular.woff"), + include_bytes!("static/FiraSans-Regular.woff"))); + try!(write(cx.dst.join("FiraSans-Medium.woff"), + include_bytes!("static/FiraSans-Medium.woff"))); + try!(write(cx.dst.join("Heuristica-Italic.woff"), + include_bytes!("static/Heuristica-Italic.woff"))); + try!(write(cx.dst.join("SourceSerifPro-Regular.woff"), + include_bytes!("static/SourceSerifPro-Regular.woff"))); + try!(write(cx.dst.join("SourceSerifPro-Bold.woff"), + include_bytes!("static/SourceSerifPro-Bold.woff"))); + try!(write(cx.dst.join("SourceCodePro-Regular.woff"), + include_bytes!("static/SourceCodePro-Regular.woff"))); + try!(write(cx.dst.join("SourceCodePro-Semibold.woff"), + include_bytes!("static/SourceCodePro-Semibold.woff")));*/ + + fn collect(path: &Path, krate: &str, + key: &str) -> io::Result> { + let mut ret = Vec::new(); + if path.exists() { + for line in BufReader::new(try!(File::open(path))).lines() { + let line = try!(line); + if !line.starts_with(key) { + continue + } + if line.starts_with(&format!("{}['{}']", key, krate)) { + continue + } + ret.push(line.to_string()); + } + } + return Ok(ret); + } + + // Update the search index + let dst = cx.dst.join("search-index.js"); + let all_indexes = try!(collect(&dst, &krate.name, "searchIndex")); + let mut w = try!(File::create(&dst)); + try!(writeln!(&mut w, "var searchIndex = {{}};")); + try!(writeln!(&mut w, "{}", search_index)); + for index in &all_indexes { + try!(writeln!(&mut w, "{}", *index)); + } + try!(writeln!(&mut w, "initSearch(searchIndex);")); + + // Update the list of all implementors for traits + let dst = cx.dst.join("implementors"); + try!(mkdir(&dst)); + for (&did, imps) in &cache.implementors { + // Private modules can leak through to this phase of rustdoc, which + // could contain implementations for otherwise private types. In some + // rare cases we could find an implementation for an item which wasn't + // indexed, so we just skip this step in that case. + // + // FIXME: this is a vague explanation for why this can't be a `get`, in + // theory it should be... + let &(ref remote_path, remote_item_type) = match cache.paths.get(&did) { + Some(p) => p, + None => continue, + }; + + let mut mydst = dst.clone(); + for part in &remote_path[..remote_path.len() - 1] { + mydst.push(part); + try!(mkdir(&mydst)); + } + mydst.push(&format!("{}.{}.js", + remote_item_type.to_static_str(), + remote_path[remote_path.len() - 1])); + let all_implementors = try!(collect(&mydst, &krate.name, + "implementors")); + + try!(mkdir(mydst.parent().unwrap())); + let mut f = BufWriter::new(try!(File::create(&mydst))); + try!(writeln!(&mut f, "(function() {{var implementors = {{}};")); + + for implementor in &all_implementors { + try!(write!(&mut f, "{}", *implementor)); + } + + try!(write!(&mut f, r"implementors['{}'] = [", krate.name)); + for imp in imps { + // If the trait and implementation are in the same crate, then + // there's no need to emit information about it (there's inlining + // going on). If they're in different crates then the crate defining + // the trait will be interested in our implementation. + if imp.def_id.krate == did.krate { continue } + try!(write!(&mut f, r#""impl{} {}{} for {}","#, + imp.generics.get_display(), + if imp.polarity == Some(clean::ImplPolarity::Negative) { "!" } else { "" }, + imp.trait_.get_display(), imp.for_.get_display())); + } + try!(writeln!(&mut f, r"];")); + try!(writeln!(&mut f, "{}", r" + if (window.register_implementors) { + window.register_implementors(implementors); + } else { + window.pending_implementors = implementors; + } + ")); + try!(writeln!(&mut f, r"}})()")); + } + Ok(()) +} + +fn render_sources(cx: &mut Context, + krate: clean::Crate) -> io::Result { + info!("emitting source files"); + let dst = cx.dst.join("src"); + try!(mkdir(&dst)); + let dst = dst.join(&krate.name); + try!(mkdir(&dst)); + let mut folder = SourceCollector { + dst: dst, + seen: HashSet::new(), + cx: cx, + }; + // skip all invalid spans + folder.seen.insert("".to_string()); + Ok(folder.fold_crate(krate)) +} + +/// Writes the entire contents of a string to a destination, not attempting to +/// catch any errors. +fn write(dst: PathBuf, contents: &[u8]) -> io::Result<()> { + try!(File::create(&dst)).write_all(contents) +} + +/// Makes a directory on the filesystem, failing the thread if an error occurs and +/// skipping if the directory already exists. +fn mkdir(path: &Path) -> io::Result<()> { + if !path.exists() { + fs::create_dir(path) + } else { + Ok(()) + } +} + +/// Returns a documentation-level item type from the item. +fn shortty(item: &clean::Item) -> ItemType { + ItemType::from_item(item) +} + +/// Takes a path to a source file and cleans the path to it. This canonicalizes +/// things like ".." to components which preserve the "top down" hierarchy of a +/// static HTML tree. Each component in the cleaned path will be passed as an +/// argument to `f`. The very last component of the path (ie the file name) will +/// be passed to `f` if `keep_filename` is true, and ignored otherwise. +// FIXME (#9639): The closure should deal with &[u8] instead of &str +// FIXME (#9639): This is too conservative, rejecting non-UTF-8 paths +fn clean_srcpath(src_root: &Path, p: &Path, keep_filename: bool, mut f: F) where + F: FnMut(&str), +{ + // make it relative, if possible + let p = p.relative_from(src_root).unwrap_or(p); + + let mut iter = p.iter().map(|x| x.to_str().unwrap()).peekable(); + while let Some(c) = iter.next() { + if !keep_filename && iter.peek().is_none() { + break; + } + + if ".." == c { + f("up"); + } else { + f(c) + } + } +} + +/// Attempts to find where an external crate is located, given that we're +/// rendering in to the specified source destination. +fn extern_location(e: &clean::ExternalCrate, dst: &Path) -> ExternalLocation { + // See if there's documentation generated into the local directory + let local_location = dst.join(&e.name); + if local_location.is_dir() { + return Local; + } + + // Failing that, see if there's an attribute specifying where to find this + // external crate + for attr in &e.attrs { + match *attr { + clean::List(ref x, ref list) if "doc" == *x => { + for attr in list { + match *attr { + clean::NameValue(ref x, ref s) + if "html_root_url" == *x => { + if s.ends_with("/") { + return Remote(s.to_string()); + } + return Remote(format!("{}/", s)); + } + _ => {} + } + } + } + _ => {} + } + } + + // Well, at least we tried. + return Unknown; +} + +impl<'a> DocFolder for SourceCollector<'a> { + fn fold_item(&mut self, item: clean::Item) -> Option { + // If we're including source files, and we haven't seen this file yet, + // then we need to render it out to the filesystem + if self.cx.include_sources && !self.seen.contains(&item.source.filename) { + + // If it turns out that we couldn't read this file, then we probably + // can't read any of the files (generating html output from json or + // something like that), so just don't include sources for the + // entire crate. The other option is maintaining this mapping on a + // per-file basis, but that's probably not worth it... + self.cx + .include_sources = match self.emit_source(&item.source .filename) { + Ok(()) => true, + Err(e) => { + println!("warning: source code was requested to be rendered, \ + but processing `{}` had an error: {}", + item.source.filename, e); + println!(" skipping rendering of source code"); + false + } + }; + self.seen.insert(item.source.filename.clone()); + } + + self.fold_item_recur(item) + } +} + +impl<'a> SourceCollector<'a> { + /// Renders the given filename into its corresponding HTML source file. + fn emit_source(&mut self, filename: &str) -> io::Result<()> { + let p = PathBuf::from(filename); + + // If we couldn't open this file, then just returns because it + // probably means that it's some standard library macro thing and we + // can't have the source to it anyway. + let mut contents = Vec::new(); + match File::open(&p).and_then(|mut f| f.read_to_end(&mut contents)) { + Ok(r) => r, + // macros from other libraries get special filenames which we can + // safely ignore + Err(..) if filename.starts_with("<") && + filename.ends_with("macros>") => return Ok(()), + Err(e) => return Err(e) + }; + let contents = str::from_utf8(&contents).unwrap(); + + // Remove the utf-8 BOM if any + let contents = if contents.starts_with("\u{feff}") { + &contents[3..] + } else { + contents + }; + + // Create the intermediate directories + let mut cur = self.dst.clone(); + let mut root_path = String::from_str("../../"); + clean_srcpath(&self.cx.src_root, &p, false, |component| { + cur.push(component); + mkdir(&cur).unwrap(); + root_path.push_str("../"); + }); + + let mut fname = p.file_name().expect("source has no filename") + .to_os_string(); + fname.push(".ascii"); + cur.push(&fname[..]); + let mut w = BufWriter::new(try!(File::create(&cur))); + + let title = format!("{} -- source", cur.file_name().unwrap() + .to_string_lossy()); + let desc = format!("Source to the Rust file `{}`.", filename); + let page = layout::Page { + title: &title, + ty: "source", + root_path: &root_path, + description: &desc, + keywords: get_basic_keywords(), + }; + try!(layout::render(&mut w, &self.cx.layout, + &page, &(""), &Source(contents))); + try!(w.flush()); + return Ok(()); + } +} + +impl DocFolder for Cache { + fn fold_item(&mut self, item: clean::Item) -> Option { + // If this is a private module, we don't want it in the search index. + let orig_privmod = match item.inner { + clean::ModuleItem(..) => { + let prev = self.privmod; + self.privmod = prev || (self.remove_priv && item.visibility != Some(ast::Public)); + prev + } + _ => self.privmod, + }; + + // Register any generics to their corresponding string. This is used + // when pretty-printing types + match item.inner { + clean::StructItem(ref s) => self.generics(&s.generics), + clean::EnumItem(ref e) => self.generics(&e.generics), + clean::FunctionItem(ref f) => self.generics(&f.generics), + clean::TypedefItem(ref t, _) => self.generics(&t.generics), + clean::TraitItem(ref t) => self.generics(&t.generics), + clean::ImplItem(ref i) => self.generics(&i.generics), + clean::TyMethodItem(ref i) => self.generics(&i.generics), + clean::MethodItem(ref i) => self.generics(&i.generics), + clean::ForeignFunctionItem(ref f) => self.generics(&f.generics), + _ => {} + } + + // Propagate a trait methods' documentation to all implementors of the + // trait + if let clean::TraitItem(ref t) = item.inner { + self.traits.insert(item.def_id, t.clone()); + } + + // Collect all the implementors of traits. + if let clean::ImplItem(ref i) = item.inner { + match i.trait_ { + Some(clean::ResolvedPath{ did, .. }) => { + self.implementors.entry(did).or_insert(vec![]).push(Implementor { + def_id: item.def_id, + generics: i.generics.clone(), + trait_: i.trait_.as_ref().unwrap().clone(), + for_: i.for_.clone(), + stability: item.stability.clone(), + polarity: i.polarity.clone(), + }); + } + Some(..) | None => {} + } + } + + // Index this method for searching later on + if let Some(ref s) = item.name { + let (parent, is_method) = match item.inner { + clean::AssociatedTypeItem(..) | + clean::AssociatedConstItem(..) | + clean::TyMethodItem(..) | + clean::StructFieldItem(..) | + clean::VariantItem(..) => { + ((Some(*self.parent_stack.last().unwrap()), + Some(&self.stack[..self.stack.len() - 1])), + false) + } + clean::MethodItem(..) => { + if self.parent_stack.is_empty() { + ((None, None), false) + } else { + let last = self.parent_stack.last().unwrap(); + let did = *last; + let path = match self.paths.get(&did) { + Some(&(_, ItemType::Trait)) => + Some(&self.stack[..self.stack.len() - 1]), + // The current stack not necessarily has correlation + // for where the type was defined. On the other + // hand, `paths` always has the right + // information if present. + Some(&(ref fqp, ItemType::Struct)) | + Some(&(ref fqp, ItemType::Enum)) => + Some(&fqp[..fqp.len() - 1]), + Some(..) => Some(&*self.stack), + None => None + }; + ((Some(*last), path), true) + } + } + clean::TypedefItem(_, true) => { + // skip associated types in impls + ((None, None), false) + } + _ => ((None, Some(&*self.stack)), false) + }; + let hidden_field = match item.inner { + clean::StructFieldItem(clean::HiddenStructField) => true, + _ => false + }; + + match parent { + (parent, Some(path)) if is_method || (!self.privmod && !hidden_field) => { + // Needed to determine `self` type. + let parent_basename = self.parent_stack.first().and_then(|parent| { + match self.paths.get(parent) { + Some(&(ref fqp, _)) => Some(fqp[fqp.len() - 1].clone()), + _ => None + } + }); + + self.search_index.push(IndexItem { + ty: shortty(&item), + name: s.to_string(), + path: path.connect("::").to_string(), + desc: shorter(item.doc_value()), + parent: parent, + search_type: get_index_search_type(&item, parent_basename), + }); + } + (Some(parent), None) if is_method || (!self.privmod && !hidden_field)=> { + if ast_util::is_local(parent) { + // We have a parent, but we don't know where they're + // defined yet. Wait for later to index this item. + self.orphan_methods.push((parent.node, item.clone())) + } + } + _ => {} + } + } + + // Keep track of the fully qualified path for this item. + let pushed = if item.name.is_some() { + let n = item.name.as_ref().unwrap(); + if !n.is_empty() { + self.stack.push(n.to_string()); + true + } else { false } + } else { false }; + match item.inner { + clean::StructItem(..) | clean::EnumItem(..) | + clean::TypedefItem(..) | clean::TraitItem(..) | + clean::FunctionItem(..) | clean::ModuleItem(..) | + clean::ForeignFunctionItem(..) if !self.privmod => { + // Reexported items mean that the same id can show up twice + // in the rustdoc ast that we're looking at. We know, + // however, that a reexported item doesn't show up in the + // `public_items` map, so we can skip inserting into the + // paths map if there was already an entry present and we're + // not a public item. + let id = item.def_id.node; + if !self.paths.contains_key(&item.def_id) || + !ast_util::is_local(item.def_id) || + self.public_items.contains(&id) { + self.paths.insert(item.def_id, + (self.stack.clone(), shortty(&item))); + } + } + // link variants to their parent enum because pages aren't emitted + // for each variant + clean::VariantItem(..) if !self.privmod => { + let mut stack = self.stack.clone(); + stack.pop(); + self.paths.insert(item.def_id, (stack, ItemType::Enum)); + } + + clean::PrimitiveItem(..) if item.visibility.is_some() => { + self.paths.insert(item.def_id, (self.stack.clone(), + shortty(&item))); + } + + _ => {} + } + + // Maintain the parent stack + let parent_pushed = match item.inner { + clean::TraitItem(..) | clean::EnumItem(..) | clean::StructItem(..) => { + self.parent_stack.push(item.def_id); + true + } + clean::ImplItem(ref i) => { + match i.for_ { + clean::ResolvedPath{ did, .. } => { + self.parent_stack.push(did); + true + } + ref t => { + match t.primitive_type() { + Some(prim) => { + let did = ast_util::local_def(prim.to_node_id()); + self.parent_stack.push(did); + true + } + _ => false, + } + } + } + } + _ => false + }; + + // Once we've recursively found all the generics, then hoard off all the + // implementations elsewhere + let ret = match self.fold_item_recur(item) { + Some(item) => { + match item { + clean::Item{ attrs, inner: clean::ImplItem(i), .. } => { + // extract relevant documentation for this impl + let dox = match attrs.into_iter().find(|a| { + match *a { + clean::NameValue(ref x, _) + if "doc" == *x => { + true + } + _ => false + } + }) { + Some(clean::NameValue(_, dox)) => Some(dox), + Some(..) | None => None, + }; + + // Figure out the id of this impl. This may map to a + // primitive rather than always to a struct/enum. + let did = match i.for_ { + clean::ResolvedPath { did, .. } | + clean::BorrowedRef { + type_: box clean::ResolvedPath { did, .. }, .. + } => { + Some(did) + } + + ref t => { + t.primitive_type().and_then(|t| { + self.primitive_locations.get(&t).map(|n| { + let id = t.to_node_id(); + ast::DefId { krate: *n, node: id } + }) + }) + } + }; + + if let Some(did) = did { + self.impls.entry(did).or_insert(vec![]).push(Impl { + impl_: i, + dox: dox, + stability: item.stability.clone(), + }); + } + + None + } + + i => Some(i), + } + } + i => i, + }; + + if pushed { self.stack.pop().unwrap(); } + if parent_pushed { self.parent_stack.pop().unwrap(); } + self.privmod = orig_privmod; + return ret; + } +} + +impl<'a> Cache { + fn generics(&mut self, generics: &clean::Generics) { + for typ in &generics.type_params { + self.typarams.insert(typ.did, typ.name.clone()); + } + } +} + +impl Context { + /// Recurse in the directory structure and change the "root path" to make + /// sure it always points to the top (relatively) + fn recurse(&mut self, s: String, f: F) -> T where + F: FnOnce(&mut Context) -> T, + { + if s.is_empty() { + panic!("Unexpected empty destination: {:?}", self.current); + } + let prev = self.dst.clone(); + self.dst.push(&s); + self.root_path.push_str("../"); + self.current.push(s); + + info!("Recursing into {}", self.dst.display()); + + mkdir(&self.dst).unwrap(); + let ret = f(self); + + info!("Recursed; leaving {}", self.dst.display()); + + // Go back to where we were at + self.dst = prev; + let len = self.root_path.len(); + self.root_path.truncate(len - 3); + self.current.pop().unwrap(); + + return ret; + } + + /// Main method for rendering a crate. + /// + /// This currently isn't parallelized, but it'd be pretty easy to add + /// parallelization to this function. + fn krate(self, mut krate: clean::Crate) -> io::Result<()> { + let mut item = match krate.module.take() { + Some(i) => i, + None => return Ok(()) + }; + item.name = Some(krate.name); + + // render the crate documentation + let mut work = vec!((self, item)); + loop { + match work.pop() { + Some((mut cx, item)) => try!(cx.item(item, |cx, item| { + work.push((cx.clone(), item)); + })), + None => break, + } + } + + Ok(()) + } + + /// Non-parallelized version of rendering an item. This will take the input + /// item, render its contents, and then invoke the specified closure with + /// all sub-items which need to be rendered. + /// + /// The rendering driver uses this closure to queue up more work. + fn item(&mut self, item: clean::Item, mut f: F) -> io::Result<()> where + F: FnMut(&mut Context, clean::Item), + { + fn render(w: File, cx: &Context, it: &clean::Item, + pushname: bool) -> io::Result<()> { + // A little unfortunate that this is done like this, but it sure + // does make formatting *a lot* nicer. + CURRENT_LOCATION_KEY.with(|slot| { + *slot.borrow_mut() = cx.current.clone(); + }); + + let mut title = cx.current.connect("::"); + if pushname { + if !title.is_empty() { + title.push_str("::"); + } + title.push_str(it.name.as_ref().unwrap()); + } + title.push_str(" - Rust"); + let tyname = shortty(it).to_static_str(); + let is_crate = match it.inner { + clean::ModuleItem(clean::Module { items: _, is_crate: true }) => true, + _ => false + }; + let desc = if is_crate { + format!("API documentation for the Rust `{}` crate.", + cx.layout.krate) + } else { + format!("API documentation for the Rust `{}` {} in crate `{}`.", + it.name.as_ref().unwrap(), tyname, cx.layout.krate) + }; + let keywords = make_item_keywords(it); + let page = layout::Page { + ty: tyname, + root_path: &cx.root_path, + title: &title, + description: &desc, + keywords: &keywords, + }; + + markdown::reset_headers(); + + // We have a huge number of calls to write, so try to alleviate some + // of the pain by using a buffered writer instead of invoking the + // write syscall all the time. + let mut writer = BufWriter::new(w); + if !cx.render_redirect_pages { + try!(layout::render(&mut writer, &cx.layout, &page, + &Sidebar{ cx: cx, item: it }, + &Item{ cx: cx, item: it })); + } else { + let mut url = repeat("../").take(cx.current.len()) + .collect::(); + match cache().paths.get(&it.def_id) { + Some(&(ref names, _)) => { + for name in &names[..names.len() - 1] { + url.push_str(name); + url.push_str("/"); + } + url.push_str(&item_path(it)); + try!(layout::redirect(&mut writer, &url)); + } + None => {} + } + } + writer.flush() + } + + // Private modules may survive the strip-private pass if they + // contain impls for public types. These modules can also + // contain items such as publicly reexported structures. + // + // External crates will provide links to these structures, so + // these modules are recursed into, but not rendered normally (a + // flag on the context). + if !self.render_redirect_pages { + self.render_redirect_pages = self.ignore_private_item(&item); + } + + match item.inner { + // modules are special because they add a namespace. We also need to + // recurse into the items of the module as well. + clean::ModuleItem(..) => { + let name = item.name.as_ref().unwrap().to_string(); + let mut item = Some(item); + self.recurse(name, |this| { + let item = item.take().unwrap(); + let dst = this.dst.join("index.ascii"); + let dst = try!(File::create(&dst)); + try!(render(dst, this, &item, false)); + + let m = match item.inner { + clean::ModuleItem(m) => m, + _ => unreachable!() + }; + + // render sidebar-items.js used throughout this module + { + let items = this.build_sidebar_items(&m); + let js_dst = this.dst.join("sidebar-items.js"); + let mut js_out = BufWriter::new(try!(File::create(&js_dst))); + try!(write!(&mut js_out, "initSidebarItems({});", + json::as_json(&items))); + } + + for item in m.items { + f(this,item); + } + Ok(()) + }) + } + + // Things which don't have names (like impls) don't get special + // pages dedicated to them. + _ if item.name.is_some() => { + let dst = self.dst.join(&item_path(&item)); + let dst = try!(File::create(&dst)); + render(dst, self, &item, true) + } + + _ => Ok(()) + } + } + + fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap> { + // BTreeMap instead of HashMap to get a sorted output + let mut map = BTreeMap::new(); + for item in &m.items { + if self.ignore_private_item(item) { continue } + + let short = shortty(item).to_static_str(); + let myname = match item.name { + None => continue, + Some(ref s) => s.to_string(), + }; + let short = short.to_string(); + map.entry(short).or_insert(vec![]) + .push((myname, Some(plain_summary_line(item.doc_value())))); + } + + for (_, items) in &mut map { + items.sort(); + } + return map; + } + + fn ignore_private_item(&self, it: &clean::Item) -> bool { + match it.inner { + clean::ModuleItem(ref m) => { + (m.items.is_empty() && + it.doc_value().is_none() && + it.visibility != Some(ast::Public)) || + (self.passes.contains("strip-private") && it.visibility != Some(ast::Public)) + } + clean::PrimitiveItem(..) => it.visibility != Some(ast::Public), + _ => false, + } + } +} + +impl<'a> Item<'a> { + fn ismodule(&self) -> bool { + match self.item.inner { + clean::ModuleItem(..) => true, _ => false + } + } + + /// Generate a url appropriate for an `href` attribute back to the source of + /// this item. + /// + /// The url generated, when clicked, will redirect the browser back to the + /// original source code. + /// + /// If `None` is returned, then a source link couldn't be generated. This + /// may happen, for example, with externally inlined items where the source + /// of their crate documentation isn't known. + fn href(&self, cx: &Context) -> Option { + let href = if self.item.source.loline == self.item.source.hiline { + format!("{}", self.item.source.loline) + } else { + format!("{}-{}", self.item.source.loline, self.item.source.hiline) + }; + + // First check to see if this is an imported macro source. In this case + // we need to handle it specially as cross-crate inlined macros have... + // odd locations! + let imported_macro_from = match self.item.inner { + clean::MacroItem(ref m) => m.imported_from.as_ref(), + _ => None, + }; + if let Some(krate) = imported_macro_from { + let cache = cache(); + let root = cache.extern_locations.values().find(|&&(ref n, _)| { + *krate == *n + }).map(|l| &l.1); + let root = match root { + Some(&Remote(ref s)) => s.to_string(), + Some(&Local) => self.cx.root_path.clone(), + None | Some(&Unknown) => return None, + }; + Some(format!("{root}/{krate}/macro.{name}.ascii?gotomacrosrc=1", + root = root, + krate = krate, + name = self.item.name.as_ref().unwrap())) + + // If this item is part of the local crate, then we're guaranteed to + // know the span, so we plow forward and generate a proper url. The url + // has anchors for the line numbers that we're linking to. + } else if ast_util::is_local(self.item.def_id) { + let mut path = Vec::new(); + clean_srcpath(&cx.src_root, Path::new(&self.item.source.filename), + true, |component| { + path.push(component.to_string()); + }); + Some(format!("{root}src/{krate}/{path}.ascii#{href}", + root = self.cx.root_path, + krate = self.cx.layout.krate, + path = path.connect("/"), + href = href)) + + // If this item is not part of the local crate, then things get a little + // trickier. We don't actually know the span of the external item, but + // we know that the documentation on the other end knows the span! + // + // In this case, we generate a link to the *documentation* for this type + // in the original crate. There's an extra URL parameter which says that + // we want to go somewhere else, and the JS on the destination page will + // pick it up and instantly redirect the browser to the source code. + // + // If we don't know where the external documentation for this crate is + // located, then we return `None`. + } else { + let cache = cache(); + let path = &cache.external_paths[&self.item.def_id]; + let root = match cache.extern_locations[&self.item.def_id.krate] { + (_, Remote(ref s)) => s.to_string(), + (_, Local) => self.cx.root_path.clone(), + (_, Unknown) => return None, + }; + Some(format!("{root}{path}/{file}?gotosrc={goto}", + root = root, + path = path[..path.len() - 1].connect("/"), + file = item_path(self.item), + goto = self.item.def_id.node)) + } + } +} + + +impl<'a> fmt::Display for Item<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + // Write the breadcrumb trail header for the top + try!(write!(fmt, "\x1b[32;1m")); + match self.item.inner { + clean::ModuleItem(ref m) => if m.is_crate { + try!(write!(fmt, "Crate ")); + } else { + try!(write!(fmt, "Module ")); + }, + clean::FunctionItem(..) => try!(write!(fmt, "Function ")), + clean::TraitItem(..) => try!(write!(fmt, "Trait ")), + clean::StructItem(..) => try!(write!(fmt, "Struct ")), + clean::EnumItem(..) => try!(write!(fmt, "Enum ")), + clean::PrimitiveItem(..) => try!(write!(fmt, "Primitive Type ")), + _ => {} + } + try!(write!(fmt, "\x1b[0m")); + let is_primitive = match self.item.inner { + clean::PrimitiveItem(..) => true, + _ => false, + }; + if !is_primitive { + let cur = &self.cx.current; + let amt = if self.ismodule() { cur.len() - 1 } else { cur.len() }; + for (_, component) in cur.iter().enumerate().take(amt) { + try!(write!(fmt, "{}{}{}::", + RED, + component, + NORMAL)); + } + } + try!(write!(fmt, "{}{}{}\n\n", + "\x1b[33;1m", + self.item.name.as_ref().unwrap(), + NORMAL)); + + /*try!(write!(fmt, "")); // in-band + try!(write!(fmt, ""));*/ + /*try!(write!(fmt, + r##" +
+ [] + + "##));*/ + + // Write `src` tag + // + // When this item is part of a `pub use` in a downstream crate, the + // [src] link in the downstream documentation will actually come back to + // this page, and this link will be auto-clicked. The `id` attribute is + // used to find the link to auto-click. + /*if self.cx.include_sources && !is_primitive { + match self.href(self.cx) { + Some(l) => { + try!(write!(fmt, "[src]", + self.item.def_id.node, l, "goto source code")); + } + None => {} + } + } + + try!(write!(fmt, "")); // out-of-band + + try!(write!(fmt, "\n"));*/ + + match self.item.inner { + clean::ModuleItem(ref m) => { + item_module(fmt, self.cx, self.item, &m.items) + } + clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => + item_function(fmt, self.item, f), + clean::TraitItem(ref t) => item_trait(fmt, self.cx, self.item, t), + clean::StructItem(ref s) => item_struct(fmt, self.item, s), + clean::EnumItem(ref e) => item_enum(fmt, self.item, e), + clean::TypedefItem(ref t, _) => item_typedef(fmt, self.item, t), + clean::MacroItem(ref m) => item_macro(fmt, self.item, m), + clean::PrimitiveItem(ref p) => item_primitive(fmt, self.item, p), + clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => + item_static(fmt, self.item, i), + clean::ConstantItem(ref c) => item_constant(fmt, self.item, c), + _ => Ok(()) + } + } +} + +fn item_path(item: &clean::Item) -> String { + match item.inner { + clean::ModuleItem(..) => { + format!("{}/index.ascii", item.name.as_ref().unwrap()) + } + _ => { + format!("{}.{}.ascii", + shortty(item).to_static_str(), + *item.name.as_ref().unwrap()) + } + } +} + +fn full_path(cx: &Context, item: &clean::Item) -> String { + let mut s = cx.current.connect("::"); + s.push_str("::"); + s.push_str(item.name.as_ref().unwrap()); + return s +} + +fn shorter<'a>(s: Option<&'a str>) -> String { + match s { + Some(s) => s.lines().take_while(|line|{ + (*line).chars().any(|chr|{ + !chr.is_whitespace() + }) + }).collect::>().connect("\n"), + None => "".to_string() + } +} + +#[inline] +fn plain_summary_line(s: Option<&str>) -> String { + let line = shorter(s).replace("\n", " "); + markdown::plain_summary_line(&line[..]) +} + +fn document(w: &mut fmt::Formatter, item: &clean::Item) -> fmt::Result { + if let Some(s) = short_stability(item, true) { + //try!(write!(w, "
{}
", s)); + try!(write!(w, "{}{}{}\n", GREEN, s, NORMAL)); + } + if let Some(s) = item.doc_value() { + //try!(write!(w, "
{}
", Markdown(s))); + try!(write!(w, "{}{}{}\n", BLUE, s, NORMAL)); + } + Ok(()) +} + +fn item_module(w: &mut fmt::Formatter, cx: &Context, + item: &clean::Item, items: &[clean::Item]) -> fmt::Result { + try!(document(w, item)); + + let mut indices = (0..items.len()).filter(|i| { + !cx.ignore_private_item(&items[*i]) + }).collect::>(); + + // the order of item types in the listing + fn reorder(ty: ItemType) -> u8 { + match ty { + ItemType::ExternCrate => 0, + ItemType::Import => 1, + ItemType::Primitive => 2, + ItemType::Module => 3, + ItemType::Macro => 4, + ItemType::Struct => 5, + ItemType::Enum => 6, + ItemType::Constant => 7, + ItemType::Static => 8, + ItemType::Trait => 9, + ItemType::Function => 10, + ItemType::Typedef => 12, + _ => 13 + ty as u8, + } + } + + fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: usize, idx2: usize) -> Ordering { + let ty1 = shortty(i1); + let ty2 = shortty(i2); + if ty1 != ty2 { + return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)) + } + let s1 = i1.stability.as_ref().map(|s| s.level); + let s2 = i2.stability.as_ref().map(|s| s.level); + match (s1, s2) { + (Some(attr::Unstable), Some(attr::Stable)) => return Ordering::Greater, + (Some(attr::Stable), Some(attr::Unstable)) => return Ordering::Less, + _ => {} + } + i1.name.cmp(&i2.name) + } + + indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2)); + + debug!("{:?}", indices); + let mut curty = None; + for &idx in &indices { + let myitem = &items[idx]; + + let myty = Some(shortty(myitem)); + if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) { + // Put `extern crate` and `use` re-exports in the same section. + curty = myty; + } else if myty != curty { + /*if curty.is_some() { + try!(write!(w, "")); + }*/ + curty = myty; + let (short, name) = match myty.unwrap() { + ItemType::ExternCrate | + ItemType::Import => ("reexports", "Reexports"), + ItemType::Module => ("modules", "Modules"), + ItemType::Struct => ("structs", "Structs"), + ItemType::Enum => ("enums", "Enums"), + ItemType::Function => ("functions", "Functions"), + ItemType::Typedef => ("types", "Type Definitions"), + ItemType::Static => ("statics", "Statics"), + ItemType::Constant => ("constants", "Constants"), + ItemType::Trait => ("traits", "Traits"), + ItemType::Impl => ("impls", "Implementations"), + ItemType::TyMethod => ("tymethods", "Type Methods"), + ItemType::Method => ("methods", "Methods"), + ItemType::StructField => ("fields", "Struct Fields"), + ItemType::Variant => ("variants", "Variants"), + ItemType::Macro => ("macros", "Macros"), + ItemType::Primitive => ("primitives", "Primitive Types"), + ItemType::AssociatedType => ("associated-types", "Associated Types"), + ItemType::AssociatedConst => ("associated-consts", "Associated Constants"), + }; + try!(write!(w, + "\n{brown}{name}{normal}\n\n", + brown = BROWN, + name = name, + normal = NORMAL)); + } + + match myitem.inner { + clean::ExternCrateItem(ref name, ref src) => { + match *src { + Some(ref src) => { + /*try!(write!(w, "{}extern crate {} as {};", + VisSpace(myitem.visibility), + src, + name))*/ + try!(write!(w, "{vis}extern crate {brown}{src}{normal} as {brown}{name}{normal};", + vis = VisSpace(myitem.visibility).get_display(), + src = src, + name = name, + brown = BROWN, + normal = NORMAL)) + } + None => { + /*try!(write!(w, "{}extern crate {};", + VisSpace(myitem.visibility), name))*/ + try!(write!(w, "{}extern crate {}{}{};", + VisSpace(myitem.visibility).get_display(), + BROWN, + name, + NORMAL)); + } + } + //try!(write!(w, "")); + } + + clean::ImportItem(ref import) => { + /*try!(write!(w, "{}{}", + VisSpace(myitem.visibility), *import));*/ + try!(write!(w, "{}{}", + VisSpace(myitem.visibility).get_display(), *import)); + } + + _ => { + if myitem.name.is_none() { continue } + let stab_docs = if let Some(s) = short_stability(myitem, false) { + format!("[{}]", s) + } else { + String::new() + }; + /*try!(write!(w, " + + {name} + + {stab_docs} {docs} + + + ", + name = *myitem.name.as_ref().unwrap(), + stab_docs = stab_docs, + docs = Markdown(&shorter(myitem.doc_value())), + class = shortty(myitem), + stab = myitem.stability_class(), + href = item_path(myitem), + title = full_path(cx, myitem)));*/ + try!(write!(w, "{brown}{name}{normal}{stab_docs} {docs}", + brown = BROWN, + normal = NORMAL, + name = *myitem.name.as_ref().unwrap(), + stab_docs = stab_docs, + docs = Markdown(&shorter(myitem.doc_value())))); + } + } + } + + //write!(w, "") + Ok(()) +} + +fn short_stability(item: &clean::Item, show_reason: bool) -> Option { + item.stability.as_ref().and_then(|stab| { + let reason = if show_reason && !stab.reason.is_empty() { + format!(": {}", stab.reason) + } else { + String::new() + }; + let text = if !stab.deprecated_since.is_empty() { + let since = if show_reason { + format!(" since {}", &stab.deprecated_since) + } else { + String::new() + }; + format!("Deprecated{}{}", since, Markdown(&reason)) + } else if stab.level == attr::Unstable { + format!("Unstable{}", Markdown(&reason)) + } else { + return None + }; + /*Some(format!("{}", + item.stability_class(), text))*/ + Some(format!("{}|{}| {}{}", + BLUE, item.stability_class(), text, NORMAL)) + }) +} + +struct Initializer<'a>(&'a str); + +impl<'a> fmt::Display for Initializer<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Initializer(s) = *self; + if s.is_empty() { return Ok(()); } + //try!(write!(f, " = ")); + //write!(f, "{}", s) + try!(write!(f, " = ")); + write!(f, "{}{}{}", RED, s, NORMAL) + } +} + +fn item_constant(w: &mut fmt::Formatter, it: &clean::Item, + c: &clean::Constant) -> fmt::Result { + /*try!(write!(w, "
{vis}const \
+                    {name}: {typ}{init}
", + vis = VisSpace(it.visibility), + name = it.name.as_ref().unwrap(), + typ = c.type_, + init = Initializer(&c.expr)));*/ + try!(write!(w, "{vis}const \ + {name}: {typ}{init}", + vis = VisSpace(it.visibility).get_display(), + name = it.name.as_ref().unwrap(), + typ = c.type_.get_display(), + init = Initializer(&c.expr))); + document(w, it) +} + +fn item_static(w: &mut fmt::Formatter, it: &clean::Item, + s: &clean::Static) -> fmt::Result { + /*try!(write!(w, "
{vis}static {mutability}\
+                    {name}: {typ}{init}
", + vis = VisSpace(it.visibility), + mutability = MutableSpace(s.mutability), + name = it.name.as_ref().unwrap(), + typ = s.type_, + init = Initializer(&s.expr)));*/ + try!(write!(w, "{vis}static {mutability}\ + {name}: {typ}{init}", + vis = VisSpace(it.visibility).get_display(), + mutability = MutableSpace(s.mutability).get_display(), + name = it.name.as_ref().unwrap(), + typ = s.type_.get_display(), + init = Initializer(&s.expr))); + document(w, it) +} + +fn item_function(w: &mut fmt::Formatter, it: &clean::Item, + f: &clean::Function) -> fmt::Result { + try!(write!(w, "\n\n{red}{vis}{unsafety}{abi}{constness}fn \ + {name}{generics}{decl}{where_clause}{normal}", + red = RED, + vis = VisSpace(it.visibility).get_display(), + unsafety = UnsafetySpace(f.unsafety).get_display(), + abi = AbiSpace(f.abi).get_display(), + constness = ConstnessSpace(f.constness).get_display(), + name = it.name.as_ref().unwrap(), + generics = f.generics.get_display(), + where_clause = WhereClause(&f.generics).get_display(), + decl = f.decl, + normal = NORMAL)); + document(w, it) +} + +fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, + t: &clean::Trait) -> fmt::Result { + let mut bounds = String::new(); + if !t.bounds.is_empty() { + if !bounds.is_empty() { + bounds.push(' '); + } + bounds.push_str(": "); + for (i, p) in t.bounds.iter().enumerate() { + if i > 0 { bounds.push_str(" + "); } + bounds.push_str(&format!("{}", *p)); + } + } + + // Output the trait definition + try!(write!(w, "{}{}{}trait {}{}{}{}{} ", + BLUE, + VisSpace(it.visibility).get_display(), + UnsafetySpace(t.unsafety).get_display(), + it.name.as_ref().unwrap(), + t.generics.get_display(), + bounds, + WhereClause(&t.generics).get_display(), + NORMAL)); + + let types = t.items.iter().filter(|m| { + match m.inner { clean::AssociatedTypeItem(..) => true, _ => false } + }).collect::>(); + let consts = t.items.iter().filter(|m| { + match m.inner { clean::AssociatedConstItem(..) => true, _ => false } + }).collect::>(); + let required = t.items.iter().filter(|m| { + match m.inner { clean::TyMethodItem(_) => true, _ => false } + }).collect::>(); + let provided = t.items.iter().filter(|m| { + match m.inner { clean::MethodItem(_) => true, _ => false } + }).collect::>(); + + if t.items.is_empty() { + try!(write!(w, "{{ }}")); + } else { + try!(write!(w, "{{\n")); + for t in &types { + try!(write!(w, " ")); + try!(render_assoc_item(w, t, AssocItemLink::Anchor)); + try!(write!(w, ";\n")); + } + if !types.is_empty() && !consts.is_empty() { + try!(w.write_str("\n")); + } + for t in &consts { + try!(write!(w, " ")); + try!(render_assoc_item(w, t, AssocItemLink::Anchor)); + try!(write!(w, ";\n")); + } + if !consts.is_empty() && !required.is_empty() { + try!(w.write_str("\n")); + } + for m in &required { + try!(write!(w, " ")); + try!(render_assoc_item(w, m, AssocItemLink::Anchor)); + try!(write!(w, ";\n")); + } + if !required.is_empty() && !provided.is_empty() { + try!(w.write_str("\n")); + } + for m in &provided { + try!(write!(w, " ")); + try!(render_assoc_item(w, m, AssocItemLink::Anchor)); + try!(write!(w, " {{ ... }}\n")); + } + try!(write!(w, "}}")); + } + //try!(write!(w, "")); + + // Trait documentation + try!(document(w, it)); + + fn trait_item(w: &mut fmt::Formatter, m: &clean::Item) + -> fmt::Result { + /*try!(write!(w, "

", + ty = shortty(m), + name = *m.name.as_ref().unwrap(), + stab = m.stability_class()));*/ + try!(write!(w, "{}", MAGENTA)); + try!(render_assoc_item(w, m, AssocItemLink::Anchor)); + //try!(write!(w, "

")); + try!(write!(w, "{}", NORMAL)); + try!(document(w, m)); + Ok(()) + } + + if !types.is_empty() { + /*try!(write!(w, " +

Associated Types

+
+ "));*/ + try!(write!(w, "\n\x1b[32;1mAssociated Types\x1b[0m\n\n")); + for t in &types { + try!(trait_item(w, *t)); + } + //try!(write!(w, "
")); + } + + if !consts.is_empty() { + /*try!(write!(w, " +

Associated Constants

+
+ "));*/ + try!(write!(w, "\n\x1b[32;1mAssociated Constants\x1b[0m\n\n")); + for t in &consts { + try!(trait_item(w, *t)); + } + //try!(write!(w, "
")); + } + + // Output the documentation for each function individually + if !required.is_empty() { + /*try!(write!(w, " +

Required Methods

+
+ "));*/ + try!(write!(w, "\n\x1b[33;1mRequired Methods\x1b[0m\n\n")); + for m in &required { + try!(trait_item(w, *m)); + } + //try!(write!(w, "
")); + } + if !provided.is_empty() { + /*try!(write!(w, " +

Provided Methods

+
+ "));*/ + try!(write!(w, "\n\x1b[34;1mProvided Methods\x1b[0m\n\n")); + for m in &provided { + try!(trait_item(w, *m)); + } + //try!(write!(w, "
")); + } + + // If there are methods directly on this trait object, render them here. + try!(render_assoc_items(w, it.def_id, AssocItemRender::All)); + + let cache = cache(); + /*try!(write!(w, " +

Implementors

+
    + "));*/ + try!(write!(w, "\n\x1b[35;1mProvided Methods\x1b[0m\n\n")); + match cache.implementors.get(&it.def_id) { + Some(implementors) => { + for i in implementors { + /*try!(writeln!(w, "
  • impl{} {} for {}{}
  • ", + i.generics, i.trait_, i.for_, WhereClause(&i.generics)));*/ + try!(writeln!(w, "{}impl{} {} for {}{}{}", + BLUE, i.generics.get_display(), i.trait_.get_display(), + i.for_.get_display(), + WhereClause(&i.generics).get_display(), NORMAL)); + } + } + None => {} + } + //try!(write!(w, "
")); + /*try!(write!(w, r#""#, + root_path = repeat("..").take(cx.current.len()).collect::>().connect("/"), + path = if ast_util::is_local(it.def_id) { + cx.current.connect("/") + } else { + let path = &cache.external_paths[&it.def_id]; + path[..path.len() - 1].connect("/") + }, + ty = shortty(it).to_static_str(), + name = *it.name.as_ref().unwrap()));*/ + Ok(()) +} + +fn assoc_const(w: &mut fmt::Formatter, it: &clean::Item, + ty: &clean::Type, default: Option<&String>) + -> fmt::Result { + try!(write!(w, "const {}", it.name.as_ref().unwrap())); + try!(write!(w, ": {}", ty.get_display())); + if let Some(default) = default { + try!(write!(w, " = {}", default)); + } + Ok(()) +} + +fn assoc_type(w: &mut fmt::Formatter, it: &clean::Item, + bounds: &Vec, + default: &Option) + -> fmt::Result { + try!(write!(w, "type {}", it.name.as_ref().unwrap())); + if !bounds.is_empty() { + try!(write!(w, ": {}", TyParamBounds(bounds))) + } + if let Some(ref default) = *default { + try!(write!(w, " = {}", default.get_display())); + } + Ok(()) +} + +fn render_assoc_item(w: &mut fmt::Formatter, meth: &clean::Item, + link: AssocItemLink) -> fmt::Result { + fn method(w: &mut fmt::Formatter, + it: &clean::Item, + unsafety: ast::Unsafety, + constness: ast::Constness, + abi: abi::Abi, + g: &clean::Generics, + selfty: &clean::SelfTy, + d: &clean::FnDecl, + link: AssocItemLink) + -> fmt::Result { + use syntax::abi::Abi; + + let name = it.name.as_ref().unwrap(); + let anchor = format!("#{}.{}", shortty(it), name); + let href = match link { + AssocItemLink::Anchor => anchor, + AssocItemLink::GotoSource(did) => { + href(did).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor) + } + }; + write!(w, "{}{}{}fn {name}\ + {generics}{decl}{where_clause}", + UnsafetySpace(unsafety).get_display(), + ConstnessSpace(constness).get_display(), + match abi { + Abi::Rust => String::new(), + a => format!("extern {} ", a.to_string()) + }, + name = name, + generics = (*g).get_display(), + decl = Method(selfty, d).get_display(), + where_clause = WhereClause(g).get_display()) + } + match meth.inner { + clean::TyMethodItem(ref m) => { + method(w, meth, m.unsafety, ast::Constness::NotConst, + m.abi, &m.generics, &m.self_, &m.decl, link) + } + clean::MethodItem(ref m) => { + method(w, meth, m.unsafety, m.constness, + m.abi, &m.generics, &m.self_, &m.decl, + link) + } + clean::AssociatedConstItem(ref ty, ref default) => { + assoc_const(w, meth, ty, default.as_ref()) + } + clean::AssociatedTypeItem(ref bounds, ref default) => { + assoc_type(w, meth, bounds, default) + } + _ => panic!("render_assoc_item called on non-associated-item") + } +} + +fn item_struct(w: &mut fmt::Formatter, it: &clean::Item, + s: &clean::Struct) -> fmt::Result { + //try!(write!(w, "
"));
+    try!(write!(w, "{}", BLUE));
+    try!(render_attributes(w, it));
+    try!(render_struct(w,
+                       it,
+                       Some(&s.generics),
+                       s.struct_type,
+                       &s.fields,
+                       "",
+                       true));
+    //try!(write!(w, "
")); + try!(write!(w, "{}\n\n", NORMAL)); + + try!(document(w, it)); + let mut fields = s.fields.iter().filter(|f| { + match f.inner { + clean::StructFieldItem(clean::HiddenStructField) => false, + clean::StructFieldItem(clean::TypedStructField(..)) => true, + _ => false, + } + }).peekable(); + if let doctree::Plain = s.struct_type { + if fields.peek().is_some() { + //try!(write!(w, "

Fields

\n")); + try!(write!(w, "{}Fields{}\n", RED, NORMAL)); + for field in fields { + /*try!(write!(w, " + ")); + } + //try!(write!(w, "
\ + {name}", + stab = field.stability_class(), + name = field.name.as_ref().unwrap()));*/ + try!(write!(w, "{}{}{}\n", GREEN, field.name.as_ref().unwrap(), NORMAL)); + try!(document(w, field)); + //try!(write!(w, "
")); + } + } + render_assoc_items(w, it.def_id, AssocItemRender::All) +} + +fn item_enum(w: &mut fmt::Formatter, it: &clean::Item, + e: &clean::Enum) -> fmt::Result { + //try!(write!(w, "
"));
+    try!(write!(w, "{}", GREEN));
+    try!(render_attributes(w, it));
+    try!(write!(w, "{}{}enum {}{}{}{}",
+                  RED,
+                  VisSpace(it.visibility).get_display(),
+                  it.name.as_ref().unwrap(),
+                  e.generics.get_display(),
+                  WhereClause(&e.generics).get_display(),
+                  NORMAL));
+    if e.variants.is_empty() && !e.variants_stripped {
+        try!(write!(w, " {{}}"));
+    } else {
+        try!(write!(w, " {{\n"));
+        for v in &e.variants {
+            try!(write!(w, "    "));
+            let name = v.name.as_ref().unwrap();
+            match v.inner {
+                clean::VariantItem(ref var) => {
+                    match var.kind {
+                        clean::CLikeVariant => try!(write!(w, "{}", name)),
+                        clean::TupleVariant(ref tys) => {
+                            try!(write!(w, "{}(", name));
+                            for (i, ty) in tys.iter().enumerate() {
+                                if i > 0 {
+                                    try!(write!(w, ", "))
+                                }
+                                try!(write!(w, "{}", (*ty).get_display()));
+                            }
+                            try!(write!(w, ")"));
+                        }
+                        clean::StructVariant(ref s) => {
+                            try!(render_struct(w,
+                                               v,
+                                               None,
+                                               s.struct_type,
+                                               &s.fields,
+                                               "    ",
+                                               false));
+                        }
+                    }
+                }
+                _ => unreachable!()
+            }
+            try!(write!(w, ",\n"));
+        }
+
+        if e.variants_stripped {
+            try!(write!(w, "    // some variants omitted\n"));
+        }
+        try!(write!(w, "}}"));
+    }
+    //try!(write!(w, "
")); + + try!(document(w, it)); + if !e.variants.is_empty() { + //try!(write!(w, "

Variants

\n")); + for variant in &e.variants { + /*try!(write!(w, "")); + } + //try!(write!(w, "
{name}", + name = variant.name.as_ref().unwrap()));*/ + try!(write!(w, "{}{}{}\n", BLUE, variant.name.as_ref().unwrap(), NORMAL)); + try!(document(w, variant)); + match variant.inner { + clean::VariantItem(ref var) => { + match var.kind { + clean::StructVariant(ref s) => { + let fields = s.fields.iter().filter(|f| { + match f.inner { + clean::StructFieldItem(ref t) => match *t { + clean::HiddenStructField => false, + clean::TypedStructField(..) => true, + }, + _ => false, + } + }); + /*try!(write!(w, "

Fields

\n + "));*/ + try!(write!(w, "{}Fields{}\n", BROWN, NORMAL)); + for field in fields { + /*try!(write!(w, "")); + } + //try!(write!(w, "
\ + {f}", + v = variant.name.as_ref().unwrap(), + f = field.name.as_ref().unwrap()));*/ + try!(write!(w, "{}{}{}\n", RED, field.name.as_ref().unwrap(), NORMAL)); + try!(document(w, field)); + //try!(write!(w, "
")); + } + _ => () + } + } + _ => () + } + //try!(write!(w, "
")); + + } + try!(render_assoc_items(w, it.def_id, AssocItemRender::All)); + Ok(()) +} + +fn render_attributes(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result { + for attr in &it.attrs { + match *attr { + clean::Word(ref s) if *s == "must_use" => { + try!(write!(w, "#[{}]\n", s)); + } + clean::NameValue(ref k, ref v) if *k == "must_use" => { + try!(write!(w, "#[{} = \"{}\"]\n", k, v)); + } + _ => () + } + } + Ok(()) +} + +fn render_struct(w: &mut fmt::Formatter, it: &clean::Item, + g: Option<&clean::Generics>, + ty: doctree::StructType, + fields: &[clean::Item], + tab: &str, + structhead: bool) -> fmt::Result { + try!(write!(w, "{}{}{}", + VisSpace(it.visibility).get_display(), + if structhead {"struct "} else {""}, + it.name.as_ref().unwrap())); + match g { + Some(g) => try!(write!(w, "{}{}", (*g).get_display(), WhereClause(g).get_display())), + None => {} + } + match ty { + doctree::Plain => { + try!(write!(w, " {{\n{}", tab)); + let mut fields_stripped = false; + for field in fields { + match field.inner { + clean::StructFieldItem(clean::HiddenStructField) => { + fields_stripped = true; + } + clean::StructFieldItem(clean::TypedStructField(ref ty)) => { + try!(write!(w, " {}{}: {},\n{}", + VisSpace(field.visibility).get_display(), + field.name.as_ref().unwrap(), + (*ty).get_display(), + tab)); + } + _ => unreachable!(), + }; + } + + if fields_stripped { + try!(write!(w, " // some fields omitted\n{}", tab)); + } + try!(write!(w, "}}")); + } + doctree::Tuple | doctree::Newtype => { + try!(write!(w, "(")); + for (i, field) in fields.iter().enumerate() { + if i > 0 { + try!(write!(w, ", ")); + } + match field.inner { + clean::StructFieldItem(clean::HiddenStructField) => { + try!(write!(w, "_")) + } + clean::StructFieldItem(clean::TypedStructField(ref ty)) => { + try!(write!(w, "{}{}", VisSpace(field.visibility).get_display(), + (*ty).get_display())) + } + _ => unreachable!() + } + } + try!(write!(w, ");")); + } + doctree::Unit => { + try!(write!(w, ";")); + } + } + Ok(()) +} + +#[derive(Copy, Clone)] +enum AssocItemLink { + Anchor, + GotoSource(ast::DefId), +} + +enum AssocItemRender<'a> { + All, + DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type }, +} + +fn render_assoc_items(w: &mut fmt::Formatter, + it: ast::DefId, + what: AssocItemRender) -> fmt::Result { + let c = cache(); + let v = match c.impls.get(&it) { + Some(v) => v, + None => return Ok(()), + }; + let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| { + i.impl_.trait_.is_none() + }); + if !non_trait.is_empty() { + let render_header = match what { + AssocItemRender::All => { + //try!(write!(w, "

Methods

")); + try!(write!(w, "\n{}Methods{}\n\n", RED, NORMAL)); + true + } + AssocItemRender::DerefFor { trait_, type_ } => { + /*try!(write!(w, "

Methods from \ + {}", trait_, type_));*/ + try!(write!(w, "\nMethods from {}{}{}\n\n", BROWN, + trait_.get_display(), NORMAL)); + false + } + }; + for i in &non_trait { + try!(render_impl(w, i, AssocItemLink::Anchor, render_header)); + } + } + if let AssocItemRender::DerefFor { .. } = what { + return Ok(()) + } + if !traits.is_empty() { + let deref_impl = traits.iter().find(|t| { + match *t.impl_.trait_.as_ref().unwrap() { + clean::ResolvedPath { did, .. } => { + Some(did) == c.deref_trait_did + } + _ => false + } + }); + if let Some(impl_) = deref_impl { + try!(render_deref_methods(w, impl_)); + } + /*try!(write!(w, "

Trait \ + Implementations

"));*/ + try!(write!(w, "\n{}Trait Implementations{}\n\n", RED, NORMAL)); + let (derived, manual): (Vec<_>, _) = traits.iter().partition(|i| { + i.impl_.derived + }); + for i in &manual { + let did = i.trait_did().unwrap(); + try!(render_impl(w, i, AssocItemLink::GotoSource(did), true)); + } + if !derived.is_empty() { + /*try!(write!(w, "

\ + Derived Implementations \ +

"));*/ + try!(write!(w, "\n{}Derived Implementations{}\n\n", RED, NORMAL)); + for i in &derived { + let did = i.trait_did().unwrap(); + try!(render_impl(w, i, AssocItemLink::GotoSource(did), true)); + } + } + } + Ok(()) +} + +fn render_deref_methods(w: &mut fmt::Formatter, impl_: &Impl) -> fmt::Result { + let deref_type = impl_.impl_.trait_.as_ref().unwrap(); + let target = impl_.impl_.items.iter().filter_map(|item| { + match item.inner { + clean::TypedefItem(ref t, true) => Some(&t.type_), + _ => None, + } + }).next().expect("Expected associated type binding"); + let what = AssocItemRender::DerefFor { trait_: deref_type, type_: target }; + match *target { + clean::ResolvedPath { did, .. } => render_assoc_items(w, did, what), + _ => { + if let Some(prim) = target.primitive_type() { + if let Some(c) = cache().primitive_locations.get(&prim) { + let did = ast::DefId { krate: *c, node: prim.to_node_id() }; + try!(render_assoc_items(w, did, what)); + } + } + Ok(()) + } + } +} + +// Render_header is false when we are rendering a `Deref` impl and true +// otherwise. If render_header is false, we will avoid rendering static +// methods, since they are not accessible for the type implementing `Deref` +fn render_impl(w: &mut fmt::Formatter, i: &Impl, link: AssocItemLink, + render_header: bool) -> fmt::Result { + if render_header { + /*try!(write!(w, "

impl{} ", + i.impl_.generics));*/ + try!(write!(w, "impl{}{}{} ", BLUE, i.impl_.generics.get_display(), NORMAL)); + if let Some(clean::ImplPolarity::Negative) = i.impl_.polarity { + try!(write!(w, "!")); + } + if let Some(ref ty) = i.impl_.trait_ { + try!(write!(w, "{} for ", (*ty).get_display())); + } + /*try!(write!(w, "{}{}

", i.impl_.for_, + WhereClause(&i.impl_.generics)));*/ + try!(write!(w, "{}{}", i.impl_.for_.get_display(), + WhereClause(&i.impl_.generics).get_display())); + if let Some(ref dox) = i.dox { + //try!(write!(w, "
{}
", Markdown(dox))); + try!(write!(w, "\x1b[37;1m{}{}", Markdown(dox), NORMAL)); + } + } + + fn doctraititem(w: &mut fmt::Formatter, item: &clean::Item, + link: AssocItemLink, render_static: bool) -> fmt::Result { + match item.inner { + clean::MethodItem(..) | clean::TyMethodItem(..) => { + // Only render when the method is not static or we allow static methods + if !is_static_method(item) || render_static { + /*try!(write!(w, "

", + *item.name.as_ref().unwrap(), + shortty(item)));*/ + try!(write!(w, "{}", RED)); + try!(render_assoc_item(w, item, link)); + //try!(write!(w, "

\n")); + try!(write!(w, "{}\n", NORMAL)); + } + } + clean::TypedefItem(ref tydef, _) => { + let name = item.name.as_ref().unwrap(); + /*try!(write!(w, "

", + *name, + shortty(item)));*/ + try!(write!(w, "{}", RED)); + try!(write!(w, "type {} = {}", name, tydef.type_.get_display())); + //try!(write!(w, "

\n")); + try!(write!(w, "{}\n", NORMAL)); + } + clean::AssociatedConstItem(ref ty, ref default) => { + let name = item.name.as_ref().unwrap(); + /*try!(write!(w, "

", + *name, shortty(item)));*/ + try!(write!(w, "{}", RED)); + try!(assoc_const(w, item, ty, default.as_ref())); + //try!(write!(w, "

\n")); + try!(write!(w, "{}\n", NORMAL)); + } + clean::ConstantItem(ref c) => { + let name = item.name.as_ref().unwrap(); + /*try!(write!(w, "

", + *name, shortty(item)));*/ + try!(write!(w, "{}", RED)); + try!(assoc_const(w, item, &c.type_, Some(&c.expr))); + //try!(write!(w, "

\n")); + try!(write!(w, "{}\n", NORMAL)); + } + clean::AssociatedTypeItem(ref bounds, ref default) => { + let name = item.name.as_ref().unwrap(); + /*try!(write!(w, "

", + *name, + shortty(item)));*/ + try!(write!(w, "{}", RED)); + try!(assoc_type(w, item, bounds, default)); + //try!(write!(w, "

\n")); + try!(write!(w, "{}\n", NORMAL)); + } + _ => panic!("can't make docs for trait item with name {:?}", item.name) + } + + return if let AssocItemLink::Anchor = link { + if is_static_method(item) && !render_static { + Ok(()) + } else { + document(w, item) + } + } else { + Ok(()) + }; + + fn is_static_method(item: &clean::Item) -> bool { + match item.inner { + clean::MethodItem(ref method) => method.self_ == SelfTy::SelfStatic, + clean::TyMethodItem(ref method) => method.self_ == SelfTy::SelfStatic, + _ => false + } + } + } + + //try!(write!(w, "
")); + try!(write!(w, "\x1b[37;1m\n")); + for trait_item in i.impl_.items.iter() { + try!(doctraititem(w, trait_item, link, render_header)); + } + + fn render_default_items(w: &mut fmt::Formatter, + did: ast::DefId, + t: &clean::Trait, + i: &clean::Impl, + render_static: bool) -> fmt::Result { + for trait_item in &t.items { + let n = trait_item.name.clone(); + match i.items.iter().find(|m| { m.name == n }) { + Some(..) => continue, + None => {} + } + + try!(doctraititem(w, trait_item, AssocItemLink::GotoSource(did), render_static)); + } + Ok(()) + } + + // If we've implemented a trait, then also emit documentation for all + // default methods which weren't overridden in the implementation block. + // FIXME: this also needs to be done for associated types, whenever defaults + // for them work. + if let Some(clean::ResolvedPath { did, .. }) = i.impl_.trait_ { + if let Some(t) = cache().traits.get(&did) { + try!(render_default_items(w, did, t, &i.impl_, render_header)); + + } + } + //try!(write!(w, "
")); + try!(write!(w, "{}\n", NORMAL)); + Ok(()) +} + +fn item_typedef(w: &mut fmt::Formatter, it: &clean::Item, + t: &clean::Typedef) -> fmt::Result { + /*try!(write!(w, "
type {}{}{where_clause} = {type_};
", + it.name.as_ref().unwrap(), + t.generics, + where_clause = WhereClause(&t.generics), + type_ = t.type_));*/ + try!(write!(w, "{blue}type{normal} {green}{name}{normal}{brown}{generics}{normal}\ + {magenta}{where_clause}{normal} = {cyan}{type_}{normal};", + blue = BLUE, + normal = NORMAL, + green = GREEN, + brown = BROWN, + magenta = MAGENTA, + cyan = CYAN, + name = it.name.as_ref().unwrap(), + generics = t.generics.get_display(), + where_clause = WhereClause(&t.generics).get_display(), + type_ = t.type_.get_display())); + document(w, it) +} + +impl<'a> fmt::Display for Sidebar<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + /*let cx = self.cx; + let it = self.item; + let parentlen = cx.current.len() - if it.is_mod() {1} else {0}; + + // the sidebar is designed to display sibling functions, modules and + // other miscellaneous informations. since there are lots of sibling + // items (and that causes quadratic growth in large modules), + // we refactor common parts into a shared JavaScript file per module. + // still, we don't move everything into JS because we want to preserve + // as much HTML as possible in order to allow non-JS-enabled browsers + // to navigate the documentation (though slightly inefficiently). + + try!(write!(fmt, "

")); + for (i, name) in cx.current.iter().take(parentlen).enumerate() { + if i > 0 { + try!(write!(fmt, "::")); + } + try!(write!(fmt, "{}", + &cx.root_path[..(cx.current.len() - i - 1) * 3], + *name)); + } + try!(write!(fmt, "

")); + + // sidebar refers to the enclosing module, not this module + let relpath = if shortty(it) == ItemType::Module { "../" } else { "" }; + try!(write!(fmt, + "", + name = it.name.as_ref().map(|x| &x[..]).unwrap_or(""), + ty = shortty(it).to_static_str(), + path = relpath)); + if parentlen == 0 { + // there is no sidebar-items.js beyond the crate root path + // FIXME maybe dynamic crate loading can be merged here + } else { + try!(write!(fmt, "", + path = relpath)); + }*/ + + Ok(()) + } +} + +impl<'a> fmt::Display for Source<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let Source(s) = *self; + let lines = s.lines().count(); + let mut cols = 0; + let mut tmp = lines; + while tmp > 0 { + cols += 1; + tmp /= 10; + } + /*try!(write!(fmt, "
"));
+        for i in 1..lines + 1 {
+            try!(write!(fmt, "{0:1$}\n", i, cols));
+        }
+        try!(write!(fmt, "
"));*/ + try!(write!(fmt, "{}", highlight::highlight(s, None, None))); + Ok(()) + } +} + +fn item_macro(w: &mut fmt::Formatter, it: &clean::Item, + t: &clean::Macro) -> fmt::Result { + try!(w.write_str(&highlight::highlight(&t.source, + Some("macro"), + None))); + document(w, it) +} + +fn item_primitive(w: &mut fmt::Formatter, + it: &clean::Item, + _p: &clean::PrimitiveType) -> fmt::Result { + try!(document(w, it)); + render_assoc_items(w, it.def_id, AssocItemRender::All) +} + +fn get_basic_keywords() -> &'static str { + "rust, rustlang, rust-lang" +} + +fn make_item_keywords(it: &clean::Item) -> String { + format!("{}, {}", get_basic_keywords(), it.name.as_ref().unwrap()) +} + +fn get_index_search_type(item: &clean::Item, + parent: Option) -> Option { + let decl = match item.inner { + clean::FunctionItem(ref f) => &f.decl, + clean::MethodItem(ref m) => &m.decl, + clean::TyMethodItem(ref m) => &m.decl, + _ => return None + }; + + let mut inputs = Vec::new(); + + // Consider `self` an argument as well. + if let Some(name) = parent { + inputs.push(Type { name: Some(name.into_ascii_lowercase()) }); + } + + inputs.extend(&mut decl.inputs.values.iter().map(|arg| { + get_index_type(&arg.type_) + })); + + let output = match decl.output { + clean::FunctionRetTy::Return(ref return_type) => Some(get_index_type(return_type)), + _ => None + }; + + Some(IndexItemFunctionType { inputs: inputs, output: output }) +} + +fn get_index_type(clean_type: &clean::Type) -> Type { + Type { name: get_index_type_name(clean_type).map(|s| s.into_ascii_lowercase()) } +} + +fn get_index_type_name(clean_type: &clean::Type) -> Option { + match *clean_type { + clean::ResolvedPath { ref path, .. } => { + let segments = &path.segments; + Some(segments[segments.len() - 1].name.clone()) + }, + clean::Generic(ref s) => Some(s.clone()), + clean::Primitive(ref p) => Some(format!("{:?}", p)), + clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_), + // FIXME: add all from clean::Type. + _ => None + } +} + +pub fn cache() -> Arc { + CACHE_KEY.with(|c| c.borrow().clone()) +} diff --git a/src/librustdoc/ascii/toc.rs b/src/librustdoc/ascii/toc.rs new file mode 100644 index 0000000000000..93aa74d7005f6 --- /dev/null +++ b/src/librustdoc/ascii/toc.rs @@ -0,0 +1,282 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Table-of-contents creation. + +use std::fmt; +use std::string::String; + +/// A (recursive) table of contents +#[derive(PartialEq)] +pub struct Toc { + /// The levels are strictly decreasing, i.e. + /// + /// entries[0].level >= entries[1].level >= ... + /// + /// Normally they are equal, but can differ in cases like A and B, + /// both of which end up in the same `Toc` as they have the same + /// parent (Main). + /// + /// ```text + /// # Main + /// ### A + /// ## B + /// ``` + entries: Vec +} + +impl Toc { + fn count_entries_with_level(&self, level: u32) -> usize { + self.entries.iter().filter(|e| e.level == level).count() + } +} + +#[derive(PartialEq)] +pub struct TocEntry { + level: u32, + sec_number: String, + name: String, + id: String, + children: Toc, +} + +/// Progressive construction of a table of contents. +#[derive(PartialEq)] +pub struct TocBuilder { + top_level: Toc, + /// The current hierarchy of parent headings, the levels are + /// strictly increasing (i.e. chain[0].level < chain[1].level < + /// ...) with each entry being the most recent occurrence of a + /// heading with that level (it doesn't include the most recent + /// occurrences of every level, just, if *is* in `chain` then is is + /// the most recent one). + /// + /// We also have `chain[0].level <= top_level.entries[last]`. + chain: Vec +} + +impl TocBuilder { + pub fn new() -> TocBuilder { + TocBuilder { top_level: Toc { entries: Vec::new() }, chain: Vec::new() } + } + + + /// Convert into a true `Toc` struct. + pub fn into_toc(mut self) -> Toc { + // we know all levels are >= 1. + self.fold_until(0); + self.top_level + } + + /// Collapse the chain until the first heading more important than + /// `level` (i.e. lower level) + /// + /// Example: + /// + /// ```text + /// ## A + /// # B + /// # C + /// ## D + /// ## E + /// ### F + /// #### G + /// ### H + /// ``` + /// + /// If we are considering H (i.e. level 3), then A and B are in + /// self.top_level, D is in C.children, and C, E, F, G are in + /// self.chain. + /// + /// When we attempt to push H, we realise that first G is not the + /// parent (level is too high) so it is popped from chain and put + /// into F.children, then F isn't the parent (level is equal, aka + /// sibling), so it's also popped and put into E.children. + /// + /// This leaves us looking at E, which does have a smaller level, + /// and, by construction, it's the most recent thing with smaller + /// level, i.e. it's the immediate parent of H. + fn fold_until(&mut self, level: u32) { + let mut this = None; + loop { + match self.chain.pop() { + Some(mut next) => { + this.map(|e| next.children.entries.push(e)); + if next.level < level { + // this is the parent we want, so return it to + // its rightful place. + self.chain.push(next); + return + } else { + this = Some(next); + } + } + None => { + this.map(|e| self.top_level.entries.push(e)); + return + } + } + } + } + + /// Push a level `level` heading into the appropriate place in the + /// hierarchy, returning a string containing the section number in + /// `..` format. + pub fn push<'a>(&'a mut self, level: u32, name: String, id: String) -> &'a str { + assert!(level >= 1); + + // collapse all previous sections into their parents until we + // get to relevant heading (i.e. the first one with a smaller + // level than us) + self.fold_until(level); + + let mut sec_number; + { + let (toc_level, toc) = match self.chain.last() { + None => { + sec_number = String::new(); + (0, &self.top_level) + } + Some(entry) => { + sec_number = String::from_str(&entry.sec_number); + sec_number.push_str("."); + (entry.level, &entry.children) + } + }; + // fill in any missing zeros, e.g. for + // # Foo (1) + // ### Bar (1.0.1) + for _ in toc_level..level - 1 { + sec_number.push_str("0."); + } + let number = toc.count_entries_with_level(level); + sec_number.push_str(&format!("{}", number + 1)) + } + + self.chain.push(TocEntry { + level: level, + name: name, + sec_number: sec_number, + id: id, + children: Toc { entries: Vec::new() } + }); + + // get the thing we just pushed, so we can borrow the string + // out of it with the right lifetime + let just_inserted = self.chain.last_mut().unwrap(); + &just_inserted.sec_number + } +} + +impl fmt::Debug for Toc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for Toc { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + try!(write!(fmt, "
    ")); + for entry in &self.entries { + // recursively format this table of contents (the + // `{children}` is the key). + try!(write!(fmt, + "\n
  • {num} {name}{children}
  • ", + id = entry.id, + num = entry.sec_number, name = entry.name, + children = entry.children)) + } + write!(fmt, "
") + } +} + +#[cfg(test)] +mod tests { + use super::{TocBuilder, Toc, TocEntry}; + + #[test] + fn builder_smoke() { + let mut builder = TocBuilder::new(); + + // this is purposely not using a fancy macro like below so + // that we're sure that this is doing the correct thing, and + // there's been no macro mistake. + macro_rules! push { + ($level: expr, $name: expr) => { + assert_eq!(builder.push($level, + $name.to_string(), + "".to_string()), + $name); + } + } + push!(2, "0.1"); + push!(1, "1"); + { + push!(2, "1.1"); + { + push!(3, "1.1.1"); + push!(3, "1.1.2"); + } + push!(2, "1.2"); + { + push!(3, "1.2.1"); + push!(3, "1.2.2"); + } + } + push!(1, "2"); + push!(1, "3"); + { + push!(4, "3.0.0.1"); + { + push!(6, "3.0.0.1.0.1"); + } + push!(4, "3.0.0.2"); + push!(2, "3.1"); + { + push!(4, "3.1.0.1"); + } + } + + macro_rules! toc { + ($(($level: expr, $name: expr, $(($sub: tt))* )),*) => { + Toc { + entries: vec!( + $( + TocEntry { + level: $level, + name: $name.to_string(), + sec_number: $name.to_string(), + id: "".to_string(), + children: toc!($($sub),*) + } + ),* + ) + } + } + } + let expected = toc!( + (2, "0.1", ), + + (1, "1", + ((2, "1.1", ((3, "1.1.1", )) ((3, "1.1.2", )))) + ((2, "1.2", ((3, "1.2.1", )) ((3, "1.2.2", )))) + ), + + (1, "2", ), + + (1, "3", + ((4, "3.0.0.1", ((6, "3.0.0.1.0.1", )))) + ((4, "3.0.0.2", )) + ((2, "3.1", ((4, "3.1.0.1", )))) + ) + ); + assert_eq!(expected, builder.into_toc()); + } +} diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 38cc120698431..753a53e799c50 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -86,6 +86,7 @@ pub mod html { pub mod render; pub mod toc; } +pub mod ascii; pub mod markdown; pub mod passes; pub mod plugins; @@ -143,7 +144,7 @@ pub fn opts() -> Vec { optopt("r", "input-format", "the input type of the specified file", "[rust|json]"), optopt("w", "output-format", "the output type to write", - "[html|json]"), + "[html|json|ascii]"), optopt("o", "output", "where to place the output", "PATH"), optopt("", "crate-name", "specify the name of this crate", "NAME"), optmulti("L", "library-path", "directory to add to crate search path", @@ -297,6 +298,14 @@ pub fn main_args(args: &[String]) -> isize { Err(e) => panic!("failed to write json: {}", e), } } + Some("ascii") => { + match ascii::render::run(krate, &external_html, + output.unwrap_or(PathBuf::from("doc")), + passes.into_iter().collect()) { + Ok(()) => {} + Err(e) => panic!("failed to generate documentation: {}", e), + } + } Some(s) => { println!("unknown output format: {}", s); return 1;