git/
git.rs

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}