From 17e7b28f162b3ed75683948144237ee17f81f7a5 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sat, 14 Mar 2026 18:33:20 +0800 Subject: Add converter module and update documentation --- src/bin/jvn.rs | 5 +- src/cmds.rs | 1 + src/cmds/README.md | 266 +++++++++++++++++++++++++++++++++++++++++++++++ src/cmds/README_zh_CN.md | 266 +++++++++++++++++++++++++++++++++++++++++++++++ src/cmds/cmd/helpdoc.rs | 2 +- src/systems/helpdoc.rs | 9 +- 6 files changed, 546 insertions(+), 3 deletions(-) create mode 100644 src/cmds/README.md create mode 100644 src/cmds/README_zh_CN.md (limited to 'src') diff --git a/src/bin/jvn.rs b/src/bin/jvn.rs index 965d0ec..c2ae5ec 100644 --- a/src/bin/jvn.rs +++ b/src/bin/jvn.rs @@ -11,7 +11,10 @@ use just_enough_vcs_cli::{ processer::jv_cmd_process, }, debug::verbose_logger::init_verbose_logger, - helpdoc::{DEFAULT_HELPDOC, helpdoc_viewer}, + helpdoc::{ + DEFAULT_HELPDOC, + helpdoc_viewer::{self}, + }, }, }; use just_progress::{ diff --git a/src/cmds.rs b/src/cmds.rs index 92e587f..d3c7a27 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -1,6 +1,7 @@ pub mod arg; pub mod cmd; pub mod collect; +pub mod converter; pub mod r#in; pub mod out; pub mod r#override; 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, + + /// 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, + 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 { + 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 { + 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, 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` +- **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 { + 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, + + /// 不输出结果 + #[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, + 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 { + 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 { + 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, 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` +- **必须使用** `#[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 { + 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() } diff --git a/src/systems/helpdoc.rs b/src/systems/helpdoc.rs index c36130d..f863158 100644 --- a/src/systems/helpdoc.rs +++ b/src/systems/helpdoc.rs @@ -1,6 +1,8 @@ +use cli_utils::display::markdown::Markdown; + pub mod helpdoc_viewer; -pub const DEFAULT_HELPDOC: &str = "Welcome_To_JVCS"; +pub const DEFAULT_HELPDOC: &str = "commands"; helpdoc_system_macros::generate_helpdoc_mapping!(); helpdoc_system_macros::generate_helpdoc_list!(); @@ -18,3 +20,8 @@ pub fn get_helpdoc<'a>(doc_name: &'a str, lang: &'a str) -> &'a str { pub fn get_helpdoc_list<'a>() -> Vec<&'a str> { get_docs_list() } + +pub fn print_help_doc(doc_name: &str, lang: &str) { + let doc = get_helpdoc(doc_name, lang); + println!("{}", doc.markdown()); +} -- cgit