(index<- )        ./librustdoc/lib.rs

    git branch:    * master           5200215 auto merge of #14035 : alexcrichton/rust/experimental, r=huonw
    modified:    Fri May  9 13:02:28 2014
   1  // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
   2  // file at the top-level directory of this distribution and at
   3  // http://rust-lang.org/COPYRIGHT.
   4  //
   5  // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
   6  // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
   7  // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
   8  // option. This file may not be copied, modified, or distributed
   9  // except according to those terms.
  10  
  11  #![crate_id = "rustdoc#0.11-pre"]
  12  #![desc = "rustdoc, the Rust documentation extractor"]
  13  #![license = "MIT/ASL2"]
  14  #![crate_type = "dylib"]
  15  #![crate_type = "rlib"]
  16  
  17  #![feature(globs, struct_variant, managed_boxes, macro_rules, phase)]
  18  
  19  extern crate syntax;
  20  extern crate rustc;
  21  extern crate serialize;
  22  extern crate sync;
  23  extern crate getopts;
  24  extern crate collections;
  25  extern crate testing = "test";
  26  extern crate time;
  27  #[phase(syntax, link)]
  28  extern crate log;
  29  extern crate libc;
  30  
  31  use std::io;
  32  use std::io::{File, MemWriter};
  33  use std::str;
  34  use serialize::{json, Decodable, Encodable};
  35  
  36  // reexported from `clean` so it can be easily updated with the mod itself
  37  pub use clean::SCHEMA_VERSION;
  38  
  39  pub mod clean;
  40  pub mod core;
  41  pub mod doctree;
  42  pub mod fold;
  43  pub mod html {
  44      pub mod highlight;
  45      pub mod escape;
  46      pub mod item_type;
  47      pub mod format;
  48      pub mod layout;
  49      pub mod markdown;
  50      pub mod render;
  51      pub mod toc;
  52  }
  53  pub mod markdown;
  54  pub mod passes;
  55  pub mod plugins;
  56  pub mod visit_ast;
  57  pub mod test;
  58  mod flock;
  59  
  60  type Pass = (&'static str,                                      // name
  61               fn(clean::Crate) -> plugins::PluginResult,         // fn
  62               &'static str);                                     // description
  63  
  64  static PASSES: &'static [Pass] = &[
  65      ("strip-hidden", passes::strip_hidden,
  66       "strips all doc(hidden) items from the output"),
  67      ("unindent-comments", passes::unindent_comments,
  68       "removes excess indentation on comments in order for markdown to like it"),
  69      ("collapse-docs", passes::collapse_docs,
  70       "concatenates all document attributes into one document attribute"),
  71      ("strip-private", passes::strip_private,
  72       "strips all private items from a crate which cannot be seen externally"),
  73  ];
  74  
  75  static DEFAULT_PASSES: &'static [&'static str] = &[
  76      "strip-hidden",
  77      "strip-private",
  78      "collapse-docs",
  79      "unindent-comments",
  80  ];
  81  
  82  local_data_key!(pub ctxtkey: @core::DocContext)
  83  local_data_key!(pub analysiskey: core::CrateAnalysis)
  84  
  85  type Output = (clean::Crate, Vec<plugins::PluginJson> );
  86  
  87  pub fn main() {
  88      std::os::set_exit_status(main_args(std::os::args().as_slice()));
  89  }
  90  
  91  pub fn opts() -> Vec<getopts::OptGroup> {
  92      use getopts::*;
  93      vec!(
  94          optflag("h", "help", "show this help message"),
  95          optflag("", "version", "print rustdoc's version"),
  96          optopt("r", "input-format", "the input type of the specified file",
  97                 "[rust|json]"),
  98          optopt("w", "output-format", "the output type to write",
  99                 "[html|json]"),
 100          optopt("o", "output", "where to place the output", "PATH"),
 101          optmulti("L", "library-path", "directory to add to crate search path",
 102                   "DIR"),
 103          optmulti("", "cfg", "pass a --cfg to rustc", ""),
 104          optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
 105          optmulti("", "passes", "space separated list of passes to also run, a \
 106                                  value of `list` will print available passes",
 107                   "PASSES"),
 108          optmulti("", "plugins", "space separated list of plugins to also load",
 109                   "PLUGINS"),
 110          optflag("", "no-defaults", "don't run the default passes"),
 111          optflag("", "test", "run code examples as tests"),
 112          optmulti("", "test-args", "arguments to pass to the test runner",
 113                   "ARGS"),
 114          optmulti("", "markdown-css", "CSS files to include via <link> in a rendered Markdown file",
 115                   "FILES"),
 116          optmulti("", "markdown-in-header",
 117                   "files to include inline in the <head> section of a rendered Markdown file",
 118                   "FILES"),
 119          optmulti("", "markdown-before-content",
 120                   "files to include inline between <body> and the content of a rendered \
 121                   Markdown file",
 122                   "FILES"),
 123          optmulti("", "markdown-after-content",
 124                   "files to include inline between the content and </body> of a rendered \
 125                   Markdown file",
 126                   "FILES")
 127      )
 128  }
 129  
 130  pub fn usage(argv0: &str) {
 131      println!("{}",
 132               getopts::usage(format!("{} [options] <input>", argv0),
 133                              opts().as_slice()));
 134  }
 135  
 136  pub fn main_args(args: &[~str]) -> int {
 137      let matches = match getopts::getopts(args.tail(), opts().as_slice()) {
 138          Ok(m) => m,
 139          Err(err) => {
 140              println!("{}", err.to_err_msg());
 141              return 1;
 142          }
 143      };
 144      if matches.opt_present("h") || matches.opt_present("help") {
 145          usage(args[0]);
 146          return 0;
 147      } else if matches.opt_present("version") {
 148          rustc::version(args[0]);
 149          return 0;
 150      }
 151  
 152      if matches.free.len() == 0 {
 153          println!("expected an input file to act on");
 154          return 1;
 155      } if matches.free.len() > 1 {
 156          println!("only one input file may be specified");
 157          return 1;
 158      }
 159      let input = matches.free.get(0).as_slice();
 160  
 161      let libs = matches.opt_strs("L").iter().map(|s| Path::new(s.as_slice())).collect();
 162  
 163      let test_args = matches.opt_strs("test-args");
 164      let test_argsVec<~str> = test_args.iter()
 165                                          .flat_map(|s| s.words())
 166                                          .map(|s| s.to_owned())
 167                                          .collect();
 168  
 169      let should_test = matches.opt_present("test");
 170      let markdown_input = input.ends_with(".md") || input.ends_with(".markdown");
 171  
 172      let output = matches.opt_str("o").map(|s| Path::new(s));
 173      let cfgs = matches.opt_strs("cfg");
 174  
 175      match (should_test, markdown_input) {
 176          (true, true) => {
 177              return markdown::test(input,
 178                                    libs,
 179                                    test_args.move_iter().collect())
 180          }
 181          (true, false) => return test::run(input, cfgs.move_iter().collect(),
 182                                            libs, test_args),
 183  
 184          (false, true) => return markdown::render(input, output.unwrap_or(Path::new("doc")),
 185                                                   &matches),
 186          (false, false) => {}
 187      }
 188  
 189      if matches.opt_strs("passes").as_slice() == &["list".to_owned()] {
 190          println!("Available passes for running rustdoc:");
 191          for &(name, _, description) in PASSES.iter() {
 192              println!("{:>20s} - {}", name, description);
 193          }
 194          println!("{}", "\nDefault passes for rustdoc:"); // FIXME: #9970
 195          for &name in DEFAULT_PASSES.iter() {
 196              println!("{:>20s}", name);
 197          }
 198          return 0;
 199      }
 200  
 201      let (krate, res) = match acquire_input(input, &matches) {
 202          Ok(pair) => pair,
 203          Err(s) => {
 204              println!("input error: {}", s);
 205              return 1;
 206          }
 207      };
 208  
 209      info!("going to format");
 210      let started = time::precise_time_ns();
 211      match matches.opt_str("w").as_ref().map(|s| s.as_slice()) {
 212          Some("html") | None => {
 213              match html::render::run(krate, output.unwrap_or(Path::new("doc"))) {
 214                  Ok(()) => {}
 215                  Err(e) => fail!("failed to generate documentation: {}", e),
 216              }
 217          }
 218          Some("json") => {
 219              match json_output(krate, res, output.unwrap_or(Path::new("doc.json"))) {
 220                  Ok(()) => {}
 221                  Err(e) => fail!("failed to write json: {}", e),
 222              }
 223          }
 224          Some(s) => {
 225              println!("unknown output format: {}", s);
 226              return 1;
 227          }
 228      }
 229      let ended = time::precise_time_ns();
 230      info!("Took {:.03f}s", (ended as f64 - started as f64) / 1e9f64);
 231  
 232      return 0;
 233  }
 234  
 235  /// Looks inside the command line arguments to extract the relevant input format
 236  /// and files and then generates the necessary rustdoc output for formatting.
 237  fn acquire_input(input: &str,
 238                   matches: &getopts::Matches) -> Result<Output, ~str> {
 239      match matches.opt_str("r").as_ref().map(|s| s.as_slice()) {
 240          Some("rust") => Ok(rust_input(input, matches)),
 241          Some("json") => json_input(input),
 242          Some(s) => Err("unknown input format: " + s),
 243          None => {
 244              if input.ends_with(".json") {
 245                  json_input(input)
 246              } else {
 247                  Ok(rust_input(input, matches))
 248              }
 249          }
 250      }
 251  }
 252  
 253  /// Interprets the input file as a rust source file, passing it through the
 254  /// compiler all the way through the analysis passes. The rustdoc output is then
 255  /// generated from the cleaned AST of the crate.
 256  ///
 257  /// This form of input will run all of the plug/cleaning passes
 258  fn rust_input(cratefile: &str, matches: &getopts::Matches) -> Output {
 259      let mut default_passes = !matches.opt_present("no-defaults");
 260      let mut passes = matches.opt_strs("passes");
 261      let mut plugins = matches.opt_strs("plugins");
 262  
 263      // First, parse the crate and extract all relevant information.
 264      let libsVec<Path> = matches.opt_strs("L")
 265                                   .iter()
 266                                   .map(|s| Path::new(s.as_slice()))
 267                                   .collect();
 268      let cfgs = matches.opt_strs("cfg");
 269      let cr = Path::new(cratefile);
 270      info!("starting to run rustc");
 271      let (krate, analysis) = std::task::try(proc() {
 272          let cr = cr;
 273          core::run_core(libs.move_iter().collect(),
 274                         cfgs.move_iter().collect(),
 275                         &cr)
 276      }).unwrap();
 277      info!("finished with rustc");
 278      analysiskey.replace(Some(analysis));
 279  
 280      // Process all of the crate attributes, extracting plugin metadata along
 281      // with the passes which we are supposed to run.
 282      match krate.module.get_ref().doc_list() {
 283          Some(nested) => {
 284              for inner in nested.iter() {
 285                  match *inner {
 286                      clean::Word(ref x) if "no_default_passes" == *x => {
 287                          default_passes = false;
 288                      }
 289                      clean::NameValue(ref x, ref value) if "passes" == *x => {
 290                          for pass in value.words() {
 291                              passes.push(pass.to_owned());
 292                          }
 293                      }
 294                      clean::NameValue(ref x, ref value) if "plugins" == *x => {
 295                          for p in value.words() {
 296                              plugins.push(p.to_owned());
 297                          }
 298                      }
 299                      _ => {}
 300                  }
 301              }
 302          }
 303          None => {}
 304      }
 305      if default_passes {
 306          for name in DEFAULT_PASSES.iter().rev() {
 307              passes.unshift(name.to_owned());
 308          }
 309      }
 310  
 311      // Load all plugins/passes into a PluginManager
 312      let path = matches.opt_str("plugin-path").unwrap_or("/tmp/rustdoc/plugins".to_owned());
 313      let mut pm = plugins::PluginManager::new(Path::new(path));
 314      for pass in passes.iter() {
 315          let plugin = match PASSES.iter().position(|&(p, _, _)| p == *pass) {
 316              Some(i) => PASSES[i].val1(),
 317              None => {
 318                  error!("unknown pass {}, skipping", *pass);
 319                  continue
 320              },
 321          };
 322          pm.add_plugin(plugin);
 323      }
 324      info!("loading plugins...");
 325      for pname in plugins.move_iter() {
 326          pm.load_plugin(pname);
 327      }
 328  
 329      // Run everything!
 330      info!("Executing passes/plugins");
 331      return pm.run_plugins(krate);
 332  }
 333  
 334  /// This input format purely deserializes the json output file. No passes are
 335  /// run over the deserialized output.
 336  fn json_input(input: &str) -> Result<Output, ~str> {
 337      let mut input = match File::open(&Path::new(input)) {
 338          Ok(f) => f,
 339          Err(e) => return Err(format!("couldn't open {}{}", input, e)),
 340      };
 341      match json::from_reader(&mut input) {
 342          Err(s) => Err(s.to_str()),
 343          Ok(json::Object(obj)) => {
 344              let mut obj = obj;
 345              // Make sure the schema is what we expect
 346              match obj.pop(&"schema".to_owned()) {
 347                  Some(json::String(version)) => {
 348                      if version.as_slice() != SCHEMA_VERSION {
 349                          return Err(format!("sorry, but I only understand \
 350                                              version {}", SCHEMA_VERSION))
 351                      }
 352                  }
 353                  Some(..) => return Err("malformed json".to_owned()),
 354                  None => return Err("expected a schema version".to_owned()),
 355              }
 356              let krate = match obj.pop(&"crate".to_owned()) {
 357                  Some(json) => {
 358                      let mut d = json::Decoder::new(json);
 359                      Decodable::decode(&mut d).unwrap()
 360                  }
 361                  None => return Err("malformed json".to_owned()),
 362              };
 363              // FIXME: this should read from the "plugins" field, but currently
 364              //      Json doesn't implement decodable...
 365              let plugin_output = Vec::new();
 366              Ok((krate, plugin_output))
 367          }
 368          Ok(..) => Err("malformed json input: expected an object at the top".to_owned()),
 369      }
 370  }
 371  
 372  /// Outputs the crate/plugin json as a giant json blob at the specified
 373  /// destination.
 374  fn json_output(krateclean::Crate, resVec<plugins::PluginJson> ,
 375                 dstPath) -> io::IoResult<()> {
 376      // {
 377      //   "schema": version,
 378      //   "crate": { parsed crate ... },
 379      //   "plugins": { output of plugins ... }
 380      // }
 381      let mut json = box collections::TreeMap::new();
 382      json.insert("schema".to_owned(), json::String(SCHEMA_VERSION.to_owned()));
 383      let plugins_json = box res.move_iter().filter_map(|opt| opt).collect();
 384  
 385      // FIXME #8335: yuck, Rust -> str -> JSON round trip! No way to .encode
 386      // straight to the Rust JSON representation.
 387      let crate_json_str = {
 388          let mut w = MemWriter::new();
 389          {
 390              let mut encoder = json::Encoder::new(&mut w as &mut io::Writer);
 391              krate.encode(&mut encoder).unwrap();
 392          }
 393          str::from_utf8(w.unwrap().as_slice()).unwrap().to_owned()
 394      };
 395      let crate_json = match json::from_str(crate_json_str) {
 396          Ok(j) => j,
 397          Err(e) => fail!("Rust generated JSON is invalid: {:?}", e)
 398      };
 399  
 400      json.insert("crate".to_owned(), crate_json);
 401      json.insert("plugins".to_owned(), json::Object(plugins_json));
 402  
 403      let mut file = try!(File::create(&dst));
 404      try!(json::Object(json).to_writer(&mut file));
 405      Ok(())
 406  }


librustdoc/lib.rs:335:38-335:38 -fn- definition:
/// run over the deserialized output.
fn json_input(input: &str) -> Result<Output, ~str> {
    let mut input = match File::open(&Path::new(input)) {
references:- 2
244:             if input.ends_with(".json") {
245:                 json_input(input)
246:             } else {


librustdoc/lib.rs:90:1-90:1 -fn- definition:
pub fn opts() -> Vec<getopts::OptGroup> {
    use getopts::*;
    vec!(
references:- 2
132:              getopts::usage(format!("{} [options] <input>", argv0),
133:                             opts().as_slice()));
134: }
--
136: pub fn main_args(args: &[~str]) -> int {
137:     let matches = match getopts::getopts(args.tail(), opts().as_slice()) {
138:         Ok(m) => m,


librustdoc/lib.rs:84:1-84:1 -NK_AS_STR_TODO- definition:
type Output = (clean::Crate, Vec<plugins::PluginJson> );
pub fn main() {
    std::os::set_exit_status(main_args(std::os::args().as_slice()));
references:- 3
237: fn acquire_input(input: &str,
238:                  matches: &getopts::Matches) -> Result<Output, ~str> {
239:     match matches.opt_str("r").as_ref().map(|s| s.as_slice()) {
--
257: /// This form of input will run all of the plug/cleaning passes
258: fn rust_input(cratefile: &str, matches: &getopts::Matches) -> Output {
259:     let mut default_passes = !matches.opt_present("no-defaults");
--
335: /// run over the deserialized output.
336: fn json_input(input: &str) -> Result<Output, ~str> {
337:     let mut input = match File::open(&Path::new(input)) {


librustdoc/lib.rs:257:64-257:64 -fn- definition:
/// This form of input will run all of the plug/cleaning passes
fn rust_input(cratefile: &str, matches: &getopts::Matches) -> Output {
    let mut default_passes = !matches.opt_present("no-defaults");
references:- 2
239:     match matches.opt_str("r").as_ref().map(|s| s.as_slice()) {
240:         Some("rust") => Ok(rust_input(input, matches)),
241:         Some("json") => json_input(input),
--
246:             } else {
247:                 Ok(rust_input(input, matches))
248:             }