diff --git a/Cargo.toml b/Cargo.toml index cf3c9aa5..80a10084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,8 @@ log = "0.4" default = ["unicode"] rustc-dep-of-std = ["std", "core"] unicode = ["dep:unicode-width"] + +[workspace] +members = ["getopts-test"] +default-members = [".", "getopts-test"] +resolver = "2" diff --git a/getopts-test/Cargo.toml b/getopts-test/Cargo.toml new file mode 100644 index 00000000..c25e45d2 --- /dev/null +++ b/getopts-test/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "getopts-test" +version = "0.0.0" +edition = "2021" +rust-version = "1.66" +publish = false + +[dependencies] +# Preserve the workspace's `--no-default-features` test coverage. +getopts = { path = "..", default-features = false } + +[dev-dependencies] +assert_cmd = "2" +predicates = "3" diff --git a/getopts-test/src/main.rs b/getopts-test/src/main.rs new file mode 100644 index 00000000..baa8d180 --- /dev/null +++ b/getopts-test/src/main.rs @@ -0,0 +1,58 @@ +use getopts::{Matches, Options}; +use std::{env, process}; + +const BRIEF: &str = "Usage: getopts-test [options] INPUT..."; + +fn options() -> Options { + let mut options = Options::new(); + options.optflagmulti("v", "verbose", "increase verbosity"); + options.optopt("o", "output", "write output to FILE", "FILE"); + options.optmulti("I", "include", "add DIR to the include path", "DIR"); + options.optflagopt("", "color", "control color output", "WHEN"); + options.optflag("h", "help", "print this help menu"); + options +} + +fn fail(options: &Options, message: impl std::fmt::Display) -> ! { + eprintln!("error: {message}"); + eprintln!(); + eprint!("{}", options.usage(BRIEF)); + process::exit(2); +} + +fn print_matches(matches: &Matches) { + let output = matches + .opt_str("output") + .unwrap_or_else(|| "".into()); + let color = matches + .opt_default("color", "auto") + .unwrap_or_else(|| "never".into()); + + println!("verbosity: {}", matches.opt_count("verbose")); + println!("output: {output}"); + println!("color: {color}"); + + for include in matches.opt_strs("include") { + println!("include: {include}"); + } + for input in &matches.free { + println!("input: {input}"); + } +} + +fn main() { + let options = options(); + let matches = options + .parse(env::args().skip(1)) + .unwrap_or_else(|error| fail(&options, error)); + + if matches.opt_present("help") { + print!("{}", options.usage(BRIEF)); + return; + } + if matches.free.is_empty() { + fail(&options, "at least one INPUT is required"); + } + + print_matches(&matches); +} diff --git a/getopts-test/tests/cli.rs b/getopts-test/tests/cli.rs new file mode 100644 index 00000000..22156317 --- /dev/null +++ b/getopts-test/tests/cli.rs @@ -0,0 +1,77 @@ +use assert_cmd::Command; +use predicates::prelude::*; + +const BRIEF: &str = "Usage: getopts-test [options] INPUT..."; + +fn command() -> Command { + Command::new(env!("CARGO_BIN_EXE_getopts-test")) +} + +#[test] +fn parses_options_and_positional_arguments() { + command() + .args([ + "-vv", + "--output", + "report.txt", + "-I", + "src", + "--include", + "tests", + "--color=always", + "input.txt", + "extra.txt", + ]) + .assert() + .success() + .stdout( + "verbosity: 2\n\ + output: report.txt\n\ + color: always\n\ + include: src\n\ + include: tests\n\ + input: input.txt\n\ + input: extra.txt\n", + ) + .stderr(""); +} + +#[test] +fn uses_defaults_and_honors_the_option_terminator() { + command() + .args(["--color", "--", "-input.txt"]) + .assert() + .success() + .stdout( + "verbosity: 0\n\ + output: \n\ + color: auto\n\ + input: -input.txt\n", + ) + .stderr(""); +} + +#[test] +fn prints_help() { + command() + .arg("--help") + .assert() + .success() + .stdout(predicate::str::contains(BRIEF)) + .stdout(predicate::str::contains("--color [WHEN]")) + .stderr(""); +} + +#[test] +fn reports_parse_and_validation_errors() { + command() + .arg("--unknown") + .assert() + .code(2) + .stderr(predicate::str::contains("Unrecognized option")); + + command() + .assert() + .code(2) + .stderr(predicate::str::contains("at least one INPUT is required")); +}