Skip to main content

sz_rust_cli/
cli.rs

1//! CLI 命令定义 — 基于 clap derive
2//!
3//! 对齐 PHP ThinkPHP 6 `think` 命令体系,借鉴 Laravel Artisan 风格。
4//!
5//! ## PHP 对齐
6//!
7//! PHP `think` 命令使用 Symfony Console 组件,通过 `configure()` 定义参数和选项。
8//! Rust 端使用 clap derive 宏,通过结构体字段定义参数和选项。
9//!
10//! ## 命令对照表
11//!
12//! | PHP | Rust | 说明 |
13//! |-----|------|------|
14//! | `php think make:model User` | `sz-rust make:model User` | 生成 Model |
15//! | `php think make:controller User` | `sz-rust make:controller User` | 生成 Controller |
16//! | `php think make:migration CreateUsers` | `sz-rust make:migration create_users` | 生成迁移文件 |
17//! | `php think migrate` | `sz-rust migrate` | 执行迁移 |
18//! | `php think migrate:rollback` | `sz-rust migrate --rollback` | 回滚迁移 |
19//! | `php think migrate:status` | `sz-rust migrate:status` | 迁移状态 |
20//! | `php think route:list` | `sz-rust route:list` | 路由列表 |
21//! | `php think cache:clear` | `sz-rust cache:clear` | 清空缓存 |
22
23use clap::{Parser, Subcommand};
24
25use crate::cmd;
26use crate::error::CliError;
27
28/// SZ-Rust 命令行工具
29///
30/// 替代 PHP `think` 命令,提供代码生成、数据库迁移、路由查看、缓存管理等功能。
31#[derive(Parser, Debug)]
32#[command(
33    name = "sz-rust",
34    bin_name = "sz-rust",
35    version,
36    about = "SZ-Rust 命令行工具 — 替代 PHP think 命令",
37    long_about = "SZ-Rust CLI 对齐 PHP ThinkPHP 6 think 命令体系,提供 make:migration / make:model / make:controller / migrate / route:list / cache:clear 等命令。"
38)]
39pub struct Cli {
40    /// 子命令
41    #[command(subcommand)]
42    pub command: Option<Command>,
43}
44
45/// 顶层命令枚举
46///
47/// 对齐 PHP `think` 的命令分组(make / migrate / route / cache)。
48#[derive(Subcommand, Debug)]
49pub enum Command {
50    /// 代码生成命令组(make:migration / make:model / make:controller / make:guard / make:scaffold)
51    #[command(name = "make")]
52    Make {
53        /// make 子命令
54        #[command(subcommand)]
55        make_command: cmd::make::MakeCommand,
56    },
57
58    /// 数据库迁移命令(migrate / migrate:status / migrate:rollback)
59    #[command(name = "migrate")]
60    Migrate {
61        /// 迁移子命令参数
62        #[command(flatten)]
63        args: cmd::migrate::MigrateArgs,
64    },
65
66    /// 迁移状态查询(对齐 PHP `php think migrate:status`)
67    #[command(name = "migrate:status")]
68    MigrateStatus {
69        /// 迁移目录(默认 `migrations`)
70        #[arg(short = 'p', long, default_value = "migrations")]
71        path: String,
72    },
73
74    /// 路由列表(对齐 PHP `php think route:list`)
75    #[command(name = "route:list")]
76    RouteList {
77        /// 输出格式(table / json)
78        #[arg(short = 'f', long, default_value = "table")]
79        format: String,
80    },
81
82    /// 清空缓存(对齐 PHP `php think cache:clear`)
83    #[command(name = "cache:clear")]
84    CacheClear {
85        /// 指定缓存存储名(默认清空所有)
86        #[arg(short = 's', long)]
87        store: Option<String>,
88    },
89
90    /// 调度器命令组(scheduler:list / scheduler:run / scheduler:start)
91    #[command(name = "scheduler")]
92    Scheduler {
93        /// scheduler 子命令
94        #[command(subcommand)]
95        scheduler_command: cmd::scheduler::SchedulerCommand,
96    },
97}
98
99impl Cli {
100    /// 执行命令
101    ///
102    /// 根据 `command` 字段分发到对应的命令处理器。
103    ///
104    /// # 返回
105    ///
106    /// - `Ok(0)`:成功
107    /// - `Ok(code)`:命令指定的退出码(非 0 表示部分失败)
108    /// - `Err(_)`:内部错误
109    pub fn execute(&self) -> Result<i32, CliError> {
110        match &self.command {
111            None => {
112                // 无子命令,打印帮助
113                println!("SZ-Rust CLI — 使用 --help 查看可用命令");
114                Ok(0)
115            }
116            Some(Command::Make { make_command }) => cmd::make::execute(make_command).map(|_| 0),
117            Some(Command::Migrate { args }) => cmd::migrate::execute_migrate(args).map(|_| 0),
118            Some(Command::MigrateStatus { path }) => cmd::migrate::execute_status(path).map(|_| 0),
119            Some(Command::RouteList { format }) => {
120                cmd::route::execute_route_list(format).map(|_| 0)
121            }
122            Some(Command::CacheClear { store }) => {
123                cmd::cache::execute_cache_clear(store.as_deref()).map(|_| 0)
124            }
125            Some(Command::Scheduler { scheduler_command }) => {
126                cmd::scheduler::execute(scheduler_command).map(|_| 0)
127            }
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use clap::Parser;
136
137    #[test]
138    fn test_parse_make_model() {
139        let cli = Cli::parse_from(["sz-rust", "make", "model", "User"]);
140        match cli.command {
141            Some(Command::Make { make_command }) => {
142                assert!(matches!(make_command, cmd::make::MakeCommand::Model { .. }));
143            }
144            _ => panic!("expected Make command"),
145        }
146    }
147
148    #[test]
149    fn test_parse_make_controller() {
150        let cli = Cli::parse_from(["sz-rust", "make", "controller", "User"]);
151        match cli.command {
152            Some(Command::Make { make_command }) => {
153                assert!(matches!(
154                    make_command,
155                    cmd::make::MakeCommand::Controller { .. }
156                ));
157            }
158            _ => panic!("expected Make command"),
159        }
160    }
161
162    #[test]
163    fn test_parse_make_migration() {
164        let cli = Cli::parse_from(["sz-rust", "make", "migration", "create_users"]);
165        match cli.command {
166            Some(Command::Make { make_command }) => {
167                assert!(matches!(
168                    make_command,
169                    cmd::make::MakeCommand::Migration { .. }
170                ));
171            }
172            _ => panic!("expected Make command"),
173        }
174    }
175
176    #[test]
177    fn test_parse_migrate() {
178        let cli = Cli::parse_from(["sz-rust", "migrate"]);
179        assert!(matches!(cli.command, Some(Command::Migrate { .. })));
180    }
181
182    #[test]
183    fn test_parse_migrate_status() {
184        let cli = Cli::parse_from(["sz-rust", "migrate:status"]);
185        assert!(matches!(cli.command, Some(Command::MigrateStatus { .. })));
186    }
187
188    #[test]
189    fn test_parse_route_list() {
190        let cli = Cli::parse_from(["sz-rust", "route:list"]);
191        assert!(matches!(cli.command, Some(Command::RouteList { .. })));
192    }
193
194    #[test]
195    fn test_parse_cache_clear() {
196        let cli = Cli::parse_from(["sz-rust", "cache:clear"]);
197        assert!(matches!(cli.command, Some(Command::CacheClear { .. })));
198    }
199
200    #[test]
201    fn test_parse_cache_clear_with_store() {
202        let cli = Cli::parse_from(["sz-rust", "cache:clear", "--store", "redis"]);
203        match cli.command {
204            Some(Command::CacheClear { store }) => {
205                assert_eq!(store.as_deref(), Some("redis"));
206            }
207            _ => panic!("expected CacheClear command"),
208        }
209    }
210
211    #[test]
212    fn test_parse_scheduler() {
213        let cli = Cli::parse_from(["sz-rust", "scheduler", "list"]);
214        assert!(matches!(cli.command, Some(Command::Scheduler { .. })));
215    }
216
217    #[test]
218    fn test_execute_no_command_returns_ok() {
219        let cli = Cli { command: None };
220        let result = cli.execute();
221        assert!(result.is_ok());
222        assert_eq!(result.unwrap(), 0);
223    }
224}