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.
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.
14 use std::result::Result;
16 use std::run::ProcessOutput;
21 /// The type of document to output
22 #[deriving(Clone, Eq)]
23 pub enum OutputFormat {
26 /// HTML, via markdown and pandoc
30 /// How to organize the output
31 #[deriving(Clone, Eq)]
32 pub enum OutputStyle {
33 /// All in a single document
35 /// Each module in its own document
39 /// The configuration for a rustdoc session
44 output_format: OutputFormat,
45 output_style: OutputStyle,
46 pandoc_cmd: Option<~str>
49 fn opt_output_dir() -> ~str { ~"output-dir" }
50 fn opt_output_format() -> ~str { ~"output-format" }
51 fn opt_output_style() -> ~str { ~"output-style" }
52 fn opt_pandoc_cmd() -> ~str { ~"pandoc-cmd" }
53 fn opt_help() -> ~str { ~"h" }
55 fn opts() -> ~[(getopts::Opt, ~str)] {
57 (getopts::optopt(opt_output_dir()),
58 ~"--output-dir <val> Put documents here (default: .)"),
59 (getopts::optopt(opt_output_format()),
60 ~"--output-format <val> 'markdown' or 'html' (default)"),
61 (getopts::optopt(opt_output_style()),
62 ~"--output-style <val> 'doc-per-crate' or 'doc-per-mod' (default)"),
63 (getopts::optopt(opt_pandoc_cmd()),
64 ~"--pandoc-cmd <val> Command for running pandoc"),
65 (getopts::optflag(opt_help()),
66 ~"-h, --help Print help")
73 println("Usage: rustdoc [options] <cratefile>\n");
74 println("Options:\n");
77 printfln!(" %s", opt.second());
82 pub fn default_config(input_crate: &Path) -> Config {
84 input_crate: (*input_crate).clone(),
85 output_dir: Path("."),
86 output_format: PandocHtml,
87 output_style: DocPerMod,
92 type Process = ~fn((&str), (&[~str])) -> ProcessOutput;
94 pub fn mock_process_output(_prog: &str, _args: &[~str]) -> ProcessOutput {
102 pub fn process_output(prog: &str, args: &[~str]) -> ProcessOutput {
103 run::process_output(prog, args)
106 pub fn parse_config(args: &[~str]) -> Result<Config, ~str> {
107 parse_config_(args, process_output)
110 pub fn parse_config_(
112 process_output: Process
113 ) -> Result<Config, ~str> {
114 let args = args.tail();
115 let opts = vec::unzip(opts()).first();
116 match getopts::getopts(args, opts) {
118 if matches.free.len() == 1 {
119 let input_crate = Path(*matches.free.head());
120 config_from_opts(&input_crate, &matches, process_output)
121 } else if matches.free.is_empty() {
122 Err(~"no crates specified")
124 Err(~"multiple crates specified")
128 Err(getopts::fail_str(f))
135 matches: &getopts::Matches,
136 process_output: Process
137 ) -> Result<Config, ~str> {
139 let config = default_config(input_crate);
140 let result = result::Ok(config);
141 let result = do result.chain |config| {
142 let output_dir = getopts::opt_maybe_str(matches, opt_output_dir());
143 let output_dir = output_dir.map(|s| Path(*s));
145 output_dir: output_dir.unwrap_or_default(config.output_dir.clone()),
149 let result = do result.chain |config| {
150 let output_format = getopts::opt_maybe_str(matches, opt_output_format());
151 do output_format.map_default(result::Ok(config.clone())) |output_format| {
152 do parse_output_format(*output_format).chain |output_format| {
154 output_format: output_format,
160 let result = do result.chain |config| {
162 getopts::opt_maybe_str(matches, opt_output_style());
163 do output_style.map_default(result::Ok(config.clone())) |output_style| {
164 do parse_output_style(*output_style).chain |output_style| {
166 output_style: output_style,
172 let process_output = Cell::new(process_output);
173 let result = do result.chain |config| {
174 let pandoc_cmd = getopts::opt_maybe_str(matches, opt_pandoc_cmd());
175 let pandoc_cmd = maybe_find_pandoc(
176 &config, pandoc_cmd, process_output.take());
177 do pandoc_cmd.chain |pandoc_cmd| {
179 pandoc_cmd: pandoc_cmd,
187 fn parse_output_format(output_format: &str) -> Result<OutputFormat, ~str> {
188 match output_format.to_str() {
189 ~"markdown" => result::Ok(Markdown),
190 ~"html" => result::Ok(PandocHtml),
191 _ => result::Err(fmt!("unknown output format '%s'", output_format))
195 fn parse_output_style(output_style: &str) -> Result<OutputStyle, ~str> {
196 match output_style.to_str() {
197 ~"doc-per-crate" => result::Ok(DocPerCrate),
198 ~"doc-per-mod" => result::Ok(DocPerMod),
199 _ => result::Err(fmt!("unknown output style '%s'", output_style))
203 pub fn maybe_find_pandoc(
205 maybe_pandoc_cmd: Option<~str>,
206 process_output: Process
207 ) -> Result<Option<~str>, ~str> {
208 if config.output_format != PandocHtml {
209 return result::Ok(maybe_pandoc_cmd);
212 let possible_pandocs = match maybe_pandoc_cmd {
213 Some(pandoc_cmd) => ~[pandoc_cmd],
215 ~[~"pandoc"] + match os::homedir() {
217 ~[dir.push_rel(&Path(".cabal/bin/pandoc")).to_str()]
224 let pandoc = do possible_pandocs.iter().find_ |&pandoc| {
225 let output = process_output(*pandoc, [~"--version"]);
226 debug!("testing pandoc cmd %s: %?", *pandoc, output);
231 Some(x) => Ok(Some((*x).clone())), // ugly, shouldn't be doubly wrapped
232 None => Err(~"couldn't find pandoc")
241 use std::run::ProcessOutput;
243 fn parse_config(args: &[~str]) -> Result<Config, ~str> {
244 parse_config_(args, mock_process_output)
248 fn should_find_pandoc() {
249 let config = Config {
250 output_format: PandocHtml,
251 .. default_config(&Path("test"))
253 let mock_process_output: ~fn(&str, &[~str]) -> ProcessOutput = |_, _| {
254 ProcessOutput { status: 0, output: "pandoc 1.8.2.1".as_bytes().to_owned(), error: ~[] }
256 let result = maybe_find_pandoc(&config, None, mock_process_output);
257 assert!(result == result::Ok(Some(~"pandoc")));
261 fn should_error_with_no_pandoc() {
262 let config = Config {
263 output_format: PandocHtml,
264 .. default_config(&Path("test"))
266 let mock_process_output: ~fn(&str, &[~str]) -> ProcessOutput = |_, _| {
267 ProcessOutput { status: 1, output: ~[], error: ~[] }
269 let result = maybe_find_pandoc(&config, None, mock_process_output);
270 assert!(result == result::Err(~"couldn't find pandoc"));
274 fn should_error_with_no_crates() {
275 let config = parse_config([~"rustdoc"]);
276 assert!(config.unwrap_err() == ~"no crates specified");
280 fn should_error_with_multiple_crates() {
282 parse_config([~"rustdoc", ~"crate1.rc", ~"crate2.rc"]);
283 assert!(config.unwrap_err() == ~"multiple crates specified");
287 fn should_set_output_dir_to_cwd_if_not_provided() {
288 let config = parse_config([~"rustdoc", ~"crate.rc"]);
289 assert!(config.unwrap().output_dir == Path("."));
293 fn should_set_output_dir_if_provided() {
294 let config = parse_config([
295 ~"rustdoc", ~"crate.rc", ~"--output-dir", ~"snuggles"
297 assert!(config.unwrap().output_dir == Path("snuggles"));
301 fn should_set_output_format_to_pandoc_html_if_not_provided() {
302 let config = parse_config([~"rustdoc", ~"crate.rc"]);
303 assert!(config.unwrap().output_format == PandocHtml);
307 fn should_set_output_format_to_markdown_if_requested() {
308 let config = parse_config([
309 ~"rustdoc", ~"crate.rc", ~"--output-format", ~"markdown"
311 assert!(config.unwrap().output_format == Markdown);
315 fn should_set_output_format_to_pandoc_html_if_requested() {
316 let config = parse_config([
317 ~"rustdoc", ~"crate.rc", ~"--output-format", ~"html"
319 assert!(config.unwrap().output_format == PandocHtml);
323 fn should_error_on_bogus_format() {
324 let config = parse_config([
325 ~"rustdoc", ~"crate.rc", ~"--output-format", ~"bogus"
327 assert!(config.unwrap_err() == ~"unknown output format 'bogus'");
331 fn should_set_output_style_to_doc_per_mod_by_default() {
332 let config = parse_config([~"rustdoc", ~"crate.rc"]);
333 assert!(config.unwrap().output_style == DocPerMod);
337 fn should_set_output_style_to_one_doc_if_requested() {
338 let config = parse_config([
339 ~"rustdoc", ~"crate.rc", ~"--output-style", ~"doc-per-crate"
341 assert!(config.unwrap().output_style == DocPerCrate);
345 fn should_set_output_style_to_doc_per_mod_if_requested() {
346 let config = parse_config([
347 ~"rustdoc", ~"crate.rc", ~"--output-style", ~"doc-per-mod"
349 assert!(config.unwrap().output_style == DocPerMod);
353 fn should_error_on_bogus_output_style() {
354 let config = parse_config([
355 ~"rustdoc", ~"crate.rc", ~"--output-style", ~"bogus"
357 assert!(config.unwrap_err() == ~"unknown output style 'bogus'");
361 fn should_set_pandoc_command_if_requested() {
362 let config = parse_config([
363 ~"rustdoc", ~"crate.rc", ~"--pandoc-cmd", ~"panda-bear-doc"
365 assert!(config.unwrap().pandoc_cmd == Some(~"panda-bear-doc"));
369 fn should_set_pandoc_command_when_using_pandoc() {
370 let config = parse_config([~"rustdoc", ~"crate.rc"]);
371 assert!(config.unwrap().pandoc_cmd == Some(~"pandoc"));