1use std::ops::{Deref, Not};
2
3use clap::{
4 Args, Parser,
5 builder::{Styles, styling::AnsiColor},
6};
7
8const HELP_STYLES: Styles = Styles::styled()
9 .header(AnsiColor::Blue.on_default().bold())
10 .usage(AnsiColor::Blue.on_default().bold())
11 .literal(AnsiColor::White.on_default())
12 .placeholder(AnsiColor::Green.on_default());
13
14#[derive(Parser, Debug)]
15#[clap(version, about, author, styles = HELP_STYLES)]
16pub struct Opt {
17 #[clap(flatten)]
20 pub no_dotenv: NoDotenvOpt,
21
22 #[clap(subcommand)]
23 pub command: Command,
24}
25
26#[derive(Parser, Debug)]
27pub enum Command {
28 #[clap(alias = "db")]
29 Database(DatabaseOpt),
30
31 #[clap(alias = "mig")]
32 Migrate(MigrateOpt),
33}
34
35#[derive(Parser, Debug)]
37pub struct DatabaseOpt {
38 #[clap(subcommand)]
39 pub command: DatabaseCommand,
40}
41
42#[derive(Parser, Debug)]
43pub enum DatabaseCommand {
44 Create {
46 #[clap(flatten)]
47 connect_opts: ConnectOpts,
48 },
49
50 Drop {
52 #[clap(flatten)]
53 confirmation: Confirmation,
54
55 #[clap(flatten)]
56 connect_opts: ConnectOpts,
57
58 #[clap(long, short, default_value = "false")]
60 force: bool,
61 },
62
63 Reset {
65 #[clap(flatten)]
66 confirmation: Confirmation,
67
68 #[clap(flatten)]
69 source: Source,
70
71 #[clap(flatten)]
72 connect_opts: ConnectOpts,
73
74 #[clap(long, short, default_value = "false")]
76 force: bool,
77 },
78
79 Setup {
81 #[clap(flatten)]
82 source: Source,
83
84 #[clap(flatten)]
85 connect_opts: ConnectOpts,
86 },
87}
88
89#[derive(Parser, Debug)]
91pub struct MigrateOpt {
92 #[clap(subcommand)]
93 pub command: MigrateCommand,
94}
95
96#[derive(Parser, Debug)]
97pub enum MigrateCommand {
98 Add {
115 description: String,
116
117 #[clap(flatten)]
118 source: Source,
119
120 #[clap(short)]
123 reversible: bool,
124
125 #[clap(short, long)]
127 timestamp: bool,
128
129 #[clap(short, long, conflicts_with = "timestamp")]
131 sequential: bool,
132 },
133
134 Run {
136 #[clap(flatten)]
137 source: Source,
138
139 #[clap(long)]
141 dry_run: bool,
142
143 #[clap(flatten)]
144 ignore_missing: IgnoreMissing,
145
146 #[clap(flatten)]
147 connect_opts: ConnectOpts,
148
149 #[clap(long)]
152 target_version: Option<i64>,
153 },
154
155 Revert {
157 #[clap(flatten)]
158 source: Source,
159
160 #[clap(long)]
162 dry_run: bool,
163
164 #[clap(flatten)]
165 ignore_missing: IgnoreMissing,
166
167 #[clap(flatten)]
168 connect_opts: ConnectOpts,
169
170 #[clap(long)]
174 target_version: Option<i64>,
175 },
176
177 Info {
179 #[clap(flatten)]
180 source: Source,
181
182 #[clap(flatten)]
183 connect_opts: ConnectOpts,
184 },
185}
186
187#[derive(Args, Debug)]
189pub struct Source {
190 #[clap(long, default_value = "migrations")]
192 source: String,
193}
194
195impl Deref for Source {
196 type Target = String;
197
198 fn deref(&self) -> &Self::Target {
199 &self.source
200 }
201}
202
203#[derive(Args, Debug)]
205pub struct ConnectOpts {
206 #[clap(flatten)]
207 pub no_dotenv: NoDotenvOpt,
208
209 #[clap(long, short = 'D', env)]
211 pub database_url: Option<String>,
212
213 #[clap(long, env)]
215 pub scylladb_url: Option<String>,
216
217 #[clap(long, default_value = "10")]
220 pub connect_timeout: u64,
221}
222
223#[derive(Args, Debug)]
224pub struct NoDotenvOpt {
225 #[clap(long)]
227 #[allow(unused)] pub no_dotenv: bool,
231}
232
233impl ConnectOpts {
234 pub fn required_db_url(&self) -> anyhow::Result<&str> {
237 let database_url = if let Some(scylladb_url) = self.scylladb_url.as_deref() {
238 Some(scylladb_url)
239 } else {
240 self.database_url.as_deref()
241 };
242
243 database_url.ok_or_else(
244 || anyhow::anyhow!(
245 "the `--database-url` option or the `DATABASE_URL` or `SCYLLADB_URL` environment variable must be provided"
246 )
247 )
248 }
249}
250
251#[derive(Args, Copy, Clone, Debug)]
253pub struct Confirmation {
254 #[clap(short)]
257 pub yes: bool,
258}
259
260#[derive(Args, Copy, Clone, Debug)]
262pub struct IgnoreMissing {
263 #[clap(long)]
265 ignore_missing: bool,
266}
267
268impl Deref for IgnoreMissing {
269 type Target = bool;
270
271 fn deref(&self) -> &Self::Target {
272 &self.ignore_missing
273 }
274}
275
276impl Not for IgnoreMissing {
277 type Output = bool;
278
279 fn not(self) -> Self::Output {
280 !self.ignore_missing
281 }
282}