#![cfg(feature = "help")] use clap::{arg, builder::PossibleValue, error::ErrorKind, Arg, ArgAction, ArgGroup, Command}; use snapbox::assert_data_eq; use snapbox::str; use super::utils; fn setup() -> Command { Command::new("test") .author("Kevin K.") .about("tests stuff") .version("1.3") } fn empty_args() -> impl IntoIterator { std::iter::empty() } #[test] fn help_short() { let m = setup().try_get_matches_from(vec!["myprog", "-h"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp); } #[test] fn help_long() { let m = setup().try_get_matches_from(vec!["myprog", "--help"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp); } #[test] fn help_no_subcommand() { let m = setup().try_get_matches_from(vec!["myprog", "help"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind(), ErrorKind::UnknownArgument); } #[test] fn help_subcommand() { let m = setup() .subcommand( Command::new("test") .about("tests things") .arg(arg!(-v --verbose "with verbosity")), ) .try_get_matches_from(vec!["myprog", "help"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp); } #[test] #[cfg(feature = "error-context")] fn help_multi_subcommand_error() { let cmd = Command::new("ctest").subcommand( Command::new("subcmd").subcommand( Command::new("multi") .about("tests subcommands") .author("Kevin K. ") .version("0.1") .arg(arg!( -f --flag "tests flags" )) .arg( arg!( -o --option "tests options" ) .required(false) .num_args(1..) .action(ArgAction::Append), ), ), ); let err = cmd .try_get_matches_from(["ctest", "help", "subcmd", "multi", "foo"]) .unwrap_err(); assert_data_eq!(err.to_string(), str![[r#" error: unrecognized subcommand 'foo' Usage: ctest subcmd multi [OPTIONS] For more information, try '--help'. "#]]); } #[test] fn req_last_arg_usage() { static LAST_ARG_REQ_MULT: &str = "\ Usage: example ... -- ... Arguments: ... First ... Second Options: -h, --help Print help -V, --version Print version "; let cmd = Command::new("example") .version("1.0") .arg(Arg::new("FIRST").help("First").num_args(1..).required(true)) .arg( Arg::new("SECOND") .help("Second") .num_args(1..) .required(true) .last(true), ); utils::assert_output(cmd, "example --help", LAST_ARG_REQ_MULT, false); } #[test] fn args_with_last_usage() { static LAST_ARG_USAGE: &str = "\ Usage: flamegraph [OPTIONS] [BINFILE] [-- ...] Arguments: [BINFILE] The path of the binary to be profiled. for a binary. [ARGS]... Any arguments you wish to pass to the being profiled. Options: -v, --verbose Prints out more stuff. -t, --timeout Timeout in seconds. -f, --frequency The sampling frequency. -h, --help Print help -V, --version Print version "; let cmd = Command::new("flamegraph") .version("0.1") .arg( Arg::new("verbose") .help("Prints out more stuff.") .short('v') .long("verbose") .action(ArgAction::SetTrue), ) .arg( Arg::new("timeout") .help("Timeout in seconds.") .short('t') .long("timeout") .value_name("SECONDS"), ) .arg( Arg::new("frequency") .help("The sampling frequency.") .short('f') .long("frequency") .value_name("HERTZ"), ) .arg( Arg::new("binary path") .help("The path of the binary to be profiled. for a binary.") .value_name("BINFILE"), ) .arg( Arg::new("pass through args") .help("Any arguments you wish to pass to the being profiled.") .action(ArgAction::Set) .num_args(1..) .last(true) .value_name("ARGS"), ); utils::assert_output(cmd, "flamegraph --help", LAST_ARG_USAGE, false); } #[test] fn subcommand_short_help() { let m = utils::complex_app().try_get_matches_from(vec!["clap-test", "subcmd", "-h"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp); } #[test] fn subcommand_long_help() { let m = utils::complex_app().try_get_matches_from(vec!["clap-test", "subcmd", "--help"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp); } #[test] fn subcommand_help_rev() { let m = utils::complex_app().try_get_matches_from(vec!["clap-test", "help", "subcmd"]); assert!(m.is_err()); assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp); } #[test] fn complex_help_output() { static HELP: &str = "\ clap-test v1.4.8 Kevin K. tests clap library Usage: clap-test [OPTIONS] [positional] [positional2] [positional3]... [COMMAND] Commands: subcmd tests subcommands help Print this message or the help of the given subcommand(s) Arguments: [positional] tests positionals [positional2] tests positionals with exclusions [positional3]... tests specific values [possible values: vi, emacs] Options: -o, --option ... tests options -f, --flag... tests flags -F tests flags with exclusions --long-option-2 tests long options with exclusions -O, --option3 specific vals [possible values: fast, slow] --multvals Tests multiple values, not mult occs --multvalsmo Tests multiple values, and mult occs --minvals2 ... Tests 2 min vals --maxvals3 ... Tests 3 max vals --optvaleq[=] Tests optional value, require = sign --optvalnoeq [] Tests optional value -h, --help Print help -V, --version Print version "; utils::assert_output(utils::complex_app(), "clap-test --help", HELP, false); } #[test] fn after_and_before_help_output() { static AFTER_HELP: &str = "some text that comes before the help tests clap library Usage: clap-test Options: -h, --help Print help -V, --version Print version some text that comes after the help "; let cmd = Command::new("clap-test") .version("v1.4.8") .about("tests clap library") .before_help("some text that comes before the help") .after_help("some text that comes after the help"); utils::assert_output(cmd.clone(), "clap-test -h", AFTER_HELP, false); utils::assert_output(cmd, "clap-test --help", AFTER_HELP, false); } #[test] fn after_and_before_long_help_output() { static AFTER_HELP: &str = "some text that comes before the help tests clap library Usage: clap-test Options: -h, --help Print help (see more with '--help') -V, --version Print version some text that comes after the help "; static AFTER_LONG_HELP: &str = "some longer text that comes before the help tests clap library Usage: clap-test Options: -h, --help Print help (see a summary with '-h') -V, --version Print version some longer text that comes after the help "; let cmd = Command::new("clap-test") .version("v1.4.8") .about("tests clap library") .before_help("some text that comes before the help") .after_help("some text that comes after the help") .before_long_help("some longer text that comes before the help") .after_long_help("some longer text that comes after the help"); utils::assert_output(cmd.clone(), "clap-test --help", AFTER_LONG_HELP, false); utils::assert_output(cmd, "clap-test -h", AFTER_HELP, false); } static MULTI_SC_HELP: &str = "\ tests subcommands Usage: ctest subcmd multi [OPTIONS] Options: -f, --flag tests flags -o, --option ... tests options -h, --help Print help -V, --version Print version "; #[test] fn multi_level_sc_help() { let cmd = Command::new("ctest").subcommand( Command::new("subcmd").subcommand( Command::new("multi") .about("tests subcommands") .author("Kevin K. ") .version("0.1") .arg(arg!( -f --flag "tests flags" )) .arg( arg!( -o --option "tests options" ) .required(false) .num_args(1..) .action(ArgAction::Append), ), ), ); utils::assert_output(cmd, "ctest help subcmd multi", MULTI_SC_HELP, false); } #[test] fn no_wrap_default_help() { static DEFAULT_HELP: &str = "\ Usage: ctest Options: -h, --help Print help -V, --version Print version "; let cmd = Command::new("ctest").version("1.0").term_width(0); utils::assert_output(cmd, "ctest --help", DEFAULT_HELP, false); } #[test] #[cfg(feature = "wrap_help")] fn wrapped_help() { static WRAPPED_HELP: &str = "\ Usage: test [OPTIONS] Options: -a, --all Also do versioning for private crates (will not be published) --exact Specify inter dependency version numbers exactly with `=` --no-git-commit Do not commit version changes --no-git-push Do not push generated commit and tags to git remote -h, --help Print help "; let cmd = Command::new("test") .term_width(67) .arg( Arg::new("all") .short('a') .long("all") .action(ArgAction::SetTrue) .help("Also do versioning for private crates (will not be published)"), ) .arg( Arg::new("exact") .long("exact") .action(ArgAction::SetTrue) .help("Specify inter dependency version numbers exactly with `=`"), ) .arg( Arg::new("no_git_commit") .long("no-git-commit") .action(ArgAction::SetTrue) .help("Do not commit version changes"), ) .arg( Arg::new("no_git_push") .long("no-git-push") .action(ArgAction::SetTrue) .help("Do not push generated commit and tags to git remote"), ); utils::assert_output(cmd, "test --help", WRAPPED_HELP, false); } #[test] #[cfg(feature = "wrap_help")] fn unwrapped_help() { static UNWRAPPED_HELP: &str = "\ Usage: test [OPTIONS] Options: -a, --all Also do versioning for private crates (will not be published) --exact Specify inter dependency version numbers exactly with `=` --no-git-commit Do not commit version changes --no-git-push Do not push generated commit and tags to git remote -h, --help Print help "; let cmd = Command::new("test") .term_width(68) .arg( Arg::new("all") .short('a') .long("all") .action(ArgAction::SetTrue) .help("Also do versioning for private crates (will not be published)"), ) .arg( Arg::new("exact") .long("exact") .action(ArgAction::SetTrue) .help("Specify inter dependency version numbers exactly with `=`"), ) .arg( Arg::new("no_git_commit") .long("no-git-commit") .action(ArgAction::SetTrue) .help("Do not commit version changes"), ) .arg( Arg::new("no_git_push") .long("no-git-push") .action(ArgAction::SetTrue) .help("Do not push generated commit and tags to git remote"), ); utils::assert_output(cmd, "test --help", UNWRAPPED_HELP, false); } #[test] #[cfg(feature = "wrap_help")] fn possible_value_wrapped_help() { static WRAPPED_HELP: &str = "\ Usage: test [OPTIONS] Options: --possible-values Possible values: - short_name: Long enough help message, barely warrant wrapping - second: Short help gets handled the same --possible-values-with-new-line Possible values: - long enough name to trigger new line: Really long enough help message to clearly warrant wrapping believe me - second --possible-values-without-new-line Possible values: - name: Short enough help message with no wrapping - second: short help -h, --help Print help (see a summary with '-h') "; let cmd = Command::new("test") .term_width(67) .arg( Arg::new("possible_values") .long("possible-values") .action(ArgAction::Set) .value_parser([ PossibleValue::new("short_name") .help("Long enough help message, barely warrant wrapping"), PossibleValue::new("second").help("Short help gets handled the same"), ]), ) .arg( Arg::new("possible_values_with_new_line") .long("possible-values-with-new-line") .action(ArgAction::Set) .value_parser([ PossibleValue::new("long enough name to trigger new line").help( "Really long enough help message to clearly warrant wrapping believe me", ), PossibleValue::new("second"), ]), ) .arg( Arg::new("possible_values_without_new_line") .long("possible-values-without-new-line") .action(ArgAction::Set) .value_parser([ PossibleValue::new("name").help("Short enough help message with no wrapping"), PossibleValue::new("second").help("short help"), ]), ); utils::assert_output(cmd, "test --help", WRAPPED_HELP, false); } #[test] fn complex_subcommand_help_output() { static SC_HELP: &str = "clap-test-subcmd 0.1 Kevin K. tests subcommands Usage: clap-test subcmd [OPTIONS] [scpositional] Arguments: [scpositional] tests positionals Options: -o, --option ... tests options -f, --flag... tests flags -s, --subcmdarg tests other args -h, --help Print help -V, --version Print version "; let a = utils::complex_app(); utils::assert_output(a, "clap-test subcmd --help", SC_HELP, false); } #[test] #[cfg(feature = "wrap_help")] fn issue_626_unicode_cutoff() { static ISSUE_626_CUTOFF: &str = "\ Usage: ctest [OPTIONS] Options: -c, --cafe A coffeehouse, coffee shop, or café is an establishment which primarily serves hot coffee, related coffee beverages (e.g., café latte, cappuccino, espresso), tea, and other hot beverages. Some coffeehouses also serve cold beverages such as iced coffee and iced tea. Many cafés also serve some type of food, such as light snacks, muffins, or pastries. -h, --help Print help -V, --version Print version "; let cmd = Command::new("ctest").version("0.1").term_width(70).arg( Arg::new("cafe") .short('c') .long("cafe") .value_name("FILE") .help( "A coffeehouse, coffee shop, or café is an establishment \ which primarily serves hot coffee, related coffee beverages \ (e.g., café latte, cappuccino, espresso), tea, and other hot \ beverages. Some coffeehouses also serve cold beverages such as \ iced coffee and iced tea. Many cafés also serve some type of \ food, such as light snacks, muffins, or pastries.", ) .action(ArgAction::Set), ); utils::assert_output(cmd, "ctest --help", ISSUE_626_CUTOFF, false); } static HIDE_POS_VALS: &str = "\ Usage: ctest [OPTIONS] Options: -p, --pos Some vals [possible values: fast, slow] -c, --cafe A coffeehouse, coffee shop, or café. -h, --help Print help -V, --version Print version "; #[test] fn hide_possible_vals() { let cmd = Command::new("ctest") .version("0.1") .arg( Arg::new("pos") .short('p') .long("pos") .value_name("VAL") .value_parser(["fast", "slow"]) .help("Some vals") .action(ArgAction::Set), ) .arg( Arg::new("cafe") .short('c') .long("cafe") .value_name("FILE") .hide_possible_values(true) .value_parser(["fast", "slow"]) .help("A coffeehouse, coffee shop, or café.") .action(ArgAction::Set), ); utils::assert_output(cmd, "ctest --help", HIDE_POS_VALS, false); } #[test] fn hide_single_possible_val() { let cmd = Command::new("ctest") .version("0.1") .arg( Arg::new("pos") .short('p') .long("pos") .value_name("VAL") .value_parser([ "fast".into(), "slow".into(), PossibleValue::new("secret speed").hide(true), ]) .help("Some vals") .action(ArgAction::Set), ) .arg( Arg::new("cafe") .short('c') .long("cafe") .value_name("FILE") .help("A coffeehouse, coffee shop, or café.") .action(ArgAction::Set), ); utils::assert_output(cmd, "ctest --help", HIDE_POS_VALS, false); } #[test] fn possible_vals_with_help() { static POS_VALS_HELP: &str = "\ Usage: ctest [OPTIONS] Options: -p, --pos Some vals Possible values: - fast - slow: not as fast -c, --cafe A coffeehouse, coffee shop, or café. -h, --help Print help (see a summary with '-h') -V, --version Print version "; let app = Command::new("ctest") .version("0.1") .arg( Arg::new("pos") .short('p') .long("pos") .value_name("VAL") .value_parser([ PossibleValue::new("fast"), PossibleValue::new("slow").help("not as fast"), PossibleValue::new("secret speed").hide(true), ]) .help("Some vals") .action(ArgAction::Set), ) .arg( Arg::new("cafe") .short('c') .long("cafe") .value_name("FILE") .help("A coffeehouse, coffee shop, or café.") .action(ArgAction::Set), ); utils::assert_output(app, "ctest --help", POS_VALS_HELP, false); } #[test] fn hidden_possible_vals() { static POS_VALS_HELP: &str = "\ Usage: ctest [pos] Arguments: [pos] Options: -h, --help Print help "; let app = Command::new("ctest").arg( Arg::new("pos") .hide_possible_values(true) .value_parser([ PossibleValue::new("fast"), PossibleValue::new("slow").help("not as fast"), ]) .action(ArgAction::Set), ); utils::assert_output(app, "ctest --help", POS_VALS_HELP, false); } #[test] #[cfg(feature = "wrap_help")] fn issue_626_panic() { static ISSUE_626_PANIC: &str = "\ Usage: ctest [OPTIONS] Options: -c, --cafe La culture du café est très développée dans de nombreux pays à climat chaud d\'Amérique, d\'Afrique et d\'Asie, dans des plantations qui sont cultivées pour les marchés d\'exportation. Le café est souvent une contribution majeure aux exportations des régions productrices. -h, --help Print help -V, --version Print version "; let cmd = Command::new("ctest") .version("0.1") .term_width(52) .arg(Arg::new("cafe") .short('c') .long("cafe") .value_name("FILE") .help("La culture du café est très développée dans de nombreux pays à climat chaud d'Amérique, \ d'Afrique et d'Asie, dans des plantations qui sont cultivées pour les marchés d'exportation. \ Le café est souvent une contribution majeure aux exportations des régions productrices.") .action(ArgAction::Set)); utils::assert_output(cmd, "ctest --help", ISSUE_626_PANIC, false); } #[test] #[cfg(feature = "wrap_help")] fn issue_626_variable_panic() { for i in 10..320 { let _ = Command::new("ctest") .version("0.1") .term_width(i) .arg(Arg::new("cafe") .short('c') .long("cafe") .value_name("FILE") .help("La culture du café est très développée dans de nombreux pays à climat chaud d'Amérique, \ d'Afrique et d'Asie, dans des plantations qui sont cultivées pour les marchés d'exportation. \ Le café est souvent une contribution majeure aux exportations des régions productrices.") .action(ArgAction::Set)) .try_get_matches_from(vec!["ctest", "--help"]); } } #[test] #[cfg(feature = "wrap_help")] fn final_word_wrapping() { static FINAL_WORD_WRAPPING: &str = "\ Usage: ctest Options: -h, --help Print help -V, --version Print version "; let cmd = Command::new("ctest").version("0.1").term_width(24); utils::assert_output(cmd, "ctest --help", FINAL_WORD_WRAPPING, false); } #[test] #[cfg(feature = "wrap_help")] fn wrapping_newline_chars() { static WRAPPING_NEWLINE_CHARS: &str = "\ Usage: ctest [mode] Arguments: [mode] x, max, maximum 20 characters, contains symbols. l, long Copy-friendly, 14 characters, contains symbols. m, med, medium Copy-friendly, 8 characters, contains symbols. Options: -h, --help Print help -V, --version Print version "; let cmd = Command::new("ctest") .version("0.1") .term_width(60) .arg(Arg::new("mode").help( "x, max, maximum 20 characters, contains symbols.\n\ l, long Copy-friendly, 14 characters, contains symbols.\n\ m, med, medium Copy-friendly, 8 characters, contains symbols.\n", )); utils::assert_output(cmd, "ctest --help", WRAPPING_NEWLINE_CHARS, false); } #[test] #[cfg(feature = "wrap_help")] fn wrapped_indentation() { static HELP: &str = "\ Usage: ctest [mode] Arguments: [mode] Some values: - l, long Copy-friendly, 14 characters, contains symbols. - m, med, medium Copy-friendly, 8 characters, contains symbols. Options: -h, --help Print help -V, --version Print version "; let cmd = Command::new("ctest") .version("0.1") .term_width(60) .arg(Arg::new("mode").help( "Some values: - l, long Copy-friendly, 14 characters, contains symbols. - m, med, medium Copy-friendly, 8 characters, contains symbols.", )); utils::assert_output(cmd, "ctest --help", HELP, false); } #[test] #[cfg(feature = "wrap_help")] fn wrapping_newline_variables() { static WRAPPING_NEWLINE_CHARS: &str = "\ Usage: ctest [mode] Arguments: [mode] x, max, maximum 20 characters, contains symbols. l, long Copy-friendly, 14 characters, contains symbols. m, med, medium Copy-friendly, 8 characters, contains symbols. Options: -h, --help Print help -V, --version Print version "; let cmd = Command::new("ctest") .version("0.1") .term_width(60) .arg(Arg::new("mode").help( "x, max, maximum 20 characters, contains symbols.{n}\ l, long Copy-friendly, 14 characters, contains symbols.{n}\ m, med, medium Copy-friendly, 8 characters, contains symbols.{n}", )); utils::assert_output(cmd, "ctest --help", WRAPPING_NEWLINE_CHARS, false); } #[test] #[cfg(feature = "wrap_help")] fn dont_wrap_urls() { let cmd = Command::new("Example") .term_width(30) .subcommand(Command::new("update").arg( Arg::new("force-non-host") .help("Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains") .long("force-non-host") .action(ArgAction::SetTrue)) ); const EXPECTED: &str = "\ Usage: Example update [OPTIONS] Options: --force-non-host Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains -h, --help Print help "; utils::assert_output(cmd, "Example update --help", EXPECTED, false); } static OLD_NEWLINE_CHARS: &str = "\ Usage: ctest [OPTIONS] Options: -m Some help with some wrapping (Defaults to something) -h, --help Print help -V, --version Print version "; #[test] fn old_newline_chars() { let cmd = Command::new("ctest").version("0.1").arg( Arg::new("mode") .short('m') .action(ArgAction::SetTrue) .help("Some help with some wrapping\n(Defaults to something)"), ); utils::assert_output(cmd, "ctest --help", OLD_NEWLINE_CHARS, false); } #[test] fn old_newline_variables() { let cmd = Command::new("ctest").version("0.1").arg( Arg::new("mode") .short('m') .action(ArgAction::SetTrue) .help("Some help with some wrapping{n}(Defaults to something)"), ); utils::assert_output(cmd, "ctest --help", OLD_NEWLINE_CHARS, false); } #[test] #[cfg(feature = "wrap_help")] fn issue_688_hide_pos_vals() { #[cfg(not(feature = "unstable-v5"))] static ISSUE_688: &str = "\ Usage: ctest [OPTIONS] Options: --filter Sets the filter, or sampling method, to use for interpolation when resizing the particle images. The default is Linear (Bilinear). [possible values: Nearest, Linear, Cubic, Gaussian, Lanczos3] -h, --help Print help -V, --version Print version "; #[cfg(feature = "unstable-v5")] static ISSUE_688: &str = "\ Usage: ctest [OPTIONS] Options: --filter Sets the filter, or sampling method, to use for interpolation when resizing the particle images. The default is Linear (Bilinear). [possible values: Nearest, Linear, Cubic, Gaussian, Lanczos3] -h, --help Print help -V, --version Print version "; let filter_values = ["Nearest", "Linear", "Cubic", "Gaussian", "Lanczos3"]; let app1 = Command::new("ctest") .version("0.1") .term_width(120) .hide_possible_values(true) .arg(Arg::new("filter") .help("Sets the filter, or sampling method, to use for interpolation when resizing the particle \ images. The default is Linear (Bilinear). [possible values: Nearest, Linear, Cubic, Gaussian, Lanczos3]") .long("filter") .value_parser(filter_values) .action(ArgAction::Set)); utils::assert_output(app1, "ctest --help", ISSUE_688, false); let app2 = Command::new("ctest") .version("0.1") .term_width(120) .arg(Arg::new("filter") .help("Sets the filter, or sampling method, to use for interpolation when resizing the particle \ images. The default is Linear (Bilinear).") .long("filter") .value_parser(filter_values) .action(ArgAction::Set)); utils::assert_output(app2, "ctest --help", ISSUE_688, false); let app3 = Command::new("ctest") .version("0.1") .term_width(120) .arg(Arg::new("filter") .help("Sets the filter, or sampling method, to use for interpolation when resizing the particle \ images. The default is Linear (Bilinear). [possible values: Nearest, Linear, Cubic, Gaussian, Lanczos3]") .long("filter") .action(ArgAction::Set)); utils::assert_output(app3, "ctest --help", ISSUE_688, false); } #[test] fn issue_702_multiple_values() { static ISSUE_702: &str = "\ bar Usage: myapp [OPTIONS] [arg1] [arg2]... Arguments: [arg1] some option [arg2]... some option Options: -s, --some some option -o, --other some other option -l, --label