use clap::{ArgAction, Args, CommandFactory, Parser, Subcommand}; #[test] fn arg_help_heading_applied() { #[derive(Debug, Clone, Parser)] struct CliOptions { #[arg(long)] #[arg(help_heading = Some("HEADING A"))] should_be_in_section_a: u32, #[arg(long)] no_section: u32, } let cmd = CliOptions::command(); let should_be_in_section_a = cmd .get_arguments() .find(|a| a.get_id() == "should_be_in_section_a") .unwrap(); assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); let should_be_in_section_b = cmd .get_arguments() .find(|a| a.get_id() == "no_section") .unwrap(); assert_eq!(should_be_in_section_b.get_help_heading(), None); } #[test] fn app_help_heading_applied() { #[derive(Debug, Clone, Parser)] #[command(next_help_heading = "DEFAULT")] struct CliOptions { #[arg(long)] #[arg(help_heading = Some("HEADING A"))] should_be_in_section_a: u32, #[arg(long)] should_be_in_default_section: u32, } let cmd = CliOptions::command(); let should_be_in_section_a = cmd .get_arguments() .find(|a| a.get_id() == "should_be_in_section_a") .unwrap(); assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); let should_be_in_default_section = cmd .get_arguments() .find(|a| a.get_id() == "should_be_in_default_section") .unwrap(); assert_eq!( should_be_in_default_section.get_help_heading(), Some("DEFAULT") ); } #[test] fn app_help_heading_flattened() { // Used to help track the cause in tests #![allow(clippy::enum_variant_names)] #[derive(Debug, Clone, Parser)] struct CliOptions { #[command(flatten)] options_a: OptionsA, #[command(flatten)] options_b: OptionsB, #[command(subcommand)] sub_a: SubA, #[arg(long)] should_be_in_default_section: u32, } #[derive(Debug, Clone, Args)] #[command(next_help_heading = "HEADING A")] struct OptionsA { #[arg(long)] should_be_in_section_a: u32, } #[derive(Debug, Clone, Args)] #[command(next_help_heading = "HEADING B")] struct OptionsB { #[arg(long)] should_be_in_section_b: u32, } #[derive(Debug, Clone, Subcommand)] enum SubA { #[command(flatten)] SubB(SubB), #[command(subcommand)] SubC(SubC), SubAOne, #[command(next_help_heading = "SUB A")] SubATwo { should_be_in_sub_a: u32, }, } #[derive(Debug, Clone, Subcommand)] enum SubB { #[command(next_help_heading = "SUB B")] SubBOne { should_be_in_sub_b: u32 }, } #[derive(Debug, Clone, Subcommand)] enum SubC { #[command(next_help_heading = "SUB C")] SubCOne { should_be_in_sub_c: u32 }, } let cmd = CliOptions::command(); let should_be_in_section_a = cmd .get_arguments() .find(|a| a.get_id() == "should_be_in_section_a") .unwrap(); assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); let should_be_in_section_b = cmd .get_arguments() .find(|a| a.get_id() == "should_be_in_section_b") .unwrap(); assert_eq!(should_be_in_section_b.get_help_heading(), Some("HEADING B")); let should_be_in_default_section = cmd .get_arguments() .find(|a| a.get_id() == "should_be_in_default_section") .unwrap(); assert_eq!(should_be_in_default_section.get_help_heading(), None); let sub_a_two = cmd.find_subcommand("sub-a-two").unwrap(); let should_be_in_sub_a = sub_a_two .get_arguments() .find(|a| a.get_id() == "should_be_in_sub_a") .unwrap(); assert_eq!(should_be_in_sub_a.get_help_heading(), Some("SUB A")); let sub_b_one = cmd.find_subcommand("sub-b-one").unwrap(); let should_be_in_sub_b = sub_b_one .get_arguments() .find(|a| a.get_id() == "should_be_in_sub_b") .unwrap(); assert_eq!(should_be_in_sub_b.get_help_heading(), Some("SUB B")); let sub_c = cmd.find_subcommand("sub-c").unwrap(); let sub_c_one = sub_c.find_subcommand("sub-c-one").unwrap(); let should_be_in_sub_c = sub_c_one .get_arguments() .find(|a| a.get_id() == "should_be_in_sub_c") .unwrap(); assert_eq!(should_be_in_sub_c.get_help_heading(), Some("SUB C")); } #[test] fn flatten_field_with_help_heading() { #[derive(Debug, Clone, Parser)] struct CliOptions { #[command(flatten)] #[command(next_help_heading = "HEADING A")] options_a: OptionsA, } #[derive(Debug, Clone, Args)] struct OptionsA { #[arg(long)] should_be_in_section_a: u32, } let cmd = CliOptions::command(); let should_be_in_section_a = cmd .get_arguments() .find(|a| a.get_id() == "should_be_in_section_a") .unwrap(); assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); } // The challenge with this test is creating an error situation not caught by `clap`'s error checking // but by the code that `clap_derive` generates. // // Ultimately, the easiest way to confirm is to put a debug statement in the desired error path. #[test] fn derive_generated_error_has_full_context() { #[derive(Debug, Parser)] #[command(subcommand_negates_reqs = true)] struct Opts { #[arg(long)] req_str: String, #[command(subcommand)] cmd: Option, } #[derive(Debug, Parser)] enum SubCommands { Sub { #[arg(short, long, action = clap::ArgAction::Count)] verbose: u8, }, } let result = Opts::try_parse_from(&["test", "sub"]); assert!( result.is_err(), "`SubcommandsNegateReqs` with non-optional `req_str` should fail: {:?}", result.unwrap() ); let expected = r#"error: The following required argument was not provided: req_str Usage: clap --req-str clap For more information try --help "#; assert_eq!(result.unwrap_err().to_string(), expected); } #[test] fn derive_order_next_order() { static HELP: &str = "\ Usage: test [OPTIONS] Options: --flag-b first flag --option-b first option -h, --help Print help information -V, --version Print version information --flag-a second flag --option-a second option "; #[derive(Parser, Debug)] #[command(name = "test", version = "1.2")] struct Args { #[command(flatten)] a: A, #[command(flatten)] b: B, } #[derive(Args, Debug)] #[command(next_display_order = 10000)] struct A { /// second flag #[arg(long)] flag_a: bool, /// second option #[arg(long)] option_a: Option, } #[derive(Args, Debug)] #[command(next_display_order = 10)] struct B { /// first flag #[arg(long)] flag_b: bool, /// first option #[arg(long)] option_b: Option, } use clap::CommandFactory; let mut cmd = Args::command(); let mut buffer: Vec = Default::default(); cmd.write_help(&mut buffer).unwrap(); let help = String::from_utf8(buffer).unwrap(); snapbox::assert_eq(HELP, help); } #[test] fn derive_order_next_order_flatten() { static HELP: &str = "\ Usage: test [OPTIONS] Options: --flag-b first flag --option-b first option -h, --help Print help information -V, --version Print version information --flag-a second flag --option-a second option "; #[derive(Parser, Debug)] #[command(name = "test", version = "1.2")] struct Args { #[command(flatten)] #[command(next_display_order = 10000)] a: A, #[command(flatten)] #[command(next_display_order = 10)] b: B, } #[derive(Args, Debug)] struct A { /// second flag #[arg(long)] flag_a: bool, /// second option #[arg(long)] option_a: Option, } #[derive(Args, Debug)] struct B { /// first flag #[arg(long)] flag_b: bool, /// first option #[arg(long)] option_b: Option, } use clap::CommandFactory; let mut cmd = Args::command(); let mut buffer: Vec = Default::default(); cmd.write_help(&mut buffer).unwrap(); let help = String::from_utf8(buffer).unwrap(); snapbox::assert_eq(HELP, help); } #[test] fn derive_order_no_next_order() { static HELP: &str = "\ Usage: test [OPTIONS] Options: --flag-a first flag --flag-b second flag -h, --help Print help information --option-a first option --option-b second option -V, --version Print version information "; #[derive(Parser, Debug)] #[command(name = "test", version = "1.2")] #[command(next_display_order = None)] struct Args { #[command(flatten)] a: A, #[command(flatten)] b: B, } #[derive(Args, Debug)] struct A { /// first flag #[arg(long)] flag_a: bool, /// first option #[arg(long)] option_a: Option, } #[derive(Args, Debug)] struct B { /// second flag #[arg(long)] flag_b: bool, /// second option #[arg(long)] option_b: Option, } use clap::CommandFactory; let mut cmd = Args::command(); let mut buffer: Vec = Default::default(); cmd.write_help(&mut buffer).unwrap(); let help = String::from_utf8(buffer).unwrap(); snapbox::assert_eq(HELP, help); } #[test] fn derive_possible_value_help() { static HELP: &str = "\ Application help Usage: clap Arguments: Argument help Possible values: - foo: Foo help - bar: Bar help Options: -h, --help Print help information (use `-h` for a summary) "; /// Application help #[derive(Parser, PartialEq, Debug)] struct Args { /// Argument help #[arg(value_enum)] arg: ArgChoice, } #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] enum ArgChoice { /// Foo help Foo, /// Bar help Bar, } use clap::CommandFactory; let mut cmd = Args::command(); let mut buffer: Vec = Default::default(); cmd.write_long_help(&mut buffer).unwrap(); let help = String::from_utf8(buffer).unwrap(); snapbox::assert_eq(HELP, help); } #[test] fn custom_help_flag() { #[derive(Debug, Clone, Parser)] #[command(disable_help_flag = true)] struct CliOptions { #[arg(short = 'h', long = "verbose-help", action = ArgAction::Help)] help: bool, } let result = CliOptions::try_parse_from(["cmd", "--verbose-help"]); let err = result.unwrap_err(); assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); } #[test] fn custom_version_flag() { #[derive(Debug, Clone, Parser)] #[command(disable_version_flag = true, version = "2.0.0")] struct CliOptions { #[arg(short = 'V', long = "verbose-version", action = ArgAction::Version)] version: bool, } let result = CliOptions::try_parse_from(["cmd", "--verbose-version"]); let err = result.unwrap_err(); assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion); }