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 { |
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)] |
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 | |
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 | |