]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/config.rs
Fix bug in `match`ing struct patterns
[rust.git] / src / librustdoc / config.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
12 use std::cell::Cell;
13 use std::os;
14 use std::result::Result;
15 use std::result;
16 use std::run::ProcessOutput;
17 use std::run;
18 use std::vec;
19 use extra::getopts;
20
21 /// The type of document to output
22 #[deriving(Clone, Eq)]
23 pub enum OutputFormat {
24     /// Markdown
25     pub Markdown,
26     /// HTML, via markdown and pandoc
27     pub PandocHtml
28 }
29
30 /// How to organize the output
31 #[deriving(Clone, Eq)]
32 pub enum OutputStyle {
33     /// All in a single document
34     pub DocPerCrate,
35     /// Each module in its own document
36     pub DocPerMod
37 }
38
39 /// The configuration for a rustdoc session
40 #[deriving(Clone)]
41 pub struct Config {
42     input_crate: Path,
43     output_dir: Path,
44     output_format: OutputFormat,
45     output_style: OutputStyle,
46     pandoc_cmd: Option<~str>
47 }
48
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" }
54
55 fn opts() -> ~[(getopts::Opt, ~str)] {
56     ~[
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")
67     ]
68 }
69
70 pub fn usage() {
71     use std::io::println;
72
73     println("Usage: rustdoc [options] <cratefile>\n");
74     println("Options:\n");
75     let r = opts();
76     for opt in r.iter() {
77         printfln!("    %s", opt.second());
78     }
79     println("");
80 }
81
82 pub fn default_config(input_crate: &Path) -> Config {
83     Config {
84         input_crate: (*input_crate).clone(),
85         output_dir: Path("."),
86         output_format: PandocHtml,
87         output_style: DocPerMod,
88         pandoc_cmd: None
89     }
90 }
91
92 type Process = ~fn((&str), (&[~str])) -> ProcessOutput;
93
94 pub fn mock_process_output(_prog: &str, _args: &[~str]) -> ProcessOutput {
95     ProcessOutput {
96         status: 0,
97         output: ~[],
98         error: ~[]
99     }
100 }
101
102 pub fn process_output(prog: &str, args: &[~str]) -> ProcessOutput {
103     run::process_output(prog, args)
104 }
105
106 pub fn parse_config(args: &[~str]) -> Result<Config, ~str> {
107     parse_config_(args, process_output)
108 }
109
110 pub fn parse_config_(
111     args: &[~str],
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) {
117         Ok(matches) => {
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")
123             } else {
124                 Err(~"multiple crates specified")
125             }
126         }
127         Err(f) => {
128             Err(getopts::fail_str(f))
129         }
130     }
131 }
132
133 fn config_from_opts(
134     input_crate: &Path,
135     matches: &getopts::Matches,
136     process_output: Process
137 ) -> Result<Config, ~str> {
138
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));
144         result::Ok(Config {
145             output_dir: output_dir.unwrap_or_default(config.output_dir.clone()),
146             .. config
147         })
148     };
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| {
153                 result::Ok(Config {
154                     output_format: output_format,
155                     .. config.clone()
156                 })
157             }
158         }
159     };
160     let result = do result.chain |config| {
161         let output_style =
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| {
165                 result::Ok(Config {
166                     output_style: output_style,
167                     .. config.clone()
168                 })
169             }
170         }
171     };
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| {
178             result::Ok(Config {
179                 pandoc_cmd: pandoc_cmd,
180                 .. config.clone()
181             })
182         }
183     };
184     return result;
185 }
186
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))
192     }
193 }
194
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))
200     }
201 }
202
203 pub fn maybe_find_pandoc(
204     config: &Config,
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);
210     }
211
212     let possible_pandocs = match maybe_pandoc_cmd {
213       Some(pandoc_cmd) => ~[pandoc_cmd],
214       None => {
215         ~[~"pandoc"] + match os::homedir() {
216           Some(dir) => {
217             ~[dir.push_rel(&Path(".cabal/bin/pandoc")).to_str()]
218           }
219           None => ~[]
220         }
221       }
222     };
223
224     let pandoc = do possible_pandocs.iter().find_ |&pandoc| {
225         let output = process_output(*pandoc, [~"--version"]);
226         debug!("testing pandoc cmd %s: %?", *pandoc, output);
227         output.status == 0
228     };
229
230     match pandoc {
231         Some(x) => Ok(Some((*x).clone())), // ugly, shouldn't be doubly wrapped
232         None => Err(~"couldn't find pandoc")
233     }
234 }
235
236 #[cfg(test)]
237 mod test {
238
239     use config::*;
240     use std::result;
241     use std::run::ProcessOutput;
242
243     fn parse_config(args: &[~str]) -> Result<Config, ~str> {
244         parse_config_(args, mock_process_output)
245     }
246
247     #[test]
248     fn should_find_pandoc() {
249         let config = Config {
250             output_format: PandocHtml,
251             .. default_config(&Path("test"))
252         };
253         let mock_process_output: ~fn(&str, &[~str]) -> ProcessOutput = |_, _| {
254             ProcessOutput { status: 0, output: "pandoc 1.8.2.1".as_bytes().to_owned(), error: ~[] }
255         };
256         let result = maybe_find_pandoc(&config, None, mock_process_output);
257         assert!(result == result::Ok(Some(~"pandoc")));
258     }
259
260     #[test]
261     fn should_error_with_no_pandoc() {
262         let config = Config {
263             output_format: PandocHtml,
264             .. default_config(&Path("test"))
265         };
266         let mock_process_output: ~fn(&str, &[~str]) -> ProcessOutput = |_, _| {
267             ProcessOutput { status: 1, output: ~[], error: ~[] }
268         };
269         let result = maybe_find_pandoc(&config, None, mock_process_output);
270         assert!(result == result::Err(~"couldn't find pandoc"));
271     }
272
273     #[test]
274     fn should_error_with_no_crates() {
275         let config = parse_config([~"rustdoc"]);
276         assert!(config.unwrap_err() == ~"no crates specified");
277     }
278
279     #[test]
280     fn should_error_with_multiple_crates() {
281         let config =
282             parse_config([~"rustdoc", ~"crate1.rc", ~"crate2.rc"]);
283         assert!(config.unwrap_err() == ~"multiple crates specified");
284     }
285
286     #[test]
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("."));
290     }
291
292     #[test]
293     fn should_set_output_dir_if_provided() {
294         let config = parse_config([
295             ~"rustdoc", ~"crate.rc", ~"--output-dir", ~"snuggles"
296         ]);
297         assert!(config.unwrap().output_dir == Path("snuggles"));
298     }
299
300     #[test]
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);
304     }
305
306     #[test]
307     fn should_set_output_format_to_markdown_if_requested() {
308         let config = parse_config([
309             ~"rustdoc", ~"crate.rc", ~"--output-format", ~"markdown"
310         ]);
311         assert!(config.unwrap().output_format == Markdown);
312     }
313
314     #[test]
315     fn should_set_output_format_to_pandoc_html_if_requested() {
316         let config = parse_config([
317             ~"rustdoc", ~"crate.rc", ~"--output-format", ~"html"
318         ]);
319         assert!(config.unwrap().output_format == PandocHtml);
320     }
321
322     #[test]
323     fn should_error_on_bogus_format() {
324         let config = parse_config([
325             ~"rustdoc", ~"crate.rc", ~"--output-format", ~"bogus"
326         ]);
327         assert!(config.unwrap_err() == ~"unknown output format 'bogus'");
328     }
329
330     #[test]
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);
334     }
335
336     #[test]
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"
340         ]);
341         assert!(config.unwrap().output_style == DocPerCrate);
342     }
343
344     #[test]
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"
348         ]);
349         assert!(config.unwrap().output_style == DocPerMod);
350     }
351
352     #[test]
353     fn should_error_on_bogus_output_style() {
354         let config = parse_config([
355             ~"rustdoc", ~"crate.rc", ~"--output-style", ~"bogus"
356         ]);
357         assert!(config.unwrap_err() == ~"unknown output style 'bogus'");
358     }
359
360     #[test]
361     fn should_set_pandoc_command_if_requested() {
362         let config = parse_config([
363             ~"rustdoc", ~"crate.rc", ~"--pandoc-cmd", ~"panda-bear-doc"
364         ]);
365         assert!(config.unwrap().pandoc_cmd == Some(~"panda-bear-doc"));
366     }
367
368     #[test]
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"));
372     }
373 }