summaryrefslogtreecommitdiff
path: root/src/cmds
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-14 18:33:20 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-14 18:33:20 +0800
commit17e7b28f162b3ed75683948144237ee17f81f7a5 (patch)
tree9af1e089b517ba73f601b2ec9eacffbf0f6d2485 /src/cmds
parent707a1f7c747a08e2ce77df88edc1e72eae9cbebc (diff)
Add converter module and update documentation
Diffstat (limited to 'src/cmds')
-rw-r--r--src/cmds/README.md266
-rw-r--r--src/cmds/README_zh_CN.md266
-rw-r--r--src/cmds/cmd/helpdoc.rs2
3 files changed, 533 insertions, 1 deletions
diff --git a/src/cmds/README.md b/src/cmds/README.md
new file mode 100644
index 0000000..491163f
--- /dev/null
+++ b/src/cmds/README.md
@@ -0,0 +1,266 @@
+# Command Dev Guide
+
+This doc explains how to develop new commands for JVCS CLI. The command system is modular, with distinct components.
+
+## Directory Structure
+
+```
+src/cmds/
+├── arg/ # CLI arg definitions
+├── cmd/ # Command impl
+├── collect/ # Resource collection
+├── converter/ # Data converters
+├── in/ # Input data structs
+├── out/ # Output data structs
+├── override/ # Renderer overrides
+└── renderer/ # Renderer impls
+```
+
+## Command Components
+
+### 1. Argument
+- Impl `clap::Parser` trait
+- Naming: `JV{CommandName}Argument`
+- Location: `src/cmds/arg/{command_name}.rs`
+
+Example: sum command args
+```rust
+// src/cmds/arg/sum.rs
+use clap::Parser;
+
+#[derive(Parser, Debug)]
+pub struct JVSumArgument {
+ /// Numbers to add
+ pub numbers: Vec<i32>,
+
+ /// Don't output result
+ #[arg(long)]
+ pub no_output: bool,
+}
+```
+
+### 2. Input
+- Lifetime-free struct
+- Naming: `JV{CommandName}Input`
+- Location: `src/cmds/in/{command_name}.rs`
+- Created from `Argument` in `prepare` phase
+
+Example: sum command input
+```rust
+// src/cmds/in/sum.rs
+pub struct JVSumInput {
+ pub numbers: Vec<i32>,
+ pub should_output: bool,
+}
+```
+
+### 3. Collect
+- Lifetime-free struct
+- Naming: `JV{CommandName}Collect`
+- Location: `src/cmds/collect/{command_name}.rs`
+- Collects local resources needed for cmd execution
+
+Example: sum command collect
+```rust
+// src/cmds/collect/sum.rs
+pub struct JVSumCollect {
+ pub count: usize,
+}
+```
+
+### 4. Output
+- Impl `serde::Serialize` trait
+- Naming: `JV{CommandName}Output`
+- Location: `src/cmds/out/{command_name}.rs`
+
+Example: sum command output
+```rust
+// src/cmds/out/sum.rs
+use serde::Serialize;
+
+#[derive(Serialize)]
+pub struct JVSumOutput {
+ pub result: i32,
+}
+```
+
+## Command Execution Phases
+
+### 1. prepare phase
+- Convert `Argument` to stable `Input`
+- Detect input format errors early
+- Format input (e.g., flag inversion)
+
+Example: sum command prepare
+```rust
+async fn prepare(args: &JVSumArgument, ctx: &JVCommandContext) -> Result<JVSumInput, CmdPrepareError> {
+ trace!("Preparing sum cmd, arg count: {}", args.numbers.len());
+ debug!("no_output: {}, should_output: {}", args.no_output, should_output);
+
+ Ok(JVSumInput {
+ numbers: args.numbers.clone(),
+ should_output = !args.no_output,
+ })
+}
+```
+
+### 2. collect phase
+- Read resources based on `Argument` info
+- Fail early on resource load errors
+- Pass collected info to `exec` phase
+
+Example: sum command collect
+```rust
+async fn collect(args: &JVSumArgument, ctx: &JVCommandContext) -> Result<JVSumCollect, CmdPrepareError> {
+ trace!("Collecting sum cmd resources");
+
+ Ok(JVSumCollect {
+ count: args.numbers.len(),
+ })
+}
+```
+
+### 3. exec phase
+- Bind info from `prepare` & `collect` to core API
+- Organize result as `Output`
+- **Must use** `cmd_output!(JVSomeOutput => output)` syntax
+
+Example: sum command exec
+```rust
+#[exec]
+async fn exec(
+ input: JVSumInput,
+ collect: JVSumCollect,
+) -> Result<(Box<dyn std::any::Any + Send + 'static>, TypeId), CmdExecuteError> {
+ trace!("Exec sum cmd, processing {} numbers", collect.count);
+
+ // Calculate sum
+ let result = input.numbers.iter().sum();
+ debug!("Result: {}", result);
+
+ // Decide output type based on should_output
+ if input.should_output {
+ cmd_output!(JVSumOutput => JVSumOutput { result })
+ } else {
+ // Use JVNoneOutput for no result
+ cmd_output!(JVNoneOutput => JVNoneOutput)
+ }
+}
+```
+
+## Renderer
+
+Each `Output` needs a renderer to format data for user display.
+
+### Renderer Requirements
+- Impl async `render` function
+- Input: corresponding `Output` value
+- Output: `Result<JVRenderResult, CmdRenderError>`
+- **Must use** `#[result_renderer(JV{CommandName}Renderer)]` macro
+
+Example: sum command renderer
+```rust
+// src/cmds/renderer/sum.rs
+use render_system_macros::result_renderer;
+
+use crate::{
+ cmds::out::sum::JVSumOutput,
+ r_println,
+ systems::{cmd::errors::CmdRenderError, render::renderer::JVRenderResult},
+};
+
+#[result_renderer(JVSumRenderer)]
+pub async fn render(data: &JVSumOutput) -> Result<JVRenderResult, CmdRenderError> {
+ trace!("Rendering sum cmd result");
+
+ let mut r = JVRenderResult::default();
+ r_println!(r, "Result: {}", data.result);
+ Ok(r)
+}
+```
+
+## Dev Workflow
+
+1. **Plan Command Structure**
+ - Determine cmd name & args
+ - Design input/output data structs
+
+2. **Create Component Files**
+ - Create `.rs` files in respective dirs
+ - Impl Argument, Input, Collect, Output structs
+
+3. **Implement Command Logic**
+ - Create cmd impl file in `cmd/` dir
+ - Use cmd template (view via `cargo doc --no-deps`)
+ - Impl `prepare`, `collect`, `exec` functions
+
+4. **Implement Renderer**
+ - Create renderer file in `renderer/` dir
+ - Use `#[result_renderer]` macro
+
+5. **Test Command**
+ - Use `cargo build` to check compile errors
+ - Run cmd to test functionality
+
+## Naming Conventions
+
+| Component Type | Naming Convention | Example |
+|---------|---------|------|
+| Command | `JV{CommandName}Command` | `JVSumCommand` |
+| Argument | `JV{CommandName}Argument` | `JVSumArgument` |
+| Input | `JV{CommandName}Input` | `JVSumInput` |
+| Collect | `JV{CommandName}Collect` | `JVSumCollect` |
+| Output | `JV{CommandName}Output` | `JVSumOutput` |
+| Renderer | `JV{CommandName}Renderer` | `JVSumRenderer` |
+
+## Logging
+
+Use `log` for debugging during cmd dev:
+
+- `trace!("msg")` - Most detailed debug info
+- `debug!("msg")` - Debug info
+- `info!("msg")` - General info
+- `warn!("msg")` - Warning
+- `error!("msg")` - Error
+
+## Best Practices
+
+1. **Error Handling**
+ - Validate input in `prepare` phase
+ - Check resource availability in `collect` phase
+
+2. **Input Formatting**
+ - Standardize user input in `prepare` phase
+ - Ensure `Input` struct is clean & stable
+
+3. **Resource Management**
+ - Get all needed resources in `collect` phase
+ - Avoid filesystem ops in `exec` phase
+
+4. **Output Design**
+ - Output struct should have enough info for renderer
+ - Consider needs of different output formats
+
+## Example Commands
+
+Check existing cmd impls for inspiration:
+- `helpdoc` cmd: `src/cmds/cmd/helpdoc.rs`
+- `sheetdump` cmd: `src/cmds/cmd/sheetdump.rs`
+- `workspace` cmd: `src/cmds/cmd/workspace.rs`
+
+## Debug & Test
+
+1. **Generate Docs for Template**
+ ```bash
+ cargo doc --no-deps
+ ```
+ Docs are in `.temp/target/doc/`, see `macro.command_template.html` for full template.
+
+2. **Run Command Test**
+ ```bash
+ # Build & deploy
+ ./scripts/dev/dev_deploy.sh
+ # or Windows
+ .\scripts\dev\dev_deploy.ps1
+
+ jvn sum 1 2
diff --git a/src/cmds/README_zh_CN.md b/src/cmds/README_zh_CN.md
new file mode 100644
index 0000000..42823a8
--- /dev/null
+++ b/src/cmds/README_zh_CN.md
@@ -0,0 +1,266 @@
+# 命令开发指南
+
+本文档详细介绍了如何在 JVCS CLI 中开发新的命令。命令系统采用模块化设计,分为多个组件,每个组件有明确的职责。
+
+## 目录结构
+
+```
+src/cmds/
+├── arg/ # 命令行参数定义
+├── cmd/ # 命令实现
+├── collect/ # 资源收集信息
+├── converter/ # 数据转换器
+├── in/ # 输入数据结构
+├── out/ # 输出数据结构
+├── override/ # 渲染器重写
+└── renderer/ # 渲染器实现
+```
+
+## 命令组件
+
+### 1. Argument
+- 实现 `clap::Parser` trait
+- 命名规范:`JV{CommandName}Argument`
+- 位置:`src/cmds/arg/{command_name}.rs`
+
+示例:sum 命令的参数定义
+```rust
+// src/cmds/arg/sum.rs
+use clap::Parser;
+
+#[derive(Parser, Debug)]
+pub struct JVSumArgument {
+ /// 要相加的数字
+ pub numbers: Vec<i32>,
+
+ /// 不输出结果
+ #[arg(long)]
+ pub no_output: bool,
+}
+```
+
+### 2. Input
+- 无生命周期的结构体
+- 命名规范:`JV{CommandName}Input`
+- 位置:`src/cmds/in/{command_name}.rs`
+- 在 `prepare` 阶段由 `Argument` 转换而来
+
+示例:sum 命令的输入结构
+```rust
+// src/cmds/in/sum.rs
+pub struct JVSumInput {
+ pub numbers: Vec<i32>,
+ pub should_output: bool,
+}
+```
+
+### 3. Collect
+- 无生命周期的结构体
+- 命名规范:`JV{CommandName}Collect`
+- 位置:`src/cmds/collect/{command_name}.rs`
+- 用于收集执行命令所需的本地资源信息
+
+示例:sum 命令的资源收集
+```rust
+// src/cmds/collect/sum.rs
+pub struct JVSumCollect {
+ pub count: usize,
+}
+```
+
+### 4. Output
+- 实现 `serde::Serialize` trait
+- 命名规范:`JV{CommandName}Output`
+- 位置:`src/cmds/out/{command_name}.rs`
+
+示例:sum 命令的输出结构
+```rust
+// src/cmds/out/sum.rs
+use serde::Serialize;
+
+#[derive(Serialize)]
+pub struct JVSumOutput {
+ pub result: i32,
+}
+```
+
+## 命令执行阶段
+
+### 1. prepare 阶段
+- 将 `Argument` 转换为稳定的 `Input` 信息
+- 检测输入格式错误并提前失败
+- 对输入进行格式化处理(如标志取反)
+
+示例:sum 命令的 prepare 函数
+```rust
+async fn prepare(args: &JVSumArgument, ctx: &JVCommandContext) -> Result<JVSumInput, CmdPrepareError> {
+ trace!("开始准备 sum 命令,参数数量: {}", args.numbers.len());
+ debug!("no_output: {}, should_output: {}", args.no_output, should_output);
+
+ Ok(JVSumInput {
+ numbers: args.numbers.clone(),
+ should_output = !args.no_output,
+ })
+}
+```
+
+### 2. collect 阶段
+- 根据 `Argument` 的信息读取需要用到的资源
+- 资源加载错误时提前失败
+- 将收集的资源信息传入 `exec` 阶段
+
+示例:sum 命令的 collect 函数
+```rust
+async fn collect(args: &JVSumArgument, ctx: &JVCommandContext) -> Result<JVSumCollect, CmdPrepareError> {
+ trace!("收集 sum 命令资源");
+
+ Ok(JVSumCollect {
+ count: args.numbers.len(),
+ })
+}
+```
+
+### 3. exec 阶段
+- 将 `prepare` 和 `collect` 阶段收集的信息绑定到核心 API
+- 将结果整理为 `Output` 输出
+- **必须使用** `cmd_output!(JVSomeOutput => output)` 语法实现输出
+
+示例:sum 命令的 exec 函数
+```rust
+#[exec]
+async fn exec(
+ input: JVSumInput,
+ collect: JVSumCollect,
+) -> Result<(Box<dyn std::any::Any + Send + 'static>, TypeId), CmdExecuteError> {
+ trace!("执行 sum 命令,处理 {} 个数字", collect.count);
+
+ // 计算总和
+ let result = input.numbers.iter().sum();
+ debug!("计算结果: {}", result);
+
+ // 根据 should_output 决定输出类型
+ if input.should_output {
+ cmd_output!(JVSumOutput => JVSumOutput { result })
+ } else {
+ // 使用 JVNoneOutput 表示不输出结果
+ cmd_output!(JVNoneOutput => JVNoneOutput)
+ }
+}
+```
+
+## 渲染器
+
+每个 `Output` 需要对应一个渲染器,用于将输出数据渲染为用户可读的格式。
+
+### 渲染器实现要求
+- 实现异步的 `render` 函数
+- 输入为对应的 `Output` 值
+- 输出为 `Result<JVRenderResult, CmdRenderError>`
+- **必须使用** `#[result_renderer(JV{CommandName}Renderer)]` 宏
+
+示例:sum 命令的渲染器
+```rust
+// src/cmds/renderer/sum.rs
+use render_system_macros::result_renderer;
+
+use crate::{
+ cmds::out::sum::JVSumOutput,
+ r_println,
+ systems::{cmd::errors::CmdRenderError, render::renderer::JVRenderResult},
+};
+
+#[result_renderer(JVSumRenderer)]
+pub async fn render(data: &JVSumOutput) -> Result<JVRenderResult, CmdRenderError> {
+ trace!("渲染 sum 命令结果");
+
+ let mut r = JVRenderResult::default();
+ r_println!(r, "Result: {}", data.result);
+ Ok(r)
+}
+```
+
+## 开发流程
+
+1. **规划命令结构**
+ - 确定命令名称和参数
+ - 设计输入/输出数据结构
+
+2. **创建组件文件**
+ - 在相应目录创建 `.rs` 文件
+ - 实现 Argument、Input、Collect、Output 结构体
+
+3. **实现命令逻辑**
+ - 在 `cmd/` 目录创建命令实现文件
+ - 使用命令模板(通过 `cargo doc --no-deps` 生成文档查看完整模板)
+ - 实现 `prepare`、`collect`、`exec` 函数
+
+4. **实现渲染器**
+ - 在 `renderer/` 目录创建渲染器文件
+ - 使用 `#[result_renderer]` 宏
+
+5. **测试命令**
+ - 使用 `cargo build` 检查编译错误
+ - 运行命令测试功能
+
+## 命名规范
+
+| 组件类型 | 命名规范 | 示例 |
+|---------|---------|------|
+| 命令 | `JV{CommandName}Command` | `JVSumCommand` |
+| 参数 | `JV{CommandName}Argument` | `JVSumArgument` |
+| 输入 | `JV{CommandName}Input` | `JVSumInput` |
+| 收集 | `JV{CommandName}Collect` | `JVSumCollect` |
+| 输出 | `JV{CommandName}Output` | `JVSumOutput` |
+| 渲染器 | `JV{CommandName}Renderer` | `JVSumRenderer` |
+
+## 日志输出
+
+在命令开发中,可以使用 `log` 进行调试:
+
+- `trace!("消息")` - 最详细的调试信息
+- `debug!("消息")` - 调试信息
+- `info!("消息")` - 一般信息
+- `warn!("消息")` - 警告信息
+- `error!("消息")` - 错误信息
+
+## 最佳实践
+
+1. **错误处理**
+ - 在 `prepare` 阶段验证输入
+ - 在 `collect` 阶段检查资源可用性
+
+2. **输入格式化**
+ - 在 `prepare` 阶段对用户输入进行标准化
+ - 确保 `Input` 结构是干净、稳定的
+
+3. **资源管理**
+ - 在 `collect` 阶段获取所有需要的资源
+ - 避免在 `exec` 阶段进行文件系统操作
+
+4. **输出设计**
+ - 输出结构应包含足够的信息供渲染器使用
+ - 考虑不同输出格式的需求
+
+## 示例命令参考
+
+查看现有命令实现以获取更多灵感:
+- `helpdoc` 命令:`src/cmds/cmd/helpdoc.rs`
+- `sheetdump` 命令:`src/cmds/cmd/sheetdump.rs`
+- `workspace` 命令:`src/cmds/cmd/workspace.rs`
+
+## 调试与测试
+
+1. **生成文档查看模板**
+ ```bash
+ cargo doc --no-deps
+ ```
+ 文档生成在 `.temp/target/doc/` 目录,查看 `macro.command_template.html` 获取完整命令模板。
+
+2. **运行命令测试**
+ ```bash
+ # 构建并部署
+ ./scripts/dev/dev_deploy.sh
+ # 或 Windows
+ .\scripts\dev\dev_deploy.ps1
+
+ jvn sum 1 2
diff --git a/src/cmds/cmd/helpdoc.rs b/src/cmds/cmd/helpdoc.rs
index bebd08c..98e0309 100644
--- a/src/cmds/cmd/helpdoc.rs
+++ b/src/cmds/cmd/helpdoc.rs
@@ -22,7 +22,7 @@ type In = JVHelpdocInput;
type Collect = JVEmptyCollect;
async fn help_str() -> String {
- helpdoc_viewer::display(DEFAULT_HELPDOC).await;
+ helpdoc_viewer::display("commands/helpdoc").await;
String::new()
}