aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/_zh_CN/pages/11-resource-system.md11
-rw-r--r--docs/_zh_CN/pages/12-exit-code.md7
-rw-r--r--docs/_zh_CN/pages/13-hook.md5
-rw-r--r--docs/_zh_CN/pages/14-testing.md39
-rw-r--r--docs/_zh_CN/pages/2-define-a-dispatcher.md10
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md54
-rw-r--r--docs/_zh_CN/pages/4-render-result.md35
-rw-r--r--docs/_zh_CN/pages/5-multiple-commands.md20
-rw-r--r--docs/_zh_CN/pages/6-argument-parse-picker.md64
-rw-r--r--docs/_zh_CN/pages/9-error-handling.md40
-rw-r--r--docs/_zh_CN/pages/advanced/2-structural-renderer.md20
-rw-r--r--docs/_zh_CN/pages/concepts/2-resource.md3
-rw-r--r--docs/_zh_CN/pages/concepts/3-any-output.md6
-rw-r--r--docs/_zh_CN/pages/concepts/4-program-collect.md2
-rw-r--r--docs/_zh_CN/pages/other/features.md59
-rw-r--r--docs/_zh_CN/pages/other/naming_rule.md25
-rw-r--r--docs/dev/_sidebar.md8
-rw-r--r--docs/dev/pages/issues/_modify-dispatcher-syntax.md (renamed from docs/dev/pages/issues/t1_modify-dispatcher-syntax.md)16
-rw-r--r--docs/dev/pages/issues/_remove-pack-macros.md (renamed from docs/dev/pages/issues/t0_remove-pack-macros.md)16
-rw-r--r--docs/dev/pages/issues/_remove-parser-feature.md (renamed from docs/dev/pages/issues/t0_remove-parser-feature.md)16
-rw-r--r--docs/dev/pages/issues/_remove-with-dispatcher.md (renamed from docs/dev/pages/issues/t0_remove-with-dispatcher.md)14
-rw-r--r--docs/example-pages/examples.json17
-rw-r--r--docs/pages/11-resource-system.md11
-rw-r--r--docs/pages/12-exit-code.md3
-rw-r--r--docs/pages/13-hook.md5
-rw-r--r--docs/pages/14-testing.md39
-rw-r--r--docs/pages/2-define-a-dispatcher.md10
-rw-r--r--docs/pages/3-define-a-chain.md54
-rw-r--r--docs/pages/4-render-result.md35
-rw-r--r--docs/pages/5-multiple-commands.md20
-rw-r--r--docs/pages/6-argument-parse-picker.md64
-rw-r--r--docs/pages/9-error-handling.md40
-rw-r--r--docs/pages/advanced/2-structural-renderer.md20
-rw-r--r--docs/pages/concepts/2-resource.md3
-rw-r--r--docs/pages/concepts/3-any-output.md6
-rw-r--r--docs/pages/concepts/4-program-collect.md2
-rw-r--r--docs/pages/other/features.md59
-rw-r--r--docs/pages/other/naming_rule.md24
-rw-r--r--docs/res/changlog_examples/feat_program_res.rs5
39 files changed, 492 insertions, 395 deletions
diff --git a/docs/_zh_CN/pages/11-resource-system.md b/docs/_zh_CN/pages/11-resource-system.md
index 40d8cd9..b0fbe31 100644
--- a/docs/_zh_CN/pages/11-resource-system.md
+++ b/docs/_zh_CN/pages/11-resource-system.md
@@ -31,11 +31,12 @@ fn main() {
@@@#[derive(Default, Clone)]
@@@struct ResCurrentDir(String);
@@@dispatcher!("pwd", EntryPrintWorkingDir);
-@@@pack!(ResultPath = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultPath(String);
// 通过 &T 注入只读资源
#[chain]
fn handle_pwd(_args: EntryPrintWorkingDir, cwd: &ResCurrentDir) -> Next {
- ResultPath::new(cwd.0.clone()).to_render()
+ ResultPath(cwd.0.clone()).to_render()
}
#[renderer(buffer)]
@@ -53,7 +54,8 @@ fn render_path(result: ResultPath) {
@@@#[derive(Default, Clone)]
@@@struct ResVisitCount(u32);
@@@dispatcher!("visit", EntryVisit);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next {
counter.0 += 1;
@@ -74,7 +76,8 @@ Chain 可以同时注入任意多个资源,框架按类型自动匹配:
@@@#[derive(Default, Clone)] struct ResConfig(String);
@@@#[derive(Default, Clone)] struct ResCounter(u32);
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
// 同时注入只读 + 可修改
#[chain]
fn handle_test(_args: EntryTest, config: &ResConfig, counter: &mut ResCounter) -> Next {
diff --git a/docs/_zh_CN/pages/12-exit-code.md b/docs/_zh_CN/pages/12-exit-code.md
index 7c55b60..9270b84 100644
--- a/docs/_zh_CN/pages/12-exit-code.md
+++ b/docs/_zh_CN/pages/12-exit-code.md
@@ -3,9 +3,7 @@
如何使用资源系统管理程序退出码
</p>
-程序退出时给 shell 一个正确的退出码是 CLI 的基本素养
-
-。Mingling 提供了开箱即用的 `ExitCodeSetup`,配合 `ResExitCode` 资源,让退出码控制变得极其简单。
+程序退出时给 shell 一个正确的退出码是 CLI 的基本素养。Mingling 提供了开箱即用的 `ExitCodeSetup`,配合 `ResExitCode` 资源,让退出码控制变得极其简单。
## 启用 ExitCodeSetup
@@ -31,7 +29,8 @@ fn main() {
```rust
@@@use mingling::res::ResExitCode;
@@@use mingling::setup::ExitCodeSetup;
-@@@pack!(EntryCheck = Vec<String>);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct EntryCheck(Vec<String>);
#[chain]
fn handle_check(_args: EntryCheck, ec: &mut ResExitCode) {
// 检查失败的时候修改退出码资源
diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md
index 811957c..860bd2b 100644
--- a/docs/_zh_CN/pages/13-hook.md
+++ b/docs/_zh_CN/pages/13-hook.md
@@ -55,10 +55,11 @@ Hook 覆盖了管线的完整生命周期:
@@@use mingling::hook::ProgramHook;
@@@
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next {
-@@@ ResultName::new(args.inner.first().cloned().unwrap_or_default()).to_render()
+@@@ ResultName(args.0.first().cloned().unwrap_or_default()).to_render()
@@@}
@@@#[renderer] fn render_name(r: ResultName) -> RenderResult { RenderResult::new() }
fn main() {
diff --git a/docs/_zh_CN/pages/14-testing.md b/docs/_zh_CN/pages/14-testing.md
index 30f3fc4..13585ee 100644
--- a/docs/_zh_CN/pages/14-testing.md
+++ b/docs/_zh_CN/pages/14-testing.md
@@ -12,7 +12,8 @@ Chain 只是一个接收输入、返回输出的函数,Renderer 也只是接
Renderer 是最容易测试的——调用函数,断言返回结果:
```rust
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer]
fn render_greet(result: ResultName) -> RenderResult {
let mut r = RenderResult::new();
@@ -22,7 +23,7 @@ fn render_greet(result: ResultName) -> RenderResult {
#[test]
fn test_render_name() {
- let result = render_name(ResultName::new("Alice".to_string()));
+ let result = render_name(ResultName("Alice".to_string()));
assert_eq!(result.to_string().as_str(), "Hello, Alice!\n");
}
```
@@ -36,27 +37,29 @@ fn test_render_name() {
```rust
@@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process};
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
+@@@ let name = args.0.first().cloned().unwrap_or_default();
@@@ if name.is_empty() {
@@@ ErrorNoName::default().to_render()
@@@ } else {
-@@@ ResultName::new(name).to_render()
+@@@ ResultName(name).to_render()
@@@ }
@@@}
#[test]
fn test_handle_hello_with_name() {
- let chain_process = handle_hello(EntryGreet::new(vec!["Alice".to_string()])).into();
+ let chain_process = handle_hello(EntryHello(vec!["Alice".to_string()])).into();
// 断言这是一个渲染结果(不是继续 chain)
assert_render_result!(chain_process);
// 断言 member_id 是 ResultName
assert_member_id!(chain_process, ResultName);
// 解包出内部值
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -78,11 +81,12 @@ fn test_handle_hello_with_name() {
@@@use mingling::{assert_member_id, unpack_chain_process};
@@@use mingling::macros::entry;
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
-@@@ ResultName::new(name).to_render()
+@@@ let name = args.0.first().cloned().unwrap_or_default();
+@@@ ResultName(name).to_render()
@@@}
#[test]
fn test_with_entry_macro() {
@@ -90,7 +94,7 @@ fn test_with_entry_macro() {
let entry = entry!("--name", "Alice");
let chain_process = handle_hello(entry).into();
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -103,23 +107,24 @@ fn test_with_entry_macro() {
@@@#[derive(Default, Clone)]
@@@struct ResPrefix(String);
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultGreeting = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
@@@
#[chain]
fn handle_hello(args: EntryHello, prefix: &ResPrefix) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- ResultGreeting::new(format!("{}, {}", prefix.0, name)).to_render()
+ let name = args.0.first().cloned().unwrap_or_default();
+ ResultGreeting(format!("{}, {}", prefix.0, name)).to_render()
}
#[test]
fn test_handle_with_resource() {
// 资源需要在测试中手动传入
let result = handle_hello(
- EntryHello::new(vec!["World".to_string()]),
+ EntryHello(vec!["World".to_string()]),
&ResPrefix("Hello".to_string()),
);
let greeting = unpack_chain_process!(result, ResultGreeting, ThisProgram);
- assert_eq!(greeting.inner, "Hello, World");
+ assert_eq!(greeting.0, "Hello, World");
}
```
diff --git a/docs/_zh_CN/pages/2-define-a-dispatcher.md b/docs/_zh_CN/pages/2-define-a-dispatcher.md
index 7ae26f1..77a1513 100644
--- a/docs/_zh_CN/pages/2-define-a-dispatcher.md
+++ b/docs/_zh_CN/pages/2-define-a-dispatcher.md
@@ -48,17 +48,15 @@ dispatcher!("remote.rm", EntryRemoteRm);
```rust
// 示意,dispatcher! 宏实际生成的代码
-pub struct EntryGreet {
- pub inner: Vec<String>,
-}
+pub struct EntryGreet(pub Vec<String>);
```
-用户在命令行输入 `greet Alice Bob`,`EntryGreet.inner` 就是 `vec!["Alice", "Bob"]`。
+用户在命令行输入 `greet Alice Bob`,`EntryGreet` 包裹的 `Vec<String>`(即字段 `0`)就是 `vec!["Alice", "Bob"]`。
> [!IMPORTANT]
-> Entry 的 `inner` 只包含 **匹配后剩余的参数**。
+> Entry 包裹的参数只包含 **匹配后剩余的参数**。
>
-> 以 `remote add origin` 为例,`remote` 和 `add` 用于匹配命令路径,只有 `origin` 会进入 `EntryRemoteAdd.inner`。
+> 以 `remote add origin` 为例,`remote` 和 `add` 用于匹配命令路径,只有 `origin` 会进入 `EntryRemoteAdd`(即字段 `0`)。
## 进阶:隐式声明
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md
index c8d8a24..3356b74 100644
--- a/docs/_zh_CN/pages/3-define-a-chain.md
+++ b/docs/_zh_CN/pages/3-define-a-chain.md
@@ -17,14 +17,15 @@
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// args 就是用户输入经过匹配后剩下的参数
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
// 把结果包装成 Next,告诉调度器下一步去哪
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -32,7 +33,7 @@ fn handle_greet(args: EntryGreet) -> Next {
Chain 函数签名里写着它需要什么——`args: EntryGreet`
-然后用 `ResultName::new(name)` 返回一个新类型。
+然后用 `ResultName(name)` 返回一个新类型。
这个返回的 `Next` 会展开成 `impl Into<ChainProcess<ThisProgram>>`。
@@ -41,17 +42,30 @@ Chain 函数签名里写着它需要什么——`args: EntryGreet`
>
> 可以去 [任意输出机制](pages/concepts/3-any-output) 章节了解 `ChainProcess`。
-## `pack!` 宏
+## 用 `#[derive]` 定义管线类型
-你大概猜到了,`pack!(ResultName = String)` 定义了一个管线中传递的类型:
+你大概猜到了,`#[derive(Grouped, Wrap)] pub struct ResultName(String);` 定义了一个管线中传递的类型:
```rust
-// pack!(ResultName = String) 大概生成了这样的代码
+// 实际写代码时只需一行 #[derive(Grouped, Wrap)],它大概展开成下面这些实现:
-#[derive(Grouped)]
-pub struct ResultName {
- pub inner: String,
+pub struct ResultName(String);
+
+impl From<String> for ResultName {
+ fn from(inner: String) -> Self {
+ ResultName(inner)
+ }
+}
+
+impl std::ops::Deref for ResultName {
+ type Target = String;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
}
+
+// Grouped 生成 member_id() → ThisProgram::ResultName,
+// 赋予类型路由身份和 Into<ChainProcess> 转换。
```
你可以把它理解为一个 打了标签的 `String`。
@@ -59,7 +73,7 @@ pub struct ResultName {
调度器通过这个标签来精确路由,确保数据不会混淆 —— 比如发给 `RenderGreet` 的数据不会被误传给 `RenderError`。
> [!NOTE]
-> 与简单的类型别名 (`type`) 不同,`pack!` 会生成一个全新的类型,拥有独立的 `TypeId`。
+> 与简单的类型别名 (`type`) 不同,`#[derive(Grouped, Wrap)]` 会定义一个全新的类型,拥有独立的 `TypeId`。
命名上推荐这样的习惯:
@@ -70,25 +84,26 @@ pub struct ResultName {
| 最终结果 | `Result` + 描述 | `ResultGreetSomeone` |
| 错误 | `Error` + 描述 | `ErrorUserNotFound` |
-详见 [命名规范](pages/other/naming_rule),不过现在你只需要记住:**用 `pack!` 给你的数据取一个有意义的名字**。
+详见 [命名规范](pages/other/naming_rule),不过现在你只需要记住:**用 `#[derive(Grouped)]`(可搭配 `Wrap`)给你的数据取一个有意义的名字**。
## 从 Entry 中提取参数
-`EntryGreet` 的 `inner` 是一个 `Vec<String>`,你可以在 Chain 里自由地处理它:
+`EntryGreet` 包裹的 `Vec<String>`(即字段 `0`)你可以在 Chain 里自由地处理它:
```rust
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// 取第一个参数,没有就用默认值
let name = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -103,16 +118,17 @@ fn handle_greet(args: EntryGreet) -> Next {
dispatcher!("greet", EntryGreet);
// 2. 声明管线中的数据类型
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. 处理逻辑
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
fn main() {
diff --git a/docs/_zh_CN/pages/4-render-result.md b/docs/_zh_CN/pages/4-render-result.md
index e3e49f5..ad915ba 100644
--- a/docs/_zh_CN/pages/4-render-result.md
+++ b/docs/_zh_CN/pages/4-render-result.md
@@ -3,7 +3,7 @@
使用 renderer 宏声明渲染器,将结果输出
</p>
-现在,我们创建了 Dispatcher 和 Chain,也通过 `pack!` 产出了一个 Result 类型。最后一步:**把结果展示给用户**。
+现在,我们创建了 Dispatcher 和 Chain,也通过 `#[derive(Grouped, Wrap)]` 产出了一个 Result 类型。最后一步:**把结果展示给用户**。
## `#[renderer]` 宏
@@ -11,7 +11,8 @@
```rust
@@@use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -27,7 +28,8 @@ Renderer 接收 Chain 产出的结果,然后返回一个 `RenderResult`。在
```rust
use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -54,17 +56,18 @@ use mingling::macros::buffer;
// 1. 用 Dispatcher 声明命令
dispatcher!("greet", EntryGreet);
-// 2. 用 pack! 声明结果数据
-pack!(ResultName = String);
+// 2. 用 #[derive(Grouped, Wrap)] 声明结果数据
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. 用 Chain 处理逻辑
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
// 4. 用 Renderer 输出结果
@@ -122,10 +125,10 @@ use mingling::macros::buffer;
#[renderer(buffer)]
fn render_entry_fallback(err: EntryFallback) {
- if err.inner.is_empty() {
+ if err.0.is_empty() {
r_println!("Unknown command");
} else {
- r_println!("Command not found: \"{}\"", err.inner.join(" "));
+ r_println!("Command not found: \"{}\"", err.0.join(" "));
}
}
```
@@ -144,13 +147,13 @@ Command not found: "great"
你完成了第一个完整的 Mingling 程序!来回顾一下学到的东西:
-| 概念 | 对应宏/函数 | 一句话 |
-| -------- | ---------------- | -------------------------- |
-| 声明命令 | `dispatcher!` | 告诉程序用户能输入什么 |
-| 处理逻辑 | `#[chain]` | 收到参数后做什么 |
-| 输出结果 | `#[renderer]` | 怎么把结果告诉用户 |
-| 类型包装 | `pack!` | 给你的数据取个有意义的名字 |
-| 程序入口 | `gen_program!()` | 自动生成管线的接线图 |
+| 概念 | 对应宏/函数 | 一句话 |
+| -------- | -------------------------- | -------------------------- |
+| 声明命令 | `dispatcher!` | 告诉程序用户能输入什么 |
+| 处理逻辑 | `#[chain]` | 收到参数后做什么 |
+| 输出结果 | `#[renderer]` | 怎么把结果告诉用户 |
+| 类型包装 | `#[derive(Grouped, Wrap)]` | 给你的数据取个有意义的名字 |
+| 程序入口 | `gen_program!()` | 自动生成管线的接线图 |
在真实项目中你还会用到资源注入、hook、补全、REPL 等高级功能,不过核心骨架永远不变:**Dispatcher → Chain → Renderer**。
diff --git a/docs/_zh_CN/pages/5-multiple-commands.md b/docs/_zh_CN/pages/5-multiple-commands.md
index e0a58e7..9d9d37e 100644
--- a/docs/_zh_CN/pages/5-multiple-commands.md
+++ b/docs/_zh_CN/pages/5-multiple-commands.md
@@ -15,19 +15,21 @@
dispatcher!("greet", EntryGreet);
dispatcher!("add", EntryAdd);
-pack!(ResultGreeting = String);
-pack!(ResultSum = i32);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ResultSum(i32);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(name).into()
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
+ ResultGreeting(name).into()
}
#[chain]
fn handle_add(args: EntryAdd) -> Next {
- let sum: i32 = args.inner.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
- ResultSum::new(sum).into()
+ let sum: i32 = args.0.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
+ ResultSum(sum).into()
}
#[renderer(buffer)]
@@ -70,10 +72,10 @@ dispatcher!("remote.rm", EntryRemoteRm);
## 数据类型的独立性
-注意我们用了两个不同的 `pack!`:
+注意我们用了两个不同的类型:
-- `pack!(ResultGreeting = String)`
-- `pack!(ResultSum = i32)`
+- `#[derive(Grouped, Wrap)] pub struct ResultGreeting(String);`
+- `#[derive(Grouped, Wrap)] pub struct ResultSum(i32);`
它们都是独立的类型,`gen_program!()` 会给它们分配不同的枚举变体。
diff --git a/docs/_zh_CN/pages/6-argument-parse-picker.md b/docs/_zh_CN/pages/6-argument-parse-picker.md
index 7944d0a..6c88423 100644
--- a/docs/_zh_CN/pages/6-argument-parse-picker.md
+++ b/docs/_zh_CN/pages/6-argument-parse-picker.md
@@ -3,7 +3,7 @@
用 Picker 完成基本的参数解析
</p>
-前面教程中我们都是手动从 `EntryGreet.inner`(`Vec<String>`)中提取参数。
+前面教程中我们都是手动从 `EntryGreet.0`(`Vec<String>`)中提取参数。
```rust
@@@ fn main() {
@@ -27,14 +27,15 @@ features = ["picker"]
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -45,13 +46,14 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
-@@@ResultName::new(name).into()
+@@@ResultName(name).into()
@@@}
```
@@ -60,7 +62,8 @@ let name = prev
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -81,14 +84,15 @@ let name = prev
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![name: String, 'n'], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -99,7 +103,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -122,7 +127,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["picker"]
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultInfo = (String, u8, u32));
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultInfo((String, u8, u32));
#[chain]
fn handle_test_entry(prev: EntryTest) -> Next {
@@ -132,7 +138,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
.pick_or_default(&arg![id: u32, 'I'])
.unwrap();
- ResultInfo::new((name, age, id)).into()
+ ResultInfo((name, age, id)).into()
}
```
@@ -150,8 +156,10 @@ fn handle_test_entry(prev: EntryTest) -> Next {
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -162,7 +170,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.to_result()
);
- ResultName::new(name).into()
+ ResultName(name).into()
}
#[renderer(buffer)]
@@ -177,12 +185,13 @@ fn render_greet(result: ResultName) {
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::macros::route;
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result());
+let name = route!(args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result());
@@@ mingling::macros::empty_result!()
@@@ }
```
@@ -191,11 +200,12 @@ let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_cha
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() {
+let name = match args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result() {
Ok(r) => r,
Err(e) => return e,
};
@@ -210,7 +220,8 @@ let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chai
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -225,7 +236,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -237,7 +248,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
// Features: ["picker"]
@@@use mingling::picker::value::Flag;
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_entry(prev: EntryTest) -> Next {
@@ -280,12 +292,13 @@ impl SinglePickable for Address {
}
}
@@@dispatcher!("connect", EntryConnect);
-@@@pack!(ResultConnected = Address);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultConnected(Address);
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
let address: Address = prev.pick_or_default(&arg![Address]).unwrap();
- ResultConnected::new(address).into()
+ ResultConnected(address).into()
}
#[renderer(buffer)]
@@ -333,12 +346,13 @@ impl SinglePickable for Fruits {
}
}
@@@dispatcher!("eat", EntryEat);
-@@@pack!(ResultFruit = Fruits);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultFruit(Fruits);
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap();
- ResultFruit::new(fruit).into()
+ ResultFruit(fruit).into()
}
#[renderer(buffer)]
diff --git a/docs/_zh_CN/pages/9-error-handling.md b/docs/_zh_CN/pages/9-error-handling.md
index dce48d1..463b829 100644
--- a/docs/_zh_CN/pages/9-error-handling.md
+++ b/docs/_zh_CN/pages/9-error-handling.md
@@ -20,17 +20,19 @@
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
```
@@ -40,9 +42,11 @@ fn handle_greet(args: EntryGreet) -> Next {
```rust
@@@use mingling::macros::buffer;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultGreeting = String);
-@@@pack!(ErrorNameEmpty = String);
-@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting::new(args.inner.first().cloned().unwrap_or_default()).to_render() }
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ErrorNameEmpty(String);
+@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting(args.0.first().cloned().unwrap_or_default()).to_render() }
#[renderer(buffer)]
fn render_greet(result: ResultGreeting) {
@@ -63,16 +67,18 @@ fn render_error_name_empty(err: ErrorNameEmpty) {
@@@use mingling::macros::buffer;
dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
@@ -104,14 +110,14 @@ Hello, Alice!
Error: name is required
```
-## 关于 `pack_err!`
+## 关于错误类型
-如果你启用了 `extras`,还可以用 `pack_err!` 快速声明带有自动 `name` 字段的错误类型:
+不需要额外上下文、只起“标记”作用的错误类型,可以直接用 `#[derive(Grouped, Default)]` 声明:
```rust
// Features: ["extras"]
-pack_err!(ErrorNotFound);
-// 生成: struct ErrorNotFound { pub name: String }
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
```
详见 [特性列表](pages/other/features)。
diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
index f2e1717..f678e2f 100644
--- a/docs/_zh_CN/pages/advanced/2-structural-renderer.md
+++ b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
@@ -21,7 +21,7 @@ features = ["structural_renderer"]
## 基本用法
-启用 `StructuralRendererSetup` 后,用 `pack_structural!` 替代 `pack!` 来声明支持结构化输出的类型:
+启用 `StructuralRendererSetup` 后,用 `#[derive(StructuralData)]`(搭配 `serde::Serialize`、`Grouped` 和 `Wrap`)来声明支持结构化输出的类型:
```rust
// Features: ["structural_renderer"]
@@ -29,16 +29,18 @@ features = ["structural_renderer"]
// serde = "1"
@@@use mingling::macros::buffer;
@@@use mingling::setup::StructuralRendererSetup;
+@@@use mingling::StructuralData;
@@@dispatcher!("render", EntryRender);
-// pack_structural! 等价于 pack! + StructuralData
-pack_structural!(ResultInfo = (String, i32));
+// #[derive(Grouped, Wrap)] + StructuralData + serde::Serialize 等价于旧版的 pack_structural!
+#[derive(serde::Serialize, StructuralData, Grouped, Wrap)]
+pub struct ResultInfo((String, i32));
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
- ResultInfo::new((name, age)).into()
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ ResultInfo((name, age)).into()
}
#[renderer(buffer)]
@@ -61,7 +63,7 @@ fn render_info(r: ResultInfo) {
## 自定义输出结构
-`pack_structural!` 的默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Grouped)]` 手动定义类型:
+用 `#[derive(Grouped, Wrap)]` 包装的元组结构体默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Grouped)]` 手动定义类型:
```rust
// Features: ["structural_renderer"]
@@ -82,8 +84,8 @@ struct Info {
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
Info { name, age }.to_render()
}
diff --git a/docs/_zh_CN/pages/concepts/2-resource.md b/docs/_zh_CN/pages/concepts/2-resource.md
index 1052254..154fa7d 100644
--- a/docs/_zh_CN/pages/concepts/2-resource.md
+++ b/docs/_zh_CN/pages/concepts/2-resource.md
@@ -34,7 +34,8 @@
```rust
@@@ use mingling::res::ResExitCode;
-@@@ pack!(ErrorFileNotFound = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFileNotFound(());
#[chain]
fn handle_error_file_not_found(
error: ErrorFileNotFound,
diff --git a/docs/_zh_CN/pages/concepts/3-any-output.md b/docs/_zh_CN/pages/concepts/3-any-output.md
index 118e856..1a2164c 100644
--- a/docs/_zh_CN/pages/concepts/3-any-output.md
+++ b/docs/_zh_CN/pages/concepts/3-any-output.md
@@ -20,7 +20,7 @@ AnyOutput<G>
这里的 `G` 就是 `gen_program!()` 生成的程序枚举(也就是你熟知的 `ThisProgram`)。
-每个被 `pack!` 或 `#[derive(Grouped)]` 标记的类型都被分配到这个枚举的一个变体。
+每个被 `#[derive(Grouped)]`(可搭配 `Wrap`)标记的类型都被分配到这个枚举的一个变体。
## ChainProcess:数据 + 路由
@@ -48,7 +48,7 @@ trait Grouped<G> {
}
```
-当你用 `pack!(ResultName = String)` 时,宏自动为 `ResultName` 实现 `Grouped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
+当你用 `#[derive(Grouped, Wrap)] pub struct ResultName(String);` 时,派生宏自动为 `ResultName` 实现 `Grouped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
`to_chain()` 和 `to_render()` 本质上是 `AnyOutput` 的快捷方法,分别构造 `ChainProcess::Ok(any, Chain)` 和 `ChainProcess::Ok(any, Renderer)`。
@@ -66,7 +66,7 @@ trait Grouped<G> {
> [!TIP]
> 日常开发中你不需要手动操作 `AnyOutput` 或 `ChainProcess`。
>
-> `pack!`、`#[chain]`、`#[renderer]` 这些宏帮你处理了所有的包装和解包。
+> `#[derive(Grouped)]`、`#[chain]`、`#[renderer]` 这些宏帮你处理了所有的包装和解包。
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/_zh_CN/pages/concepts/4-program-collect.md b/docs/_zh_CN/pages/concepts/4-program-collect.md
index a0236f6..616f645 100644
--- a/docs/_zh_CN/pages/concepts/4-program-collect.md
+++ b/docs/_zh_CN/pages/concepts/4-program-collect.md
@@ -9,7 +9,7 @@
### 1. 生成枚举
-扫描当前模块中所有 `pack!`、`#[chain]`、`#[renderer]` 等宏标记的类型,为每个类型生成一个枚举变体。
+扫描当前模块中所有 `#[derive(Grouped)]`、`#[chain]`、`#[renderer]` 等宏标记的类型,为每个类型生成一个枚举变体。
这个枚举就是 `AnyOutput<G>` 中 `G` 的类型 —— 调度器靠枚举变体来区分管线中传递的不同数据。
diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md
index 30231ce..5fa7c86 100644
--- a/docs/_zh_CN/pages/other/features.md
+++ b/docs/_zh_CN/pages/other/features.md
@@ -80,11 +80,12 @@ features = ["build_full"]
```rust
// Features: ["async"]
-pack!(StateFoo = ());
+#[derive(Grouped, Wrap)]
+pub struct StateFoo(());
#[chain]
async fn handle_state_foo(foo: StateFoo) -> Next {
- StateFoo::new(()).into()
+ StateFoo(()).into()
}
```
@@ -151,14 +152,13 @@ build_comp_scripts("myprogram").unwrap();
例如,允许 `dispatcher!("greet")` 的缩写形式,自动生成 `CMDGreet` / `EntryGreet`。
-| 宏 | 说明 |
-| ------------------------------------------------------- | -------------------------------------- |
-| `empty_result!()` | 链中提前返回空结果的简写 |
-| `entry!(Type, ["a", "b"])` | 构造入口类型的测试数据 |
-| `group!(Type)` | 将外部类型注册为组成员,无需修改其定义 |
-| `pack_err!(ErrorType)` / `pack_err!(ErrorType = Inner)` | 创建带自动 `name` 字段的错误类型 |
-| `#[program_setup]` | 声明程序初始化函数 |
-| `dispatcher!("cmd.path")` **缩写形式** | 省略 `EntryStruct`,入口类型名自动推导 |
+| 宏 | 说明 |
+| -------------------------------------- | -------------------------------------- |
+| `empty_result!()` | 链中提前返回空结果的简写 |
+| `entry!(Type, ["a", "b"])` | 构造入口类型的测试数据 |
+| `group!(Type)` | 将外部类型注册为组成员,无需修改其定义 |
+| `#[program_setup]` | 声明程序初始化函数 |
+| `dispatcher!("cmd.path")` **缩写形式** | 省略 `EntryStruct`,入口类型名自动推导 |
<details>
<summary> Details </summary>
@@ -168,10 +168,13 @@ build_comp_scripts("myprogram").unwrap();
```rust
// Features: ["extras"]
-pack!(StatePrev1 = ());
-pack!(StatePrev2 = ());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev1(());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev2(());
-pack!(StateNext = ());
+#[derive(Grouped, Wrap)]
+pub struct StateNext(());
#[chain]
fn handle_state_prev2(_p: StatePrev2) {
@@ -186,7 +189,7 @@ fn handle_state_prev1(_p: StatePrev1) -> Next {
// 当需要 Next 且不需要返回值,便可以使用它
empty_result!()
} else {
- StateNext::new(()).into()
+ StateNext(()).into()
}
}
```
@@ -217,7 +220,8 @@ fn no_error_setup(program: &mut Program<ThisProgram>) {
// Features: ["extras"]
use mingling::macros::entry;
-pack!(EntryHello = Vec<String>);
+#[derive(Grouped, Wrap)]
+pub struct EntryHello(Vec<String>);
fn main() {
let result: Next = handle_hello(entry!("--name", "Bob")).into();
@@ -231,7 +235,7 @@ fn handle_hello(args: EntryHello) {}
### `group!`
将外部类型注册为程序组成员,无需修改原始类型的定义。
-类型名会直接作为枚举变体,与 `pack!` 或 `#[derive(Grouped)]` 一致。
+类型名会直接作为枚举变体,与 `#[derive(Grouped)]` 一致。
```rust
// Features: ["extras"]
@@ -243,26 +247,23 @@ use std::num::ParseIntError;
group!(std::num::ParseIntError);
```
-### `pack_err!`
+### 定义错误类型
-创建带自动 `name: String` 字段的错误结构体,字段值自动设为结构体名的蛇形命名。
-可选择包裹一个内部类型以携带额外上下文。
+0.5.0 起 `pack_err!` 已移除,错误类型直接用 derive 声明:
+不携带额外上下文时用 `#[derive(Grouped, Default)]`(仅作标记),或
+用 `#[derive(Grouped, Wrap)]` 包裹一个内部类型以携带上下文。
```rust
// Features: ["extras"]
use std::path::PathBuf;
-// 简单形式——仅包含 name 字段:
-pack_err!(ErrorNotFound);
-// 生成:
-// struct ErrorNotFound { pub name: String }
-// impl Default for ErrorNotFound { ... }
+// 简单形式——只作为标记使用:
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
-// 带类型的形式——包含额外的 info 字段:
-pack_err!(ErrorNotDir = PathBuf);
-// 生成:
-// struct ErrorNotDir { pub name: String, pub info: PathBuf }
-// impl ErrorNotDir { pub fn new(info: PathBuf) -> Self { ... } }
+// 带类型的形式——包裹一个内部类型以携带上下文:
+#[derive(Grouped, Wrap)]
+pub struct ErrorNotDir(PathBuf);
```
</details>
diff --git a/docs/_zh_CN/pages/other/naming_rule.md b/docs/_zh_CN/pages/other/naming_rule.md
index 1bca8f6..7694f56 100644
--- a/docs/_zh_CN/pages/other/naming_rule.md
+++ b/docs/_zh_CN/pages/other/naming_rule.md
@@ -94,7 +94,7 @@ Result + 内容
| `ResultGreetSomeone` | 问候结果 |
| `ResultFruitList` | 水果列表结果 |
-结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 代替 `pack!()` 包装,以获得更灵活的字段控制。
+结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 标注结构体,以获得更灵活的字段控制。
### 错误
@@ -146,7 +146,8 @@ Error + 描述
| 资源(可变) | `counter`、`cache`、`session` 等 |
```rust
-@@@ pack!(EntryRemoteAdd = Vec<String>);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct EntryRemoteAdd(Vec<String>);
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ #[derive(Default, Clone)]
@@ -168,9 +169,12 @@ fn handle_remote_add(args: EntryRemoteAdd, cwd: &ResCurrentDir, db: &mut ResData
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ impl ResDatabase { fn has_remote(&self, remote: &String) -> bool { true } }
-@@@ pack!(StateOperationRemotes = String);
-@@@ pack!(ResultRemoteAdded = String);
-@@@ pack!(ErrorRepositoryNotFound = String);
+@@@ #[derive(Grouped, Wrap, Default)]
+@@@ pub struct StateOperationRemotes(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ResultRemoteAdded(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorRepositoryNotFound(String);
// 分发器
dispatcher!("remote.add", EntryRemoteAdd);
@@ -183,23 +187,24 @@ fn handle_remote_add(args: EntryRemoteAdd) -> Next {
// 状态 → 错误或结果
#[chain]
fn handle_state_operation_remotes(state: StateOperationRemotes, db: &ResDatabase) -> Next {
- if db.has_remote(&state.inner) {
- ErrorRepositoryNotFound::new(state.inner).to_render()
+ if db.has_remote(&state.0) {
+ ErrorRepositoryNotFound(state.0).to_render()
} else {
- ResultRemoteAdded::new(state.inner).to_render()
+ ResultRemoteAdded(state.0).to_render()
}
}
// 结果渲染
+
#[renderer(buffer)]
fn render_remote_added(result: ResultRemoteAdded) {
- r_println!("Remote added: {}", result.inner);
+ r_println!("Remote added: {}", result.0);
}
// 错误渲染
#[renderer(buffer)]
fn render_error_repository_not_found(err: ErrorRepositoryNotFound) {
- r_println!("Error: remote '{}' not found", err.inner);
+ r_println!("Error: remote '{}' not found", err.0);
}
```
diff --git a/docs/dev/_sidebar.md b/docs/dev/_sidebar.md
index cfb482a..e0a77d3 100644
--- a/docs/dev/_sidebar.md
+++ b/docs/dev/_sidebar.md
@@ -1,15 +1,15 @@
- [Welcome!](README)
* ❓ Issues
* [[Solved] The Picker2 Arguments Parser](pages/issues/_add-picker2)
+ * [[T1] Modify the dispatcher! Syntax](pages/issues/_modify-dispatcher-syntax)
+ * [[T0] Remove the pack! Family of Macros](pages/issues/_remove-pack-macros)
+ * [[T0] Remove the parser Feature](pages/issues/_remove-parser-feature)
* [[Solved] Remove r_print! and r_println! Macros](pages/issues/_remove-r-print-macro)
+ * [[T0] Remove with_dispatcher and with_dispatchers](pages/issues/_remove-with-dispatcher)
* [[Solved] The Command Macro](pages/issues/_the-command-macro)
* [[Solved] The Mod Pathfinder](pages/issues/_the-mod-pathfinder)
* [[T0] Generalize the REPL System](pages/issues/t0_generalize-repl-system)
- * [[T0] Remove the pack! Family of Macros](pages/issues/t0_remove-pack-macros)
- * [[T0] Remove the parser Feature](pages/issues/t0_remove-parser-feature)
- * [[T0] Remove with_dispatcher and with_dispatchers](pages/issues/t0_remove-with-dispatcher)
* [[T1] Higher-Level Abstractions for the Completion System](pages/issues/t1_completion-higher-level-abstractions)
- * [[T1] Modify the dispatcher! Syntax](pages/issues/t1_modify-dispatcher-syntax)
* [[T1] Move structural_renderer from mingling_core to mingling](pages/issues/t1_move-structural-renderer)
* [[T1] The pathf_export Attribute Macro](pages/issues/t1_pathf-export-macro)
* [[T2] Automated dispatcher_tree Optimization Decisions](pages/issues/t2_automated-dispatch-tree-optimization)
diff --git a/docs/dev/pages/issues/t1_modify-dispatcher-syntax.md b/docs/dev/pages/issues/_modify-dispatcher-syntax.md
index f1a9346..ed409ef 100644
--- a/docs/dev/pages/issues/t1_modify-dispatcher-syntax.md
+++ b/docs/dev/pages/issues/_modify-dispatcher-syntax.md
@@ -27,17 +27,17 @@ dispatcher!("command", EntryCommand);
## Tasks
-- [ ] Update `dispatcher!` to accept the simplified explicit form (`"name", EntryType`)
-- [ ] Decide whether the old `CMDType => EntryType` form should error with a helpful message or be removed outright
-- [ ] Remove the generated `CMD*` struct machinery
-- [ ] Update `#[command]` macro internals that depend on `CMD*`
-- [ ] Migrate examples, tests, and docs
-- [ ] Keep the implicit mode (`dispatcher!("name")`) working unchanged
+- [x] Update `dispatcher!` to accept the simplified explicit form (`"name", EntryType`)
+- [x] Decide whether the old `CMDType => EntryType` form should error with a helpful message or be removed outright
+- [x] Remove the generated `CMD*` struct machinery
+- [x] Update `#[command]` macro internals that depend on `CMD*`
+- [x] Migrate examples, tests, and docs
+- [x] Keep the implicit mode (`dispatcher!("name")`) working unchanged
## 🕘 Progress
-- [ ] In Progress
-- [ ] Complete
+- [x] In Progress
+- [x] Complete
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/dev/pages/issues/t0_remove-pack-macros.md b/docs/dev/pages/issues/_remove-pack-macros.md
index 517646a..74c21aa 100644
--- a/docs/dev/pages/issues/t0_remove-pack-macros.md
+++ b/docs/dev/pages/issues/_remove-pack-macros.md
@@ -32,17 +32,17 @@ pub struct ResultNames {
## Tasks
-- [ ] Identify all usages of `pack!` / `pack_structural!` / `pack_err_structural!` across the codebase, examples, and docs
-- [ ] Migrate internal usages to `#[derive(Grouped)]`
-- [ ] Remove the macro definitions and their re-exports
-- [ ] Update the docs / helpdoc examples that reference `pack!`
-- [ ] Update downstream feature docs (`structural_renderer` etc.) where `pack_structural!` was involved
-- [ ] Verify all tests pass
+- [x] Identify all usages of `pack!` / `pack_structural!` / `pack_err_structural!` across the codebase, examples, and docs
+- [x] Migrate internal usages to `#[derive(Grouped)]`
+- [x] Remove the macro definitions and their re-exports
+- [x] Update the docs / helpdoc examples that reference `pack!`
+- [x] Update downstream feature docs (`structural_renderer` etc.) where `pack_structural!` was involved
+- [x] Verify all tests pass
## 🕘 Progress
-- [ ] In Progress
-- [ ] Complete
+- [x] In Progress
+- [x] Complete
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/dev/pages/issues/t0_remove-parser-feature.md b/docs/dev/pages/issues/_remove-parser-feature.md
index 5c1cffd..c2a11c0 100644
--- a/docs/dev/pages/issues/t0_remove-parser-feature.md
+++ b/docs/dev/pages/issues/_remove-parser-feature.md
@@ -19,17 +19,17 @@ Completely remove the `parser` feature in 0.5.0. This will directly affect downs
## Tasks
-- [ ] Identify all usages of the `parser` feature across the codebase, examples, and docs
-- [ ] Migrate internal usages (tests, examples, dev-dependencies) to `picker`
-- [ ] Remove the `parser` feature from `mingling` and its dependency (`size`)
-- [ ] Remove parser-related modules and public API
-- [ ] Update docs and helpdoc examples
-- [ ] Note the downstream migration path in the changelog
+- [x] Identify all usages of the `parser` feature across the codebase, examples, and docs
+- [x] Migrate internal usages (tests, examples, dev-dependencies) to `picker`
+- [x] Remove the `parser` feature from `mingling` and its dependency (`size`)
+- [x] Remove parser-related modules and public API
+- [x] Update docs and helpdoc examples
+- [x] Note the downstream migration path in the changelog
## 🕘 Progress
-- [ ] In Progress
-- [ ] Complete
+- [x] In Progress
+- [x] Complete
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/dev/pages/issues/t0_remove-with-dispatcher.md b/docs/dev/pages/issues/_remove-with-dispatcher.md
index 8779d74..b0d972f 100644
--- a/docs/dev/pages/issues/t0_remove-with-dispatcher.md
+++ b/docs/dev/pages/issues/_remove-with-dispatcher.md
@@ -19,16 +19,16 @@ Make `Dispatcher` registration also compile-time collected in non-`dispatcher_tr
## Tasks
-- [ ] Design how dispatchers are collected at compile time when `dispatcher_tree` is disabled (consistent with how `chain` / `renderer` / `completion` / `metadata` are collected)
-- [ ] Remove `with_dispatcher` and `with_dispatchers` from the `Program` API
-- [ ] Update `gen_program!` and the macros so registration happens automatically
-- [ ] Migrate examples, tests, and docs that call `with_dispatcher` / `with_dispatchers`
-- [ ] Verify both `dispatcher_tree`-enabled and disabled modes behave identically
+- [x] Design how dispatchers are collected at compile time when `dispatcher_tree` is disabled (consistent with how `chain` / `renderer` / `completion` / `metadata` are collected)
+- [x] Remove `with_dispatcher` and `with_dispatchers` from the `Program` API
+- [x] Update `gen_program!` and the macros so registration happens automatically
+- [x] Migrate examples, tests, and docs that call `with_dispatcher` / `with_dispatchers`
+- [x] Verify both `dispatcher_tree`-enabled and disabled modes behave identically
## 🕘 Progress
-- [ ] In Progress
-- [ ] Complete
+- [x] In Progress
+- [x] Complete
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/example-pages/examples.json b/docs/example-pages/examples.json
index 8d6eaa6..38f73cd 100644
--- a/docs/example-pages/examples.json
+++ b/docs/example-pages/examples.json
@@ -273,23 +273,6 @@
]
},
{
- "id": "example-pack-err",
- "name": "Pack an Error",
- "icon": "🛑",
- "category": "macros",
- "desc": "Demonstrates how to use the `pack_err!` macro to define error types with automatic `name` field (snake_case at compile time) and optional `info` field. Also shows `--json` serialization when `structural_renderer` is enabled.\n",
- "tags": [
- "pack_err!",
- "extras",
- "structural_renderer",
- "--json"
- ],
- "files": [
- "src/main.rs",
- "Cargo.toml"
- ]
- },
- {
"id": "example-panic-unwind",
"name": "Panic Unwind",
"icon": "💥",
diff --git a/docs/pages/11-resource-system.md b/docs/pages/11-resource-system.md
index 3a4dc36..ef0d7b8 100644
--- a/docs/pages/11-resource-system.md
+++ b/docs/pages/11-resource-system.md
@@ -31,11 +31,12 @@ In a Chain or Renderer, simply declare the resource in the parameter list:
@@@#[derive(Default, Clone)]
@@@struct ResCurrentDir(String);
@@@dispatcher!("pwd", EntryPrintWorkingDir);
-@@@pack!(ResultPath = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultPath(String);
// Inject read-only resource via &T
#[chain]
fn handle_pwd(_args: EntryPrintWorkingDir, cwd: &ResCurrentDir) -> Next {
- ResultPath::new(cwd.0.clone()).to_render()
+ ResultPath(cwd.0.clone()).to_render()
}
#[renderer(buffer)]
@@ -53,7 +54,8 @@ Use `&mut T` to inject a mutable resource:
@@@#[derive(Default, Clone)]
@@@struct ResVisitCount(u32);
@@@dispatcher!("visit", EntryVisit);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next {
counter.0 += 1;
@@ -74,7 +76,8 @@ A Chain can inject any number of resources at once — the framework matches the
@@@#[derive(Default, Clone)] struct ResConfig(String);
@@@#[derive(Default, Clone)] struct ResCounter(u32);
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
// Inject both read-only and mutable resources
#[chain]
fn handle_test(_args: EntryTest, config: &ResConfig, counter: &mut ResCounter) -> Next {
diff --git a/docs/pages/12-exit-code.md b/docs/pages/12-exit-code.md
index 6828cde..8fa320c 100644
--- a/docs/pages/12-exit-code.md
+++ b/docs/pages/12-exit-code.md
@@ -29,7 +29,8 @@ In a Chain or Renderer, inject `ResExitCode` to modify the exit code:
```rust
@@@use mingling::res::ResExitCode;
@@@use mingling::setup::ExitCodeSetup;
-@@@pack!(EntryCheck = Vec<String>);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct EntryCheck(Vec<String>);
#[chain]
fn handle_check(_args: EntryCheck, ec: &mut ResExitCode) {
// Modify exit code when check fails
diff --git a/docs/pages/13-hook.md b/docs/pages/13-hook.md
index 90df379..6eefd12 100644
--- a/docs/pages/13-hook.md
+++ b/docs/pages/13-hook.md
@@ -55,10 +55,11 @@ Each hook callback receives a corresponding `Hook*Info` struct containing contex
@@@use mingling::hook::ProgramHook;
@@@
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next {
-@@@ ResultName::new(args.inner.first().cloned().unwrap_or_default()).to_render()
+@@@ ResultName(args.0.first().cloned().unwrap_or_default()).to_render()
@@@}
@@@#[renderer] fn render_name(r: ResultName) -> RenderResult { RenderResult::new() }
fn main() {
diff --git a/docs/pages/14-testing.md b/docs/pages/14-testing.md
index 9f6b6ed..f0413a7 100644
--- a/docs/pages/14-testing.md
+++ b/docs/pages/14-testing.md
@@ -12,7 +12,8 @@ A Chain is just a function that takes input and returns output; a Renderer is ju
Renderer is the easiest to test — call the function, assert the result:
```rust
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer]
fn render_greet(result: ResultName) -> RenderResult {
let mut r = RenderResult::new();
@@ -22,7 +23,7 @@ fn render_greet(result: ResultName) -> RenderResult {
#[test]
fn test_render_name() {
- let result = render_name(ResultName::new("Alice".to_string()));
+ let result = render_name(ResultName("Alice".to_string()));
assert_eq!(result.to_string().as_str(), "Hello, Alice!\n");
}
```
@@ -36,27 +37,29 @@ Testing a Chain is slightly more complex because its return value is `Next` (act
```rust
@@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process};
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
+@@@ let name = args.0.first().cloned().unwrap_or_default();
@@@ if name.is_empty() {
@@@ ErrorNoName::default().to_render()
@@@ } else {
-@@@ ResultName::new(name).to_render()
+@@@ ResultName(name).to_render()
@@@ }
@@@}
#[test]
fn test_handle_hello_with_name() {
- let chain_process = handle_hello(EntryGreet::new(vec!["Alice".to_string()])).into();
+ let chain_process = handle_hello(EntryGreet(vec!["Alice".to_string()])).into();
// Asserts this is a render result (not continuing the chain)
assert_render_result!(chain_process);
// Asserts member_id is ResultName
assert_member_id!(chain_process, ResultName);
// Unpacks the inner value
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -78,11 +81,12 @@ If `extras` is enabled, you can use `entry!` to quickly construct an Entry:
@@@use mingling::{assert_member_id, unpack_chain_process};
@@@use mingling::macros::entry;
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
-@@@ ResultName::new(name).to_render()
+@@@ let name = args.0.first().cloned().unwrap_or_default();
+@@@ ResultName(name).to_render()
@@@}
#[test]
fn test_with_entry_macro() {
@@ -90,7 +94,7 @@ fn test_with_entry_macro() {
let entry = entry!("--name", "Alice");
let chain_process = handle_hello(entry).into();
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -103,23 +107,24 @@ If a Chain uses resources, you need to provide resource instances in the test:
@@@#[derive(Default, Clone)]
@@@struct ResPrefix(String);
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultGreeting = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
@@@
#[chain]
fn handle_hello(args: EntryHello, prefix: &ResPrefix) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- ResultGreeting::new(format!("{}, {}", prefix.0, name)).to_render()
+ let name = args.0.first().cloned().unwrap_or_default();
+ ResultGreeting(format!("{}, {}", prefix.0, name)).to_render()
}
#[test]
fn test_handle_with_resource() {
// Resources need to be passed manually in tests
let result = handle_hello(
- EntryHello::new(vec!["World".to_string()]),
+ EntryHello(vec!["World".to_string()]),
&ResPrefix("Hello".to_string()),
);
let greeting = unpack_chain_process!(result, ResultGreeting, ThisProgram);
- assert_eq!(greeting.inner, "Hello, World");
+ assert_eq!(greeting.0, "Hello, World");
}
```
diff --git a/docs/pages/2-define-a-dispatcher.md b/docs/pages/2-define-a-dispatcher.md
index efd744d..5aafb19 100644
--- a/docs/pages/2-define-a-dispatcher.md
+++ b/docs/pages/2-define-a-dispatcher.md
@@ -48,17 +48,15 @@ You might be curious about what's inside `EntryGreet`. It's essentially a struct
```rust
// Illustration of code generated by the dispatcher! macro
-pub struct EntryGreet {
- pub inner: Vec<String>,
-}
+pub struct EntryGreet(pub Vec<String>);
```
-When the user types `greet Alice Bob` on the command line, `EntryGreet.inner` becomes `vec!["Alice", "Bob"]`.
+When the user types `greet Alice Bob` on the command line, `EntryGreet`'s `.0` becomes `vec!["Alice", "Bob"]`.
> [!IMPORTANT]
-> Entry's `inner` only contains **the remaining args after matching**.
+> Entry's `.0` only contains **the remaining args after matching**.
>
-> Take `remote add origin` as an example: `remote` and `add` are used for matching the command path, only `origin` goes into `EntryRemoteAdd.inner`.
+> Take `remote add origin` as an example: `remote` and `add` are used for matching the command path, only `origin` goes into `EntryRemoteAdd.0`.
## Advanced: Implicit Declaration
diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md
index 72ade94..ea39f6b 100644
--- a/docs/pages/3-define-a-chain.md
+++ b/docs/pages/3-define-a-chain.md
@@ -17,14 +17,15 @@ We need a Chain to process it.
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// args contains the remaining params after matching user input
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
// Wrap the result into Next, telling the dispatcher where to go next
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -32,7 +33,7 @@ Notice anything?
The Chain function signature declares what it needs — `args: EntryGreet`.
-Then it returns a newtype via `ResultName::new(name)`.
+Then it returns a newtype via `ResultName(name)`.
This returned `Next` expands into `impl Into<ChainProcess<ThisProgram>>`.
@@ -41,17 +42,30 @@ This returned `Next` expands into `impl Into<ChainProcess<ThisProgram>>`.
>
> Check out the [Any Output Mechanism](pages/concepts/3-any-output) chapter to learn about `ChainProcess`.
-## The `pack!` Macro
+## Declaring Types with `#[derive(Grouped, Wrap)]`
-You've probably guessed it — `pack!(ResultName = String)` defines a type that flows through the pipeline:
+You've probably guessed it — `#[derive(Grouped, Wrap)] pub struct ResultName(String);` defines a type that flows through the pipeline:
```rust
-// pack!(ResultName = String) generates code roughly like this
+// #[derive(Grouped, Wrap)] generates code roughly like this
-#[derive(Grouped)]
-pub struct ResultName {
- pub inner: String,
+pub struct ResultName(String);
+
+impl From<String> for ResultName {
+ fn from(inner: String) -> Self {
+ ResultName(inner)
+ }
+}
+
+impl std::ops::Deref for ResultName {
+ type Target = String;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
}
+
+// Grouped generates member_id() → ThisProgram::ResultName,
+// giving the type its routing identity and Into<ChainProcess> conversion.
```
Think of it as a **tagged** `String`.
@@ -59,7 +73,7 @@ Think of it as a **tagged** `String`.
The dispatcher uses this tag for precise routing, ensuring data doesn't get mixed up — e.g., data sent to `RenderGreet` won't be misdelivered to `RenderError`.
> [!NOTE]
-> Unlike a simple type alias (`type`), `pack!` generates a completely new type with its own `TypeId`.
+> Unlike a simple type alias (`type`), `#[derive(Grouped, Wrap)]` declares a completely new type with its own `TypeId`.
Here's a recommended naming convention:
@@ -70,25 +84,26 @@ Here's a recommended naming convention:
| Result | `Result` + description | `ResultGreetSomeone` |
| Error | `Error` + description | `ErrorUserNotFound` |
-See [Naming Convention](pages/other/naming_rule) for details, but for now just remember: **use `pack!` to give your data a meaningful name**.
+See [Naming Convention](pages/other/naming_rule) for details, but for now just remember: **use `#[derive(Grouped)]` (optionally with `Wrap`) to give your data a meaningful name**.
## Extracting Params from Entry
-`EntryGreet`'s `inner` is a `Vec<String>`, which you can freely process inside a Chain:
+`EntryGreet`'s `.0` is a `Vec<String>`, which you can freely process inside a Chain:
```rust
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// Take the first param, or use a default
let name = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -103,16 +118,17 @@ Now let's connect the Dispatcher and Chain:
dispatcher!("greet", EntryGreet);
// 2. Declare the pipeline data type
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. Processing logic
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
fn main() {
diff --git a/docs/pages/4-render-result.md b/docs/pages/4-render-result.md
index fdf8b12..70ffc96 100644
--- a/docs/pages/4-render-result.md
+++ b/docs/pages/4-render-result.md
@@ -3,7 +3,7 @@
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**.
+Now we've created a Dispatcher and a Chain, and produced a Result type via `#[derive(Grouped, Wrap)]`. The final step: **present the result to the user**.
## The `#[renderer]` Macro
@@ -11,7 +11,8 @@ Similar to `#[chain]`, `#[renderer]` marks a function that produces output:
```rust
@@@use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -27,7 +28,8 @@ If you find explicitly creating and returning a `RenderResult` too verbose, you
```rust
use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -54,17 +56,18 @@ use mingling::macros::buffer;
// 1. Declare commands with a Dispatcher
dispatcher!("greet", EntryGreet);
-// 2. Declare result data with pack!
-pack!(ResultName = String);
+// 2. Declare result data with #[derive(Grouped, Wrap)]
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. Handle logic with a Chain
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
// 4. Output results with a Renderer
@@ -122,10 +125,10 @@ use mingling::macros::buffer;
#[renderer(buffer)]
fn render_entry_fallback(err: EntryFallback) {
- if err.inner.is_empty() {
+ if err.0.is_empty() {
r_println!("Unknown command");
} else {
- r_println!("Command not found: \"{}\"", err.inner.join(" "));
+ r_println!("Command not found: \"{}\"", err.0.join(" "));
}
}
```
@@ -144,13 +147,13 @@ Command not found: "great"
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 |
+| 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 | `#[derive(Grouped, Wrap)]` | 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**.
diff --git a/docs/pages/5-multiple-commands.md b/docs/pages/5-multiple-commands.md
index a5c09b0..9979cd1 100644
--- a/docs/pages/5-multiple-commands.md
+++ b/docs/pages/5-multiple-commands.md
@@ -15,19 +15,21 @@ Work in the same project:
dispatcher!("greet", EntryGreet);
dispatcher!("add", EntryAdd);
-pack!(ResultGreeting = String);
-pack!(ResultSum = i32);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ResultSum(i32);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(name).into()
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
+ ResultGreeting(name).into()
}
#[chain]
fn handle_add(args: EntryAdd) -> Next {
- let sum: i32 = args.inner.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
- ResultSum::new(sum).into()
+ let sum: i32 = args.0.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
+ ResultSum(sum).into()
}
#[renderer(buffer)]
@@ -70,10 +72,10 @@ Each subcommand's Entry, Chain, and Renderer are completely independent and don'
## Type Independence
-Notice we used two different `pack!` macros:
+Notice we used two different `#[derive(Grouped, Wrap)]` structs:
-- `pack!(ResultGreeting = String)`
-- `pack!(ResultSum = i32)`
+- `#[derive(Grouped, Wrap)] pub struct ResultGreeting(String);`
+- `#[derive(Grouped, Wrap)] pub struct ResultSum(i32);`
They are independent types, and `gen_program!()` assigns them different enum variants.
diff --git a/docs/pages/6-argument-parse-picker.md b/docs/pages/6-argument-parse-picker.md
index da0fff1..9fe74eb 100644
--- a/docs/pages/6-argument-parse-picker.md
+++ b/docs/pages/6-argument-parse-picker.md
@@ -3,7 +3,7 @@
Use Picker to perform basic argument parsing
</p>
-In previous tutorials, we manually extracted parameters from `EntryGreet.inner` (`Vec<String>`).
+In previous tutorials, we manually extracted parameters from `EntryGreet.0` (`Vec<String>`).
```rust
@@@ fn main() {
@@ -27,14 +27,15 @@ Now let's see how `Picker` is written:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -45,13 +46,14 @@ For the code above:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
-@@@ResultName::new(name).into()
+@@@ResultName(name).into()
@@@}
```
@@ -60,7 +62,8 @@ Its semantics are:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -81,14 +84,15 @@ If your program needs to parse flag arguments (e.g. `greet --name Alice`), decla
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![name: String, 'n'], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -99,7 +103,8 @@ Its semantics:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -122,7 +127,8 @@ For a single pick, `.unwrap()` returns the value directly; for multiple picks, i
```rust
// Features: ["picker"]
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultInfo = (String, u8, u32));
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultInfo((String, u8, u32));
#[chain]
fn handle_test_entry(prev: EntryTest) -> Next {
@@ -132,7 +138,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
.pick_or_default(&arg![id: u32, 'I'])
.unwrap();
- ResultInfo::new((name, age, id)).into()
+ ResultInfo((name, age, id)).into()
}
```
@@ -150,8 +156,10 @@ Here's a simple example:
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -162,7 +170,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.to_result()
);
- ResultName::new(name).into()
+ ResultName(name).into()
}
#[renderer(buffer)]
@@ -177,12 +185,13 @@ However, **Mingling**'s `extras` feature provides the `route!` macro for simplif
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::macros::route;
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result());
+let name = route!(args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result());
@@@ mingling::macros::empty_result!()
@@@ }
```
@@ -191,11 +200,12 @@ It expands to:
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() {
+let name = match args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result() {
Ok(r) => r,
Err(e) => return e,
};
@@ -210,7 +220,8 @@ After picking user input with `pick`, you can use `post` to process it immediate
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -225,7 +236,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -237,7 +248,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
// Features: ["picker"]
@@@use mingling::picker::value::Flag;
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_entry(prev: EntryTest) -> Next {
@@ -280,12 +292,13 @@ impl SinglePickable for Address {
}
}
@@@dispatcher!("connect", EntryConnect);
-@@@pack!(ResultConnected = Address);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultConnected(Address);
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
let address: Address = prev.pick_or_default(&arg![Address]).unwrap();
- ResultConnected::new(address).into()
+ ResultConnected(address).into()
}
#[renderer(buffer)]
@@ -333,12 +346,13 @@ impl SinglePickable for Fruits {
}
}
@@@dispatcher!("eat", EntryEat);
-@@@pack!(ResultFruit = Fruits);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultFruit(Fruits);
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap();
- ResultFruit::new(fruit).into()
+ ResultFruit(fruit).into()
}
#[renderer(buffer)]
diff --git a/docs/pages/9-error-handling.md b/docs/pages/9-error-handling.md
index eefc0f0..0cacf01 100644
--- a/docs/pages/9-error-handling.md
+++ b/docs/pages/9-error-handling.md
@@ -20,17 +20,19 @@ Error values can also take either path—you can render the error msg directly,
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
```
@@ -40,9 +42,11 @@ Then write separate Renderers:
```rust
@@@use mingling::macros::buffer;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultGreeting = String);
-@@@pack!(ErrorNameEmpty = String);
-@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting::new(args.inner.first().cloned().unwrap_or_default()).to_render() }
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ErrorNameEmpty(String);
+@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting(args.0.first().cloned().unwrap_or_default()).to_render() }
#[renderer(buffer)]
fn render_greet(result: ResultGreeting) {
@@ -63,16 +67,18 @@ Each Renderer does its own job; what the user sees depends on what the Chain ret
@@@use mingling::macros::buffer;
dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
@@ -104,14 +110,14 @@ Hello, Alice!
Error: name is required
```
-## About `pack_err!`
+## Declaring Error Types
-If you've enabled `extras`, you can use `pack_err!` to quickly declare an error type with an auto-generated `name` field:
+You can use `#[derive(Grouped, Default)]` to quickly declare an error type with no payload:
```rust
// Features: ["extras"]
-pack_err!(ErrorNotFound);
-// Generates: struct ErrorNotFound { pub name: String }
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
```
See [Feature List](pages/other/features) for details.
diff --git a/docs/pages/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md
index e444ee6..23c7ec3 100644
--- a/docs/pages/advanced/2-structural-renderer.md
+++ b/docs/pages/advanced/2-structural-renderer.md
@@ -21,7 +21,7 @@ For more formats, enable `structural_renderer_full` (includes JSON, YAML, TOML,
## Basic Usage
-After enabling `StructuralRendererSetup`, use `pack_structural!` instead of `pack!` to declare types that support structured output:
+After enabling `StructuralRendererSetup`, use `#[derive(StructuralData, Grouped, Wrap)]` to declare types that support structured output:
```rust
// Features: ["structural_renderer"]
@@ -29,16 +29,18 @@ After enabling `StructuralRendererSetup`, use `pack_structural!` instead of `pac
// serde = "1"
@@@use mingling::macros::buffer;
@@@use mingling::setup::StructuralRendererSetup;
+@@@use mingling::StructuralData;
@@@dispatcher!("render", EntryRender);
-// pack_structural! is equivalent to pack! + StructuralData
-pack_structural!(ResultInfo = (String, i32));
+// StructuralData + Grouped + Wrap gives the type structured output support
+#[derive(serde::Serialize, StructuralData, Grouped, Wrap)]
+pub struct ResultInfo((String, i32));
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
- ResultInfo::new((name, age)).into()
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ ResultInfo((name, age)).into()
}
#[renderer(buffer)]
@@ -61,7 +63,7 @@ When the user passes `--json`, the framework automatically serializes the render
## Customizing Output Structure
-The default output from `pack_structural!` includes an `inner` field. For full control over the output structure, define the type manually with `#[derive(StructuralData, Serialize, Grouped)]`:
+The default output from a tuple newtype (e.g. `#[derive(StructuralData, Grouped, Wrap)]`) wraps the value under an `inner` key. For full control over the output structure, define the type manually with `#[derive(StructuralData, Serialize, Grouped)]`:
```rust
// Features: ["structural_renderer"]
@@ -82,8 +84,8 @@ struct Info {
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
Info { name, age }.to_render()
}
diff --git a/docs/pages/concepts/2-resource.md b/docs/pages/concepts/2-resource.md
index ad7ee16..6f0ef99 100644
--- a/docs/pages/concepts/2-resource.md
+++ b/docs/pages/concepts/2-resource.md
@@ -34,7 +34,8 @@ For example:
```rust
@@@ use mingling::res::ResExitCode;
-@@@ pack!(ErrorFileNotFound = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFileNotFound(());
#[chain]
fn handle_error_file_not_found(
error: ErrorFileNotFound,
diff --git a/docs/pages/concepts/3-any-output.md b/docs/pages/concepts/3-any-output.md
index f02805f..2b07906 100644
--- a/docs/pages/concepts/3-any-output.md
+++ b/docs/pages/concepts/3-any-output.md
@@ -20,7 +20,7 @@ AnyOutput<G>
Here `G` is the program enum generated by `gen_program!()` (i.e., `ThisProgram` as you know it).
-Each type annotated with `pack!` or `#[derive(Grouped)]` is assigned to one variant of this enum.
+Each type annotated with `#[derive(Grouped)]` (or `#[derive(Grouped, Wrap)]`) is assigned to one variant of this enum.
## ChainProcess: Data + Routing
@@ -48,7 +48,7 @@ trait Grouped<G> {
}
```
-When you use `pack!(ResultName = String)`, the macro automatically implements `Grouped` for `ResultName`, and `member_id()` returns the corresponding enum variant. The dispatcher looks at `member_id` and finds the matching Chain or Renderer.
+When you write `#[derive(Grouped)]` on `ResultName`, the derive automatically implements `Grouped` for `ResultName`, and `member_id()` returns the corresponding enum variant. The dispatcher looks at `member_id` and finds the matching Chain or Renderer.
`to_chain()` and `to_render()` are essentially convenience methods on `AnyOutput` that construct `ChainProcess::Ok(any, Chain)` and `ChainProcess::Ok(any, Renderer)` respectively.
@@ -66,7 +66,7 @@ This mechanism ensures **type safety**: the dispatch code generated by `gen_prog
> [!TIP]
> In day-to-day dev, you don't need to manually touch `AnyOutput` or `ChainProcess`.
>
-> Macros like `pack!`, `#[chain]`, and `#[renderer]` handle all the wrapping and unwrapping for you.
+> Macros and derives like `#[derive(Grouped, Wrap)]`, `#[chain]`, and `#[renderer]` handle all the wrapping and unwrapping for you.
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/pages/concepts/4-program-collect.md b/docs/pages/concepts/4-program-collect.md
index c5203c3..bc1fbc9 100644
--- a/docs/pages/concepts/4-program-collect.md
+++ b/docs/pages/concepts/4-program-collect.md
@@ -9,7 +9,7 @@ Every Mingling program ends with a `gen_program!()` call. Behind the scenes, it
### 1. Generate an enum
-Scans the current module for all types marked with `pack!`, `#[chain]`, `#[renderer]` and similar macros, then generates an enum variant for each type.
+Scans the current module for all types marked with `#[derive(Grouped)]`, `#[chain]`, `#[renderer]` and similar macros, then generates an enum variant for each type.
This enum is the type of `G` in `AnyOutput<G>` — the scheduler uses enum variants to distinguish different data flowing through the pipeline.
diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md
index 3779390..b2c0ea5 100644
--- a/docs/pages/other/features.md
+++ b/docs/pages/other/features.md
@@ -80,11 +80,12 @@ Enables async runtime support, allowing `#[chain]` to bind `async` functions, e.
```rust
// Features: ["async"]
-pack!(StateFoo = ());
+#[derive(Grouped, Wrap)]
+pub struct StateFoo(());
#[chain]
async fn handle_state_foo(foo: StateFoo) -> Next {
- StateFoo::new(()).into()
+ StateFoo(()).into()
}
```
@@ -151,14 +152,13 @@ Enables an additional set of macros, providing more convenient syntactic sugar a
For example, allows the shorthand form `dispatcher!("greet")`, which auto-generates `CMDGreet` / `EntryGreet`.
-| Macro | Description |
-| ------------------------------------------------------- | --------------------------------------------------------------- |
-| `empty_result!()` | Shorthand for returning an empty result early in a chain |
-| `entry!(Type, ["a", "b"])` | Construct test data for an entry type |
-| `group!(Type)` | Register external types as group members without modifying them |
-| `pack_err!(ErrorType)` / `pack_err!(ErrorType = Inner)` | Create error types with an automatic `name` field |
-| `#[program_setup]` | Declare a program initialization function |
-| `dispatcher!("cmd.path")` **shorthand** | Omit `EntryStruct`, the entry name is auto-derived |
+| Macro | Description |
+| --------------------------------------- | --------------------------------------------------------------- |
+| `empty_result!()` | Shorthand for returning an empty result early in a chain |
+| `entry!(Type, ["a", "b"])` | Construct test data for an entry type |
+| `group!(Type)` | Register external types as group members without modifying them |
+| `#[program_setup]` | Declare a program initialization function |
+| `dispatcher!("cmd.path")` **shorthand** | Omit `EntryStruct`, the entry name is auto-derived |
<details>
<summary> Details </summary>
@@ -168,10 +168,13 @@ For example, allows the shorthand form `dispatcher!("greet")`, which auto-genera
```rust
// Features: ["extras"]
-pack!(StatePrev1 = ());
-pack!(StatePrev2 = ());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev1(());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev2(());
-pack!(StateNext = ());
+#[derive(Grouped, Wrap)]
+pub struct StateNext(());
#[chain]
fn handle_state_prev2(_p: StatePrev2) {
@@ -186,7 +189,7 @@ fn handle_state_prev1(_p: StatePrev1) -> Next {
// When Next is needed but no return value is required, use this
empty_result!()
} else {
- StateNext::new(()).into()
+ StateNext(()).into()
}
}
```
@@ -217,7 +220,8 @@ fn no_error_setup(program: &mut Program<ThisProgram>) {
// Features: ["extras"]
use mingling::macros::entry;
-pack!(EntryHello = Vec<String>);
+#[derive(Grouped, Wrap)]
+pub struct EntryHello(Vec<String>);
fn main() {
let result: Next = handle_hello(entry!("--name", "Bob")).into();
@@ -231,7 +235,7 @@ fn handle_hello(args: EntryHello) {}
### `group!`
Registers an external type as a member of the program group without modifying its definition.
-The type's simple name is used as the enum variant, just like `pack!` or `#[derive(Grouped)]`.
+The type's simple name is used as the enum variant, just like `#[derive(Grouped)]`.
```rust
// Features: ["extras"]
@@ -243,26 +247,23 @@ use std::num::ParseIntError;
group!(std::num::ParseIntError);
```
-### `pack_err!`
+### Declaring Error Types
-Creates an error struct with an automatic `name: String` field set to the snake_case
-of the struct name. Optionally wraps an inner type for additional context.
+Error types are declared with derives — the old `pack_err!` macro was removed in 0.5.0.
+Use `#[derive(Grouped, Default)]` for a unit error (no payload), or
+`#[derive(Grouped, Wrap)]` to wrap an inner type for additional context.
```rust
// Features: ["extras"]
use std::path::PathBuf;
-// Simple form — only a name field:
-pack_err!(ErrorNotFound);
-// Generates:
-// struct ErrorNotFound { pub name: String }
-// impl Default for ErrorNotFound { ... }
+// Unit form — no payload:
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
-// Typed form — with additional info field:
-pack_err!(ErrorNotDir = PathBuf);
-// Generates:
-// struct ErrorNotDir { pub name: String, pub info: PathBuf }
-// impl ErrorNotDir { pub fn new(info: PathBuf) -> Self { ... } }
+// Typed form — wraps an inner type:
+#[derive(Grouped, Wrap)]
+pub struct ErrorNotDir(PathBuf);
```
</details>
diff --git a/docs/pages/other/naming_rule.md b/docs/pages/other/naming_rule.md
index 770fd10..fb678aa 100644
--- a/docs/pages/other/naming_rule.md
+++ b/docs/pages/other/naming_rule.md
@@ -94,7 +94,7 @@ Result + Content
| `ResultGreetSomeone` | Greeting result |
| `ResultFruitList` | Fruit list result |
-Result structs are expected to be consumed by the Renderer, and their internal structure should be designed for rendering aesthetics. Generally use `#[derive(Grouped)]` instead of `pack!()` wrapping for more flexible field control.
+Result structs are expected to be consumed by the Renderer, and their internal structure should be designed for rendering aesthetics. Generally prefer a named-field struct with `#[derive(Grouped)]` over a single-field tuple wrapper (`#[derive(Grouped, Wrap)]`) for more flexible field control.
### Error
@@ -146,7 +146,8 @@ Error + Description
| Resource (mutable) | `counter`, `cache`, `session`, etc. |
```rust
-@@@ pack!(EntryRemoteAdd = Vec<String>);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct EntryRemoteAdd(Vec<String>);
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ #[derive(Default, Clone)]
@@ -168,9 +169,12 @@ fn handle_remote_add(args: EntryRemoteAdd, cwd: &ResCurrentDir, db: &mut ResData
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ impl ResDatabase { fn has_remote(&self, remote: &String) -> bool { true } }
-@@@ pack!(StateOperationRemotes = String);
-@@@ pack!(ResultRemoteAdded = String);
-@@@ pack!(ErrorRepositoryNotFound = String);
+@@@ #[derive(Grouped, Wrap, Default)]
+@@@ pub struct StateOperationRemotes(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ResultRemoteAdded(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorRepositoryNotFound(String);
// Dispatcher
dispatcher!("remote.add", EntryRemoteAdd);
@@ -183,10 +187,10 @@ fn handle_remote_add(args: EntryRemoteAdd) -> Next {
// State → Error or Result
#[chain]
fn handle_state_operation_remotes(state: StateOperationRemotes, db: &ResDatabase) -> Next {
- if db.has_remote(&state.inner) {
- ErrorRepositoryNotFound::new(state.inner).to_render()
+ if db.has_remote(&state.0) {
+ ErrorRepositoryNotFound(state.0).to_render()
} else {
- ResultRemoteAdded::new(state.inner).to_render()
+ ResultRemoteAdded(state.0).to_render()
}
}
@@ -194,13 +198,13 @@ fn handle_state_operation_remotes(state: StateOperationRemotes, db: &ResDatabase
#[renderer(buffer)]
fn render_remote_added(result: ResultRemoteAdded) {
- r_println!("Remote added: {}", result.inner);
+ r_println!("Remote added: {}", result.0);
}
// Error rendering
#[renderer(buffer)]
fn render_error_repository_not_found(err: ErrorRepositoryNotFound) {
- r_println!("Error: remote '{}' not found", err.inner);
+ r_println!("Error: remote '{}' not found", err.0);
}
```
diff --git a/docs/res/changlog_examples/feat_program_res.rs b/docs/res/changlog_examples/feat_program_res.rs
index 5133000..e9cc4e2 100644
--- a/docs/res/changlog_examples/feat_program_res.rs
+++ b/docs/res/changlog_examples/feat_program_res.rs
@@ -22,11 +22,12 @@ fn main() {
dispatcher!("modify", EntryModify);
-pack!(DisplayGlobal = ());
+#[derive(Grouped, Wrap)]
+pub struct DisplayGlobal(());
#[chain]
fn modify(prev: EntryModify) {
- let (name, age) = Picker::<()>::new(prev.inner)
+ let (name, age) = Picker::<()>::new(prev.0)
.pick::<String>("--name")
.pick::<i32>("--age")
.unpack_directly();