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 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) => cmd::optimize::execute_optimize_route().map(|_| 0),
233 Some(Command::OptimizeConfig) => cmd::optimize::execute_optimize_config().map(|_| 0),
234 Some(Command::OptimizeSchema) => cmd::optimize::execute_optimize_schema().map(|_| 0),
235 Some(Command::RouteClear) => cmd::optimize::execute_route_clear().map(|_| 0),
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use clap::Parser;
244
245 #[test]
246 fn test_parse_make_model() {
247 let cli = Cli::parse_from(["sz-rust", "make", "model", "User"]);
248 match cli.command {
249 Some(Command::Make { make_command }) => {
250 assert!(matches!(make_command, cmd::make::MakeCommand::Model { .. }));
251 }
252 _ => panic!("expected Make command"),
253 }
254 }
255
256 #[test]
257 fn test_parse_make_controller() {
258 let cli = Cli::parse_from(["sz-rust", "make", "controller", "User"]);
259 match cli.command {
260 Some(Command::Make { make_command }) => {
261 assert!(matches!(
262 make_command,
263 cmd::make::MakeCommand::Controller { .. }
264 ));
265 }
266 _ => panic!("expected Make command"),
267 }
268 }
269
270 #[test]
271 fn test_parse_make_migration() {
272 let cli = Cli::parse_from(["sz-rust", "make", "migration", "create_users"]);
273 match cli.command {
274 Some(Command::Make { make_command }) => {
275 assert!(matches!(
276 make_command,
277 cmd::make::MakeCommand::Migration { .. }
278 ));
279 }
280 _ => panic!("expected Make command"),
281 }
282 }
283
284 #[test]
285 fn test_parse_optimize_schema() {
286 let cli = Cli::parse_from(["sz-rust", "optimize:schema"]);
287 assert!(matches!(cli.command, Some(Command::OptimizeSchema)));
288 }
289
290 #[test]
291 fn test_parse_make_validate() {
292 let cli = Cli::parse_from(["sz-rust", "make", "validate", "User"]);
293 match cli.command {
294 Some(Command::Make { make_command }) => {
295 assert!(matches!(
296 make_command,
297 cmd::make::MakeCommand::Validate { .. }
298 ));
299 }
300 _ => panic!("expected Make command"),
301 }
302 }
303
304 #[test]
305 fn test_parse_make_seeder() {
306 let cli = Cli::parse_from(["sz-rust", "make", "seeder", "001_users"]);
307 match cli.command {
308 Some(Command::Make { make_command }) => {
309 assert!(matches!(
310 make_command,
311 cmd::make::MakeCommand::Seeder { .. }
312 ));
313 }
314 _ => panic!("expected Make command"),
315 }
316 }
317
318 #[test]
319 fn test_parse_migrate() {
320 let cli = Cli::parse_from(["sz-rust", "migrate"]);
321 assert!(matches!(cli.command, Some(Command::Migrate { .. })));
322 }
323
324 #[test]
325 fn test_parse_migrate_status() {
326 let cli = Cli::parse_from(["sz-rust", "migrate:status"]);
327 assert!(matches!(cli.command, Some(Command::MigrateStatus { .. })));
328 }
329
330 #[test]
331 fn test_parse_route_list() {
332 let cli = Cli::parse_from(["sz-rust", "route:list"]);
333 assert!(matches!(cli.command, Some(Command::RouteList { .. })));
334 }
335
336 #[test]
337 fn test_parse_cache_clear() {
338 let cli = Cli::parse_from(["sz-rust", "cache:clear"]);
339 assert!(matches!(cli.command, Some(Command::CacheClear { .. })));
340 }
341
342 #[test]
343 fn test_parse_cache_clear_with_store() {
344 let cli = Cli::parse_from(["sz-rust", "cache:clear", "--store", "redis"]);
345 match cli.command {
346 Some(Command::CacheClear { store }) => {
347 assert_eq!(store.as_deref(), Some("redis"));
348 }
349 _ => panic!("expected CacheClear command"),
350 }
351 }
352
353 #[test]
354 fn test_parse_scheduler() {
355 let cli = Cli::parse_from(["sz-rust", "scheduler", "list"]);
356 assert!(matches!(cli.command, Some(Command::Scheduler { .. })));
357 }
358
359 #[test]
360 fn test_parse_db_seed() {
361 let cli = Cli::parse_from(["sz-rust", "db:seed"]);
362 assert!(matches!(cli.command, Some(Command::Seed { .. })));
363 }
364
365 #[test]
366 fn test_parse_db_seed_with_options() {
367 let cli = Cli::parse_from([
368 "sz-rust",
369 "db:seed",
370 "--path",
371 "custom_seeds",
372 "--db-type",
373 "mysql",
374 "--show-sql",
375 "--url",
376 "mysql://user:pass@host:3306/db",
377 "--class",
378 "001_users",
379 ]);
380 match cli.command {
381 Some(Command::Seed {
382 path,
383 db_type,
384 show_sql,
385 url,
386 class,
387 }) => {
388 assert_eq!(path, "custom_seeds");
389 assert_eq!(db_type, "mysql");
390 assert!(show_sql);
391 assert_eq!(url.as_deref(), Some("mysql://user:pass@host:3306/db"));
392 assert_eq!(class.as_deref(), Some("001_users"));
393 }
394 _ => panic!("expected Seed command"),
395 }
396 }
397
398 #[test]
399 fn test_execute_no_command_returns_ok() {
400 let cli = Cli { command: None };
401 let result = cli.execute();
402 assert!(result.is_ok());
403 assert_eq!(result.unwrap(), 0);
404 }
405}