aboutsummaryrefslogtreecommitdiff
path: root/docs/_zh_CN/pages/concepts
diff options
context:
space:
mode:
Diffstat (limited to 'docs/_zh_CN/pages/concepts')
-rw-r--r--docs/_zh_CN/pages/concepts/.name1
-rw-r--r--docs/_zh_CN/pages/concepts/1-the-pipeline.md129
-rw-r--r--docs/_zh_CN/pages/concepts/2-resource.md60
-rw-r--r--docs/_zh_CN/pages/concepts/3-any-output.md73
-rw-r--r--docs/_zh_CN/pages/concepts/4-program-collect.md52
5 files changed, 315 insertions, 0 deletions
diff --git a/docs/_zh_CN/pages/concepts/.name b/docs/_zh_CN/pages/concepts/.name
new file mode 100644
index 0000000..dfd4543
--- /dev/null
+++ b/docs/_zh_CN/pages/concepts/.name
@@ -0,0 +1 @@
+核心概念
diff --git a/docs/_zh_CN/pages/concepts/1-the-pipeline.md b/docs/_zh_CN/pages/concepts/1-the-pipeline.md
new file mode 100644
index 0000000..b0bb19d
--- /dev/null
+++ b/docs/_zh_CN/pages/concepts/1-the-pipeline.md
@@ -0,0 +1,129 @@
+<h1 align="center">基础管线</h1>
+<p align="center">
+ Mingling 的核心执行流程
+</p>
+
+Mingling 把命令的处理拆成三个独立的阶段:Dispatcher → Chain → Renderer。这篇讲实际的执行逻辑——从用户输入到最终输出,每一步发生了什么。
+
+## 完整流程
+
+```mermaid
+graph TD
+ A["program.exec_and_exit()"] --> B["Hook: pre_dispatch"]
+ B --> C["Dispatch<br/>匹配命令 → Entry"]
+ C --> D["Hook: post_dispatch"]
+ D --> E{"user_context.help?"}
+ E -->|"true"| F["render_help<br/>直接跳帮助渲染"]
+ E -->|"false"| G{"has_chain?"}
+ G -->|"有"| H["Hook: pre_chain"]
+ H --> I["do_chain<br/>执行业务逻辑"]
+ 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 -->|"无"| M{"has_renderer?"}
+ M -->|"有"| K
+ M -->|"无"| N["build_renderer_not_found"]
+ N --> G
+ K --> O["Hook: finish → 返回 RenderResult"]
+ L --> O
+ F --> O
+```
+
+## 阶段详解
+
+### 1. 分发
+
+`exec_with_args` 首先调用 `dispatch_args_dynamic` 或 `dispatch_args_trie`(取决于是否启用 `dispatch_tree` 特性),将用户输入的参数与注册的 Dispatcher 匹配。
+
+匹配规则是按空格分割的**前缀匹配**——优先匹配最长的那个。例如注册了 `remote.add` 和 `remote`,输入 `remote add origin` 会匹配 `remote.add`。
+
+```mermaid
+graph LR
+ Input["用户输入"] --> M{匹配 Dispatcher}
+ M -->|"匹配到"| E["调用 dispatcher.begin(args)<br/>返回包装好的 Entry"]
+ M -->|"未匹配"| NF["build_dispatcher_not_found<br/>生成 ErrorDispatcherNotFound"]
+```
+
+匹配成功后调用 `dispatcher.begin(args)`,返回 `ChainProcess::Ok((AnyOutput, _))`,即包装好用户输入参数的 Entry 类型。
+
+如果没有匹配到任何 Dispatcher,则生成 `ErrorDispatcherNotFound`(包裹完整的输入参数),后续可以被 Renderer 处理显示 "Command not found"。
+
+### 2. Help 短路
+
+在进入主循环前,检查 `program.user_context.help`。如果为 `true`(由 `BasicProgramSetup` 中的 `HelpFlagSetup` 在解析到 `--help` 时设置),直接调用 `render_help` 跳过整个管线。
+
+### 3. Chain 主循环
+
+这是核心调度逻辑。每次循环检查当前的 `AnyOutput`:
+
+1. **有 Chain 能处理** → 执行 `C::do_chain(current)`
+ - 返回 `(AnyOutput, Renderer)` → 退出循环,进入渲染
+ - 返回 `(AnyOutput, Chain)` → 继续循环,把结果交给下一个 Chain
+ - 返回 `Err` → 终止程序
+
+2. **没有 Chain,但有 Renderer** → 直接渲染
+
+3. **两者都没有** → 生成 `renderer_not_found`,再循环一次(因为刚生成的这个类型可能有 Renderer)
+
+```mermaid
+graph TD
+ Start["当前 AnyOutput"] --> C{"has_chain?"}
+ C -->|"是"| Chain["do_chain"]
+ Chain -->|"返回 (any, Chain)"| C
+ Chain -->|"返回 (any, Renderer)"| Render["render"]
+ Chain -->|"Err"| Exit["退出"]
+ C -->|"否"| R{"has_renderer?"}
+ R -->|"是"| Render
+ R -->|"否"| N["build_renderer_not_found<br/>再试一次"]
+ N --> C
+```
+
+### 4. Render
+
+渲染阶段调用 `C::render(any, &mut render_result)`,根据 `member_id` 找到对应的 `#[renderer]` 函数,将结果写入 `RenderResult`。如果启用了 `structural_renderer`,还会根据 `program.structural_renderer_name` 将结果序列化为 JSON/YAML 等格式。
+
+### 5. 退出
+
+设置 `exit_code`,触发 `finish` hook,返回 `RenderResult`。
+
+> [!TIP]
+> 这套运行时调度代码由 `gen_program!()` 生成的枚举和 `ProgramCollect` 实现来驱动。
+>
+> 编译期只生成了类型到 Chain / Renderer / Help / Completion 的映射关系,实际的匹配和路由都是在运行时完成的。
+
+## 这和直接函数调用的区别
+
+这套管线可以避免你写出如下的代码:
+
+```rust
+@@@ struct Config;
+@@@ impl Config { fn read() -> Self { Config } }
+@@@ fn main() {
+@@@ let json = true;
+// 读取配置
+let mut config = Config::read();
+
+// 执行操作
+let Ok(result) = operation(&mut config) else {
+ panic!("错误处理");
+};
+
+// 渲染结果
+if json {
+ print_json();
+} else {
+ println!("成功!");
+}
+@@@ }
+@@@ fn operation(config: &mut Config) -> Result<(),()> { Ok(()) }
+@@@ fn print_json() {}
+```
+
+Mingling 的管线把 **命令匹配**、**业务逻辑**、**输出展示** 拆成三个独立的位置,每个位置只负责一件事。
+
+更重要的是,管线经过 hook 和 `AnyOutput` 机制,允许横切关注点(日志、鉴权、退出码)无侵入地插入,不会污染你的业务代码。
+
+<p align="center" style="font-size: 0.85em; color: gray;">
+ Written by @Weicao-CatilGrass
+</p>
diff --git a/docs/_zh_CN/pages/concepts/2-resource.md b/docs/_zh_CN/pages/concepts/2-resource.md
new file mode 100644
index 0000000..1052254
--- /dev/null
+++ b/docs/_zh_CN/pages/concepts/2-resource.md
@@ -0,0 +1,60 @@
+<h1 align="center">资源系统</h1>
+<p align="center">
+ Mingling 如何管理全局状态
+</p>
+
+命令行程序经常需要共享一些全局的东西——配置文件、数据库连接、计数器、当前工作目录。
+
+在普通 Rust 里你可能会用 `OnceCell` 或 `lazy_static`,在 Mingling 里有一套统一的机制:**资源系统**。
+
+## 什么是资源?
+
+资源就是在多个 Chain 和 Renderer 之间共享的数据。
+
+你只需要定义一个类型、注册到 Program,然后在函数签名里声明你需要它——剩下的注入和生命周期管理都由框架完成。
+
+## 核心机制:ResourceMarker
+
+任何同时实现了 `Default + Clone` 的类型都可以自动成为资源。框架会为它实现 `ResourceMarker` trait,使其具备:
+
+- **`res_clone()`** —— 当多个 Chain 同时访问时,框架可以通过 clone 来避免锁竞争
+- **`res_default()`** —— 资源未注册时提供兜底值
+
+如果你需要更精细的生命周期控制,可以使用 `LazyRes<T>`。它允许资源在第一次被访问时才初始化,并且可以在析构时执行回调(比如退出前保存状态到磁盘)。
+
+## 为什么不用全局变量?
+
+传统做法的静态变量是隐式依赖 —— 你看函数签名根本不知道它用了什么全局状态。而 Mingling 的资源注入让 **依赖显式化**:
+
+- 函数需要什么资源,参数列表就写什么
+- `&T` 表示只读访问,`&mut T` 表示可修改
+- 调用者一眼就能看出这个函数的副作用
+
+例如:
+
+```rust
+@@@ use mingling::res::ResExitCode;
+@@@ pack!(ErrorFileNotFound = ());
+#[chain]
+fn handle_error_file_not_found(
+ error: ErrorFileNotFound,
+ ec: &mut ResExitCode // 通过签名可以看出副作用!
+) {
+ ec.exit_code = 2; // 这里修改了退出码
+}
+```
+
+## 资源与 Setup 的关系
+
+资源通常通过两个途径注册到 Program:
+
+1. **直接注册** —— 在 `main` 中调用 `program.with_resource(...)`
+2. **通过 Setup** —— 使用 `DirectoryEnvironmentSetup` 等内置 Setup 批量注册(如 `ResCurrentDir`、`ResHomeDir`)
+
+Setup 是比资源更高层的抽象,一个 Setup 可以注册多个资源并做其他初始化工作。
+
+详见教程中的 [程序装配](./pages/8-setup-and-resources) 一章。
+
+<p align="center" style="font-size: 0.85em; color: gray;">
+ Written by @Weicao-CatilGrass
+</p>
diff --git a/docs/_zh_CN/pages/concepts/3-any-output.md b/docs/_zh_CN/pages/concepts/3-any-output.md
new file mode 100644
index 0000000..9b820da
--- /dev/null
+++ b/docs/_zh_CN/pages/concepts/3-any-output.md
@@ -0,0 +1,73 @@
+<h1 align="center">任意输出机制</h1>
+<p align="center">
+ 关于 AnyOutput 和 ChainProcess 的运作模式
+</p>
+
+Dispatcher → Chain → Renderer 三阶段之间传递的数据是什么?
+
+Chain 的输出可能是一个成功结果、一个错误、或者还需要继续交给下一个 Chain——这些类型各不相同,管线如何在编译期不知道具体类型的情况下,把它们送到正确的地方?
+
+## AnyOutput:类型擦除 + 组标签
+
+Mingling 的解法是把**所有类型擦除到同一个包装里**,然后用一个**枚举标签**来区分它们:
+
+```
+AnyOutput<G>
+├── inner: Box<dyn Any + Send> ← 真正的数据,类型已被擦除
+├── type_id: TypeId ← 运行时类型 ID,用于安全 downcast
+└── member_id: G ← 枚举标签,标记"这是谁"
+```
+
+这里的 `G` 就是 `gen_program!()` 生成的程序枚举(也就是你熟知的 `ThisProgram`)。
+
+每个被 `pack!` 或 `#[derive(Groupped)]` 标记的类型都被分配到这个枚举的一个变体。
+
+## ChainProcess:数据 + 路由
+
+在 `AnyOutput` 的基础上,`ChainProcess<G>` 加了一个**路由信息**:
+
+```
+ChainProcess<G>
+├── Ok(AnyOutput<G>, NextProcess) ← 携带数据,告诉调度器下一步去哪
+│ ├── NextProcess::Chain ← "还没完,继续交给下一个 Chain"
+│ └── NextProcess::Renderer ← "出结果了,展示给用户"
+└── Err(ChainProcessError) ← "出错了,终止程序"
+```
+
+这就是为什么 Chain 函数返回的不是裸数据,而是 `ChainProcess`——它把 **"下一步去哪"** 和 **"数据"** 打包在一起。
+
+调度器根据 `NextProcess` 决定是继续循环还是退出渲染。
+
+## Groupped:谁是谁
+
+调度器如何知道 `AnyOutput` 里装的是 `ResultName` 还是 `ErrorUserBlocked`?答案是 `Groupped` trait:
+
+```
+trait Groupped<G> {
+ fn member_id() -> G;
+}
+```
+
+当你用 `pack!(ResultName = String)` 时,宏自动为 `ResultName` 实现 `Groupped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
+
+`to_chain()` 和 `to_render()` 本质上是 `AnyOutput` 的快捷方法,分别构造 `ChainProcess::Ok(any, Chain)` 和 `ChainProcess::Ok(any, Renderer)`。
+
+## 调度的执行
+
+在运行时,主循环的工作就是:
+
+1. 看当前 `AnyOutput` 的 `member_id`
+2. 查这个变体有没有对应的 Chain → 有就执行,拿到新的 `AnyOutput` 和 `NextProcess`
+3. 如果 `NextProcess` 是 `Chain` → 回到第 1 步
+4. 如果 `NextProcess` 是 `Renderer` → 退出循环,渲染
+
+这套机制保证了**类型安全**:`gen_program!()` 生成的调度代码在做 `restore`(从 `Box<dyn Any>` 还原为具体类型)时,一定是在匹配的 `member_id` 分支内做的,不可能把 `ResultName` 的数据当作 `ErrorUserBlocked` 来解包。
+
+> [!TIP]
+> 日常开发中你不需要手动操作 `AnyOutput` 或 `ChainProcess`。
+>
+> `pack!`、`#[chain]`、`#[renderer]` 这些宏帮你处理了所有的包装和解包。
+
+<p align="center" style="font-size: 0.85em; color: gray;">
+ Written by @Weicao-CatilGrass
+</p>
diff --git a/docs/_zh_CN/pages/concepts/4-program-collect.md b/docs/_zh_CN/pages/concepts/4-program-collect.md
new file mode 100644
index 0000000..f5e6b8f
--- /dev/null
+++ b/docs/_zh_CN/pages/concepts/4-program-collect.md
@@ -0,0 +1,52 @@
+<h1 align="center">关于 ProgramCollect</h1>
+<p align="center">
+ 了解 gen_program!() 是如何生成程序的
+</p>
+
+每个 Mingling 程序最后都有一行 `gen_program!()`。它在背后做了三件事,把整个程序的骨架搭建出来。
+
+## gen_program!() 的三件事
+
+### 1. 生成枚举
+
+扫描当前模块中所有 `pack!`、`#[chain]`、`#[renderer]` 等宏标记的类型,为每个类型生成一个枚举变体。
+
+这个枚举就是 `AnyOutput<G>` 中 `G` 的类型 —— 调度器靠枚举变体来区分管线中传递的不同数据。
+
+### 2. 生成 ProgramCollect 实现
+
+`ProgramCollect` 是一个 trait,定义了 **"每个枚举变体对应什么类型、由谁处理"** 的映射关系:
+
+- **`do_chain`** —— 根据 `member_id` 调用对应的 `#[chain]` 函数,返回新的 `AnyOutput` 和 `NextProcess`
+- **`render`** —— 根据 `member_id` 调用对应的 `#[renderer]` 函数,写入 `RenderResult`
+- **`render_help`** —— 根据 `member_id` 调用对应的 `#[help]` 函数
+- **`has_chain` / `has_renderer`** —— 判断某个变体有没有对应的处理函数
+- **`build_dispatcher_not_found` / `build_renderer_not_found` / `build_empty_result`** —— 三个内置降级类型,处理边界情况
+
+这套映射在运行时通过枚举匹配来完成——编译期只生成了枚举和匹配分支,实际的函数调用发生在运行时。
+
+### 3. 生成 ThisProgram
+
+生成 `ThisProgram` 类型别名,指向 `Program<生成的枚举>`。这就是为什么在 `main` 中可以直接写 `ThisProgram::new()`——它就是你整个程序的完整类型。
+
+---
+
+## 关于 `pathf` 和 `dispatch_tree` 下的差异
+
+以上是默认行为,但在启用特定 feature 时会有变化:
+
+### 1. `dispatch_tree` 特性
+
+Dispatcher 的匹配不再使用 `Vec<Box<dyn Dispatcher>>` 做线性匹配,而是在编译期将子命令结构构建为前缀树(Trie)。
+
+匹配复杂度从 `O(n)` 降到 `O(k)` —— `k` 是输入长度,与命令数量无关。
+
+### 2. `pathf` 特性(Module Pathfinder)
+
+默认情况下所有宏标记的类型必须在同一模块才能被 `gen_program!()` 收集到
+
+启用 `pathf` 后,编译期自动扫描所有子模块,找到所有用宏标记的类型并生成完整的模块路径引用 —— 类型定义在深层子模块也无需手动 `use`。
+
+<p align="center" style="font-size: 0.85em; color: gray;">
+ Written by @Weicao-CatilGrass
+</p>