lib.rs 11.2 KB
Newer Older
S
sevrak 已提交
1
// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 3 4 5 6 7 8 9 10
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

B
Brian Anderson 已提交
11
#[link(name = "rustdoc",
12
       package_id = "rustdoc",
13
       vers = "0.9-pre",
14 15
       uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6",
       url = "https://github.com/mozilla/rust/tree/master/src/librustdoc")];
B
Brian Anderson 已提交
16

17
#[desc = "rustdoc, the Rust documentation extractor"];
18
#[license = "MIT/ASL2"];
19
#[crate_type = "lib"];
20

D
Daniel Micay 已提交
21
#[feature(globs, struct_variant, managed_boxes)];
22

23
extern mod syntax;
24 25
extern mod rustc;
extern mod extra;
26

27
use std::cell::Cell;
28
use std::local_data;
A
Alex Crichton 已提交
29 30 31 32
use std::io;
use std::io::File;
use std::io::mem::MemWriter;
use std::io::Decorator;
A
Alex Crichton 已提交
33
use std::str;
34 35 36 37 38
use extra::getopts;
use extra::getopts::groups;
use extra::json;
use extra::serialize::{Decodable, Encodable};
use extra::time;
39 40 41 42

pub mod clean;
pub mod core;
pub mod doctree;
43
pub mod fold;
44
pub mod html {
45 46
    pub mod escape;
    pub mod format;
47 48
    pub mod layout;
    pub mod markdown;
49
    pub mod render;
50 51 52 53 54
}
pub mod passes;
pub mod plugins;
pub mod visit_ast;

S
Steven Fackler 已提交
55
pub static SCHEMA_VERSION: &'static str = "0.8.1";
56

57 58 59 60 61 62 63 64 65 66 67
type Pass = (&'static str,                                      // name
             extern fn(clean::Crate) -> plugins::PluginResult,  // fn
             &'static str);                                     // description

static PASSES: &'static [Pass] = &[
    ("strip-hidden", passes::strip_hidden,
     "strips all doc(hidden) items from the output"),
    ("unindent-comments", passes::unindent_comments,
     "removes excess indentation on comments in order for markdown to like it"),
    ("collapse-docs", passes::collapse_docs,
     "concatenates all document attributes into one document attribute"),
68 69
    ("strip-private", passes::strip_private,
     "strips all private items from a crate which cannot be seen externally"),
70 71 72
];

static DEFAULT_PASSES: &'static [&'static str] = &[
73
    "strip-hidden",
74
    "strip-private",
75 76
    "collapse-docs",
    "unindent-comments",
77 78
];

79
local_data_key!(pub ctxtkey: @core::DocContext)
80
local_data_key!(pub analysiskey: core::CrateAnalysis)
81

82
type Output = (clean::Crate, ~[plugins::PluginJson]);
B
Brian Anderson 已提交
83

84
pub fn main() {
B
Brian Anderson 已提交
85
    std::os::set_exit_status(main_args(std::os::args()));
86 87 88 89 90
}

pub fn opts() -> ~[groups::OptGroup] {
    use extra::getopts::groups::*;
    ~[
91 92 93 94 95 96
        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"),
97 98 99
        optmulti("L", "library-path", "directory to add to crate search path",
                 "DIR"),
        optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
100 101
        optmulti("", "passes", "space separated list of passes to also run, a \
                                value of `list` will print available passes",
102 103 104
                 "PASSES"),
        optmulti("", "plugins", "space separated list of plugins to also load",
                 "PLUGINS"),
A
Alex Crichton 已提交
105
        optflag("", "no-defaults", "don't run the default passes"),
106 107 108 109
    ]
}

pub fn usage(argv0: &str) {
110
    println(groups::usage(format!("{} [options] <input>", argv0), opts()));
111
}
B
Brian Anderson 已提交
112

B
Brian Anderson 已提交
113
pub fn main_args(args: &[~str]) -> int {
114 115 116
    let matches = groups::getopts(args.tail(), opts()).unwrap();
    if matches.opt_present("h") || matches.opt_present("help") {
        usage(args[0]);
B
Brian Anderson 已提交
117
        return 0;
B
Brian Anderson 已提交
118 119
    }

120
    if matches.opt_strs("passes") == ~[~"list"] {
121 122 123 124 125 126 127 128
        println("Available passes for running rustdoc:");
        for &(name, _, description) in PASSES.iter() {
            println!("{:>20s} - {}", name, description);
        }
        println("\nDefault passes for rustdoc:");
        for &name in DEFAULT_PASSES.iter() {
            println!("{:>20s}", name);
        }
A
Alex Crichton 已提交
129
        return 0;
130 131
    }

132 133 134 135
    let (crate, res) = match acquire_input(&matches) {
        Ok(pair) => pair,
        Err(s) => {
            println!("input error: {}", s);
B
Brian Anderson 已提交
136
            return 1;
137
        }
138 139
    };

140
    info!("going to format");
141
    let started = time::precise_time_ns();
142
    let output = matches.opt_str("o").map(|s| Path::new(s));
143 144
    match matches.opt_str("w") {
        Some(~"html") | None => {
145
            html::render::run(crate, output.unwrap_or(Path::new("doc")))
146
        }
147
        Some(~"json") => {
148
            json_output(crate, res, output.unwrap_or(Path::new("doc.json")))
149
        }
150 151
        Some(s) => {
            println!("unknown output format: {}", s);
B
Brian Anderson 已提交
152
            return 1;
153
        }
154 155
    }
    let ended = time::precise_time_ns();
156
    info!("Took {:.03f}s", (ended as f64 - started as f64) / 1e9f64);
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

    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 {
A
Alex Crichton 已提交
191
    let mut default_passes = !matches.opt_present("no-defaults");
192 193
    let mut passes = matches.opt_strs("passes");
    let mut plugins = matches.opt_strs("plugins");
B
Brian Anderson 已提交
194

195
    // First, parse the crate and extract all relevant information.
196 197
    let libs = Cell::new(matches.opt_strs("L").map(|s| Path::new(s.as_slice())));
    let cr = Cell::new(Path::new(cratefile));
198
    info!("starting to run rustc");
199
    let (crate, analysis) = do std::task::try {
200
        let cr = cr.take();
201
        core::run_core(libs.take().move_iter().collect(), &cr)
202
    }.unwrap();
203
    info!("finished with rustc");
204
    local_data::set(analysiskey, analysis);
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

    // Process all of the crate attributes, extracting plugin metadata along
    // with the passes which we are supposed to run.
    match crate.module.get_ref().doc_list() {
        Some(nested) => {
            for inner in nested.iter() {
                match *inner {
                    clean::Word(~"no_default_passes") => {
                        default_passes = false;
                    }
                    clean::NameValue(~"passes", ref value) => {
                        for pass in value.word_iter() {
                            passes.push(pass.to_owned());
                        }
                    }
                    clean::NameValue(~"plugins", ref value) => {
                        for p in value.word_iter() {
                            plugins.push(p.to_owned());
                        }
                    }
                    _ => {}
                }
            }
        }
        None => {}
    }
    if default_passes {
232 233 234
        for name in DEFAULT_PASSES.rev_iter() {
            passes.unshift(name.to_owned());
        }
235
    }
B
Brian Anderson 已提交
236

237
    // Load all plugins/passes into a PluginManager
A
Alex Crichton 已提交
238
    let path = matches.opt_str("plugin-path").unwrap_or(~"/tmp/rustdoc_ng/plugins");
239
    let mut pm = plugins::PluginManager::new(Path::new(path));
240
    for pass in passes.iter() {
241 242 243
        let plugin = match PASSES.iter().position(|&(p, _, _)| p == *pass) {
            Some(i) => PASSES[i].n1(),
            None => {
244
                error!("unknown pass {}, skipping", *pass);
245
                continue
246
            },
247 248 249
        };
        pm.add_plugin(plugin);
    }
250
    info!("loading plugins...");
251 252 253
    for pname in plugins.move_iter() {
        pm.load_plugin(pname);
    }
B
Brian Anderson 已提交
254

255
    // Run everything!
256
    info!("Executing passes/plugins");
257 258
    return pm.run_plugins(crate);
}
B
Brian Anderson 已提交
259

260 261 262
/// 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> {
263
    let input = match File::open(&Path::new(input)) {
A
Alex Crichton 已提交
264 265
        Some(f) => f,
        None => return Err(format!("couldn't open {} for reading", input)),
266
    };
A
Alex Crichton 已提交
267
    match json::from_reader(@mut input as @mut io::Reader) {
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
        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"),
295 296
    }
}
B
Brian Anderson 已提交
297

298 299 300
/// 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) {
301 302 303 304 305 306
    // {
    //   "schema": version,
    //   "crate": { parsed crate ... },
    //   "plugins": { output of plugins ... }
    // }
    let mut json = ~extra::treemap::TreeMap::new();
307
    json.insert(~"schema", json::String(SCHEMA_VERSION.to_owned()));
308 309 310 311
    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.
A
Alex Crichton 已提交
312 313 314 315
    let crate_json_str = {
        let w = @mut MemWriter::new();
        crate.encode(&mut json::Encoder(w as @mut io::Writer));
        str::from_utf8(*w.inner_ref())
316
    };
317
    let crate_json = match json::from_str(crate_json_str) {
318
        Ok(j) => j,
319
        Err(_) => fail!("Rust generated JSON is invalid??")
320
    };
B
Brian Anderson 已提交
321

322
    json.insert(~"crate", crate_json);
323
    json.insert(~"plugins", json::Object(plugins_json));
324

325
    let mut file = File::create(&dst).unwrap();
326
    let output = json::Object(json).to_str();
327
    file.write(output.as_bytes());
B
Brian Anderson 已提交
328
}