1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
<h1 align="center">Rendering Results</h1>
<p align="center">
Declare a renderer using the <code>#[renderer]</code> macro to output results.
</p>
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<String>`—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**.
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
</p>
|