1 use std::ffi::OsString;
2 use std::path::PathBuf;
3 
4 use clap::{arg, Command};
5 
cli() -> Command6 fn cli() -> Command {
7     Command::new("git")
8         .about("A fictional versioning CLI")
9         .subcommand_required(true)
10         .arg_required_else_help(true)
11         .allow_external_subcommands(true)
12         .subcommand(
13             Command::new("clone")
14                 .about("Clones repos")
15                 .arg(arg!(<REMOTE> "The remote to clone"))
16                 .arg_required_else_help(true),
17         )
18         .subcommand(
19             Command::new("diff")
20                 .about("Compare two commits")
21                 .arg(arg!(base: [COMMIT]))
22                 .arg(arg!(head: [COMMIT]))
23                 .arg(arg!(path: [PATH]).last(true))
24                 .arg(
25                     arg!(--color <WHEN>)
26                         .value_parser(["always", "auto", "never"])
27                         .num_args(0..=1)
28                         .require_equals(true)
29                         .default_value("auto")
30                         .default_missing_value("always"),
31                 ),
32         )
33         .subcommand(
34             Command::new("push")
35                 .about("pushes things")
36                 .arg(arg!(<REMOTE> "The remote to target"))
37                 .arg_required_else_help(true),
38         )
39         .subcommand(
40             Command::new("add")
41                 .about("adds things")
42                 .arg_required_else_help(true)
43                 .arg(arg!(<PATH> ... "Stuff to add").value_parser(clap::value_parser!(PathBuf))),
44         )
45         .subcommand(
46             Command::new("stash")
47                 .args_conflicts_with_subcommands(true)
48                 .flatten_help(true)
49                 .args(push_args())
50                 .subcommand(Command::new("push").args(push_args()))
51                 .subcommand(Command::new("pop").arg(arg!([STASH])))
52                 .subcommand(Command::new("apply").arg(arg!([STASH]))),
53         )
54 }
55 
push_args() -> Vec<clap::Arg>56 fn push_args() -> Vec<clap::Arg> {
57     vec![arg!(-m --message <MESSAGE>)]
58 }
59 
main()60 fn main() {
61     let matches = cli().get_matches();
62 
63     match matches.subcommand() {
64         Some(("clone", sub_matches)) => {
65             println!(
66                 "Cloning {}",
67                 sub_matches.get_one::<String>("REMOTE").expect("required")
68             );
69         }
70         Some(("diff", sub_matches)) => {
71             let color = sub_matches
72                 .get_one::<String>("color")
73                 .map(|s| s.as_str())
74                 .expect("defaulted in clap");
75 
76             let mut base = sub_matches.get_one::<String>("base").map(|s| s.as_str());
77             let mut head = sub_matches.get_one::<String>("head").map(|s| s.as_str());
78             let mut path = sub_matches.get_one::<String>("path").map(|s| s.as_str());
79             if path.is_none() {
80                 path = head;
81                 head = None;
82                 if path.is_none() {
83                     path = base;
84                     base = None;
85                 }
86             }
87             let base = base.unwrap_or("stage");
88             let head = head.unwrap_or("worktree");
89             let path = path.unwrap_or("");
90             println!("Diffing {base}..{head} {path} (color={color})");
91         }
92         Some(("push", sub_matches)) => {
93             println!(
94                 "Pushing to {}",
95                 sub_matches.get_one::<String>("REMOTE").expect("required")
96             );
97         }
98         Some(("add", sub_matches)) => {
99             let paths = sub_matches
100                 .get_many::<PathBuf>("PATH")
101                 .into_iter()
102                 .flatten()
103                 .collect::<Vec<_>>();
104             println!("Adding {paths:?}");
105         }
106         Some(("stash", sub_matches)) => {
107             let stash_command = sub_matches.subcommand().unwrap_or(("push", sub_matches));
108             match stash_command {
109                 ("apply", sub_matches) => {
110                     let stash = sub_matches.get_one::<String>("STASH");
111                     println!("Applying {stash:?}");
112                 }
113                 ("pop", sub_matches) => {
114                     let stash = sub_matches.get_one::<String>("STASH");
115                     println!("Popping {stash:?}");
116                 }
117                 ("push", sub_matches) => {
118                     let message = sub_matches.get_one::<String>("message");
119                     println!("Pushing {message:?}");
120                 }
121                 (name, _) => {
122                     unreachable!("Unsupported subcommand `{name}`")
123                 }
124             }
125         }
126         Some((ext, sub_matches)) => {
127             let args = sub_matches
128                 .get_many::<OsString>("")
129                 .into_iter()
130                 .flatten()
131                 .collect::<Vec<_>>();
132             println!("Calling out to {ext:?} with {args:?}");
133         }
134         _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!()
135     }
136 
137     // Continued program logic goes here...
138 }
139