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 #[command(flatten_help = true)]
80 struct StashArgs {
81     #[command(subcommand)]
82     command: Option<StashCommands>,
83 
84     #[command(flatten)]
85     push: StashPushArgs,
86 }
87 
88 #[derive(Debug, Subcommand)]
89 enum StashCommands {
90     Push(StashPushArgs),
91     Pop { stash: Option<String> },
92     Apply { stash: Option<String> },
93 }
94 
95 #[derive(Debug, Args)]
96 struct StashPushArgs {
97     #[arg(short, long)]
98     message: Option<String>,
99 }
100 
main()101 fn 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