• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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