Rendering Results

Declare a renderer using the #[renderer] macro to output results.

Now we've created a Dispatcher and a Chain, and produced a Result type via `pack!`. The final step: **present the result to the user**. ## The `#[renderer]` Macro Similar to `#[chain]`, `#[renderer]` marks an output function: ```rust use std::io::Write; pack!(ResultName = String); #[renderer] fn render_name(name: ResultName) -> RenderResult { let mut result = RenderResult::new(); writeln!(result, "Hello, {}!", *name).ok(); result } ``` A Renderer takes the result produced by a Chain and returns a `RenderResult`. Inside the function, create a `RenderResult`, write content using `write!` / `writeln!` (from [`std::io::Write`](https://doc.rust-lang.org/std/io/trait.Write.html)), and return it. ## The `RenderResult` Type `RenderResult` is a buffer type that holds rendered text and an exit code. Instead of writing directly to the terminal, it writes content into an internal buffer. This approach gives us: 1. **Exit code support**—you can set the program to exit with a specific exit code 2. **Testability**—rendered output can be captured and asserted against 3. **Post-processing**—the result can be captured and further processed uniformly ## A Complete Runnable Program Putting all three tutorials together, here's your first complete Mingling program: ```rust use std::io::Write; // 1. Declare commands with a Dispatcher dispatcher!("greet", CMDGreet => EntryGreet); // 2. Declare result data with pack! pack!(ResultName = String); // 3. Handle logic with a Chain #[chain] fn handle_greet(args: EntryGreet) -> Next { let name = args.inner .first() .cloned() .unwrap_or_else(|| "World".to_string()); ResultName::new(name) } // 4. Output results with a Renderer #[renderer] fn render_name(name: ResultName) -> RenderResult { let mut result = RenderResult::new(); writeln!(result, "Hello, {}!", *name).ok(); result } // 5. Assemble and run the program in main fn main() { let mut program = ThisProgram::new(); program.with_dispatcher(CMDGreet); program.exec_and_exit(); } // 6. Use gen_program! to generate the full program gen_program!(); ``` ## Try It Out ```bash ~# cargo run -- greet Alice ``` ```text Hello, Alice! ``` Try without arguments: ```bash ~# cargo run -- greet ``` ```text Hello, World! ``` Try a non-existent command: ```bash cargo run -- great ``` ```text # No output! ``` ## Adding a Fallback `gen_program!()` auto-generates an `ErrorDispatcherNotFound` type wrapping `Vec`—it holds the user input that didn't match any command. You just need to write a Renderer for it: ```rust use std::io::Write; #[renderer] fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { let mut result = RenderResult::new(); if err.inner.is_empty() { writeln!(result, "Unknown command").ok(); } else { writeln!(result, "Command not found: \"{}\"", err.inner.join(" ")).ok(); } result } ``` With that added, try the non-existent command again: ```bash cargo run -- great ``` ```text Command not found: "great" ``` ## Congratulations You've completed your first full Mingling program! Let's recap what you've learned: | Concept | Macro / Function | One-liner | | -------------- | ---------------- | --------------------------------------- | | Declare cmds | `dispatcher!` | Tell the program what the user can type | | Handle logic | `#[chain]` | What to do when args are received | | Output results | `#[renderer]` | How to present results to the user | | Type wrapping | `pack!` | Give your data a meaningful name | | Program entry | `gen_program!()` | Auto-generate the pipeline wiring | In real projects you'll also use advanced features like resource injection, hooks, completions, REPL, etc., but the core skeleton stays the same: **Dispatcher → Chain → Renderer**.

Written by @Weicao-CatilGrass