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