1 // Copyright 2012-2014 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.
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.
11 // Do not remove on snapshot creation. Needed for bootstrap. (Issue #22364)
12 #![cfg_attr(stage0, feature(custom_attribute))]
13 #![crate_name = "rustdoc"]
14 #![unstable(feature = "rustdoc")]
16 #![crate_type = "dylib"]
17 #![crate_type = "rlib"]
18 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
19 html_favicon_url
= "http://www.rust-lang.org/favicon.ico",
20 html_root_url
= "http://doc.rust-lang.org/nightly/",
21 html_playground_url
= "http://play.rust-lang.org/")]
23 #![feature(box_patterns)]
24 #![feature(box_syntax)]
25 #![feature(collections)]
26 #![feature(exit_status)]
27 #![feature(set_stdio)]
29 #![feature(rustc_private)]
30 #![feature(staged_api)]
35 #![feature(path_relative_from)]
36 #![feature(slice_patterns)]
42 extern crate rustc_trans
;
43 extern crate rustc_driver
;
44 extern crate rustc_resolve
;
45 extern crate rustc_lint
;
46 extern crate rustc_back
;
47 extern crate serialize
;
49 extern crate test
as testing
;
50 extern crate rustc_unicode
;
51 #[macro_use] extern crate log;
53 extern crate serialize
as rustc_serialize
; // used by deriving
55 use std
::cell
::RefCell
;
56 use std
::collections
::HashMap
;
59 use std
::io
::{self, Read, Write}
;
60 use std
::path
::PathBuf
;
62 use std
::sync
::mpsc
::channel
;
64 use externalfiles
::ExternalHtml
;
65 use serialize
::Decodable
;
66 use serialize
::json
::{self, Json}
;
67 use rustc
::session
::search_paths
::SearchPaths
;
69 // reexported from `clean` so it can be easily updated with the mod itself
70 pub use clean
::SCHEMA_VERSION
;
73 pub mod externalfiles
;
96 type Pass
= (&'
static str, // name
97 fn(clean
::Crate
) -> plugins
::PluginResult
, // fn
98 &'
static str); // description
100 const PASSES
: &'
static [Pass
] = &[
101 ("strip-hidden", passes
::strip_hidden
,
102 "strips all doc(hidden) items from the output"),
103 ("unindent-comments", passes
::unindent_comments
,
104 "removes excess indentation on comments in order for markdown to like it"),
105 ("collapse-docs", passes
::collapse_docs
,
106 "concatenates all document attributes into one document attribute"),
107 ("strip-private", passes
::strip_private
,
108 "strips all private items from a crate which cannot be seen externally"),
111 const DEFAULT_PASSES
: &'
static [&'
static str] = &[
118 thread_local
!(pub static ANALYSISKEY
: Rc
<RefCell
<Option
<core
::CrateAnalysis
>>> = {
119 Rc
::new(RefCell
::new(None
))
124 json_plugins
: Vec
<plugins
::PluginJson
>,
129 const STACK_SIZE
: usize = 32000000; // 32MB
130 let res
= std
::thread
::Builder
::new().stack_size(STACK_SIZE
).spawn(move || {
131 let s
= env
::args().collect
::<Vec
<_
>>();
133 }).unwrap().join().unwrap();
134 env
::set_exit_status(res
as i32);
137 pub fn opts() -> Vec
<getopts
::OptGroup
> {
140 optflag("h", "help", "show this help message"),
141 optflag("V", "version", "print rustdoc's version"),
142 optflag("v", "verbose", "use verbose output"),
143 optopt("r", "input-format", "the input type of the specified file",
145 optopt("w", "output-format", "the output type to write",
147 optopt("o", "output", "where to place the output", "PATH"),
148 optopt("", "crate-name", "specify the name of this crate", "NAME"),
149 optmulti("L", "library-path", "directory to add to crate search path",
151 optmulti("", "cfg", "pass a --cfg to rustc", ""),
152 optmulti("", "extern", "pass an --extern to rustc", "NAME=PATH"),
153 optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
154 optmulti("", "passes", "list of passes to also run, you might want \
155 to pass it multiple times; a value of `list` \
156 will print available passes",
158 optmulti("", "plugins", "space separated list of plugins to also load",
160 optflag("", "no-defaults", "don't run the default passes"),
161 optflag("", "test", "run code examples as tests"),
162 optmulti("", "test-args", "arguments to pass to the test runner",
164 optopt("", "target", "target triple to document", "TRIPLE"),
165 optmulti("", "markdown-css", "CSS files to include via <link> in a rendered Markdown file",
167 optmulti("", "html-in-header",
168 "files to include inline in the <head> section of a rendered Markdown file \
169 or generated documentation",
171 optmulti("", "html-before-content",
172 "files to include inline between <body> and the content of a rendered \
173 Markdown file or generated documentation",
175 optmulti("", "html-after-content",
176 "files to include inline between the content and </body> of a rendered \
177 Markdown file or generated documentation",
179 optopt("", "markdown-playground-url",
180 "URL to send code snippets to", "URL"),
181 optflag("", "markdown-no-toc", "don't include table of contents")
185 pub fn usage(argv0
: &str) {
187 getopts
::usage(&format
!("{} [options] <input>", argv0
),
191 pub fn main_args(args
: &[String
]) -> isize {
192 let matches
= match getopts
::getopts(args
.tail(), &opts()) {
199 if matches
.opt_present("h") || matches
.opt_present("help") {
202 } else if matches
.opt_present("version") {
203 rustc_driver
::version("rustdoc", &matches
);
207 if matches
.opt_strs("passes") == ["list"] {
208 println
!("Available passes for running rustdoc:");
209 for &(name
, _
, description
) in PASSES
{
210 println
!("{:>20} - {}", name
, description
);
212 println
!("{}", "\nDefault passes for rustdoc:"); // FIXME: #9970
213 for &name
in DEFAULT_PASSES
{
214 println
!("{:>20}", name
);
219 if matches
.free
.is_empty() {
220 println
!("expected an input file to act on");
222 } if matches
.free
.len() > 1 {
223 println
!("only one input file may be specified");
226 let input
= &matches
.free
[0];
228 let mut libs
= SearchPaths
::new();
229 for s
in &matches
.opt_strs("L") {
232 let externs
= match parse_externs(&matches
) {
240 let test_args
= matches
.opt_strs("test-args");
241 let test_args
: Vec
<String
> = test_args
.iter()
242 .flat_map(|s
| s
.split_whitespace())
243 .map(|s
| s
.to_string())
246 let should_test
= matches
.opt_present("test");
247 let markdown_input
= input
.ends_with(".md") || input
.ends_with(".markdown");
249 let output
= matches
.opt_str("o").map(|s
| PathBuf
::from(&s
));
250 let cfgs
= matches
.opt_strs("cfg");
252 let external_html
= match ExternalHtml
::load(
253 &matches
.opt_strs("html-in-header"),
254 &matches
.opt_strs("html-before-content"),
255 &matches
.opt_strs("html-after-content")) {
259 let crate_name
= matches
.opt_str("crate-name");
261 match (should_test
, markdown_input
) {
263 return markdown
::test(input
, libs
, externs
, test_args
)
266 return test
::run(input
, cfgs
, libs
, externs
, test_args
, crate_name
)
268 (false, true) => return markdown
::render(input
,
269 output
.unwrap_or(PathBuf
::from("doc")),
270 &matches
, &external_html
,
271 !matches
.opt_present("markdown-no-toc")),
275 let out
= match acquire_input(input
, externs
, &matches
) {
278 println
!("input error: {}", s
);
282 let Output { krate, json_plugins, passes, }
= out
;
283 info
!("going to format");
284 match matches
.opt_str("w").as_ref().map(|s
| &**s
) {
285 Some("html") | None
=> {
286 match html
::render
::run(krate
, &external_html
,
287 output
.unwrap_or(PathBuf
::from("doc")),
288 passes
.into_iter().collect()) {
290 Err(e
) => panic
!("failed to generate documentation: {}", e
),
294 match json_output(krate
, json_plugins
,
295 output
.unwrap_or(PathBuf
::from("doc.json"))) {
297 Err(e
) => panic
!("failed to write json: {}", e
),
301 println
!("unknown output format: {}", s
);
309 /// Looks inside the command line arguments to extract the relevant input format
310 /// and files and then generates the necessary rustdoc output for formatting.
311 fn acquire_input(input
: &str,
312 externs
: core
::Externs
,
313 matches
: &getopts
::Matches
) -> Result
<Output
, String
> {
314 match matches
.opt_str("r").as_ref().map(|s
| &**s
) {
315 Some("rust") => Ok(rust_input(input
, externs
, matches
)),
316 Some("json") => json_input(input
),
317 Some(s
) => Err(format
!("unknown input format: {}", s
)),
319 if input
.ends_with(".json") {
322 Ok(rust_input(input
, externs
, matches
))
328 /// Extracts `--extern CRATE=PATH` arguments from `matches` and
329 /// returns a `HashMap` mapping crate names to their paths or else an
331 fn parse_externs(matches
: &getopts
::Matches
) -> Result
<core
::Externs
, String
> {
332 let mut externs
= HashMap
::new();
333 for arg
in &matches
.opt_strs("extern") {
334 let mut parts
= arg
.splitn(2, '
='
);
335 let name
= match parts
.next() {
338 return Err("--extern value must not be empty".to_string());
341 let location
= match parts
.next() {
344 return Err("--extern value must be of the format `foo=bar`".to_string());
347 let name
= name
.to_string();
348 externs
.entry(name
).or_insert(vec
![]).push(location
.to_string());
353 /// Interprets the input file as a rust source file, passing it through the
354 /// compiler all the way through the analysis passes. The rustdoc output is then
355 /// generated from the cleaned AST of the crate.
357 /// This form of input will run all of the plug/cleaning passes
358 #[allow(deprecated)] // for old Path in plugin manager
359 fn rust_input(cratefile
: &str, externs
: core
::Externs
, matches
: &getopts
::Matches
) -> Output
{
360 let mut default_passes
= !matches
.opt_present("no-defaults");
361 let mut passes
= matches
.opt_strs("passes");
362 let mut plugins
= matches
.opt_strs("plugins");
364 // First, parse the crate and extract all relevant information.
365 let mut paths
= SearchPaths
::new();
366 for s
in &matches
.opt_strs("L") {
369 let cfgs
= matches
.opt_strs("cfg");
370 let triple
= matches
.opt_str("target");
372 let cr
= PathBuf
::from(cratefile
);
373 info
!("starting to run rustc");
375 let (tx
, rx
) = channel();
376 std
::thread
::spawn(move || {
377 use rustc
::session
::config
::Input
;
379 tx
.send(core
::run_core(paths
, cfgs
, externs
, Input
::File(cr
),
381 }).join().map_err(|_
| "rustc failed").unwrap();
382 let (mut krate
, analysis
) = rx
.recv().unwrap();
383 info
!("finished with rustc");
384 let mut analysis
= Some(analysis
);
385 ANALYSISKEY
.with(|s
| {
386 *s
.borrow_mut() = analysis
.take();
389 match matches
.opt_str("crate-name") {
390 Some(name
) => krate
.name
= name
,
394 // Process all of the crate attributes, extracting plugin metadata along
395 // with the passes which we are supposed to run.
396 match krate
.module
.as_ref().unwrap().doc_list() {
398 for inner
in nested
{
401 if "no_default_passes" == *x
=> {
402 default_passes
= false;
404 clean
::NameValue(ref x
, ref value
)
405 if "passes" == *x
=> {
406 for pass
in value
.split_whitespace() {
407 passes
.push(pass
.to_string());
410 clean
::NameValue(ref x
, ref value
)
411 if "plugins" == *x
=> {
412 for p
in value
.split_whitespace() {
413 plugins
.push(p
.to_string());
423 for name
in DEFAULT_PASSES
.iter().rev() {
424 passes
.insert(0, name
.to_string());
428 // Load all plugins/passes into a PluginManager
429 let path
= matches
.opt_str("plugin-path")
430 .unwrap_or("/tmp/rustdoc/plugins".to_string());
431 let mut pm
= plugins
::PluginManager
::new(PathBuf
::from(path
));
432 for pass
in &passes
{
433 let plugin
= match PASSES
.iter()
434 .position(|&(p
, _
, _
)| {
437 Some(i
) => PASSES
[i
].1,
439 error
!("unknown pass {}, skipping", *pass
);
443 pm
.add_plugin(plugin
);
445 info
!("loading plugins...");
446 for pname
in plugins
{
447 pm
.load_plugin(pname
);
451 info
!("Executing passes/plugins");
452 let (krate
, json
) = pm
.run_plugins(krate
);
453 return Output { krate: krate, json_plugins: json, passes: passes, }
;
456 /// This input format purely deserializes the json output file. No passes are
457 /// run over the deserialized output.
458 fn json_input(input
: &str) -> Result
<Output
, String
> {
459 let mut bytes
= Vec
::new();
460 match File
::open(input
).and_then(|mut f
| f
.read_to_end(&mut bytes
)) {
462 Err(e
) => return Err(format
!("couldn't open {}: {}", input
, e
)),
464 match json
::from_reader(&mut &bytes
[..]) {
465 Err(s
) => Err(format
!("{:?}", s
)),
466 Ok(Json
::Object(obj
)) => {
468 // Make sure the schema is what we expect
469 match obj
.remove(&"schema".to_string()) {
470 Some(Json
::String(version
)) => {
471 if version
!= SCHEMA_VERSION
{
473 "sorry, but I only understand version {}",
477 Some(..) => return Err("malformed json".to_string()),
478 None
=> return Err("expected a schema version".to_string()),
480 let krate
= match obj
.remove(&"crate".to_string()) {
482 let mut d
= json
::Decoder
::new(json
);
483 Decodable
::decode(&mut d
).unwrap()
485 None
=> return Err("malformed json".to_string()),
487 // FIXME: this should read from the "plugins" field, but currently
488 // Json doesn't implement decodable...
489 let plugin_output
= Vec
::new();
490 Ok(Output { krate: krate, json_plugins: plugin_output, passes: Vec::new(), }
)
493 Err("malformed json input: expected an object at the \
499 /// Outputs the crate/plugin json as a giant json blob at the specified
501 fn json_output(krate
: clean
::Crate
, res
: Vec
<plugins
::PluginJson
> ,
502 dst
: PathBuf
) -> io
::Result
<()> {
504 // "schema": version,
505 // "crate": { parsed crate ... },
506 // "plugins": { output of plugins ... }
508 let mut json
= std
::collections
::BTreeMap
::new();
509 json
.insert("schema".to_string(), Json
::String(SCHEMA_VERSION
.to_string()));
510 let plugins_json
= res
.into_iter()
514 Some((string
, json
)) => {
515 Some((string
.to_string(), json
))
520 // FIXME #8335: yuck, Rust -> str -> JSON round trip! No way to .encode
521 // straight to the Rust JSON representation.
522 let crate_json_str
= format
!("{}", json
::as_json(&krate
));
523 let crate_json
= match json
::from_str(&crate_json_str
) {
525 Err(e
) => panic
!("Rust generated JSON is invalid: {:?}", e
)
528 json
.insert("crate".to_string(), crate_json
);
529 json
.insert("plugins".to_string(), Json
::Object(plugins_json
));
531 let mut file
= try
!(File
::create(&dst
));
532 write
!(&mut file
, "{}", Json
::Object(json
))