1use std::ffi::OsString;
2use std::path::PathBuf;
3
4use clap::{arg, Command};
5
6fn 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
56fn push_args() -> Vec<clap::Arg> {
57 vec![arg!(-m --message <MESSAGE>)]
58}
59
60fn 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