config.rs 10.0 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 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

#[doc = "The type of document to output"]
enum output_format {
    #[doc = "Markdown"]
    markdown,
    #[doc = "HTML, via markdown and pandoc"]
    pandoc_html
}

#[doc = "How to organize the output"]
enum output_style {
    #[doc = "All in a single document"]
    doc_per_crate,
    #[doc = "Each module in its own document"]
    doc_per_mod
}

#[doc = "The configuration for a rustdoc session"]
type config = {
    input_crate: str,
    output_dir: str,
    output_format: output_format,
    output_style: output_style,
    pandoc_cmd: option<str>
};

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" }
B
Brian Anderson 已提交
40
fn opt_help() -> str { "h" }
41

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

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

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

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

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

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

90
fn parse_config(args: ~[str]) -> result<config, str> {
91
    parse_config_(args, run::program_output)
92 93 94
}

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

fn config_from_opts(
    input_crate: str,
119 120
    match: getopts::match,
    program_output: program_output
121
) -> result<config, str> {
122 123 124

    let config = default_config(input_crate);
    let result = result::ok(config);
125
    let result = do result::chain(result) {|config|
126 127
        let output_dir = getopts::opt_maybe_str(match, opt_output_dir());
        result::ok({
128
            output_dir: option::get_default(output_dir, config.output_dir)
129 130 131
            with config
        })
    };
132
    let result = do result::chain(result) {|config|
133 134
        let output_format = getopts::opt_maybe_str(
            match, opt_output_format());
135
        do option::map_default(output_format, result::ok(config))
136
           {|output_format|
137 138 139
            do result::chain(parse_output_format(output_format))
                {|output_format|

140 141 142 143 144 145 146
                result::ok({
                    output_format: output_format
                    with config
                })
            }
        }
    };
147
    let result = do result::chain(result) {|config|
148
        let output_style = getopts::opt_maybe_str(match, opt_output_style());
149
        do option::map_default(output_style, result::ok(config))
150
          {|output_style|
151
            do result::chain(parse_output_style(output_style)) {|output_style|
152 153 154 155 156 157 158
                result::ok({
                    output_style: output_style
                    with config
                })
            }
        }
    };
159
    let result = do result::chain(result) {|config|
160
        let pandoc_cmd = getopts::opt_maybe_str(match, opt_pandoc_cmd());
161 162
        let pandoc_cmd = maybe_find_pandoc(
            config, pandoc_cmd, program_output);
163
        do result::chain(pandoc_cmd) {|pandoc_cmd|
164 165 166 167 168 169 170 171 172
            result::ok({
                pandoc_cmd: pandoc_cmd
                with config
            })
        }
    };
    ret result;
}

173
fn parse_output_format(output_format: str) -> result<output_format, str> {
174 175 176 177 178 179 180
    alt output_format {
      "markdown" { result::ok(markdown) }
      "html" { result::ok(pandoc_html) }
      _ { result::err(#fmt("unknown output format '%s'", output_format)) }
    }
}

181
fn parse_output_style(output_style: str) -> result<output_style, str> {
182 183 184 185 186 187 188 189
    alt output_style {
      "doc-per-crate" { result::ok(doc_per_crate) }
      "doc-per-mod" { result::ok(doc_per_mod) }
      _ { result::err(#fmt("unknown output style '%s'", output_style)) }
    }
}

fn maybe_find_pandoc(
190 191 192
    config: config,
    maybe_pandoc_cmd: option<str>,
    program_output: program_output
193
) -> result<option<str>, str> {
194 195 196 197 198
    if config.output_format != pandoc_html {
        ret result::ok(maybe_pandoc_cmd);
    }

    let possible_pandocs = alt maybe_pandoc_cmd {
199
      some(pandoc_cmd) { ~[pandoc_cmd] }
200
      none {
201
        ~["pandoc"] + alt os::homedir() {
202
          some(dir) {
203
            ~[path::connect(dir, ".cabal/bin/pandoc")]
204
          }
205
          none { ~[] }
206
        }
207 208 209
      }
    };

210
    let pandoc = do vec::find(possible_pandocs) {|pandoc|
211
        let output = program_output(pandoc, ~["--version"]);
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
        #debug("testing pandoc cmd %s: %?", pandoc, output);
        output.status == 0
    };

    if option::is_some(pandoc) {
        result::ok(pandoc)
    } else {
        result::err("couldn't find pandoc")
    }
}

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

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

#[cfg(test)]
mod test {
259
    fn parse_config(args: ~[str]) -> result<config, str> {
260
        parse_config_(args, mock_program_output)
261 262 263 264 265
    }
}

#[test]
fn should_error_with_no_crates() {
266
    let config = test::parse_config(~["rustdoc"]);
267 268 269 270 271
    assert result::get_err(config) == "no crates specified";
}

#[test]
fn should_error_with_multiple_crates() {
272
    let config =
273
        test::parse_config(~["rustdoc", "crate1.rc", "crate2.rc"]);
274 275 276 277 278
    assert result::get_err(config) == "multiple crates specified";
}

#[test]
fn should_set_output_dir_to_cwd_if_not_provided() {
279
    let config = test::parse_config(~["rustdoc", "crate.rc"]);
280 281 282 283 284
    assert result::get(config).output_dir == ".";
}

#[test]
fn should_set_output_dir_if_provided() {
285
    let config = test::parse_config(~[
286
        "rustdoc", "crate.rc", "--output-dir", "snuggles"
287
    ]);
288 289 290 291 292
    assert result::get(config).output_dir == "snuggles";
}

#[test]
fn should_set_output_format_to_pandoc_html_if_not_provided() {
293
    let config = test::parse_config(~["rustdoc", "crate.rc"]);
294 295 296 297 298
    assert result::get(config).output_format == pandoc_html;
}

#[test]
fn should_set_output_format_to_markdown_if_requested() {
299
    let config = test::parse_config(~[
300
        "rustdoc", "crate.rc", "--output-format", "markdown"
301
    ]);
302 303 304 305 306
    assert result::get(config).output_format == markdown;
}

#[test]
fn should_set_output_format_to_pandoc_html_if_requested() {
307
    let config = test::parse_config(~[
308
        "rustdoc", "crate.rc", "--output-format", "html"
309
    ]);
310 311 312 313 314
    assert result::get(config).output_format == pandoc_html;
}

#[test]
fn should_error_on_bogus_format() {
315
    let config = test::parse_config(~[
316
        "rustdoc", "crate.rc", "--output-format", "bogus"
317
    ]);
318 319 320 321 322
    assert result::get_err(config) == "unknown output format 'bogus'";
}

#[test]
fn should_set_output_style_to_doc_per_mod_by_default() {
323
    let config = test::parse_config(~["rustdoc", "crate.rc"]);
324 325 326 327 328
    assert result::get(config).output_style == doc_per_mod;
}

#[test]
fn should_set_output_style_to_one_doc_if_requested() {
329
    let config = test::parse_config(~[
330
        "rustdoc", "crate.rc", "--output-style", "doc-per-crate"
331
    ]);
332 333 334 335 336
    assert result::get(config).output_style == doc_per_crate;
}

#[test]
fn should_set_output_style_to_doc_per_mod_if_requested() {
337
    let config = test::parse_config(~[
338
        "rustdoc", "crate.rc", "--output-style", "doc-per-mod"
339
    ]);
340 341 342 343 344
    assert result::get(config).output_style == doc_per_mod;
}

#[test]
fn should_error_on_bogus_output_style() {
345
    let config = test::parse_config(~[
346
        "rustdoc", "crate.rc", "--output-style", "bogus"
347
    ]);
348 349 350 351 352
    assert result::get_err(config) == "unknown output style 'bogus'";
}

#[test]
fn should_set_pandoc_command_if_requested() {
353
    let config = test::parse_config(~[
354
        "rustdoc", "crate.rc", "--pandoc-cmd", "panda-bear-doc"
355
    ]);
356 357 358 359 360
    assert result::get(config).pandoc_cmd == some("panda-bear-doc");
}

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