1use clap::{Parser, Subcommand};
35
36use crate::cmd;
37use crate::error::CliError;
38
39#[derive(Parser, Debug)]
43#[command(
44 name = "sz-rust",
45 bin_name = "sz-rust",
46 version,
47 about = "SZ-Rust 命令行工具 — 替代 PHP think 命令",
48 long_about = "SZ-Rust CLI 对齐 PHP ThinkPHP 6 think 命令体系,提供 make:migration / make:model / make:controller / migrate / route:list / cache:clear 等命令。"
49)]
50pub struct Cli {
51 #[command(subcommand)]
53 pub command: Option<Command>,
54}
55
56#[derive(Subcommand, Debug)]
60pub enum Command {
61 #[command(name = "make")]
63 Make {
64 #[command(subcommand)]
66 make_command: cmd::make::MakeCommand,
67 },
68
69 #[command(name = "migrate")]
71 Migrate {
72 #[command(flatten)]
74 args: cmd::migrate::MigrateArgs,
75 },
76
77 #[command(name = "migrate:status")]
79 MigrateStatus {
80 #[arg(short = 'p', long, default_value = "migrations")]
82 path: String,
83
84 #[arg(long, default_value = "postgres")]
86 db_type: String,
87
88 #[arg(long)]
90 show_sql: bool,
91
92 #[arg(long)]
96 url: Option<String>,
97 },
98
99 #[command(name = "route:list")]
101 RouteList {
102 #[arg(short = 'f', long, default_value = "table")]
104 format: String,
105 },
106
107 #[command(name = "cache:clear")]
109 CacheClear {
110 #[arg(short = 's', long)]
112 store: Option<String>,
113 },
114
115 #[command(name = "db:seed")]
120 Seed {
121 #[arg(short = 'p', long, default_value = "seeds")]
123 path: String,
124
125 #[arg(long, default_value = "postgres")]
127 db_type: String,
128
129 #[arg(long)]
131 show_sql: bool,
132
133 #[arg(long)]
137 url: Option<String>,
138
139 #[arg(short = 'c', long)]
143 class: Option<String>,
144 },
145
146 #[command(name = "scheduler")]
148 Scheduler {
149 #[command(subcommand)]
151 scheduler_command: cmd::scheduler::SchedulerCommand,
152 },
153
154 #[command(name = "optimize:route")]
158 OptimizeRoute,
159
160 #[command(name = "optimize:config")]
164 OptimizeConfig,
165
166 #[command(name = "optimize:schema")]
172 OptimizeSchema,
173
174 #[command(name = "route:clear")]
178 RouteClear,
179}
180
181impl Cli {
182 pub async fn execute(&self) -> Result<i32, CliError> {
192 match &self.command {
193 None => {
194 println!("SZ-Rust CLI — 使用 --help 查看可用命令");
196 Ok(0)
197 }
198 Some(Command::Make { make_command }) => cmd::make::execute(make_command).map(|_| 0),
199 Some(Command::Migrate { args }) => cmd::migrate::execute_migrate(args).map(|_| 0),
200 Some(Command::MigrateStatus {
201 path,
202 db_type,
203 show_sql,
204 url,
205 }) => cmd::migrate::execute_status_full(path, db_type, *show_sql, url.as_deref())
206 .map(|_| 0),
207 Some(Command::RouteList { format }) => {
208 cmd::route::execute_route_list(format).map(|_| 0)
209 }
210 Some(Command::CacheClear { store }) => {
211 cmd::cache::execute_cache_clear(store.as_deref()).map(|_| 0)
212 }
213 Some(Command::Seed {
214 path,
215 db_type,
216 show_sql,
217 url,
218 class,
219 }) => {
220 let args = cmd::seed::SeedArgs {
221 path: path.clone(),
222 db_type: db_type.clone(),
223 show_sql: *show_sql,
224 url: url.clone(),
225 class: class.clone(),
226 };
227 cmd::seed::execute_seed(&args).map(|_| 0)
228 }
229 Some(Command::Scheduler { scheduler_command }) => {
230 cmd::scheduler::execute(scheduler_command).map(|_| 0)
231 }
232 Some(Command::OptimizeRoute) => {
233 cmd::optimize::execute_optimize_route().await.map(|_| 0)
234 }
235 Some(Command::OptimizeConfig) => {
236 cmd::optimize::execute_optimize_config().await.map(|_| 0)
237 }
238 Some(Command::OptimizeSchema) => {
239 cmd::optimize::execute_optimize_schema().await.map(|_| 0)
240 }
241 Some(Command::RouteClear) => cmd::optimize::execute_route_clear().await.map(|_| 0),
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use clap::Parser;
250
251 #[test]
252 fn test_parse_make_model() {
253 let cli = Cli::parse_from(["sz-rust", "make", "model", "User"]);
254 match cli.command {
255 Some(Command::Make { make_command }) => {
256 assert!(matches!(make_command, cmd::make::MakeCommand::Model { .. }));
257 }
258 _ => panic!("expected Make command"),
259 }
260 }
261
262 #[test]
263 fn test_parse_make_controller() {
264 let cli = Cli::parse_from(["sz-rust", "make", "controller", "User"]);
265 match cli.command {
266 Some(Command::Make { make_command }) => {
267 assert!(matches!(
268 make_command,
269 cmd::make::MakeCommand::Controller { .. }
270 ));
271 }
272 _ => panic!("expected Make command"),
273 }
274 }
275
276 #[test]
277 fn test_parse_make_migration() {
278 let cli = Cli::parse_from(["sz-rust", "make", "migration", "create_users"]);
279 match cli.command {
280 Some(Command::Make { make_command }) => {
281 assert!(matches!(
282 make_command,
283 cmd::make::MakeCommand::Migration { .. }
284 ));
285 }
286 _ => panic!("expected Make command"),
287 }
288 }
289
290 #[test]
291 fn test_parse_optimize_schema() {
292 let cli = Cli::parse_from(["sz-rust", "optimize:schema"]);
293 assert!(matches!(cli.command, Some(Command::OptimizeSchema)));
294 }
295
296 #[test]
297 fn test_parse_make_validate() {
298 let cli = Cli::parse_from(["sz-rust", "make", "validate", "User"]);
299 match cli.command {
300 Some(Command::Make { make_command }) => {
301 assert!(matches!(
302 make_command,
303 cmd::make::MakeCommand::Validate { .. }
304 ));
305 }
306 _ => panic!("expected Make command"),
307 }
308 }
309
310 #[test]
311 fn test_parse_make_seeder() {
312 let cli = Cli::parse_from(["sz-rust", "make", "seeder", "001_users"]);
313 match cli.command {
314 Some(Command::Make { make_command }) => {
315 assert!(matches!(
316 make_command,
317 cmd::make::MakeCommand::Seeder { .. }
318 ));
319 }
320 _ => panic!("expected Make command"),
321 }
322 }
323
324 #[test]
325 fn test_parse_migrate() {
326 let cli = Cli::parse_from(["sz-rust", "migrate"]);
327 assert!(matches!(cli.command, Some(Command::Migrate { .. })));
328 }
329
330 #[test]
331 fn test_parse_migrate_status() {
332 let cli = Cli::parse_from(["sz-rust", "migrate:status"]);
333 assert!(matches!(cli.command, Some(Command::MigrateStatus { .. })));
334 }
335
336 #[test]
337 fn test_parse_route_list() {
338 let cli = Cli::parse_from(["sz-rust", "route:list"]);
339 assert!(matches!(cli.command, Some(Command::RouteList { .. })));
340 }
341
342 #[test]
343 fn test_parse_cache_clear() {
344 let cli = Cli::parse_from(["sz-rust", "cache:clear"]);
345 assert!(matches!(cli.command, Some(Command::CacheClear { .. })));
346 }
347
348 #[test]
349 fn test_parse_cache_clear_with_store() {
350 let cli = Cli::parse_from(["sz-rust", "cache:clear", "--store", "redis"]);
351 match cli.command {
352 Some(Command::CacheClear { store }) => {
353 assert_eq!(store.as_deref(), Some("redis"));
354 }
355 _ => panic!("expected CacheClear command"),
356 }
357 }
358
359 #[test]
360 fn test_parse_scheduler() {
361 let cli = Cli::parse_from(["sz-rust", "scheduler", "list"]);
362 assert!(matches!(cli.command, Some(Command::Scheduler { .. })));
363 }
364
365 #[test]
366 fn test_parse_db_seed() {
367 let cli = Cli::parse_from(["sz-rust", "db:seed"]);
368 assert!(matches!(cli.command, Some(Command::Seed { .. })));
369 }
370
371 #[test]
372 fn test_parse_db_seed_with_options() {
373 let cli = Cli::parse_from([
374 "sz-rust",
375 "db:seed",
376 "--path",
377 "custom_seeds",
378 "--db-type",
379 "mysql",
380 "--show-sql",
381 "--url",
382 "mysql://user:pass@host:3306/db",
383 "--class",
384 "001_users",
385 ]);
386 match cli.command {
387 Some(Command::Seed {
388 path,
389 db_type,
390 show_sql,
391 url,
392 class,
393 }) => {
394 assert_eq!(path, "custom_seeds");
395 assert_eq!(db_type, "mysql");
396 assert!(show_sql);
397 assert_eq!(url.as_deref(), Some("mysql://user:pass@host:3306/db"));
398 assert_eq!(class.as_deref(), Some("001_users"));
399 }
400 _ => panic!("expected Seed command"),
401 }
402 }
403
404 #[tokio::test]
405 async fn test_execute_no_command_returns_ok() {
406 let cli = Cli { command: None };
407 let result = cli.execute().await;
408 assert!(result.is_ok());
409 assert_eq!(result.unwrap(), 0);
410 }
411}