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
15use crate::utils;
16
17use clap::{Args, Parser, Subcommand};
18
19#[derive(Parser, PartialEq, Eq, Debug)]
20enum 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]
41fn 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]
61fn 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]
79fn 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)]
91enum Opt2 {
92    DoSomething { arg: String },
93}
94
95#[test]
96/// This test is specifically to make sure that hyphenated subcommands get
97/// processed correctly.
98fn 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)]
108enum Opt3 {
109    Add,
110    Init,
111    Fetch,
112}
113
114#[test]
115fn 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")]
126struct Add {
127    file: String,
128}
129/// Not shown
130#[derive(Parser, PartialEq, Eq, Debug)]
131struct Fetch {
132    remote: String,
133}
134#[derive(Parser, PartialEq, Eq, Debug)]
135enum 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]
145fn 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]
168fn 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]
207fn 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]
247fn 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]
273fn 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]
297fn 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]
332fn 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]
378fn 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]
405fn 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]
476fn 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]
526fn 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]
547fn 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]
608fn 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