1use clap::{ArgAction, Args, CommandFactory, Parser, Subcommand}; 2 3#[test] 4fn arg_help_heading_applied() { 5 #[derive(Debug, Clone, Parser)] 6 struct CliOptions { 7 #[arg(long)] 8 #[arg(help_heading = Some("HEADING A"))] 9 should_be_in_section_a: u32, 10 11 #[arg(long)] 12 no_section: u32, 13 } 14 15 let cmd = CliOptions::command(); 16 17 let should_be_in_section_a = cmd 18 .get_arguments() 19 .find(|a| a.get_id() == "should_be_in_section_a") 20 .unwrap(); 21 assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); 22 23 let should_be_in_section_b = cmd 24 .get_arguments() 25 .find(|a| a.get_id() == "no_section") 26 .unwrap(); 27 assert_eq!(should_be_in_section_b.get_help_heading(), None); 28} 29 30#[test] 31fn app_help_heading_applied() { 32 #[derive(Debug, Clone, Parser)] 33 #[command(next_help_heading = "DEFAULT")] 34 struct CliOptions { 35 #[arg(long)] 36 #[arg(help_heading = Some("HEADING A"))] 37 should_be_in_section_a: u32, 38 39 #[arg(long)] 40 should_be_in_default_section: u32, 41 } 42 43 let cmd = CliOptions::command(); 44 45 let should_be_in_section_a = cmd 46 .get_arguments() 47 .find(|a| a.get_id() == "should_be_in_section_a") 48 .unwrap(); 49 assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); 50 51 let should_be_in_default_section = cmd 52 .get_arguments() 53 .find(|a| a.get_id() == "should_be_in_default_section") 54 .unwrap(); 55 assert_eq!( 56 should_be_in_default_section.get_help_heading(), 57 Some("DEFAULT") 58 ); 59} 60 61#[test] 62fn app_help_heading_flattened() { 63 // Used to help track the cause in tests 64 #![allow(clippy::enum_variant_names)] 65 66 #[derive(Debug, Clone, Parser)] 67 struct CliOptions { 68 #[command(flatten)] 69 options_a: OptionsA, 70 71 #[command(flatten)] 72 options_b: OptionsB, 73 74 #[command(subcommand)] 75 sub_a: SubA, 76 77 #[arg(long)] 78 should_be_in_default_section: u32, 79 } 80 81 #[derive(Debug, Clone, Args)] 82 #[command(next_help_heading = "HEADING A")] 83 struct OptionsA { 84 #[arg(long)] 85 should_be_in_section_a: u32, 86 } 87 88 #[derive(Debug, Clone, Args)] 89 #[command(next_help_heading = "HEADING B")] 90 struct OptionsB { 91 #[arg(long)] 92 should_be_in_section_b: u32, 93 } 94 95 #[derive(Debug, Clone, Subcommand)] 96 enum SubA { 97 #[command(flatten)] 98 SubB(SubB), 99 #[command(subcommand)] 100 SubC(SubC), 101 SubAOne, 102 #[command(next_help_heading = "SUB A")] 103 SubATwo { 104 should_be_in_sub_a: u32, 105 }, 106 } 107 108 #[derive(Debug, Clone, Subcommand)] 109 enum SubB { 110 #[command(next_help_heading = "SUB B")] 111 SubBOne { should_be_in_sub_b: u32 }, 112 } 113 114 #[derive(Debug, Clone, Subcommand)] 115 enum SubC { 116 #[command(next_help_heading = "SUB C")] 117 SubCOne { should_be_in_sub_c: u32 }, 118 } 119 120 let cmd = CliOptions::command(); 121 122 let should_be_in_section_a = cmd 123 .get_arguments() 124 .find(|a| a.get_id() == "should_be_in_section_a") 125 .unwrap(); 126 assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); 127 128 let should_be_in_section_b = cmd 129 .get_arguments() 130 .find(|a| a.get_id() == "should_be_in_section_b") 131 .unwrap(); 132 assert_eq!(should_be_in_section_b.get_help_heading(), Some("HEADING B")); 133 134 let should_be_in_section_b = cmd 135 .get_arguments() 136 .find(|a| a.get_id() == "should_be_in_default_section") 137 .unwrap(); 138 assert_eq!(should_be_in_section_b.get_help_heading(), Some("HEADING B")); 139 140 let sub_a_two = cmd.find_subcommand("sub-a-two").unwrap(); 141 142 let should_be_in_sub_a = sub_a_two 143 .get_arguments() 144 .find(|a| a.get_id() == "should_be_in_sub_a") 145 .unwrap(); 146 assert_eq!(should_be_in_sub_a.get_help_heading(), Some("SUB A")); 147 148 let sub_b_one = cmd.find_subcommand("sub-b-one").unwrap(); 149 150 let should_be_in_sub_b = sub_b_one 151 .get_arguments() 152 .find(|a| a.get_id() == "should_be_in_sub_b") 153 .unwrap(); 154 assert_eq!(should_be_in_sub_b.get_help_heading(), Some("SUB B")); 155 156 let sub_c = cmd.find_subcommand("sub-c").unwrap(); 157 let sub_c_one = sub_c.find_subcommand("sub-c-one").unwrap(); 158 159 let should_be_in_sub_c = sub_c_one 160 .get_arguments() 161 .find(|a| a.get_id() == "should_be_in_sub_c") 162 .unwrap(); 163 assert_eq!(should_be_in_sub_c.get_help_heading(), Some("SUB C")); 164} 165 166#[test] 167fn flatten_field_with_help_heading() { 168 #[derive(Debug, Clone, Parser)] 169 struct CliOptions { 170 #[command(flatten)] 171 #[command(next_help_heading = "HEADING A")] 172 options_a: OptionsA, 173 } 174 175 #[derive(Debug, Clone, Args)] 176 struct OptionsA { 177 #[arg(long)] 178 should_be_in_section_a: u32, 179 } 180 181 let cmd = CliOptions::command(); 182 183 let should_be_in_section_a = cmd 184 .get_arguments() 185 .find(|a| a.get_id() == "should_be_in_section_a") 186 .unwrap(); 187 assert_eq!(should_be_in_section_a.get_help_heading(), Some("HEADING A")); 188} 189 190// The challenge with this test is creating an error situation not caught by `clap`'s error checking 191// but by the code that `clap_derive` generates. 192// 193// Ultimately, the easiest way to confirm is to put a debug statement in the desired error path. 194#[test] 195fn derive_generated_error_has_full_context() { 196 #[derive(Debug, Parser)] 197 #[command(subcommand_negates_reqs = true)] 198 struct Opts { 199 #[arg(long)] 200 req_str: String, 201 202 #[command(subcommand)] 203 cmd: Option<SubCommands>, 204 } 205 206 #[derive(Debug, Parser)] 207 enum SubCommands { 208 Sub { 209 #[arg(short, long, action = clap::ArgAction::Count)] 210 verbose: u8, 211 }, 212 } 213 214 let result = Opts::try_parse_from(["test", "sub"]); 215 assert!( 216 result.is_err(), 217 "`SubcommandsNegateReqs` with non-optional `req_str` should fail: {:?}", 218 result.unwrap() 219 ); 220 221 let expected = r#"error: The following required argument was not provided: req_str 222 223Usage: clap --req-str <REQ_STR> 224 clap <COMMAND> 225 226For more information, try '--help'. 227"#; 228 assert_eq!(result.unwrap_err().to_string(), expected); 229} 230 231#[test] 232fn derive_order_next_order() { 233 static HELP: &str = "\ 234Usage: test [OPTIONS] 235 236Options: 237 --flag-b first flag 238 --option-b <OPTION_B> first option 239 -h, --help Print help 240 -V, --version Print version 241 --flag-a second flag 242 --option-a <OPTION_A> second option 243"; 244 245 #[derive(Parser, Debug)] 246 #[command(name = "test", version = "1.2")] 247 struct Args { 248 #[command(flatten)] 249 a: A, 250 #[command(flatten)] 251 b: B, 252 } 253 254 #[derive(Args, Debug)] 255 #[command(next_display_order = 10000)] 256 struct A { 257 /// second flag 258 #[arg(long)] 259 flag_a: bool, 260 /// second option 261 #[arg(long)] 262 option_a: Option<String>, 263 } 264 265 #[derive(Args, Debug)] 266 #[command(next_display_order = 10)] 267 struct B { 268 /// first flag 269 #[arg(long)] 270 flag_b: bool, 271 /// first option 272 #[arg(long)] 273 option_b: Option<String>, 274 } 275 276 use clap::CommandFactory; 277 let mut cmd = Args::command(); 278 279 let help = cmd.render_help().to_string(); 280 snapbox::assert_eq(HELP, help); 281} 282 283#[test] 284fn derive_order_next_order_flatten() { 285 static HELP: &str = "\ 286Usage: test [OPTIONS] 287 288Options: 289 --flag-b first flag 290 --option-b <OPTION_B> first option 291 -h, --help Print help 292 -V, --version Print version 293 --flag-a second flag 294 --option-a <OPTION_A> second option 295"; 296 297 #[derive(Parser, Debug)] 298 #[command(name = "test", version = "1.2")] 299 struct Args { 300 #[command(flatten)] 301 #[command(next_display_order = 10000)] 302 a: A, 303 #[command(flatten)] 304 #[command(next_display_order = 10)] 305 b: B, 306 } 307 308 #[derive(Args, Debug)] 309 struct A { 310 /// second flag 311 #[arg(long)] 312 flag_a: bool, 313 /// second option 314 #[arg(long)] 315 option_a: Option<String>, 316 } 317 318 #[derive(Args, Debug)] 319 struct B { 320 /// first flag 321 #[arg(long)] 322 flag_b: bool, 323 /// first option 324 #[arg(long)] 325 option_b: Option<String>, 326 } 327 328 use clap::CommandFactory; 329 let mut cmd = Args::command(); 330 331 let help = cmd.render_help().to_string(); 332 snapbox::assert_eq(HELP, help); 333} 334 335#[test] 336fn derive_order_no_next_order() { 337 static HELP: &str = "\ 338Usage: test [OPTIONS] 339 340Options: 341 --flag-a first flag 342 --flag-b second flag 343 -h, --help Print help 344 --option-a <OPTION_A> first option 345 --option-b <OPTION_B> second option 346 -V, --version Print version 347"; 348 349 #[derive(Parser, Debug)] 350 #[command(name = "test", version = "1.2")] 351 #[command(next_display_order = None)] 352 struct Args { 353 #[command(flatten)] 354 a: A, 355 #[command(flatten)] 356 b: B, 357 } 358 359 #[derive(Args, Debug)] 360 struct A { 361 /// first flag 362 #[arg(long)] 363 flag_a: bool, 364 /// first option 365 #[arg(long)] 366 option_a: Option<String>, 367 } 368 369 #[derive(Args, Debug)] 370 struct B { 371 /// second flag 372 #[arg(long)] 373 flag_b: bool, 374 /// second option 375 #[arg(long)] 376 option_b: Option<String>, 377 } 378 379 use clap::CommandFactory; 380 let mut cmd = Args::command(); 381 382 let help = cmd.render_help().to_string(); 383 snapbox::assert_eq(HELP, help); 384} 385 386#[test] 387fn derive_possible_value_help() { 388 static HELP: &str = "\ 389Application help 390 391Usage: clap <ARG> 392 393Arguments: 394 <ARG> 395 Argument help 396 397 Possible values: 398 - foo: Foo help 399 - bar: Bar help 400 401Options: 402 -h, --help 403 Print help (see a summary with '-h') 404"; 405 406 /// Application help 407 #[derive(Parser, PartialEq, Debug)] 408 struct Args { 409 /// Argument help 410 #[arg(value_enum)] 411 arg: ArgChoice, 412 } 413 414 #[derive(clap::ValueEnum, PartialEq, Debug, Clone)] 415 enum ArgChoice { 416 /// Foo help 417 Foo, 418 /// Bar help 419 Bar, 420 } 421 422 use clap::CommandFactory; 423 let mut cmd = Args::command(); 424 425 let help = cmd.render_long_help().to_string(); 426 snapbox::assert_eq(HELP, help); 427} 428 429#[test] 430fn custom_help_flag() { 431 #[derive(Debug, Clone, Parser)] 432 #[command(disable_help_flag = true)] 433 struct CliOptions { 434 #[arg(short = 'h', long = "verbose-help", action = ArgAction::Help, value_parser = clap::value_parser!(bool))] 435 help: (), 436 } 437 438 let result = CliOptions::try_parse_from(["cmd", "--verbose-help"]); 439 let err = result.unwrap_err(); 440 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); 441 442 CliOptions::try_parse_from(["cmd"]).unwrap(); 443} 444 445#[test] 446fn custom_version_flag() { 447 #[derive(Debug, Clone, Parser)] 448 #[command(disable_version_flag = true, version = "2.0.0")] 449 struct CliOptions { 450 #[arg(short = 'V', long = "verbose-version", action = ArgAction::Version, value_parser = clap::value_parser!(bool))] 451 version: (), 452 } 453 454 let result = CliOptions::try_parse_from(["cmd", "--verbose-version"]); 455 let err = result.unwrap_err(); 456 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion); 457 458 CliOptions::try_parse_from(["cmd"]).unwrap(); 459} 460