From 0bed1f9063d81aab827dc8b26d925341b59d12e6 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 13 Aug 2026 01:53:21 +0800 Subject: docs: rename command macro issue as solved --- docs/dev/pages/issues/_the-command-macro.md | 169 ++++++++++++++++++++++++++++ docs/dev/pages/issues/the-command-macro.md | 169 ---------------------------- 2 files changed, 169 insertions(+), 169 deletions(-) create mode 100644 docs/dev/pages/issues/_the-command-macro.md delete mode 100644 docs/dev/pages/issues/the-command-macro.md diff --git a/docs/dev/pages/issues/_the-command-macro.md b/docs/dev/pages/issues/_the-command-macro.md new file mode 100644 index 0000000..47f1e51 --- /dev/null +++ b/docs/dev/pages/issues/_the-command-macro.md @@ -0,0 +1,169 @@ +

[Solved] The Command Macro

+

+ A macro for quickly building commands +

+ +Mingling's macro syntax has been largely stabilized since 0.3.0, and significant changes are not expected going forward — now is the perfect time to introduce some syntactic sugar. + +## The Problem + +For a long time, creating a command in Mingling required the following steps: + +**Step 1: Declare the entry point with `dispatcher!`** + +```rust +// Full form: declare dispatcher and entry types +dispatcher!("greet", CMDGreet => EntryGreet); +``` + +Or use the implicit syntax provided by `extras` (automatically deriving `CMDGreet` and `EntryGreet`): + +```rust +dispatcher!("greet"); +``` + +**Step 2: Register the dispatcher in `main`** + +```rust +fn main() { + let mut program = ThisProgram::new(); + program.with_dispatcher(CMDGreet); + program.exec_and_exit(); +} +``` + +**Step 3: Write the handling logic with `#[chain]`** + +```rust +#[chain] +fn handle_greet(args: EntryGreet) -> Next { + // ... +} +``` + +Three steps, three concepts (`dispatcher!`, `CMD*`, `#[chain]`), repeated for every new command. It made me wonder: do we really need to write this much boilerplate so often? + +## The Design: `#[command]` + +To provide a more concise approach, I plan to add the `#[command]` attribute macro, merging `dispatcher!` and `#[chain]` into a single step. + +### Basic Usage + +`#[command]` is applied to a function whose signature must satisfy two constraints: + +- **First argument**: Any type `T` such that `EntryGreet: Into` (equivalent to `T: From`) +- **Return type**: Any type `R` such that `R: Into` (`Next` is `ChainProcess`, generated by `gen_program!()`) + +The `pack!(EntryGreet = Vec)` generated by `dispatcher!("greet")` automatically provides `From for Vec`, so the most common pattern is to use `Vec` as the first argument: + +```rust +use mingling::prelude::*; + +#[command] +fn greet(args: Vec) -> Next { + let name = args.first().cloned().unwrap_or_else(|| "World".into()); + ResultName::new(name).into() +} +``` + +This expands to: + +```rust +// Automatically generates the dispatcher +dispatcher!("greet"); + +// Automatically generates the chain handler, bridging EntryGreet → T +// Here T = Vec, EntryGreet: Into> +#[chain] +fn __command_handle_greet(args: EntryGreet) -> Next { + greet(args.into()).into() +} + +// The original function remains unchanged +fn greet(args: Vec) -> Next { + let name = args.first().cloned().unwrap_or_else(|| "World".into()); + ResultName::new(name).into() +} +``` + +| Mapping | Rule | +| ----------------------- | ------------------------------------------------------------------------------------------------------- | +| Command name | The function name is used directly as the command name, e.g. `fn greet(...)` → `"greet"` | +| First argument | Any `T: From`, the macro-generated chain performs the conversion via `args.into()` | +| Return value | Any `R: Into`, the macro-generated chain converts it via `.into()` to `ChainProcess` | +| Dispatcher registration | You must manually call `program.with_dispatcher(CMDGreet)` in `main` | + +### Resource Injection + +`#[command]` also supports resource injection, following the same rules as `#[chain]` — from the second argument onward, use `&T` or `&mut T` to declare resource references: + +```rust +#[command] +fn greet(args: Vec, ec: &mut ResExitCode) -> Next { + ec.exit_code = 1; + let name = args.first().cloned().unwrap_or_else(|| "World".into()); + ResultName::new(name).into() +} +``` + +This expands to: + +```rust +dispatcher!("greet"); + +#[chain] +fn __command_handle_greet(args: EntryGreet, ec: &mut ResExitCode) -> Next { + greet(args.into(), ec).into() +} + +fn greet(args: Vec, ec: &mut ResExitCode) -> Next { + ec.exit_code = 1; + let name = args.first().cloned().unwrap_or_else(|| "World".into()); + ResultName::new(name).into() +} +``` + +### Implicit vs Explicit + +`#[command]` is syntactic sugar over `dispatcher!("name")` + `#[chain]`. It makes the generation of the `dispatcher` and `chain` more implicit, but dramatically reduces boilerplate: + +```rust +// Before +dispatcher!("greet", CMDGreet => EntryGreet); + +#[chain] +fn handle_greet(args: EntryGreet) -> Next { + // ... +} + +// After +#[command] +fn greet(args: Vec) -> Next { + // ... +} +``` + +Whenever you need fine-grained control over the behavior of the Dispatcher or Chain (e.g., customizing the `node` path, adding extra derive attributes to the Entry type), you can always fall back to the original explicit form. The two approaches do not conflict. + +## On Naming + +In Mingling, there is no concept called `Command` — all behavior is the result of dispatchers producing Entry types that are routed through the Program pipeline. The combination of `Dispatcher` + `Chain` is the standard way to build a Command, so using `#[command]` to generate `dispatcher!` and `#[chain]` makes semantic sense. + +## Enabling + +This macro requires the `extras` feature. Enable it in your `Cargo.toml`: + +```toml +[dependencies] +mingling = { version = "...", features = ["extras"] } +``` + +## Caveats + +- The `CMDGreet` generated by `#[command]` is a regular struct and still needs to be manually registered with the Program via `program.with_dispatcher(CMDGreet)`. +- If the function name contains non-alphabetic characters (such as underscores), the command name will be used as-is. For example, `fn add_remote(...)` corresponds to the command name `"add_remote"`. +- Nested commands (e.g., `"remote.add"`) cannot be expressed directly with `#[command]`; use the traditional `dispatcher!("remote.add")` form instead. + +

+ Written by @Weicao-CatilGrass · Revised for clarity +

diff --git a/docs/dev/pages/issues/the-command-macro.md b/docs/dev/pages/issues/the-command-macro.md deleted file mode 100644 index 82d733c..0000000 --- a/docs/dev/pages/issues/the-command-macro.md +++ /dev/null @@ -1,169 +0,0 @@ -

The Command Macro

-

- A macro for quickly building commands -

- -Mingling's macro syntax has been largely stabilized since 0.3.0, and significant changes are not expected going forward — now is the perfect time to introduce some syntactic sugar. - -## The Problem - -For a long time, creating a command in Mingling required the following steps: - -**Step 1: Declare the entry point with `dispatcher!`** - -```rust -// Full form: declare dispatcher and entry types -dispatcher!("greet", CMDGreet => EntryGreet); -``` - -Or use the implicit syntax provided by `extras` (automatically deriving `CMDGreet` and `EntryGreet`): - -```rust -dispatcher!("greet"); -``` - -**Step 2: Register the dispatcher in `main`** - -```rust -fn main() { - let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); - program.exec_and_exit(); -} -``` - -**Step 3: Write the handling logic with `#[chain]`** - -```rust -#[chain] -fn handle_greet(args: EntryGreet) -> Next { - // ... -} -``` - -Three steps, three concepts (`dispatcher!`, `CMD*`, `#[chain]`), repeated for every new command. It made me wonder: do we really need to write this much boilerplate so often? - -## The Design: `#[command]` - -To provide a more concise approach, I plan to add the `#[command]` attribute macro, merging `dispatcher!` and `#[chain]` into a single step. - -### Basic Usage - -`#[command]` is applied to a function whose signature must satisfy two constraints: - -- **First argument**: Any type `T` such that `EntryGreet: Into` (equivalent to `T: From`) -- **Return type**: Any type `R` such that `R: Into` (`Next` is `ChainProcess`, generated by `gen_program!()`) - -The `pack!(EntryGreet = Vec)` generated by `dispatcher!("greet")` automatically provides `From for Vec`, so the most common pattern is to use `Vec` as the first argument: - -```rust -use mingling::prelude::*; - -#[command] -fn greet(args: Vec) -> Next { - let name = args.first().cloned().unwrap_or_else(|| "World".into()); - ResultName::new(name).into() -} -``` - -This expands to: - -```rust -// Automatically generates the dispatcher -dispatcher!("greet"); - -// Automatically generates the chain handler, bridging EntryGreet → T -// Here T = Vec, EntryGreet: Into> -#[chain] -fn __command_handle_greet(args: EntryGreet) -> Next { - greet(args.into()).into() -} - -// The original function remains unchanged -fn greet(args: Vec) -> Next { - let name = args.first().cloned().unwrap_or_else(|| "World".into()); - ResultName::new(name).into() -} -``` - -| Mapping | Rule | -| ----------------------- | ------------------------------------------------------------------------------------------------------- | -| Command name | The function name is used directly as the command name, e.g. `fn greet(...)` → `"greet"` | -| First argument | Any `T: From`, the macro-generated chain performs the conversion via `args.into()` | -| Return value | Any `R: Into`, the macro-generated chain converts it via `.into()` to `ChainProcess` | -| Dispatcher registration | You must manually call `program.with_dispatcher(CMDGreet)` in `main` | - -### Resource Injection - -`#[command]` also supports resource injection, following the same rules as `#[chain]` — from the second argument onward, use `&T` or `&mut T` to declare resource references: - -```rust -#[command] -fn greet(args: Vec, ec: &mut ResExitCode) -> Next { - ec.exit_code = 1; - let name = args.first().cloned().unwrap_or_else(|| "World".into()); - ResultName::new(name).into() -} -``` - -This expands to: - -```rust -dispatcher!("greet"); - -#[chain] -fn __command_handle_greet(args: EntryGreet, ec: &mut ResExitCode) -> Next { - greet(args.into(), ec).into() -} - -fn greet(args: Vec, ec: &mut ResExitCode) -> Next { - ec.exit_code = 1; - let name = args.first().cloned().unwrap_or_else(|| "World".into()); - ResultName::new(name).into() -} -``` - -### Implicit vs Explicit - -`#[command]` is syntactic sugar over `dispatcher!("name")` + `#[chain]`. It makes the generation of the `dispatcher` and `chain` more implicit, but dramatically reduces boilerplate: - -```rust -// Before -dispatcher!("greet", CMDGreet => EntryGreet); - -#[chain] -fn handle_greet(args: EntryGreet) -> Next { - // ... -} - -// After -#[command] -fn greet(args: Vec) -> Next { - // ... -} -``` - -Whenever you need fine-grained control over the behavior of the Dispatcher or Chain (e.g., customizing the `node` path, adding extra derive attributes to the Entry type), you can always fall back to the original explicit form. The two approaches do not conflict. - -## On Naming - -In Mingling, there is no concept called `Command` — all behavior is the result of dispatchers producing Entry types that are routed through the Program pipeline. The combination of `Dispatcher` + `Chain` is the standard way to build a Command, so using `#[command]` to generate `dispatcher!` and `#[chain]` makes semantic sense. - -## Enabling - -This macro requires the `extras` feature. Enable it in your `Cargo.toml`: - -```toml -[dependencies] -mingling = { version = "...", features = ["extras"] } -``` - -## Caveats - -- The `CMDGreet` generated by `#[command]` is a regular struct and still needs to be manually registered with the Program via `program.with_dispatcher(CMDGreet)`. -- If the function name contains non-alphabetic characters (such as underscores), the command name will be used as-is. For example, `fn add_remote(...)` corresponds to the command name `"add_remote"`. -- Nested commands (e.g., `"remote.add"`) cannot be expressed directly with `#[command]`; use the traditional `dispatcher!("remote.add")` form instead. - -

- Written by @Weicao-CatilGrass · Revised for clarity -

-- cgit