]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/lib.rs
7cab7846f15b85b95fc31c0701e14988c370e9b0
[rust.git] / src / librustdoc / lib.rs
1 // Copyright 2012-2013 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 #[link(name = "rustdoc",
12        package_id = "rustdoc",
13        vers = "0.9-pre",
14        uuid = "8c6e4598-1596-4aa5-a24c-b811914bbbc6",
15        url = "https://github.com/mozilla/rust/tree/master/src/librustdoc")];
16
17 #[desc = "rustdoc, the Rust documentation extractor"];
18 #[license = "MIT/ASL2"];
19 #[crate_type = "lib"]; // NOTE: remove after stage0 snapshot
20 #[crate_type = "dylib"];
21
22 #[feature(globs, struct_variant, managed_boxes)];
23
24 extern mod syntax;
25 extern mod rustc;
26 extern mod extra;
27
28 use std::cell::Cell;
29 use std::local_data;
30 use std::io;
31 use std::io::File;
32 use std::io::mem::MemWriter;
33 use std::io::Decorator;
34 use std::str;
35 use extra::getopts;
36 use extra::getopts::groups;
37 use extra::json;
38 use extra::serialize::{Decodable, Encodable};
39 use extra::time;
40
41 pub mod clean;
42 pub mod core;
43 pub mod doctree;
44 pub mod fold;
45 pub mod html {
46     pub mod escape;
47     pub mod format;
48     pub mod layout;
49     pub mod markdown;
50     pub mod render;
51 }
52 pub mod passes;
53 pub mod plugins;
54 pub mod visit_ast;
55
56 pub static SCHEMA_VERSION: &'static str = "0.8.1";
57
58 type Pass = (&'static str,                                      // name
59              extern fn(clean::Crate) -> plugins::PluginResult,  // fn
60              &'static str);                                     // description
61
62 static PASSES: &'static [Pass] = &[
63     ("strip-hidden", passes::strip_hidden,
64      "strips all doc(hidden) items from the output"),
65     ("unindent-comments", passes::unindent_comments,
66      "removes excess indentation on comments in order for markdown to like it"),
67     ("collapse-docs", passes::collapse_docs,
68      "concatenates all document attributes into one document attribute"),
69     ("strip-private", passes::strip_private,
70      "strips all private items from a crate which cannot be seen externally"),
71 ];
72
73 static DEFAULT_PASSES: &'static [&'static str] = &[
74     "strip-hidden",
75     "strip-private",
76     "collapse-docs",
77     "unindent-comments",
78 ];
79
80 local_data_key!(pub ctxtkey: @core::DocContext)
81 local_data_key!(pub analysiskey: core::CrateAnalysis)
82
83 type Output = (clean::Crate, ~[plugins::PluginJson]);
84
85 pub fn main() {
86     std::os::set_exit_status(main_args(std::os::args()));
87 }
88
89 pub fn opts() -> ~[groups::OptGroup] {
90     use extra::getopts::groups::*;
91     ~[
92         optflag("h", "help", "show this help message"),
93         optopt("r", "input-format", "the input type of the specified file",
94                "[rust|json]"),
95         optopt("w", "output-format", "the output type to write",
96                "[html|json]"),
97         optopt("o", "output", "where to place the output", "PATH"),
98         optmulti("L", "library-path", "directory to add to crate search path",
99                  "DIR"),
100         optmulti("", "cfg", "pass a --cfg to rustc", ""),
101         optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
102         optmulti("", "passes", "space separated list of passes to also run, a \
103                                 value of `list` will print available passes",
104                  "PASSES"),
105         optmulti("", "plugins", "space separated list of plugins to also load",
106                  "PLUGINS"),
107         optflag("", "no-defaults", "don't run the default passes"),
108     ]
109 }
110
111 pub fn usage(argv0: &str) {
112     println(groups::usage(format!("{} [options] <input>", argv0), opts()));
113 }
114
115 pub fn main_args(args: &[~str]) -> int {
116     let matches = groups::getopts(args.tail(), opts()).unwrap();
117     if matches.opt_present("h") || matches.opt_present("help") {
118         usage(args[0]);
119         return 0;
120     }
121
122     if matches.opt_strs("passes") == ~[~"list"] {
123         println("Available passes for running rustdoc:");
124         for &(name, _, description) in PASSES.iter() {
125             println!("{:>20s} - {}", name, description);
126         }
127         println("\nDefault passes for rustdoc:");
128         for &name in DEFAULT_PASSES.iter() {
129             println!("{:>20s}", name);
130         }
131         return 0;
132     }
133
134     let (crate, res) = match acquire_input(&matches) {
135         Ok(pair) => pair,
136         Err(s) => {
137             println!("input error: {}", s);
138             return 1;
139         }
140     };
141
142     info!("going to format");
143     let started = time::precise_time_ns();
144     let output = matches.opt_str("o").map(|s| Path::init(s));
145     match matches.opt_str("w") {
146         Some(~"html") | None => {
147             html::render::run(crate, output.unwrap_or(Path::init("doc")))
148         }
149         Some(~"json") => {
150             json_output(crate, res, output.unwrap_or(Path::init("doc.json")))
151         }
152         Some(s) => {
153             println!("unknown output format: {}", s);
154             return 1;
155         }
156     }
157     let ended = time::precise_time_ns();
158     info!("Took {:.03f}s", (ended as f64 - started as f64) / 1e9f64);
159
160     return 0;
161 }
162
163 /// Looks inside the command line arguments to extract the relevant input format
164 /// and files and then generates the necessary rustdoc output for formatting.
165 fn acquire_input(matches: &getopts::Matches) -> Result<Output, ~str> {
166     if matches.free.len() == 0 {
167         return Err(~"expected an input file to act on");
168     } if matches.free.len() > 1 {
169         return Err(~"only one input file may be specified");
170     }
171
172     let input = matches.free[0].as_slice();
173     match matches.opt_str("r") {
174         Some(~"rust") => Ok(rust_input(input, matches)),
175         Some(~"json") => json_input(input),
176         Some(s) => Err("unknown input format: " + s),
177         None => {
178             if input.ends_with(".json") {
179                 json_input(input)
180             } else {
181                 Ok(rust_input(input, matches))
182             }
183         }
184     }
185 }
186
187 /// Interprets the input file as a rust source file, passing it through the
188 /// compiler all the way through the analysis passes. The rustdoc output is then
189 /// generated from the cleaned AST of the crate.
190 ///
191 /// This form of input will run all of the plug/cleaning passes
192 fn rust_input(cratefile: &str, matches: &getopts::Matches) -> Output {
193     let mut default_passes = !matches.opt_present("no-defaults");
194     let mut passes = matches.opt_strs("passes");
195     let mut plugins = matches.opt_strs("plugins");
196
197     // First, parse the crate and extract all relevant information.
198     let libs = Cell::new(matches.opt_strs("L").map(|s| Path::init(s.as_slice())));
199     let cfgs = Cell::new(matches.opt_strs("cfg"));
200     let cr = Cell::new(Path::init(cratefile));
201     info!("starting to run rustc");
202     let (crate, analysis) = do std::task::try {
203         let cr = cr.take();
204         core::run_core(libs.take().move_iter().collect(), cfgs.take(), &cr)
205     }.unwrap();
206     info!("finished with rustc");
207     local_data::set(analysiskey, analysis);
208
209     // Process all of the crate attributes, extracting plugin metadata along
210     // with the passes which we are supposed to run.
211     match crate.module.get_ref().doc_list() {
212         Some(nested) => {
213             for inner in nested.iter() {
214                 match *inner {
215                     clean::Word(~"no_default_passes") => {
216                         default_passes = false;
217                     }
218                     clean::NameValue(~"passes", ref value) => {
219                         for pass in value.words() {
220                             passes.push(pass.to_owned());
221                         }
222                     }
223                     clean::NameValue(~"plugins", ref value) => {
224                         for p in value.words() {
225                             plugins.push(p.to_owned());
226                         }
227                     }
228                     _ => {}
229                 }
230             }
231         }
232         None => {}
233     }
234     if default_passes {
235         for name in DEFAULT_PASSES.rev_iter() {
236             passes.unshift(name.to_owned());
237         }
238     }
239
240     // Load all plugins/passes into a PluginManager
241     let path = matches.opt_str("plugin-path").unwrap_or(~"/tmp/rustdoc_ng/plugins");
242     let mut pm = plugins::PluginManager::new(Path::init(path));
243     for pass in passes.iter() {
244         let plugin = match PASSES.iter().position(|&(p, _, _)| p == *pass) {
245             Some(i) => PASSES[i].n1(),
246             None => {
247                 error!("unknown pass {}, skipping", *pass);
248                 continue
249             },
250         };
251         pm.add_plugin(plugin);
252     }
253     info!("loading plugins...");
254     for pname in plugins.move_iter() {
255         pm.load_plugin(pname);
256     }
257
258     // Run everything!
259     info!("Executing passes/plugins");
260     return pm.run_plugins(crate);
261 }
262
263 /// This input format purely deserializes the json output file. No passes are
264 /// run over the deserialized output.
265 fn json_input(input: &str) -> Result<Output, ~str> {
266     let input = match File::open(&Path::init(input)) {
267         Some(f) => f,
268         None => return Err(format!("couldn't open {} for reading", input)),
269     };
270     match json::from_reader(@mut input as @mut io::Reader) {
271         Err(s) => Err(s.to_str()),
272         Ok(json::Object(obj)) => {
273             let mut obj = obj;
274             // Make sure the schema is what we expect
275             match obj.pop(&~"schema") {
276                 Some(json::String(version)) => {
277                     if version.as_slice() != SCHEMA_VERSION {
278                         return Err(format!("sorry, but I only understand \
279                                             version {}", SCHEMA_VERSION))
280                     }
281                 }
282                 Some(..) => return Err(~"malformed json"),
283                 None => return Err(~"expected a schema version"),
284             }
285             let crate = match obj.pop(&~"crate") {
286                 Some(json) => {
287                     let mut d = json::Decoder::init(json);
288                     Decodable::decode(&mut d)
289                 }
290                 None => return Err(~"malformed json"),
291             };
292             // XXX: this should read from the "plugins" field, but currently
293             //      Json doesn't implement decodable...
294             let plugin_output = ~[];
295             Ok((crate, plugin_output))
296         }
297         Ok(..) => Err(~"malformed json input: expected an object at the top"),
298     }
299 }
300
301 /// Outputs the crate/plugin json as a giant json blob at the specified
302 /// destination.
303 fn json_output(crate: clean::Crate, res: ~[plugins::PluginJson], dst: Path) {
304     // {
305     //   "schema": version,
306     //   "crate": { parsed crate ... },
307     //   "plugins": { output of plugins ... }
308     // }
309     let mut json = ~extra::treemap::TreeMap::new();
310     json.insert(~"schema", json::String(SCHEMA_VERSION.to_owned()));
311     let plugins_json = ~res.move_iter().filter_map(|opt| opt).collect();
312
313     // FIXME #8335: yuck, Rust -> str -> JSON round trip! No way to .encode
314     // straight to the Rust JSON representation.
315     let crate_json_str = {
316         let mut w = MemWriter::new();
317         {
318             let mut encoder = json::Encoder::init(&mut w as &mut io::Writer);
319             crate.encode(&mut encoder);
320         }
321         str::from_utf8_owned(w.inner())
322     };
323     let crate_json = match json::from_str(crate_json_str) {
324         Ok(j) => j,
325         Err(_) => fail!("Rust generated JSON is invalid??")
326     };
327
328     json.insert(~"crate", crate_json);
329     json.insert(~"plugins", json::Object(plugins_json));
330
331     let mut file = File::create(&dst).unwrap();
332     let output = json::Object(json).to_str();
333     file.write(output.as_bytes());
334 }