• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
2 // Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
3 // Ana Hobden (@hoverbear) <operator@hoverbear.org>
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10 //
11 // This work was derived from Structopt (https://github.com/TeXitoi/structopt)
12 // commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
13 // MIT/Apache 2.0 license.
14 
15 use crate::utils;
16 
17 use clap::{Args, Parser, Subcommand};
18 
19 #[derive(Parser, PartialEq, Eq, Debug)]
20 enum Opt {
21     /// Fetch stuff from GitHub
22     Fetch {
23         #[arg(long)]
24         all: bool,
25         /// Overwrite local branches.
26         #[arg(short, long)]
27         force: bool,
28 
29         repo: String,
30     },
31 
32     Add {
33         #[arg(short, long)]
34         interactive: bool,
35         #[arg(short, long)]
36         verbose: bool,
37     },
38 }
39 
40 #[test]
test_fetch()41 fn test_fetch() {
42     assert_eq!(
43         Opt::Fetch {
44             all: true,
45             force: false,
46             repo: "origin".to_string()
47         },
48         Opt::try_parse_from(["test", "fetch", "--all", "origin"]).unwrap()
49     );
50     assert_eq!(
51         Opt::Fetch {
52             all: false,
53             force: true,
54             repo: "origin".to_string()
55         },
56         Opt::try_parse_from(["test", "fetch", "-f", "origin"]).unwrap()
57     );
58 }
59 
60 #[test]
test_add()61 fn test_add() {
62     assert_eq!(
63         Opt::Add {
64             interactive: false,
65             verbose: false
66         },
67         Opt::try_parse_from(["test", "add"]).unwrap()
68     );
69     assert_eq!(
70         Opt::Add {
71             interactive: true,
72             verbose: true
73         },
74         Opt::try_parse_from(["test", "add", "-i", "-v"]).unwrap()
75     );
76 }
77 
78 #[test]
test_no_parse()79 fn test_no_parse() {
80     let result = Opt::try_parse_from(["test", "badcmd", "-i", "-v"]);
81     assert!(result.is_err());
82 
83     let result = Opt::try_parse_from(["test", "add", "--badoption"]);
84     assert!(result.is_err());
85 
86     let result = Opt::try_parse_from(["test"]);
87     assert!(result.is_err());
88 }
89 
90 #[derive(Parser, PartialEq, Eq, Debug)]
91 enum Opt2 {
92     DoSomething { arg: String },
93 }
94 
95 #[test]
96 /// This test is specifically to make sure that hyphenated subcommands get
97 /// processed correctly.
test_hyphenated_subcommands()98 fn test_hyphenated_subcommands() {
99     assert_eq!(
100         Opt2::DoSomething {
101             arg: "blah".to_string()
102         },
103         Opt2::try_parse_from(["test", "do-something", "blah"]).unwrap()
104     );
105 }
106 
107 #[derive(Parser, PartialEq, Eq, Debug)]
108 enum Opt3 {
109     Add,
110     Init,
111     Fetch,
112 }
113 
114 #[test]
test_null_commands()115 fn test_null_commands() {
116     assert_eq!(Opt3::Add, Opt3::try_parse_from(["test", "add"]).unwrap());
117     assert_eq!(Opt3::Init, Opt3::try_parse_from(["test", "init"]).unwrap());
118     assert_eq!(
119         Opt3::Fetch,
120         Opt3::try_parse_from(["test", "fetch"]).unwrap()
121     );
122 }
123 
124 #[derive(Parser, PartialEq, Eq, Debug)]
125 #[command(about = "Not shown")]
126 struct Add {
127     file: String,
128 }
129 /// Not shown
130 #[derive(Parser, PartialEq, Eq, Debug)]
131 struct Fetch {
132     remote: String,
133 }
134 #[derive(Parser, PartialEq, Eq, Debug)]
135 enum Opt4 {
136     // Not shown
137     /// Add a file
138     Add(Add),
139     Init,
140     /// download history from remote
141     Fetch(Fetch),
142 }
143 
144 #[test]
test_tuple_commands()145 fn test_tuple_commands() {
146     assert_eq!(
147         Opt4::Add(Add {
148             file: "f".to_string()
149         }),
150         Opt4::try_parse_from(["test", "add", "f"]).unwrap()
151     );
152     assert_eq!(Opt4::Init, Opt4::try_parse_from(["test", "init"]).unwrap());
153     assert_eq!(
154         Opt4::Fetch(Fetch {
155             remote: "origin".to_string()
156         }),
157         Opt4::try_parse_from(["test", "fetch", "origin"]).unwrap()
158     );
159 
160     let output = utils::get_long_help::<Opt4>();
161 
162     assert!(output.contains("download history from remote"));
163     assert!(output.contains("Add a file"));
164     assert!(!output.contains("Not shown"));
165 }
166 
167 #[test]
global_passed_down()168 fn global_passed_down() {
169     #[derive(Debug, PartialEq, Eq, Parser)]
170     struct Opt {
171         #[arg(global = true, long)]
172         other: bool,
173         #[command(subcommand)]
174         sub: Subcommands,
175     }
176 
177     #[derive(Debug, PartialEq, Eq, Subcommand)]
178     enum Subcommands {
179         Add,
180         Global(GlobalCmd),
181     }
182 
183     #[derive(Debug, PartialEq, Eq, Args)]
184     struct GlobalCmd {
185         #[arg(from_global)]
186         other: bool,
187     }
188 
189     assert_eq!(
190         Opt::try_parse_from(["test", "global"]).unwrap(),
191         Opt {
192             other: false,
193             sub: Subcommands::Global(GlobalCmd { other: false })
194         }
195     );
196 
197     assert_eq!(
198         Opt::try_parse_from(["test", "global", "--other"]).unwrap(),
199         Opt {
200             other: true,
201             sub: Subcommands::Global(GlobalCmd { other: true })
202         }
203     );
204 }
205 
206 #[test]
external_subcommand()207 fn external_subcommand() {
208     #[derive(Debug, PartialEq, Eq, Parser)]
209     struct Opt {
210         #[command(subcommand)]
211         sub: Subcommands,
212     }
213 
214     #[derive(Debug, PartialEq, Eq, Subcommand)]
215     enum Subcommands {
216         Add,
217         Remove,
218         #[command(external_subcommand)]
219         Other(Vec<String>),
220     }
221 
222     assert_eq!(
223         Opt::try_parse_from(["test", "add"]).unwrap(),
224         Opt {
225             sub: Subcommands::Add
226         }
227     );
228 
229     assert_eq!(
230         Opt::try_parse_from(["test", "remove"]).unwrap(),
231         Opt {
232             sub: Subcommands::Remove
233         }
234     );
235 
236     assert!(Opt::try_parse_from(["test"]).is_err());
237 
238     assert_eq!(
239         Opt::try_parse_from(["test", "git", "status"]).unwrap(),
240         Opt {
241             sub: Subcommands::Other(vec!["git".into(), "status".into()])
242         }
243     );
244 }
245 
246 #[test]
external_subcommand_os_string()247 fn external_subcommand_os_string() {
248     use std::ffi::OsString;
249 
250     #[derive(Debug, PartialEq, Eq, Parser)]
251     struct Opt {
252         #[command(subcommand)]
253         sub: Subcommands,
254     }
255 
256     #[derive(Debug, PartialEq, Eq, Subcommand)]
257     enum Subcommands {
258         #[command(external_subcommand)]
259         Other(Vec<OsString>),
260     }
261 
262     assert_eq!(
263         Opt::try_parse_from(["test", "git", "status"]).unwrap(),
264         Opt {
265             sub: Subcommands::Other(vec!["git".into(), "status".into()])
266         }
267     );
268 
269     assert!(Opt::try_parse_from(["test"]).is_err());
270 }
271 
272 #[test]
external_subcommand_optional()273 fn external_subcommand_optional() {
274     #[derive(Debug, PartialEq, Eq, Parser)]
275     struct Opt {
276         #[command(subcommand)]
277         sub: Option<Subcommands>,
278     }
279 
280     #[derive(Debug, PartialEq, Eq, Subcommand)]
281     enum Subcommands {
282         #[command(external_subcommand)]
283         Other(Vec<String>),
284     }
285 
286     assert_eq!(
287         Opt::try_parse_from(["test", "git", "status"]).unwrap(),
288         Opt {
289             sub: Some(Subcommands::Other(vec!["git".into(), "status".into()]))
290         }
291     );
292 
293     assert_eq!(Opt::try_parse_from(["test"]).unwrap(), Opt { sub: None });
294 }
295 
296 #[test]
enum_in_enum_subsubcommand()297 fn enum_in_enum_subsubcommand() {
298     #[derive(Parser, Debug, PartialEq, Eq)]
299     pub enum Opt {
300         #[command(alias = "l")]
301         List,
302         #[command(subcommand, alias = "d")]
303         Daemon(DaemonCommand),
304     }
305 
306     #[derive(Subcommand, Debug, PartialEq, Eq)]
307     pub enum DaemonCommand {
308         Start,
309         Stop,
310     }
311 
312     let result = Opt::try_parse_from(["test"]);
313     assert!(result.is_err());
314 
315     let result = Opt::try_parse_from(["test", "list"]).unwrap();
316     assert_eq!(Opt::List, result);
317 
318     let result = Opt::try_parse_from(["test", "l"]).unwrap();
319     assert_eq!(Opt::List, result);
320 
321     let result = Opt::try_parse_from(["test", "daemon"]);
322     assert!(result.is_err());
323 
324     let result = Opt::try_parse_from(["test", "daemon", "start"]).unwrap();
325     assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
326 
327     let result = Opt::try_parse_from(["test", "d", "start"]).unwrap();
328     assert_eq!(Opt::Daemon(DaemonCommand::Start), result);
329 }
330 
331 #[test]
update_subcommands()332 fn update_subcommands() {
333     #[derive(Parser, PartialEq, Eq, Debug)]
334     enum Opt {
335         Command1(Command1),
336         Command2(Command2),
337     }
338 
339     #[derive(Parser, PartialEq, Eq, Debug)]
340     struct Command1 {
341         arg1: i32,
342 
343         arg2: i32,
344     }
345 
346     #[derive(Parser, PartialEq, Eq, Debug)]
347     struct Command2 {
348         arg2: i32,
349     }
350 
351     // Full subcommand update
352     let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
353     opt.try_update_from(["test", "command1", "42", "44"])
354         .unwrap();
355     assert_eq!(
356         Opt::try_parse_from(["test", "command1", "42", "44"]).unwrap(),
357         opt
358     );
359 
360     // Partial subcommand update
361     let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
362     opt.try_update_from(["test", "command1", "42"]).unwrap();
363     assert_eq!(
364         Opt::try_parse_from(["test", "command1", "42", "14"]).unwrap(),
365         opt
366     );
367 
368     // Change subcommand
369     let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
370     opt.try_update_from(["test", "command2", "43"]).unwrap();
371     assert_eq!(
372         Opt::try_parse_from(["test", "command2", "43"]).unwrap(),
373         opt
374     );
375 }
376 
377 #[test]
update_subcommands_explicit_required()378 fn update_subcommands_explicit_required() {
379     #[derive(Parser, PartialEq, Eq, Debug)]
380     #[command(subcommand_required = true)]
381     enum Opt {
382         Command1(Command1),
383         Command2(Command2),
384     }
385 
386     #[derive(Parser, PartialEq, Eq, Debug)]
387     struct Command1 {
388         arg1: i32,
389 
390         arg2: i32,
391     }
392 
393     #[derive(Parser, PartialEq, Eq, Debug)]
394     struct Command2 {
395         arg2: i32,
396     }
397 
398     // Full subcommand update
399     let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
400     opt.try_update_from(["test"]).unwrap();
401     assert_eq!(Opt::Command1(Command1 { arg1: 12, arg2: 14 }), opt);
402 }
403 
404 #[test]
update_sub_subcommands()405 fn update_sub_subcommands() {
406     #[derive(Parser, PartialEq, Eq, Debug)]
407     enum Opt {
408         #[command(subcommand)]
409         Child1(Child1),
410         #[command(subcommand)]
411         Child2(Child2),
412     }
413 
414     #[derive(Subcommand, PartialEq, Eq, Debug)]
415     enum Child1 {
416         Command1(Command1),
417         Command2(Command2),
418     }
419 
420     #[derive(Subcommand, PartialEq, Eq, Debug)]
421     enum Child2 {
422         Command1(Command1),
423         Command2(Command2),
424     }
425 
426     #[derive(Args, PartialEq, Eq, Debug)]
427     struct Command1 {
428         arg1: i32,
429 
430         arg2: i32,
431     }
432 
433     #[derive(Args, PartialEq, Eq, Debug)]
434     struct Command2 {
435         arg2: i32,
436     }
437 
438     // Full subcommand update
439     let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
440     opt.try_update_from(["test", "child1", "command1", "42", "44"])
441         .unwrap();
442     assert_eq!(
443         Opt::try_parse_from(["test", "child1", "command1", "42", "44"]).unwrap(),
444         opt
445     );
446 
447     // Partial subcommand update
448     let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
449     opt.try_update_from(["test", "child1", "command1", "42"])
450         .unwrap();
451     assert_eq!(
452         Opt::try_parse_from(["test", "child1", "command1", "42", "14"]).unwrap(),
453         opt
454     );
455 
456     // Partial subcommand update
457     let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
458     opt.try_update_from(["test", "child1", "command2", "43"])
459         .unwrap();
460     assert_eq!(
461         Opt::try_parse_from(["test", "child1", "command2", "43"]).unwrap(),
462         opt
463     );
464 
465     // Change subcommand
466     let mut opt = Opt::Child1(Child1::Command1(Command1 { arg1: 12, arg2: 14 }));
467     opt.try_update_from(["test", "child2", "command2", "43"])
468         .unwrap();
469     assert_eq!(
470         Opt::try_parse_from(["test", "child2", "command2", "43"]).unwrap(),
471         opt
472     );
473 }
474 
475 #[test]
update_ext_subcommand()476 fn update_ext_subcommand() {
477     #[derive(Parser, PartialEq, Eq, Debug)]
478     enum Opt {
479         Command1(Command1),
480         Command2(Command2),
481         #[command(external_subcommand)]
482         Ext(Vec<String>),
483     }
484 
485     #[derive(Args, PartialEq, Eq, Debug)]
486     struct Command1 {
487         arg1: i32,
488 
489         arg2: i32,
490     }
491 
492     #[derive(Args, PartialEq, Eq, Debug)]
493     struct Command2 {
494         arg2: i32,
495     }
496 
497     // Full subcommand update
498     let mut opt = Opt::Ext(vec!["12".into(), "14".into()]);
499     opt.try_update_from(["test", "ext", "42", "44"]).unwrap();
500     assert_eq!(
501         Opt::try_parse_from(["test", "ext", "42", "44"]).unwrap(),
502         opt
503     );
504 
505     // No partial subcommand update
506     let mut opt = Opt::Ext(vec!["12".into(), "14".into()]);
507     opt.try_update_from(["test", "ext", "42"]).unwrap();
508     assert_eq!(Opt::try_parse_from(["test", "ext", "42"]).unwrap(), opt);
509 
510     // Change subcommand
511     let mut opt = Opt::Ext(vec!["12".into(), "14".into()]);
512     opt.try_update_from(["test", "command2", "43"]).unwrap();
513     assert_eq!(
514         Opt::try_parse_from(["test", "command2", "43"]).unwrap(),
515         opt
516     );
517 
518     let mut opt = Opt::Command1(Command1 { arg1: 12, arg2: 14 });
519     opt.try_update_from(["test", "ext", "42", "44"]).unwrap();
520     assert_eq!(
521         Opt::try_parse_from(["test", "ext", "42", "44"]).unwrap(),
522         opt
523     );
524 }
525 #[test]
subcommand_name_not_literal()526 fn subcommand_name_not_literal() {
527     fn get_name() -> &'static str {
528         "renamed"
529     }
530 
531     #[derive(Parser, PartialEq, Eq, Debug)]
532     struct Opt {
533         #[command(subcommand)]
534         subcmd: SubCmd,
535     }
536 
537     #[derive(Subcommand, PartialEq, Eq, Debug)]
538     enum SubCmd {
539         #[command(name = get_name())]
540         SubCmd1,
541     }
542 
543     assert!(Opt::try_parse_from(["test", "renamed"]).is_ok());
544 }
545 
546 #[test]
skip_subcommand()547 fn skip_subcommand() {
548     #[derive(Debug, PartialEq, Eq, Parser)]
549     struct Opt {
550         #[command(subcommand)]
551         sub: Subcommands,
552     }
553 
554     #[derive(Debug, PartialEq, Eq, Subcommand)]
555     enum Subcommands {
556         Add,
557         Remove,
558 
559         #[allow(dead_code)]
560         #[command(skip)]
561         Skip,
562 
563         #[allow(dead_code)]
564         #[command(skip)]
565         Other(Other),
566     }
567 
568     #[allow(dead_code)]
569     #[derive(Debug, PartialEq, Eq)]
570     enum Other {
571         One,
572         Twp,
573     }
574 
575     assert!(Subcommands::has_subcommand("add"));
576     assert!(Subcommands::has_subcommand("remove"));
577     assert!(!Subcommands::has_subcommand("skip"));
578     assert!(!Subcommands::has_subcommand("other"));
579 
580     assert_eq!(
581         Opt::try_parse_from(["test", "add"]).unwrap(),
582         Opt {
583             sub: Subcommands::Add
584         }
585     );
586 
587     assert_eq!(
588         Opt::try_parse_from(["test", "remove"]).unwrap(),
589         Opt {
590             sub: Subcommands::Remove
591         }
592     );
593 
594     let res = Opt::try_parse_from(["test", "skip"]);
595     assert_eq!(
596         res.unwrap_err().kind(),
597         clap::error::ErrorKind::InvalidSubcommand,
598     );
599 
600     let res = Opt::try_parse_from(["test", "other"]);
601     assert_eq!(
602         res.unwrap_err().kind(),
603         clap::error::ErrorKind::InvalidSubcommand,
604     );
605 }
606 
607 #[test]
built_in_subcommand_escaped()608 fn built_in_subcommand_escaped() {
609     #[derive(Debug, PartialEq, Eq, Parser)]
610     enum Command {
611         Install {
612             arg: Option<String>,
613         },
614         #[command(external_subcommand)]
615         Custom(Vec<String>),
616     }
617 
618     assert_eq!(
619         Command::try_parse_from(["test", "install", "arg"]).unwrap(),
620         Command::Install {
621             arg: Some(String::from("arg"))
622         }
623     );
624     assert_eq!(
625         Command::try_parse_from(["test", "--", "install"]).unwrap(),
626         Command::Custom(vec![String::from("install")])
627     );
628     assert_eq!(
629         Command::try_parse_from(["test", "--", "install", "arg"]).unwrap(),
630         Command::Custom(vec![String::from("install"), String::from("arg")])
631     );
632 }
633