config.rs 10.2 KB
Newer Older
1
import result::result;
2 3
import std::getopts;

4 5
export output_format;
export output_style;
6
export config;
B
Brian Anderson 已提交
7
export default_config;
8
export parse_config;
B
Brian Anderson 已提交
9
export usage;
10 11
export markdown, pandoc_html;
export doc_per_crate, doc_per_mod;
12

13
/// The type of document to output
14
enum output_format {
15
    /// Markdown
16
    markdown,
17
    /// HTML, via markdown and pandoc
18 19 20
    pandoc_html
}

21
/// How to organize the output
22
enum output_style {
23
    /// All in a single document
24
    doc_per_crate,
25
    /// Each module in its own document
26 27 28
    doc_per_mod
}

29
/// The configuration for a rustdoc session
30
type config = {
31 32
    input_crate: ~str,
    output_dir: ~str,
33 34
    output_format: output_format,
    output_style: output_style,
35
    pandoc_cmd: option<~str>
36 37
};

38 39 40 41 42
fn opt_output_dir() -> ~str { ~"output-dir" }
fn opt_output_format() -> ~str { ~"output-format" }
fn opt_output_style() -> ~str { ~"output-style" }
fn opt_pandoc_cmd() -> ~str { ~"pandoc-cmd" }
fn opt_help() -> ~str { ~"h" }
43

44
fn opts() -> ~[(getopts::opt, ~str)] {
45
    ~[
46
        (getopts::optopt(opt_output_dir()),
47
         ~"--output-dir <val>     put documents here"),
48
        (getopts::optopt(opt_output_format()),
49
         ~"--output-format <val>  either 'markdown' or 'html'"),
50
        (getopts::optopt(opt_output_style()),
51
         ~"--output-style <val>   either 'doc-per-crate' or 'doc-per-mod'"),
52
        (getopts::optopt(opt_pandoc_cmd()),
53
         ~"--pandoc-cmd <val>     the command for running pandoc"),
B
Brian Anderson 已提交
54
        (getopts::optflag(opt_help()),
55
         ~"-h                     print help")
56
    ]
57 58
}

B
Brian Anderson 已提交
59
fn usage() {
60
    import io::println;
B
Brian Anderson 已提交
61

62 63
    println(~"Usage: rustdoc ~[options] <cratefile>\n");
    println(~"Options:\n");
B
Brian Anderson 已提交
64
    for opts().each |opt| {
65
        println(fmt!{"    %s", opt.second()});
B
Brian Anderson 已提交
66
    }
67
    println(~"");
B
Brian Anderson 已提交
68 69
}

70
fn default_config(input_crate: ~str) -> config {
71 72
    {
        input_crate: input_crate,
73
        output_dir: ~".",
74 75 76 77 78 79
        output_format: pandoc_html,
        output_style: doc_per_mod,
        pandoc_cmd: none
    }
}

80 81
type program_output = fn~(~str, ~[~str]) ->
    {status: int, out: ~str, err: ~str};
82

83 84
fn mock_program_output(_prog: ~str, _args: ~[~str]) -> {
    status: int, out: ~str, err: ~str
85 86 87
} {
    {
        status: 0,
88 89
        out: ~"",
        err: ~""
90 91 92
    }
}

93
fn parse_config(args: ~[~str]) -> result<config, ~str> {
94
    parse_config_(args, run::program_output)
95 96 97
}

fn parse_config_(
98
    args: ~[~str],
99
    program_output: program_output
100
) -> result<config, ~str> {
101
    let args = vec::tail(args);
102
    let opts = vec::unzip(opts()).first();
103
    alt getopts::getopts(args, opts) {
104 105 106 107 108
        result::ok(matches) {
            if vec::len(matches.free) == 1u {
                let input_crate = vec::head(matches.free);
                config_from_opts(input_crate, matches, program_output)
            } else if vec::is_empty(matches.free) {
109
                result::err(~"no crates specified")
110
            } else {
111
                result::err(~"multiple crates specified")
112 113 114 115 116 117 118 119 120
            }
        }
        result::err(f) {
            result::err(getopts::fail_str(f))
        }
    }
}

fn config_from_opts(
121
    input_crate: ~str,
122
    matches: getopts::matches,
123
    program_output: program_output
124
) -> result<config, ~str> {
125 126 127

    let config = default_config(input_crate);
    let result = result::ok(config);
B
Brian Anderson 已提交
128
    let result = do result::chain(result) |config| {
129
        let output_dir = getopts::opt_maybe_str(matches, opt_output_dir());
130
        result::ok({
131
            output_dir: option::get_default(output_dir, config.output_dir)
132 133 134
            with config
        })
    };
B
Brian Anderson 已提交
135
    let result = do result::chain(result) |config| {
136
        let output_format = getopts::opt_maybe_str(
137
            matches, opt_output_format());
138
        do option::map_default(output_format, result::ok(config))
B
Brian Anderson 已提交
139
            |output_format| {
140
            do result::chain(parse_output_format(output_format))
B
Brian Anderson 已提交
141
                |output_format| {
142

143 144 145 146 147 148 149
                result::ok({
                    output_format: output_format
                    with config
                })
            }
        }
    };
B
Brian Anderson 已提交
150
    let result = do result::chain(result) |config| {
151 152
        let output_style =
            getopts::opt_maybe_str(matches, opt_output_style());
153
        do option::map_default(output_style, result::ok(config))
B
Brian Anderson 已提交
154 155 156
            |output_style| {
            do result::chain(parse_output_style(output_style))
                |output_style| {
157 158 159 160 161 162 163
                result::ok({
                    output_style: output_style
                    with config
                })
            }
        }
    };
B
Brian Anderson 已提交
164
    let result = do result::chain(result) |config| {
165
        let pandoc_cmd = getopts::opt_maybe_str(matches, opt_pandoc_cmd());
166 167
        let pandoc_cmd = maybe_find_pandoc(
            config, pandoc_cmd, program_output);
B
Brian Anderson 已提交
168
        do result::chain(pandoc_cmd) |pandoc_cmd| {
169 170 171 172 173 174 175 176 177
            result::ok({
                pandoc_cmd: pandoc_cmd
                with config
            })
        }
    };
    ret result;
}

178
fn parse_output_format(output_format: ~str) -> result<output_format, ~str> {
179
    alt output_format {
180 181
      ~"markdown" { result::ok(markdown) }
      ~"html" { result::ok(pandoc_html) }
182
      _ { result::err(fmt!{"unknown output format '%s'", output_format}) }
183 184 185
    }
}

186
fn parse_output_style(output_style: ~str) -> result<output_style, ~str> {
187
    alt output_style {
188 189
      ~"doc-per-crate" { result::ok(doc_per_crate) }
      ~"doc-per-mod" { result::ok(doc_per_mod) }
190
      _ { result::err(fmt!{"unknown output style '%s'", output_style}) }
191 192 193 194
    }
}

fn maybe_find_pandoc(
195
    config: config,
196
    maybe_pandoc_cmd: option<~str>,
197
    program_output: program_output
198
) -> result<option<~str>, ~str> {
199 200 201 202 203
    if config.output_format != pandoc_html {
        ret result::ok(maybe_pandoc_cmd);
    }

    let possible_pandocs = alt maybe_pandoc_cmd {
204
      some(pandoc_cmd) { ~[pandoc_cmd] }
205
      none {
206
        ~[~"pandoc"] + alt os::homedir() {
207
          some(dir) {
208
            ~[path::connect(dir, ~".cabal/bin/pandoc")]
209
          }
210
          none { ~[] }
211
        }
212 213 214
      }
    };

B
Brian Anderson 已提交
215
    let pandoc = do vec::find(possible_pandocs) |pandoc| {
216
        let output = program_output(pandoc, ~[~"--version"]);
217
        debug!{"testing pandoc cmd %s: %?", pandoc, output};
218 219 220 221 222 223
        output.status == 0
    };

    if option::is_some(pandoc) {
        result::ok(pandoc)
    } else {
224
        result::err(~"couldn't find pandoc")
225 226 227 228 229 230 231
    }
}

#[test]
fn should_find_pandoc() {
    let config = {
        output_format: pandoc_html
232
        with default_config(~"test")
233
    };
234 235
    let mock_program_output = fn~(_prog: ~str, _args: ~[~str]) -> {
        status: int, out: ~str, err: ~str
236 237
    } {
        {
238
            status: 0, out: ~"pandoc 1.8.2.1", err: ~""
239 240 241
        }
    };
    let result = maybe_find_pandoc(config, none, mock_program_output);
242
    assert result == result::ok(some(~"pandoc"));
243 244 245 246 247 248
}

#[test]
fn should_error_with_no_pandoc() {
    let config = {
        output_format: pandoc_html
249
        with default_config(~"test")
250
    };
251 252
    let mock_program_output = fn~(_prog: ~str, _args: ~[~str]) -> {
        status: int, out: ~str, err: ~str
253 254
    } {
        {
255
            status: 1, out: ~"", err: ~""
256 257 258
        }
    };
    let result = maybe_find_pandoc(config, none, mock_program_output);
259
    assert result == result::err(~"couldn't find pandoc");
260 261 262 263
}

#[cfg(test)]
mod test {
264
    fn parse_config(args: ~[~str]) -> result<config, ~str> {
265
        parse_config_(args, mock_program_output)
266 267 268 269 270
    }
}

#[test]
fn should_error_with_no_crates() {
271 272
    let config = test::parse_config(~[~"rustdoc"]);
    assert result::get_err(config) == ~"no crates specified";
273 274 275 276
}

#[test]
fn should_error_with_multiple_crates() {
277
    let config =
278 279
        test::parse_config(~[~"rustdoc", ~"crate1.rc", ~"crate2.rc"]);
    assert result::get_err(config) == ~"multiple crates specified";
280 281 282 283
}

#[test]
fn should_set_output_dir_to_cwd_if_not_provided() {
284 285
    let config = test::parse_config(~[~"rustdoc", ~"crate.rc"]);
    assert result::get(config).output_dir == ~".";
286 287 288 289
}

#[test]
fn should_set_output_dir_if_provided() {
290
    let config = test::parse_config(~[
291
        ~"rustdoc", ~"crate.rc", ~"--output-dir", ~"snuggles"
292
    ]);
293
    assert result::get(config).output_dir == ~"snuggles";
294 295 296 297
}

#[test]
fn should_set_output_format_to_pandoc_html_if_not_provided() {
298
    let config = test::parse_config(~[~"rustdoc", ~"crate.rc"]);
299 300 301 302 303
    assert result::get(config).output_format == pandoc_html;
}

#[test]
fn should_set_output_format_to_markdown_if_requested() {
304
    let config = test::parse_config(~[
305
        ~"rustdoc", ~"crate.rc", ~"--output-format", ~"markdown"
306
    ]);
307 308 309 310 311
    assert result::get(config).output_format == markdown;
}

#[test]
fn should_set_output_format_to_pandoc_html_if_requested() {
312
    let config = test::parse_config(~[
313
        ~"rustdoc", ~"crate.rc", ~"--output-format", ~"html"
314
    ]);
315 316 317 318 319
    assert result::get(config).output_format == pandoc_html;
}

#[test]
fn should_error_on_bogus_format() {
320
    let config = test::parse_config(~[
321
        ~"rustdoc", ~"crate.rc", ~"--output-format", ~"bogus"
322
    ]);
323
    assert result::get_err(config) == ~"unknown output format 'bogus'";
324 325 326 327
}

#[test]
fn should_set_output_style_to_doc_per_mod_by_default() {
328
    let config = test::parse_config(~[~"rustdoc", ~"crate.rc"]);
329 330 331 332 333
    assert result::get(config).output_style == doc_per_mod;
}

#[test]
fn should_set_output_style_to_one_doc_if_requested() {
334
    let config = test::parse_config(~[
335
        ~"rustdoc", ~"crate.rc", ~"--output-style", ~"doc-per-crate"
336
    ]);
337 338 339 340 341
    assert result::get(config).output_style == doc_per_crate;
}

#[test]
fn should_set_output_style_to_doc_per_mod_if_requested() {
342
    let config = test::parse_config(~[
343
        ~"rustdoc", ~"crate.rc", ~"--output-style", ~"doc-per-mod"
344
    ]);
345 346 347 348 349
    assert result::get(config).output_style == doc_per_mod;
}

#[test]
fn should_error_on_bogus_output_style() {
350
    let config = test::parse_config(~[
351
        ~"rustdoc", ~"crate.rc", ~"--output-style", ~"bogus"
352
    ]);
353
    assert result::get_err(config) == ~"unknown output style 'bogus'";
354 355 356 357
}

#[test]
fn should_set_pandoc_command_if_requested() {
358
    let config = test::parse_config(~[
359
        ~"rustdoc", ~"crate.rc", ~"--pandoc-cmd", ~"panda-bear-doc"
360
    ]);
361
    assert result::get(config).pandoc_cmd == some(~"panda-bear-doc");
362 363 364 365
}

#[test]
fn should_set_pandoc_command_when_using_pandoc() {
366 367
    let config = test::parse_config(~[~"rustdoc", ~"crate.rc"]);
    assert result::get(config).pandoc_cmd == some(~"pandoc");
368
}