diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-03-14 18:33:20 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-03-14 18:33:20 +0800 |
| commit | 17e7b28f162b3ed75683948144237ee17f81f7a5 (patch) | |
| tree | 9af1e089b517ba73f601b2ec9eacffbf0f6d2485 /src/cmds/README.md | |
| parent | 707a1f7c747a08e2ce77df88edc1e72eae9cbebc (diff) | |
Add converter module and update documentation
Diffstat (limited to 'src/cmds/README.md')
| -rw-r--r-- | src/cmds/README.md | 266 |
1 files changed, 266 insertions, 0 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 |
