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.

C
Corey Richardson 已提交
11
// NOTE: remove after snapshot
A
Alex Crichton 已提交
12
#[pkgid = "rustdoc#0.9-pre"];
C
Corey Richardson 已提交
13
#[crate_id = "rustdoc#0.9-pre"];
14
#[desc = "rustdoc, the Rust documentation extractor"];
15
#[license = "MIT/ASL2"];
16
#[crate_type = "dylib"];
17

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

20
extern mod syntax;
21 22
extern mod rustc;
extern mod extra;
23

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

pub mod clean;
pub mod core;
pub mod doctree;
39
pub mod fold;
40
pub mod html {
41 42
    pub mod escape;
    pub mod format;
43 44
    pub mod layout;
    pub mod markdown;
45
    pub mod render;
46 47 48 49 50
}
pub mod passes;
pub mod plugins;
pub mod visit_ast;

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

53 54 55 56 57 58 59 60 61 62 63
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"),
64 65
    ("strip-private", passes::strip_private,
     "strips all private items from a crate which cannot be seen externally"),
66 67 68
];

static DEFAULT_PASSES: &'static [&'static str] = &[
69
    "strip-hidden",
70
    "strip-private",
71 72
    "collapse-docs",
    "unindent-comments",
73 74
];

75
local_data_key!(pub ctxtkey: @core::DocContext)
76
local_data_key!(pub analysiskey: core::CrateAnalysis)
77

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

80
pub fn main() {
B
Brian Anderson 已提交
81
    std::os::set_exit_status(main_args(std::os::args()));
82 83 84 85 86
}

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

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

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

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

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

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

    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 已提交
188
    let mut default_passes = !matches.opt_present("no-defaults");
189 190
    let mut passes = matches.opt_strs("passes");
    let mut plugins = matches.opt_strs("plugins");
B
Brian Anderson 已提交
191

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

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

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

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

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

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

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

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