From 6f3158896977a60c7c886c0960c8f9c0f2f0b2d9 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 15 Apr 2026 13:22:06 +0800 Subject: Add documentation for Dispatcher, Chain, and Renderer --- docs/pages/2-basic/3-dispatcher.md | 84 ++++++++++++++++++++++++++++++++++++++ docs/pages/2-basic/4-chain.md | 71 ++++++++++++++++++++++++++++++++ docs/pages/2-basic/5-renderer.md | 71 ++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+) (limited to 'docs/pages/2-basic') diff --git a/docs/pages/2-basic/3-dispatcher.md b/docs/pages/2-basic/3-dispatcher.md index 643a752..d0858d2 100644 --- a/docs/pages/2-basic/3-dispatcher.md +++ b/docs/pages/2-basic/3-dispatcher.md @@ -5,8 +5,92 @@ --- +## Intro +`Dispatcher` is a core concept in **Mingling**, used to dispatch user-input arguments to corresponding types, which are then handled by [Chain](pages/2-basic/4-chain) or [Renderer](pages/2-basic/5-renderer). +To define a `Dispatcher`, it is recommended to use the `dispatcher!` macro provided by `mingling_macros`: + +```rust +// User input: your_bin hello +// Will access HelloCommand and +// dispatch arguments to HelloEntry +dispatcher!("hello", + HelloCommand => HelloEntry); + +// User input: your_bin sub foo +// Will access FooCommand and +// dispatch arguments to FooEntry +dispatcher!("sub.foo", + FooCommand => FooEntry); + +// Same as above +dispatcher!("sub.bar", + BarCommand => BarEntry); +``` + +If you explicitly specify a name in the `gen_program!` macro, for example: + +```rust +gen_program!(MyProgram); +``` + +Then when using the `dispatcher!` macro, you must also explicitly specify the [Program](pages/2-basic/1-program): + +```rust +dispatcher!(MyProgram, "hello", + HelloCommand => HelloEntry); +``` + +**Tips:** Finally, add the `Dispatcher` you created to the [Program](pages/2-basic/1-program): + +```rust +#[tokio::main] +async fn main() { + let mut program = ThisProgram::new(); + program.with_dispatcher(HelloCommand); + program.with_dispatcher(SubFooCommand); + program.with_dispatcher(SubBarCommand); + program.exec().await; +} +``` + +## Manual Impl + +You can also manually implement the basic `Dispatcher` for more fine-grained control. However, compared to the procmacro, it is more cumbersome and cannot intelligently introduce certain traits based on the state of feature flags. + +```rust +// Define AddMemberEntry +// Use the `Groupped` derive to +// mark AddMemberEntry as a member of ThisProgram +#[derive(Debug, Groupped)] +pub struct AddMemberEntry { + // Define arguments to store user input + pub(crate) args: Vec, +} + +// Implement the Dispatcher trait +impl Dispatcher for AddMemberCommand { + // Return the node name of this Dispatcher + fn node(&self) -> Node { + node!("member.add") + } + + // When executing this Dispatcher, output AddMemberEntry + fn begin(&self, args: Vec) + -> ChainProcess + { + AnyOutput::new(AddMemberEntry { args }).route_chain() + } + + // Used to implement the clone trait for this Dispatcher + fn clone_dispatcher(&self) + -> Box> + { + Box::new(AddMemberCommand) + } +} +``` ## 💡 Next Page > **Basic Component** - Chain [Go](./pages/2-basic/4-chain) diff --git a/docs/pages/2-basic/4-chain.md b/docs/pages/2-basic/4-chain.md index e9676c2..87a50f2 100644 --- a/docs/pages/2-basic/4-chain.md +++ b/docs/pages/2-basic/4-chain.md @@ -5,6 +5,77 @@ --- +## Intro + +Like `Dispatcher`, `Chain` is also a core concept in building the entire **Mingling** framework. It is used to receive a dispatch of one type and convert it into another type. + +```rust +dispatcher!("hello", + HelloCommand => HelloEntry); + +// Define intermediate type ParsedHello, internally a String +pack!(ParsedHello = String); + +// Define chain parse_hello (expands to ParseHello) +// Declare conversion from HelloEntry +#[chain] +async fn parse_hello(prev: HelloEntry) -> NextProcess { + // Take the inner reference of HelloEntry + let args = &*prev; + + // Extract the first argument, use default value "World" + // if it doesn't exist + let first = args.first().cloned().unwrap_or_else(|| "World".to_string()); + + // Pack the extracted argument into ParsedHello and + // dispatch to the next chain + ParsedHello::new(first).to_chain() +} +``` + +> **About NextProcess** +> +> `NextProcess` is a marker type in **Mingling**, from `mingling::marker`. +> +> It serves no functional purpose other than to simplify the declaration of chain functions. After the `chain!` macro expands, `NextProcess` will be replaced with `mingling::ChainProcess`. + +## Manual Impl + +> ⚠️ WARNING +> +> The following content is not yet fully implemented; currently, only the `chain!` macro is allowed for implementation. + +You can also manually implement the basic `Chain` for finer control. + +However, please note that within the `chain!` macro, a `register_type!` macro is executed. This macro does not expand to any content; it only informs the `gen_program` context that this type exists. + +```rust +dispatcher!("hello", + HelloCommand => HelloEntry); + +pack!(ParsedHello = String); + +struct ParseHello; +impl Chain for ParseHello { + type Previous = HelloEntry; + async fn proc(prev: Self::Previous) + -> ChainProcess + { + let args = &*prev; + let first = args + .first() + .cloned() + .unwrap_or_else(|| + "World".to_string() + ); + ParsedHello::new(first).to_chain() + } +} + +// Register HelloEntry to the context and +// assign an ID for it +register_type!(HelloEntry); +``` ## 💡 Next Page > **Basic Component** - Renderer [Go](./pages/2-basic/5-renderer) diff --git a/docs/pages/2-basic/5-renderer.md b/docs/pages/2-basic/5-renderer.md index f3e09de..1f566f2 100644 --- a/docs/pages/2-basic/5-renderer.md +++ b/docs/pages/2-basic/5-renderer.md @@ -4,3 +4,74 @@

--- + +## Intro + +`Renderer` is similar to [Chain](pages/2-basic/4-chain) in that they both handle processing for a specific type. The difference is: [Chain](pages/2-basic/4-chain) transforms the type, while `Renderer` terminates the program and prints the information of that type to the terminal. + +A type can be processed by both [Chain](pages/2-basic/4-chain) and `Renderer`. If the type is `route_chain`ed, the system will search for a [Chain](pages/2-basic/4-chain) capable of handling that type. If none is found, it will automatically be routed to the `Renderer` to print the result of that type. + +The following example demonstrates how to handle rendering logic: + +```rust +dispatcher!("hello", + HelloCommand => HelloEntry); + +pack!(ParsedHello = String); + +// It's the Chain defined in the Dispatcher chapter +#[chain] +async fn parse_hello(prev: HelloEntry) -> NextProcess { + let args = &*prev; + let first = args + .first() + .cloned() + .unwrap_or_else(|| + "World".to_string() + ); + + // Distribute the type to the Renderer + ParsedHello::new(first).to_render() +} + +// Define the renderer to +// handle rendering of ParsedHello +#[renderer] +fn render_hello(prev: ParsedHello) { + // Use r_println or r_print to + // render the content of ParsedHello + r_println!("Hello, {}!", *prev) +} +``` + +> **About r_print** +> +> `r_print!` can only be used inside a `Renderer`. This is because after the `renderer!` macro expands, it injects `r: &mut RenderResult` into the context. +> +> And `r_print!` directly writes content to the value `r`. +> This means: if there is no `&mut RenderResult` named `r` in the context, `r_print!` cannot be used. + +## Manual Impl + +> ⚠️ WARNING +> +> The following content is not yet fully implemented; currently, only the `renderer!` macro is allowed for implementation. + +Similarly, you can also manually implement `Renderer`, + +but note that inside the `renderer!` macro, a `register_type!` macro is executed. This macro itself does not expand into any content; it is only used to inform the `gen_program` context that the type exists: + +```rust +struct RenderHello; +impl Renderer for RenderHello { + type Previous = ParsedHello; + fn render( + prev: Self::Previous, + r: &mut RenderResult + ) { + r_println!("Hello, {}!", *prev) + } +} + +register_type!(ParsedHello); +``` -- cgit