1 use clap::{ArgAction, Args, CommandFactory, Parser, Subcommand};
2
3 #[test]
arg_help_heading_applied()4 fn 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]
app_help_heading_applied()31 fn 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]
app_help_heading_flattened()62 fn 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]
flatten_field_with_help_heading()167 fn 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]
derive_generated_error_has_full_context()195 fn 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
223 Usage: clap --req-str <REQ_STR>
224 clap <COMMAND>
225
226 For more information, try '--help'.
227 "#;
228 assert_eq!(result.unwrap_err().to_string(), expected);
229 }
230
231 #[test]
derive_order_next_order()232 fn derive_order_next_order() {
233 static HELP: &str = "\
234 Usage: test [OPTIONS]
235
236 Options:
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]
derive_order_next_order_flatten()284 fn derive_order_next_order_flatten() {
285 static HELP: &str = "\
286 Usage: test [OPTIONS]
287
288 Options:
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]
derive_order_no_next_order()336 fn derive_order_no_next_order() {
337 static HELP: &str = "\
338 Usage: test [OPTIONS]
339
340 Options:
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]
derive_possible_value_help()387 fn derive_possible_value_help() {
388 static HELP: &str = "\
389 Application help
390
391 Usage: clap <ARG>
392
393 Arguments:
394 <ARG>
395 Argument help
396
397 Possible values:
398 - foo: Foo help
399 - bar: Bar help
400
401 Options:
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]
custom_help_flag()430 fn 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]
custom_version_flag()446 fn 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