lib.rs 11.4 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 = "dylib"];
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
        optmulti("L", "library-path", "directory to add to crate search path",
                 "DIR"),
99
        optmulti("", "cfg", "pass a --cfg to rustc", ""),
100
        optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
101 102
        optmulti("", "passes", "space separated list of passes to also run, a \
                                value of `list` will print available passes",
103 104 105
                 "PASSES"),
        optmulti("", "plugins", "space separated list of plugins to also load",
                 "PLUGINS"),
A
Alex Crichton 已提交
106
        optflag("", "no-defaults", "don't run the default passes"),
107 108 109 110
    ]
}

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

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

121
    if matches.opt_strs("passes") == ~[~"list"] {
122 123 124 125 126 127 128 129
        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 已提交
130
        return 0;
131 132
    }

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

141
    info!("going to format");
142
    let started = time::precise_time_ns();
143
    let output = matches.opt_str("o").map(|s| Path::new(s));
144 145
    match matches.opt_str("w") {
        Some(~"html") | None => {
146
            html::render::run(crate, output.unwrap_or(Path::new("doc")))
147
        }
148
        Some(~"json") => {
149
            json_output(crate, res, output.unwrap_or(Path::new("doc.json")))
150
        }
151 152
        Some(s) => {
            println!("unknown output format: {}", s);
B
Brian Anderson 已提交
153
            return 1;
154
        }
155 156
    }
    let ended = time::precise_time_ns();
157
    info!("Took {:.03f}s", (ended as f64 - started as f64) / 1e9f64);
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 191

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

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

    // 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) => {
218
                        for pass in value.words() {
219 220 221 222
                            passes.push(pass.to_owned());
                        }
                    }
                    clean::NameValue(~"plugins", ref value) => {
223
                        for p in value.words() {
224 225 226 227 228 229 230 231 232 233
                            plugins.push(p.to_owned());
                        }
                    }
                    _ => {}
                }
            }
        }
        None => {}
    }
    if default_passes {
234 235 236
        for name in DEFAULT_PASSES.rev_iter() {
            passes.unshift(name.to_owned());
        }
237
    }
B
Brian Anderson 已提交
238

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

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

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

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

327
    json.insert(~"crate", crate_json);
328
    json.insert(~"plugins", json::Object(plugins_json));
329

330 331
    let file = @mut File::create(&dst).unwrap();
    json::Object(json).to_writer(file as @mut io::Writer);
B
Brian Anderson 已提交
332
}