1 use std::ffi::OsStr;
2 use std::ffi::OsString;
3 use std::path::PathBuf;
4
5 use clap::{Args, Parser, Subcommand, ValueEnum};
6
7 /// A fictional versioning CLI
8 #[derive(Debug, Parser)] // requires `derive` feature
9 #[command(name = "git")]
10 #[command(about = "A fictional versioning CLI", long_about = None)]
11 struct Cli {
12 #[command(subcommand)]
13 command: Commands,
14 }
15
16 #[derive(Debug, Subcommand)]
17 enum Commands {
18 /// Clones repos
19 #[command(arg_required_else_help = true)]
20 Clone {
21 /// The remote to clone
22 remote: String,
23 },
24 /// Compare two commits
25 Diff {
26 #[arg(value_name = "COMMIT")]
27 base: Option<OsString>,
28 #[arg(value_name = "COMMIT")]
29 head: Option<OsString>,
30 #[arg(last = true)]
31 path: Option<OsString>,
32 #[arg(
33 long,
34 require_equals = true,
35 value_name = "WHEN",
36 num_args = 0..=1,
37 default_value_t = ColorWhen::Auto,
38 default_missing_value = "always",
39 value_enum
40 )]
41 color: ColorWhen,
42 },
43 /// pushes things
44 #[command(arg_required_else_help = true)]
45 Push {
46 /// The remote to target
47 remote: String,
48 },
49 /// adds things
50 #[command(arg_required_else_help = true)]
51 Add {
52 /// Stuff to add
53 #[arg(required = true)]
54 path: Vec<PathBuf>,
55 },
56 Stash(StashArgs),
57 #[command(external_subcommand)]
58 External(Vec<OsString>),
59 }
60
61 #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
62 enum ColorWhen {
63 Always,
64 Auto,
65 Never,
66 }
67
68 impl std::fmt::Display for ColorWhen {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 self.to_possible_value()
71 .expect("no values are skipped")
72 .get_name()
73 .fmt(f)
74 }
75 }
76
77 #[derive(Debug, Args)]
78 #[command(args_conflicts_with_subcommands = true)]
79 struct StashArgs {
80 #[command(subcommand)]
81 command: Option<StashCommands>,
82
83 #[command(flatten)]
84 push: StashPushArgs,
85 }
86
87 #[derive(Debug, Subcommand)]
88 enum StashCommands {
89 Push(StashPushArgs),
90 Pop { stash: Option<String> },
91 Apply { stash: Option<String> },
92 }
93
94 #[derive(Debug, Args)]
95 struct StashPushArgs {
96 #[arg(short, long)]
97 message: Option<String>,
98 }
99
main()100 fn main() {
101 let args = Cli::parse();
102
103 match args.command {
104 Commands::Clone { remote } => {
105 println!("Cloning {remote}");
106 }
107 Commands::Diff {
108 mut base,
109 mut head,
110 mut path,
111 color,
112 } => {
113 if path.is_none() {
114 path = head;
115 head = None;
116 if path.is_none() {
117 path = base;
118 base = None;
119 }
120 }
121 let base = base
122 .as_deref()
123 .map(|s| s.to_str().unwrap())
124 .unwrap_or("stage");
125 let head = head
126 .as_deref()
127 .map(|s| s.to_str().unwrap())
128 .unwrap_or("worktree");
129 let path = path.as_deref().unwrap_or_else(|| OsStr::new(""));
130 println!(
131 "Diffing {}..{} {} (color={})",
132 base,
133 head,
134 path.to_string_lossy(),
135 color
136 );
137 }
138 Commands::Push { remote } => {
139 println!("Pushing to {remote}");
140 }
141 Commands::Add { path } => {
142 println!("Adding {path:?}");
143 }
144 Commands::Stash(stash) => {
145 let stash_cmd = stash.command.unwrap_or(StashCommands::Push(stash.push));
146 match stash_cmd {
147 StashCommands::Push(push) => {
148 println!("Pushing {push:?}");
149 }
150 StashCommands::Pop { stash } => {
151 println!("Popping {stash:?}");
152 }
153 StashCommands::Apply { stash } => {
154 println!("Applying {stash:?}");
155 }
156 }
157 }
158 Commands::External(args) => {
159 println!("Calling out to {:?} with {:?}", &args[0], &args[1..]);
160 }
161 }
162
163 // Continued program logic goes here...
164 }
165