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