Skip to content

Commit e523f99

Browse files
committed
rustdoc: Add the ability to input json
This modifies the command-line usage of rustdoc to intake its own JSON output as well as a rust source file. This also alters the command line from `rustdoc input file` to `rustdoc file` with the input/output formats specified as -r and -w, respectively. When using a JSON input, no passes or plugins are re-run over the json, instead the output is generated directly from the JSON that was provided. Passes and plugins are still run on rust source input, however.
1 parent a3ccbdc commit e523f99

File tree

3 files changed

+142
-58
lines changed

3 files changed

+142
-58
lines changed

mk/docs.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ RUSTDOC = $(HBIN2_H_$(CFG_BUILD_TRIPLE))/rustdoc$(X_$(CFG_BUILD_TRIPLE))
227227
define libdoc
228228
doc/$(1)/index.html: $$(RUSTDOC) $$(TLIB2_T_$(3)_H_$(3))/$(CFG_STDLIB_$(3))
229229
@$$(call E, rustdoc: $$@)
230-
$(Q)$(RUSTDOC) html $(2)
230+
$(Q)$(RUSTDOC) $(2)
231231

232232
DOCS += doc/$(1)/index.html
233233
endef

src/librustdoc/html/render.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ struct Cache {
7676
struct SourceCollector<'self> {
7777
seen: HashSet<~str>,
7878
dst: Path,
79-
cx: &'self Context,
79+
cx: &'self mut Context,
8080
}
8181

8282
struct Item<'self> { cx: &'self Context, item: &'self clean::Item, }
@@ -179,15 +179,15 @@ pub fn run(mut crate: clean::Crate, dst: Path) {
179179
w.flush();
180180
}
181181

182-
if cx.include_sources {
182+
{
183183
let dst = cx.dst.push("src");
184184
mkdir(&dst);
185185
let dst = dst.push(crate.name);
186186
mkdir(&dst);
187187
let mut folder = SourceCollector {
188188
dst: dst,
189189
seen: HashSet::new(),
190-
cx: &cx,
190+
cx: &mut cx,
191191
};
192192
crate = folder.fold_crate(crate);
193193
}
@@ -229,16 +229,28 @@ fn clean_srcpath(src: &str, f: &fn(&str)) {
229229

230230
impl<'self> DocFolder for SourceCollector<'self> {
231231
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
232-
if !self.seen.contains(&item.source.filename) {
233-
self.emit_source(item.source.filename);
232+
if self.cx.include_sources && !self.seen.contains(&item.source.filename) {
233+
// If it turns out that we couldn't read this file, then we probably
234+
// can't read any of the files (generating html output from json or
235+
// something like that), so just don't include sources for the
236+
// entire crate. The other option is maintaining this mapping on a
237+
// per-file basis, but that's probably not worth it...
238+
self.cx.include_sources = self.emit_source(item.source.filename);
234239
self.seen.insert(item.source.filename.clone());
240+
241+
if !self.cx.include_sources {
242+
println!("warning: source code was requested to be rendered, \
243+
but `{}` is a missing source file.",
244+
item.source.filename);
245+
println!(" skipping rendering of source code");
246+
}
235247
}
236248
self.fold_item_recur(item)
237249
}
238250
}
239251

240252
impl<'self> SourceCollector<'self> {
241-
fn emit_source(&self, filename: &str) {
253+
fn emit_source(&mut self, filename: &str) -> bool {
242254
let p = Path(filename);
243255

244256
// Read the contents of the file
@@ -251,7 +263,11 @@ impl<'self> SourceCollector<'self> {
251263
// If we couldn't open this file, then just returns because it
252264
// probably means that it's some standard library macro thing and we
253265
// can't have the source to it anyway.
254-
let mut r = match r { Some(r) => r, None => return };
266+
let mut r = match r {
267+
Some(r) => r,
268+
// eew macro hacks
269+
None => return filename == "<std-macros>"
270+
};
255271

256272
// read everything
257273
loop {
@@ -283,6 +299,7 @@ impl<'self> SourceCollector<'self> {
283299
};
284300
layout::render(&mut w as &mut io::Writer, &self.cx.layout,
285301
&page, &(""), &Source(contents.as_slice()));
302+
return true;
286303
}
287304
}
288305

src/librustdoc/rustdoc.rs

Lines changed: 117 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ extern mod syntax;
2121
extern mod rustc;
2222
extern mod extra;
2323

24-
use extra::serialize::Encodable;
25-
use extra::time;
26-
use extra::getopts::groups;
2724
use std::cell::Cell;
28-
use std::rt::io;
2925
use std::rt::io::Writer;
3026
use std::rt::io::file::FileInfo;
27+
use std::rt::io;
28+
use extra::getopts;
29+
use extra::getopts::groups;
30+
use extra::json;
31+
use extra::serialize::{Decodable, Encodable};
32+
use extra::time;
3133

3234
pub mod clean;
3335
pub mod core;
@@ -70,9 +72,7 @@ static DEFAULT_PASSES: &'static [&'static str] = &[
7072

7173
local_data_key!(pub ctxtkey: @core::DocContext)
7274

73-
enum OutputFormat {
74-
HTML, JSON
75-
}
75+
type Output = (clean::Crate, ~[plugins::PluginJson]);
7676

7777
pub fn main() {
7878
std::os::set_exit_status(main_args(std::os::args()));
@@ -81,6 +81,12 @@ pub fn main() {
8181
pub fn opts() -> ~[groups::OptGroup] {
8282
use extra::getopts::groups::*;
8383
~[
84+
optflag("h", "help", "show this help message"),
85+
optopt("r", "input-format", "the input type of the specified file",
86+
"[rust|json]"),
87+
optopt("w", "output-format", "the output type to write",
88+
"[html|json]"),
89+
optopt("o", "output", "where to place the output", "PATH"),
8490
optmulti("L", "library-path", "directory to add to crate search path",
8591
"DIR"),
8692
optmulti("", "plugin-path", "directory to load plugins from", "DIR"),
@@ -89,32 +95,22 @@ pub fn opts() -> ~[groups::OptGroup] {
8995
"PASSES"),
9096
optmulti("", "plugins", "space separated list of plugins to also load",
9197
"PLUGINS"),
92-
optflag("h", "help", "show this help message"),
9398
optflag("", "nodefaults", "don't run the default passes"),
94-
optopt("o", "output", "where to place the output", "PATH"),
9599
]
96100
}
97101

98102
pub fn usage(argv0: &str) {
99-
println(groups::usage(format!("{} [options] [html|json] <crate>",
100-
argv0), opts()));
103+
println(groups::usage(format!("{} [options] <input>", argv0), opts()));
101104
}
102105

103106
pub fn main_args(args: &[~str]) -> int {
104-
//use extra::getopts::groups::*;
105-
106107
let matches = groups::getopts(args.tail(), opts()).unwrap();
107-
108108
if matches.opt_present("h") || matches.opt_present("help") {
109109
usage(args[0]);
110110
return 0;
111111
}
112112

113-
let mut default_passes = !matches.opt_present("nodefaults");
114-
let mut passes = matches.opt_strs("passes");
115-
let mut plugins = matches.opt_strs("plugins");
116-
117-
if passes == ~[~"list"] {
113+
if matches.opt_strs("passes") == ~[~"list"] {
118114
println("Available passes for running rustdoc:");
119115
for &(name, _, description) in PASSES.iter() {
120116
println!("{:>20s} - {}", name, description);
@@ -126,25 +122,68 @@ pub fn main_args(args: &[~str]) -> int {
126122
return 0;
127123
}
128124

129-
let (format, cratefile) = match matches.free.clone() {
130-
[~"json", crate] => (JSON, crate),
131-
[~"html", crate] => (HTML, crate),
132-
[s, _] => {
133-
println!("Unknown output format: `{}`", s);
134-
usage(args[0]);
125+
let (crate, res) = match acquire_input(&matches) {
126+
Ok(pair) => pair,
127+
Err(s) => {
128+
println!("input error: {}", s);
135129
return 1;
136130
}
137-
[_, .._] => {
138-
println!("Expected exactly one crate to process");
139-
usage(args[0]);
140-
return 1;
131+
};
132+
133+
info2!("going to format");
134+
let started = time::precise_time_ns();
135+
let output = matches.opt_str("o").map(|s| Path(*s));
136+
match matches.opt_str("w") {
137+
Some(~"html") | None => {
138+
html::render::run(crate, output.unwrap_or(Path("doc")))
141139
}
142-
_ => {
143-
println!("Expected an output format and then one crate");
144-
usage(args[0]);
140+
Some(~"json") => {
141+
json_output(crate, res, output.unwrap_or(Path("doc.json")))
142+
}
143+
Some(s) => {
144+
println!("unknown output format: {}", s);
145145
return 1;
146146
}
147-
};
147+
}
148+
let ended = time::precise_time_ns();
149+
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1000000000f64);
150+
151+
return 0;
152+
}
153+
154+
/// Looks inside the command line arguments to extract the relevant input format
155+
/// and files and then generates the necessary rustdoc output for formatting.
156+
fn acquire_input(matches: &getopts::Matches) -> Result<Output, ~str> {
157+
if matches.free.len() == 0 {
158+
return Err(~"expected an input file to act on");
159+
} if matches.free.len() > 1 {
160+
return Err(~"only one input file may be specified");
161+
}
162+
163+
let input = matches.free[0].as_slice();
164+
match matches.opt_str("r") {
165+
Some(~"rust") => Ok(rust_input(input, matches)),
166+
Some(~"json") => json_input(input),
167+
Some(s) => Err("unknown input format: " + s),
168+
None => {
169+
if input.ends_with(".json") {
170+
json_input(input)
171+
} else {
172+
Ok(rust_input(input, matches))
173+
}
174+
}
175+
}
176+
}
177+
178+
/// Interprets the input file as a rust source file, passing it through the
179+
/// compiler all the way through the analysis passes. The rustdoc output is then
180+
/// generated from the cleaned AST of the crate.
181+
///
182+
/// This form of input will run all of the plug/cleaning passes
183+
fn rust_input(cratefile: &str, matches: &getopts::Matches) -> Output {
184+
let mut default_passes = !matches.opt_present("nodefaults");
185+
let mut passes = matches.opt_strs("passes");
186+
let mut plugins = matches.opt_strs("plugins");
148187

149188
// First, parse the crate and extract all relevant information.
150189
let libs = Cell::new(matches.opt_strs("L").map(|s| Path(*s)));
@@ -206,45 +245,73 @@ pub fn main_args(args: &[~str]) -> int {
206245

207246
// Run everything!
208247
info2!("Executing passes/plugins");
209-
let (crate, res) = pm.run_plugins(crate);
248+
return pm.run_plugins(crate);
249+
}
210250

211-
info2!("going to format");
212-
let started = time::precise_time_ns();
213-
let output = matches.opt_str("o").map(|s| Path(*s));
214-
match format {
215-
HTML => { html::render::run(crate, output.unwrap_or(Path("doc"))) }
216-
JSON => { jsonify(crate, res, output.unwrap_or(Path("doc.json"))) }
251+
/// This input format purely deserializes the json output file. No passes are
252+
/// run over the deserialized output.
253+
fn json_input(input: &str) -> Result<Output, ~str> {
254+
let input = match ::std::io::file_reader(&Path(input)) {
255+
Ok(i) => i,
256+
Err(s) => return Err(s),
257+
};
258+
match json::from_reader(input) {
259+
Err(s) => Err(s.to_str()),
260+
Ok(json::Object(obj)) => {
261+
let mut obj = obj;
262+
// Make sure the schema is what we expect
263+
match obj.pop(&~"schema") {
264+
Some(json::String(version)) => {
265+
if version.as_slice() != SCHEMA_VERSION {
266+
return Err(format!("sorry, but I only understand \
267+
version {}", SCHEMA_VERSION))
268+
}
269+
}
270+
Some(*) => return Err(~"malformed json"),
271+
None => return Err(~"expected a schema version"),
272+
}
273+
let crate = match obj.pop(&~"crate") {
274+
Some(json) => {
275+
let mut d = json::Decoder(json);
276+
Decodable::decode(&mut d)
277+
}
278+
None => return Err(~"malformed json"),
279+
};
280+
// XXX: this should read from the "plugins" field, but currently
281+
// Json doesn't implement decodable...
282+
let plugin_output = ~[];
283+
Ok((crate, plugin_output))
284+
}
285+
Ok(*) => Err(~"malformed json input: expected an object at the top"),
217286
}
218-
let ended = time::precise_time_ns();
219-
info2!("Took {:.03f}s", (ended as f64 - started as f64) / 1000000000f64);
220-
221-
return 0;
222287
}
223288

224-
fn jsonify(crate: clean::Crate, res: ~[plugins::PluginJson], dst: Path) {
289+
/// Outputs the crate/plugin json as a giant json blob at the specified
290+
/// destination.
291+
fn json_output(crate: clean::Crate, res: ~[plugins::PluginJson], dst: Path) {
225292
// {
226293
// "schema": version,
227294
// "crate": { parsed crate ... },
228295
// "plugins": { output of plugins ... }
229296
// }
230297
let mut json = ~extra::treemap::TreeMap::new();
231-
json.insert(~"schema", extra::json::String(SCHEMA_VERSION.to_owned()));
298+
json.insert(~"schema", json::String(SCHEMA_VERSION.to_owned()));
232299
let plugins_json = ~res.move_iter().filter_map(|opt| opt).collect();
233300

234301
// FIXME #8335: yuck, Rust -> str -> JSON round trip! No way to .encode
235302
// straight to the Rust JSON representation.
236303
let crate_json_str = do std::io::with_str_writer |w| {
237-
crate.encode(&mut extra::json::Encoder(w));
304+
crate.encode(&mut json::Encoder(w));
238305
};
239-
let crate_json = match extra::json::from_str(crate_json_str) {
306+
let crate_json = match json::from_str(crate_json_str) {
240307
Ok(j) => j,
241308
Err(_) => fail!("Rust generated JSON is invalid??")
242309
};
243310

244311
json.insert(~"crate", crate_json);
245-
json.insert(~"plugins", extra::json::Object(plugins_json));
312+
json.insert(~"plugins", json::Object(plugins_json));
246313

247314
let mut file = dst.open_writer(io::Create).unwrap();
248-
let output = extra::json::Object(json).to_str();
315+
let output = json::Object(json).to_str();
249316
file.write(output.as_bytes());
250317
}

0 commit comments

Comments
 (0)