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 .args(push_args())
49 .subcommand(Command::new("push").args(push_args()))
50 .subcommand(Command::new("pop").arg(arg!([STASH])))
51 .subcommand(Command::new("apply").arg(arg!([STASH]))),
52 )
53 }
54
push_args() -> Vec<clap::Arg>55 fn push_args() -> Vec<clap::Arg> {
56 vec![arg!(-m --message <MESSAGE>)]
57 }
58
main()59 fn main() {
60 let matches = cli().get_matches();
61
62 match matches.subcommand() {
63 Some(("clone", sub_matches)) => {
64 println!(
65 "Cloning {}",
66 sub_matches.get_one::<String>("REMOTE").expect("required")
67 );
68 }
69 Some(("diff", sub_matches)) => {
70 let color = sub_matches
71 .get_one::<String>("color")
72 .map(|s| s.as_str())
73 .expect("defaulted in clap");
74
75 let mut base = sub_matches.get_one::<String>("base").map(|s| s.as_str());
76 let mut head = sub_matches.get_one::<String>("head").map(|s| s.as_str());
77 let mut path = sub_matches.get_one::<String>("path").map(|s| s.as_str());
78 if path.is_none() {
79 path = head;
80 head = None;
81 if path.is_none() {
82 path = base;
83 base = None;
84 }
85 }
86 let base = base.unwrap_or("stage");
87 let head = head.unwrap_or("worktree");
88 let path = path.unwrap_or("");
89 println!("Diffing {base}..{head} {path} (color={color})");
90 }
91 Some(("push", sub_matches)) => {
92 println!(
93 "Pushing to {}",
94 sub_matches.get_one::<String>("REMOTE").expect("required")
95 );
96 }
97 Some(("add", sub_matches)) => {
98 let paths = sub_matches
99 .get_many::<PathBuf>("PATH")
100 .into_iter()
101 .flatten()
102 .collect::<Vec<_>>();
103 println!("Adding {paths:?}");
104 }
105 Some(("stash", sub_matches)) => {
106 let stash_command = sub_matches.subcommand().unwrap_or(("push", sub_matches));
107 match stash_command {
108 ("apply", sub_matches) => {
109 let stash = sub_matches.get_one::<String>("STASH");
110 println!("Applying {stash:?}");
111 }
112 ("pop", sub_matches) => {
113 let stash = sub_matches.get_one::<String>("STASH");
114 println!("Popping {stash:?}");
115 }
116 ("push", sub_matches) => {
117 let message = sub_matches.get_one::<String>("message");
118 println!("Pushing {message:?}");
119 }
120 (name, _) => {
121 unreachable!("Unsupported subcommand `{}`", name)
122 }
123 }
124 }
125 Some((ext, sub_matches)) => {
126 let args = sub_matches
127 .get_many::<OsString>("")
128 .into_iter()
129 .flatten()
130 .collect::<Vec<_>>();
131 println!("Calling out to {ext:?} with {args:?}");
132 }
133 _ => unreachable!(), // If all subcommands are defined above, anything else is unreachable!()
134 }
135
136 // Continued program logic goes here...
137 }
138