aboutsummaryrefslogtreecommitdiff
path: root/docs/pages/concepts/1-the-pipeline.md
blob: e73379d324588012de1ee6f5453f4c57058b736a (plain) (blame)
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
<h1 align="center">The Pipeline</h1>
<p align="center">
    How Mingling executes commands, step by step
</p>

Mingling splits command handling into three independent phases: Dispatcher → Chain → Renderer. This doc covers the actual execution logic — what happens at each step from user input to final output.

## Full Flow

```mermaid
graph TD
    A["program.exec_and_exit()"] --> B["Hook: pre_dispatch"]
    B --> C["Dispatch<br/>match cmd → Entry"]
    C --> D["Hook: post_dispatch"]
    D --> E{"user_context.help?"}
    E -->|"true"| F["render_help<br/>skip to help rendering"]
    E -->|"false"| G{"has_chain?"}
    G -->|"yes"| H["Hook: pre_chain"]
    H --> I["do_chain<br/>run business logic"]
    I --> J{"ChainProcess?"}
    J -->|"Ok(any, Renderer)"| K["Hook: pre_render →<br/>render → post_render"]
    J -->|"Ok(any, Chain)"| G
    J -->|"Err"| L["finish"]
    G -->|"no"| M{"has_renderer?"}
    M -->|"yes"| K
    M -->|"no"| N["build_renderer_not_found"]
    N --> G
    K --> O["Hook: finish → return RenderResult"]
    L --> O
    F --> O
```
 
## Phase Breakdown

### 1. Dispatch

`exec_with_args` first calls `dispatch_args_dynamic` or `dispatch_args_trie` (depending on whether the `dispatch_tree` feature is enabled), matching user input against registered Dispatchers.

The matching rule is **prefix matching** on space-separated tokens — the longest match wins. For example, if both `remote.add` and `remote` are registered, input `remote add origin` will match `remote.add`.

```mermaid
graph LR
    Input["user input"] --> M{"match Dispatcher"}
    M -->|"matched"| E["call dispatcher.begin(args)<br/>return wrapped Entry"]
    M -->|"no match"| NF["build_dispatcher_not_found<br/>generate ErrorDispatcherNotFound"]
```
 
On a match, `dispatcher.begin(args)` is called, returning `ChainProcess::Ok((AnyOutput, _))` — the Entry type wrapping the user's input params.

If no Dispatcher matches, `ErrorDispatcherNotFound` is generated (wrapping the full input), which a Renderer can later handle to display "Command not found".

### 2. Help Shortcut

Before entering the main loop, `program.user_context.help` is checked. If `true` (set by `HelpFlagSetup` in `BasicProgramSetup` when `--help` is parsed), `render_help` is called directly, skipping the entire pipeline.

### 3. Chain Main Loop

This is the core scheduling logic. Each iteration checks the current `AnyOutput`:

1. **Has a Chain** → execute `C::do_chain(current)`
   - Returns `(AnyOutput, Renderer)` → exit loop, go to rendering
   - Returns `(AnyOutput, Chain)` → continue loop, pass result to next Chain
   - Returns `Err` → terminate

2. **No Chain, but has a Renderer** → render directly

3. **Neither** → generate `renderer_not_found`, then loop again (the newly generated type might have a Renderer)

```mermaid
graph TD
    Start["current AnyOutput"] --> C{"has_chain?"}
    C -->|"yes"| Chain["do_chain"]
    Chain -->|"returns (any, Chain)"| C
    Chain -->|"returns (any, Renderer)"| Render["render"]
    Chain -->|"Err"| Exit["exit"]
    C -->|"no"| R{"has_renderer?"}
    R -->|"yes"| Render
    R -->|"no"| N["build_renderer_not_found<br/>try again"]
    N --> C
```
 
### 4. Render

The rendering phase calls `C::render(any, &mut render_result)`, which finds the matching `#[renderer]` function via `member_id` and writes the result into `RenderResult`. If `structural_renderer` is enabled, the result is also serialized to JSON/YAML (etc.) based on `program.structural_renderer_name`.

### 5. Exit

Sets `exit_code`, triggers the `finish` hook, and returns `RenderResult`.

> [!TIP]
> This runtime dispatch code is driven by the enums generated by `gen_program!()` and the `ProgramCollect` implementation.
>
> At compile time only the type-to-Chain / Renderer / Help / Completion mapping is generated; actual matching and routing happens at runtime.

## How This Is Different from Direct Function Calls

This pipeline helps avoid writing code like this:

```rust
@@@ struct Config;
@@@ impl Config { fn read() -> Self { Config } }
@@@ fn main() {
@@@ let json = true;
// read config
let mut config = Config::read();
 
// run operation
let Ok(result) = operation(&mut config) else {
    panic!("error handling");
};
 
// render result
if json {
    print_json();
} else {
    println!("success!");
}
@@@ }
@@@ fn operation(config: &mut Config) -> Result<(),()> { Ok(()) }
@@@ fn print_json() {}
```
 
Mingling's pipeline separates **cmd matching**, **business logic**, and **output rendering** into three independent slots, each responsible for one thing.

More importantly, through hooks and the `AnyOutput` mechanism, the pipeline lets cross-cutting concerns (logging, auth, exit codes) be inserted non-invasively — no pollution of your business code.

<p align="center" style="font-size: 0.85em; color: gray;">
    Written by @Weicao-CatilGrass
</p>