auto merge of #9636 : alexcrichton/rust/rustdoc, r=huonw

Commits have all the juicy details.

Import thing to note in this pull request is that `rustdoc html crate.rs` becomes `rustdoc crate.rs`
This commit is contained in:
bors
2013-10-01 04:31:31 -07:00
5 changed files with 298 additions and 117 deletions

View File

@@ -21,13 +21,15 @@ extern mod syntax;
extern mod rustc;
extern mod extra;
use extra::serialize::Encodable;
use extra::time;
use extra::getopts::groups;
use std::cell::Cell;
use std::rt::io;
use std::rt::io::Writer;
use std::rt::io::file::FileInfo;
use std::rt::io;
use extra::getopts;
use extra::getopts::groups;
use extra::json;
use extra::serialize::{Decodable, Encodable};
use extra::time;
pub mod clean;
pub mod core;
@@ -70,9 +72,7 @@ static DEFAULT_PASSES: &'static [&'static str] = &[
local_data_key!(pub ctxtkey: @core::DocContext)
enum OutputFormat {
HTML, JSON
}
type Output = (clean::Crate, ~[plugins::PluginJson]);
pub fn main() {
std::os::set_exit_status(main_args(std::os::args()));
@@ -81,6 +81,12 @@ pub fn main() {
pub fn opts() -> ~[groups::OptGroup] {
use extra::getopts::groups::*;
~[
optflag("h", "help", "show this help message"),
optopt("r", "input-format", "the input type of the specified file",
"[rust|json]"),
optopt("w", "output-format", "the output type to write",
"[html|json]"),
optopt("o", "output", "where to place the output", "PATH"),
optmulti("L", "library-path", "directory to add to crate search path",
"DIR"),
optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
@@ -89,32 +95,22 @@ pub fn opts() -> ~[groups::OptGroup] {
"PASSES"),
optmulti("", "plugins", "space separated list of plugins to also load",
"PLUGINS"),
optflag("h", "help", "show this help message"),
optflag("", "nodefaults", "don't run the default passes"),
optopt("o", "output", "where to place the output", "PATH"),
optflag("", "no-defaults", "don't run the default passes"),
]
}
pub fn usage(argv0: &str) {
println(groups::usage(format!("{} [options] [html|json] <crate>",
argv0), opts()));
println(groups::usage(format!("{} [options] <input>", argv0), opts()));
}
pub fn main_args(args: &[~str]) -> int {
//use extra::getopts::groups::*;
let matches = groups::getopts(args.tail(), opts()).unwrap();
if matches.opt_present("h") || matches.opt_present("help") {
usage(args[0]);
return 0;
}
let mut default_passes = !matches.opt_present("nodefaults");
let mut passes = matches.opt_strs("passes");
let mut plugins = matches.opt_strs("plugins");
if passes == ~[~"list"] {
if matches.opt_strs("passes") == ~[~"list"] {
println("Available passes for running rustdoc:");
for &(name, _, description) in PASSES.iter() {
println!("{:>20s} - {}", name, description);
@@ -126,26 +122,69 @@ pub fn main_args(args: &[~str]) -> int {
return 0;
}
let (format, cratefile) = match matches.free.clone() {
[~"json", crate] => (JSON, crate),
[~"html", crate] => (HTML, crate),
[s, _] => {
println!("Unknown output format: `{}`", s);
usage(args[0]);
return 1;
}
[_, .._] => {
println!("Expected exactly one crate to process");
usage(args[0]);
return 1;
}
_ => {
println!("Expected an output format and then one crate");
usage(args[0]);
let (crate, res) = match acquire_input(&matches) {
Ok(pair) => pair,
Err(s) => {
println!("input error: {}", s);
return 1;
}
};
info2!("going to format");
let started = time::precise_time_ns();
let output = matches.opt_str("o").map(|s| Path(*s));
match matches.opt_str("w") {
Some(~"html") | None => {
html::render::run(crate, output.unwrap_or(Path("doc")))
}
Some(~"json") => {
json_output(crate, res, output.unwrap_or(Path("doc.json")))
}
Some(s) => {
println!("unknown output format: {}", s);
return 1;
}
}
let ended = time::precise_time_ns();
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1e9f64);
return 0;
}
/// Looks inside the command line arguments to extract the relevant input format
/// and files and then generates the necessary rustdoc output for formatting.
fn acquire_input(matches: &getopts::Matches) -> Result<Output, ~str> {
if matches.free.len() == 0 {
return Err(~"expected an input file to act on");
} if matches.free.len() > 1 {
return Err(~"only one input file may be specified");
}
let input = matches.free[0].as_slice();
match matches.opt_str("r") {
Some(~"rust") => Ok(rust_input(input, matches)),
Some(~"json") => json_input(input),
Some(s) => Err("unknown input format: " + s),
None => {
if input.ends_with(".json") {
json_input(input)
} else {
Ok(rust_input(input, matches))
}
}
}
}
/// Interprets the input file as a rust source file, passing it through the
/// compiler all the way through the analysis passes. The rustdoc output is then
/// generated from the cleaned AST of the crate.
///
/// 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");
let mut passes = matches.opt_strs("passes");
let mut plugins = matches.opt_strs("plugins");
// First, parse the crate and extract all relevant information.
let libs = Cell::new(matches.opt_strs("L").map(|s| Path(*s)));
let cr = Cell::new(Path(cratefile));
@@ -188,7 +227,8 @@ pub fn main_args(args: &[~str]) -> int {
}
// Load all plugins/passes into a PluginManager
let mut pm = plugins::PluginManager::new(Path("/tmp/rustdoc_ng/plugins"));
let path = matches.opt_str("plugin-path").unwrap_or(~"/tmp/rustdoc_ng/plugins");
let mut pm = plugins::PluginManager::new(Path(path));
for pass in passes.iter() {
let plugin = match PASSES.iter().position(|&(p, _, _)| p == *pass) {
Some(i) => PASSES[i].n1(),
@@ -206,45 +246,73 @@ pub fn main_args(args: &[~str]) -> int {
// Run everything!
info2!("Executing passes/plugins");
let (crate, res) = pm.run_plugins(crate);
info2!("going to format");
let started = time::precise_time_ns();
let output = matches.opt_str("o").map(|s| Path(*s));
match format {
HTML => { html::render::run(crate, output.unwrap_or(Path("doc"))) }
JSON => { jsonify(crate, res, output.unwrap_or(Path("doc.json"))) }
}
let ended = time::precise_time_ns();
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1000000000f64);
return 0;
return pm.run_plugins(crate);
}
fn jsonify(crate: clean::Crate, res: ~[plugins::PluginJson], dst: Path) {
/// This input format purely deserializes the json output file. No passes are
/// run over the deserialized output.
fn json_input(input: &str) -> Result<Output, ~str> {
let input = match ::std::io::file_reader(&Path(input)) {
Ok(i) => i,
Err(s) => return Err(s),
};
match json::from_reader(input) {
Err(s) => Err(s.to_str()),
Ok(json::Object(obj)) => {
let mut obj = obj;
// Make sure the schema is what we expect
match obj.pop(&~"schema") {
Some(json::String(version)) => {
if version.as_slice() != SCHEMA_VERSION {
return Err(format!("sorry, but I only understand \
version {}", SCHEMA_VERSION))
}
}
Some(*) => return Err(~"malformed json"),
None => return Err(~"expected a schema version"),
}
let crate = match obj.pop(&~"crate") {
Some(json) => {
let mut d = json::Decoder(json);
Decodable::decode(&mut d)
}
None => return Err(~"malformed json"),
};
// XXX: this should read from the "plugins" field, but currently
// Json doesn't implement decodable...
let plugin_output = ~[];
Ok((crate, plugin_output))
}
Ok(*) => Err(~"malformed json input: expected an object at the top"),
}
}
/// Outputs the crate/plugin json as a giant json blob at the specified
/// destination.
fn json_output(crate: clean::Crate, res: ~[plugins::PluginJson], dst: Path) {
// {
// "schema": version,
// "crate": { parsed crate ... },
// "plugins": { output of plugins ... }
// }
let mut json = ~extra::treemap::TreeMap::new();
json.insert(~"schema", extra::json::String(SCHEMA_VERSION.to_owned()));
json.insert(~"schema", json::String(SCHEMA_VERSION.to_owned()));
let plugins_json = ~res.move_iter().filter_map(|opt| opt).collect();
// FIXME #8335: yuck, Rust -> str -> JSON round trip! No way to .encode
// straight to the Rust JSON representation.
let crate_json_str = do std::io::with_str_writer |w| {
crate.encode(&mut extra::json::Encoder(w));
crate.encode(&mut json::Encoder(w));
};
let crate_json = match extra::json::from_str(crate_json_str) {
let crate_json = match json::from_str(crate_json_str) {
Ok(j) => j,
Err(_) => fail2!("Rust generated JSON is invalid??")
};
json.insert(~"crate", crate_json);
json.insert(~"plugins", extra::json::Object(plugins_json));
json.insert(~"plugins", json::Object(plugins_json));
let mut file = dst.open_writer(io::Create).unwrap();
let output = extra::json::Object(json).to_str();
let output = json::Object(json).to_str();
file.write(output.as_bytes());
}