1use std::ffi::OsStr;
2use std::ffi::OsString;
3use std::path::PathBuf;
4
5use 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)]
11struct Cli {
12 #[command(subcommand)]
13 command: Commands,
14}
15
16#[derive(Debug, Subcommand)]
17enum 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)]
62enum ColorWhen {
63 Always,
64 Auto,
65 Never,
66}
67
68impl std::fmt::Display for ColorWhen {
69 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#[command(flatten_help = true)]
80struct StashArgs {
81 #[command(subcommand)]
82 command: Option<StashCommands>,
83
84 #[command(flatten)]
85 push: StashPushArgs,
86}
87
88#[derive(Debug, Subcommand)]
89enum StashCommands {
90 Push(StashPushArgs),
91 Pop { stash: Option<String> },
92 Apply { stash: Option<String> },
93}
94
95#[derive(Debug, Args)]
96struct StashPushArgs {
97 #[arg(short, long)]
98 message: Option<String>,
99}
100
101fn main() {
102 let args = Cli::parse();
103
104 match args.command {
105 Commands::Clone { remote } => {
106 println!("Cloning {remote}");
107 }
108 Commands::Diff {
109 mut base,
110 mut head,
111 mut path,
112 color,
113 } => {
114 if path.is_none() {
115 path = head;
116 head = None;
117 if path.is_none() {
118 path = base;
119 base = None;
120 }
121 }
122 let base = base
123 .as_deref()
124 .map(|s| s.to_str().unwrap())
125 .unwrap_or("stage");
126 let head = head
127 .as_deref()
128 .map(|s| s.to_str().unwrap())
129 .unwrap_or("worktree");
130 let path = path.as_deref().unwrap_or_else(|| OsStr::new(""));
131 println!(
132 "Diffing {}..{} {} (color={})",
133 base,
134 head,
135 path.to_string_lossy(),
136 color
137 );
138 }
139 Commands::Push { remote } => {
140 println!("Pushing to {remote}");
141 }
142 Commands::Add { path } => {
143 println!("Adding {path:?}");
144 }
145 Commands::Stash(stash) => {
146 let stash_cmd = stash.command.unwrap_or(StashCommands::Push(stash.push));
147 match stash_cmd {
148 StashCommands::Push(push) => {
149 println!("Pushing {push:?}");
150 }
151 StashCommands::Pop { stash } => {
152 println!("Popping {stash:?}");
153 }
154 StashCommands::Apply { stash } => {
155 println!("Applying {stash:?}");
156 }
157 }
158 }
159 Commands::External(args) => {
160 println!("Calling out to {:?} with {:?}", &args[0], &args[1..]);
161 }
162 }
163
164 // Continued program logic goes here...
165}
166