diff options
Diffstat (limited to 'docs/_zh_CN')
20 files changed, 404 insertions, 485 deletions
diff --git a/docs/_zh_CN/pages/1-getting-started.md b/docs/_zh_CN/pages/1-getting-started.md index 6aa6229..47fdda8 100644 --- a/docs/_zh_CN/pages/1-getting-started.md +++ b/docs/_zh_CN/pages/1-getting-started.md @@ -13,7 +13,7 @@ cd my-cli ```toml [dependencies.mingling] -version = "0.4.0" +version = "0.5.0" features = [] ``` @@ -25,7 +25,7 @@ features = [] ```toml [dependencies.mingling] -version = "0.4.0" +version = "0.5.0" features = [ "parser", "comp", diff --git a/docs/_zh_CN/pages/10-help.md b/docs/_zh_CN/pages/10-help.md index a399ae3..6043abc 100644 --- a/docs/_zh_CN/pages/10-help.md +++ b/docs/_zh_CN/pages/10-help.md @@ -14,7 +14,7 @@ Mingling 里用 `#[help]` 宏给命令添加帮助文本。 ```rust @@@use mingling::macros::help; @@@use mingling::macros::buffer; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); #[help(buffer)] fn help_greet(_entry: EntryGreet) { r_println!("Usage: greet [name]"); @@ -51,11 +51,10 @@ fn help_root(entry: EntryFallback) { ```rust @@@use mingling::macros::help; @@@use mingling::setup::BasicProgramSetup; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); fn main() { let mut program = ThisProgram::new(); program.with_setup(BasicProgramSetup); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } ``` diff --git a/docs/_zh_CN/pages/11-resource-system.md b/docs/_zh_CN/pages/11-resource-system.md index b127a3a..b0fbe31 100644 --- a/docs/_zh_CN/pages/11-resource-system.md +++ b/docs/_zh_CN/pages/11-resource-system.md @@ -30,12 +30,13 @@ fn main() { @@@use mingling::macros::buffer; @@@#[derive(Default, Clone)] @@@struct ResCurrentDir(String); -@@@dispatcher!("pwd", CMDPrintWorkingDir => EntryPrintWorkingDir); -@@@pack!(ResultPath = String); +@@@dispatcher!("pwd", EntryPrintWorkingDir); +@@@#[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)] @@ -52,8 +53,9 @@ fn render_path(result: ResultPath) { @@@use mingling::macros::buffer; @@@#[derive(Default, Clone)] @@@struct ResVisitCount(u32); -@@@dispatcher!("visit", CMDVisit => EntryVisit); -@@@pack!(ResultDone = ()); +@@@dispatcher!("visit", EntryVisit); +@@@#[derive(Grouped, Wrap, Default)] +@@@pub struct ResultDone(()); #[chain] fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next { counter.0 += 1; @@ -73,8 +75,9 @@ Chain 可以同时注入任意多个资源,框架按类型自动匹配: ```rust @@@#[derive(Default, Clone)] struct ResConfig(String); @@@#[derive(Default, Clone)] struct ResCounter(u32); -@@@dispatcher!("test", CMDTest => EntryTest); -@@@pack!(ResultDone = ()); +@@@dispatcher!("test", EntryTest); +@@@#[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..1f9e05c 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 @@ -14,7 +12,7 @@ @@@use mingling::setup::ExitCodeSetup; fn main() { let mut program = ThisProgram::new(); - program.with_setup(ExitCodeSetup::default()); + program.with_setup(ExitCodeSetup); @@@ program.exec_and_exit(); } ``` @@ -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) { // 检查失败的时候修改退出码资源 @@ -56,7 +55,7 @@ fn handle_check(_args: EntryCheck, ec: &mut ResExitCode) { @@@use mingling::setup::ExitCodeSetup; fn main() { let mut program = ThisProgram::new(); - program.with_setup(ExitCodeSetup::default()); + program.with_setup(ExitCodeSetup); // 获取退出码自行处理 let exit_code = program.exec(); diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md index ad3008a..860bd2b 100644 --- a/docs/_zh_CN/pages/13-hook.md +++ b/docs/_zh_CN/pages/13-hook.md @@ -54,11 +54,12 @@ Hook 覆盖了管线的完整生命周期: @@@use mingling::prelude::*; @@@use mingling::hook::ProgramHook; @@@ -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +@@@dispatcher!("greet", EntryGreet); +@@@#[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() { @@ -75,7 +76,6 @@ fn main() { }), ); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } ``` diff --git a/docs/_zh_CN/pages/14-testing.md b/docs/_zh_CN/pages/14-testing.md index 3567aef..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"); } ``` @@ -35,28 +36,30 @@ fn test_render_name() { ```rust @@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process}; -@@@dispatcher!("hello", CMDHello => EntryHello); -@@@pack!(ResultName = String); -@@@pack!(ErrorNoName = ()); +@@@dispatcher!("hello", EntryHello); +@@@#[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"); } ``` @@ -77,12 +80,13 @@ fn test_handle_hello_with_name() { @@@use mingling::{assert_member_id, unpack_chain_process}; @@@use mingling::macros::entry; -@@@dispatcher!("hello", CMDHello => EntryHello); -@@@pack!(ResultName = String); +@@@dispatcher!("hello", EntryHello); +@@@#[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"); } ``` @@ -102,24 +106,25 @@ fn test_with_entry_macro() { @@@use mingling::{assert_render_result, unpack_chain_process}; @@@#[derive(Default, Clone)] @@@struct ResPrefix(String); -@@@dispatcher!("hello", CMDHello => EntryHello); -@@@pack!(ResultGreeting = String); +@@@dispatcher!("hello", EntryHello); +@@@#[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 b238d9f..77a1513 100644 --- a/docs/_zh_CN/pages/2-define-a-dispatcher.md +++ b/docs/_zh_CN/pages/2-define-a-dispatcher.md @@ -19,42 +19,25 @@ Mingling 的管线从 Dispatcher 开始。 写法是固定的三个部分: ```rust -dispatcher!("命令路径", 分发器类型 => 入口类型); +dispatcher!("命令路径", 入口类型); ``` 看一个具体的例子: ```rust -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); ``` > [!NOTE] > 命令名(`"greet"`)会自动转换为 kebab-case。即使你写 `"GreetUser"`,匹配时也会变成 `greet-user`。 -## 注册到 Program - -有了分发器之后,需要告诉 Program 它的存在: - -```rust -@@@ dispatcher!("greet", CMDGreet => EntryGreet); -@@@ fn main() { -@@@ let mut program = ThisProgram::new(); -// 注册分发器 -program.with_dispatcher(CMDGreet); -@@@ } -@@@ gen_program!(); -``` - -> [!TIP] -> 如果命令多了,可以用 `with_dispatchers` 一次注册多个:`program.with_dispatchers((CMDGreet, CMDAdd, CMDRemoteRm))`。 - ## 多级命令 如果你的程序有层级结构——比如 `remote add`、`remote rm`——只需要在命令名里加点号分隔: ```rust -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); -dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm); +dispatcher!("remote.add", EntryRemoteAdd); +dispatcher!("remote.rm", EntryRemoteRm); ``` 用户在终端输入 `remote add` 时,Mingling 会依次匹配 `remote` 和 `add` 两个层级。 @@ -65,17 +48,15 @@ dispatcher!("remote.rm", CMDRemoteRm => 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`)。 ## 进阶:隐式声明 @@ -85,7 +66,7 @@ pub struct EntryGreet { // Features: ["extras"] // 省略 CMDType 和 EntryType,名字自动推导 dispatcher!("greet"); -// dispatcher!("greet", CMDGreet => EntryGreet); +// dispatcher!("greet", EntryGreet); ``` 这种写法会自动生成 `CMDGreet` 和 `EntryGreet`,效果跟显式声明完全一样。 diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md index f845d11..3356b74 100644 --- a/docs/_zh_CN/pages/3-define-a-chain.md +++ b/docs/_zh_CN/pages/3-define-a-chain.md @@ -3,7 +3,7 @@ 使用 chain 宏声明链,并承接 Entry 输入 </p> -上一节我们声明了 `dispatcher!("greet", CMDGreet => EntryGreet)` +上一节我们声明了 `dispatcher!("greet", EntryGreet)` 现在用户输入 `greet` 时会被匹配并包装成 `EntryGreet`。 @@ -16,15 +16,16 @@ `#[chain]` 用来标记一个处理函数,格式非常直接: ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); -pack!(ResultName = String); +@@@dispatcher!("greet", EntryGreet); +#[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,29 +84,30 @@ 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", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +@@@dispatcher!("greet", EntryGreet); +@@@#[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() } ``` -如果你启用了 `parser` 特性,还可以用 `Picker` 做更灵活的参数提取,不过那是后话了。 +如果你启用了 `picker` 特性,还可以用 `Picker` 做更灵活的参数提取,不过那是后话了。 ## 组合起来 @@ -100,24 +115,24 @@ fn handle_greet(args: EntryGreet) -> Next { ```rust // 1. 声明命令 -dispatcher!("greet", CMDGreet => EntryGreet); +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() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } diff --git a/docs/_zh_CN/pages/4-render-result.md b/docs/_zh_CN/pages/4-render-result.md index 7f66c71..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); @@ -52,19 +54,20 @@ fn render_name(name: ResultName) { use mingling::macros::buffer; // 1. 用 Dispatcher 声明命令 -dispatcher!("greet", CMDGreet => EntryGreet); +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 输出结果 @@ -76,7 +79,6 @@ fn render_name(name: ResultName) { // 5. 在 main 函数内装配程序并运行 fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } @@ -123,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(" ")); } } ``` @@ -145,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 681fe0a..9d9d37e 100644 --- a/docs/_zh_CN/pages/5-multiple-commands.md +++ b/docs/_zh_CN/pages/5-multiple-commands.md @@ -12,22 +12,24 @@ ```rust @@@use mingling::macros::buffer; // 声明两个命令 -dispatcher!("greet", CMDGreet => EntryGreet); -dispatcher!("add", CMDAdd => EntryAdd); +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)] @@ -42,8 +44,6 @@ fn render_sum(result: ResultSum) { fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); - program.with_dispatcher(CMDAdd); program.exec_and_exit(); } @@ -64,18 +64,18 @@ Sum: 6 多层级的命令也是同理——每个点号分隔的层级都只是名字的一部分: ```rust -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); -dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm); +dispatcher!("remote.add", EntryRemoteAdd); +dispatcher!("remote.rm", EntryRemoteRm); ``` 每个子命令的 Entry、Chain、Renderer 完全独立,互不干扰。 ## 数据类型的独立性 -注意我们用了两个不同的 `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 462a912..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() { @@ -19,112 +19,126 @@ let name = args.first().cloned().unwrap_or_else(|| "World".to_string()); ```toml # Cargo.toml [dependencies.mingling] -features = ["parser"] +features = ["picker"] ``` 好了,让我们看看 `Picker` 的写法: ```rust -// Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +// Features: ["picker"] +@@@dispatcher!("greet", EntryGreet); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultName(String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let name = prev.pick_or((), "World").unpack(); - ResultName::new(name).into() + let name = prev + .pick_or(&arg![String], || "World".to_string()) + .unwrap(); + ResultName(name).into() } ``` -`AsPicker` 为所有可以转换为 `Vec<String>` 的类型实现了 `pick`、`pick_or`、`pick_or_route` 函数:它们可以语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。 +`EntryPicker` 为所有入口类型实现了 `pick`、`pick_or`、`pick_or_default` 和 `pick_or_route` 函数:它们可以通过 `arg!` 宏声明要拾取的内容,语义化地从字符串列表中 **拾取 (Pick)** 参数,并转换为结构化数据。 对于上述示例中的代码: ```rust -// Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +// Features: ["picker"] +@@@dispatcher!("greet", EntryGreet); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultName(String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) -> Next { -let name = prev.pick_or((), "World").unpack(); -@@@ResultName::new(name).into() +let name = prev + .pick_or(&arg![String], || "World".to_string()) + .unwrap(); +@@@ResultName(name).into() @@@} ``` 它的语义为: ```rust -// Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +// Features: ["picker"] +@@@dispatcher!("greet", EntryGreet); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultName(String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = - prev.pick_or((), "World").unpack(); -// ~~~~ ~~~~~~~ ~~ ~~~~~~~ ~~~~~~~~ -// | | | | |_ 解包为 String -// | | | |__________ 默认值为 "World" -// | | |______________ 取出第一个位置参数(不指定标志) -// | |______________________ 拾取或使用默认 -// |___________________________ 从前一个输入中 + prev.pick_or(&arg![String], || "World".to_string()).unwrap(); +// ~~~~ ~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ +// | | | | |_ 解包为 String +// | | | |__________________________ 默认值为 "World" +// | | |________________________________________ 取出第一个位置参数(声明为 String) +// | |________________________________________________ 拾取或使用默认 +// |_____________________________________________________ 从前一个输入中 @@@} ``` ## 解析标志参数 -若你的程序需要解析标志参数(例如 `greet --name Alice`),可以使用如下方式 +若你的程序需要解析标志参数(例如 `greet --name Alice`),可以在 `arg!` 中声明一个具名标志: ```rust -// Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +// Features: ["picker"] +@@@dispatcher!("greet", EntryGreet); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultName(String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let name = prev.pick_or(["--name", "-n"], "World").unpack(); - ResultName::new(name).into() + let name = prev + .pick_or(&arg![name: String, 'n'], || "World".to_string()) + .unwrap(); + ResultName(name).into() } ``` +`arg!` 宏会从字段名推导长标志名(`--name`),`'n'` 则添加短别名(`-n`)。 + 同理,它的语义为: ```rust -// Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +// Features: ["picker"] +@@@dispatcher!("greet", EntryGreet); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultName(String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@@let name: String = - prev.pick_or(["--name", "-n"], "World").unpack(); -// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~ -// | | | | |_ 解包为 String -// | | | |__________ 默认值为 "World" -// | | |____________________________ 取出 "--name" 或 "-n" 后面的参数 -// | |____________________________________ 拾取或使用默认 -// |_________________________________________ 从前一个输入中 + prev.pick_or(&arg![name: String, 'n'], || "World".to_string()).unwrap(); +// ~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ +// | | | | |_ 解包为 String +// | | | |________________________ 默认值为 "World" +// | | |___________________________________________________ 取出 "--name" 或 "-n" 后面的参数 +// | |___________________________________________________________ 拾取或使用默认 +// |________________________________________________________________ 从前一个输入中 @@@} ``` -## 关于 `.unpack()` +## 关于 `.unwrap()` 与 `route!` -你可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unpack()` 函数,它的作用是将前面解析出来的结果,转换为结构化信息。 +你可能注意到了,`Picker` 在命令解析的最后,会执行一个 `.unwrap()`(或 `route!`)函数,它的作用是将前面解析出来的结果,转换为结构化信息。 -对于只拾取了一次的数据来说,`.unpack()` 会返回单个数据,而对于多次拾取,`Picker` 则会返回元组: +对于只拾取了一次的数据来说,`.unwrap()` 会返回单个数据,而对于多次拾取,`Picker` 则会返回元组: ```rust -// Features: ["parser"] -@@@dispatcher!("test", CMDTest => EntryTest); -@@@pack!(ResultInfo = (String, u8, u32)); +// Features: ["picker"] +@@@dispatcher!("test", EntryTest); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultInfo((String, u8, u32)); #[chain] fn handle_test_entry(prev: EntryTest) -> Next { let (name, age, id) = prev - .pick::<String>(["--name", "-n"]) - .pick::<u8>(["--age", "-a"]) - .pick::<u32>(["--id", "-I"]) - .unpack(); + .pick_or_default(&arg![name: String, 'n']) + .pick_or_default(&arg![age: u8, 'a']) + .pick_or_default(&arg![id: u32, 'I']) + .unwrap(); - ResultInfo::new((name, age, id)).into() + ResultInfo((name, age, id)).into() } ``` @@ -138,22 +152,25 @@ fn handle_test_entry(prev: EntryTest) -> Next { 先来看一个简单示例 ```rust -// Features: ["parser", "extras"] +// Features: ["picker", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); -@@@pack!(ErrorNoName = ()); +@@@dispatcher!("greet", EntryGreet); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultName(String); +@@@#[derive(Grouped, Wrap, Default)] +@@@pub struct ErrorNoName(()); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { - let pick_result = prev - .pick_or_route(["--name", "-n"], ErrorNoName::default()) - .unpack(); - - // 使用 route! 宏展开 pick_result - let name = route!(pick_result); - ResultName::new(name).into() + // 使用 route! 宏展开 Result<Value, Route> + let name = route!( + prev.pick_or_route(&arg![name: String, 'n'], || { + ErrorNoName::default().to_chain() + }) + .to_result() + ); + ResultName(name).into() } #[renderer(buffer)] @@ -162,18 +179,19 @@ fn render_greet(result: ResultName) { } ``` -若使用 `pick_or_route`,写法会变得相对复杂:因为 `.unpack()` 不再直接返回参数,而是 `Result<Value, Route>`。 +若使用 `pick_or_route`,`.to_result()` 不再直接返回参数,而是 `Result<Value, Route>`。 不过 **Mingling** 的 `extras` 特性提供了简化展开的宏 `route!`,它不复杂,只是省略了一部分样板代码: ```rust -// Features: ["parser", "extras"] -@@@ pack!(ErrorFail = ()); +// Features: ["picker", "extras"] +@@@ #[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 pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack(); -let name = route!(pick_result); +let name = route!(args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result()); @@@ mingling::macros::empty_result!() @@@ } ``` @@ -181,14 +199,15 @@ let name = route!(pick_result); 它展开为: ```rust -// Features: ["parser", "extras"] -@@@ pack!(ErrorFail = ()); +// Features: ["picker", "extras"] +@@@ #[derive(Grouped, Wrap)] +@@@ pub struct ErrorFail(()); +@@@ use mingling::picker::IntoPicker; @@@ fn func() -> mingling::ChainProcess<ThisProgram> { @@@ let args: Vec<String> = vec![]; -@@@ let pick_result = args.pick_or_route::<String, _>((), ErrorFail::new(())).unpack(); -let name = match pick_result { +let name = match args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result() { Ok(r) => r, - Err(e) => return e.to_chain(), + Err(e) => return e, }; @@@ mingling::macros::empty_result!() @@@ } @@ -196,122 +215,61 @@ let name = match pick_result { ## 提取值的后处理 -在您使用 `pick` 提取了用户输入后,可以使用 `after` 立刻处理该参数 +在您使用 `pick` 提取了用户输入后,可以使用 `post` 立刻处理该参数 -````rust -// Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); +```rust +// Features: ["picker"] +@@@dispatcher!("greet", EntryGreet); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultName(String); #[chain] fn handle_greet_entry(prev: EntryGreet) -> Next { let name = prev - .pick_or(["--name", "-n"], "World") + .pick_or(&arg![name: String, 'n'], || "World".to_string()) // 在提取出 --name 后,立刻格式化 - .after(|name: String| { + .post(|name: String| { name.replace(['-', '_', '.'], " ") .to_lowercase() .trim() .to_string() }) - .unpack(); + .unwrap(); - ResultName::new(name).into() -} -``` - -同样,你可以使用 `after_or_route` 来处理输入参数的格式错误 - -```rust -// Features: ["parser", "extras"] -@@@use mingling::macros::buffer; -@@@use mingling::macros::route; -@@@dispatcher!("greet", CMDGreet => EntryGreet); -@@@pack!(ResultName = String); -@@@pack!(ErrorNameTooLong = usize); - -#[chain] -fn handle_greet_entry(prev: EntryGreet) -> Next { - let pick_result = prev - .pick_or(["--name", "-n"], "World") - .after_or_route(|name: &String| { - if name.len() < 32 { - Ok(name.clone()) - } else { - Err(ErrorNameTooLong::new(name.len())) - } - }) - .unpack(); - let name = route!(pick_result); - - ResultName::new(name).into() -} - -#[renderer(buffer)] -fn render_name_too_long(prev: ErrorNameTooLong) { - let len = *prev; - r_println!("Error: name too long (length: {} > 32)", len); -} - -#[renderer(buffer)] -fn render_name(prev: ResultName) { - r_println!("Hello, {}!", *prev); + ResultName(name).into() } ``` ## 布尔值解析 -`Picker` 当然也可以解析布尔类型,但是布尔类型分为显式和隐式模式: - -| 模式 | 格式 | -| ---- | ----------------------------------- | -| 隐式 | `--confirmed` | -| 显式 | `--confirm true` 或 `--confirm yes` | - -- 使用 `.pick::<bool>(flag)` 时,采用隐式解析:只要标志存在即为 `true` -- 使用 `.pick::<Yes>(flag)` 或 `.pick::<True>(flag)` 时,采用显式解析 - -一般来说使用隐式解析即可,但在处理重要的确认行为时,显式逻辑更符合语义。 +`Picker` 将布尔值解析为**标志**:标志存在即为 `true`。 ```rust -// Features: ["parser"] -@@@use mingling::parser::Yes; -@@@dispatcher!("test", CMDTest => EntryTest); -@@@pack!(ResultDone = ()); +// Features: ["picker"] +@@@use mingling::picker::value::Flag; +@@@dispatcher!("test", EntryTest); +@@@#[derive(Grouped, Wrap, Default)] +@@@pub struct ResultDone(()); #[chain] fn handle_entry(prev: EntryTest) -> Next { -@@@ let prev1 = prev.clone(); - let _confirmed: bool = prev.pick::<Yes>(()).unpack().is_yes(); -@@@ let prev = prev1; - let _confirm: bool = prev.pick::<bool>(["--confirm", "-C"]).unpack(); + // `--confirm` / `-C` 存在 → true + let _confirm: bool = *prev.pick(&arg![confirm: Flag, 'C']).unwrap(); ResultDone::default().to_render() } ``` -## 特殊用法:`usize` 解析 +> [!NOTE] +> 对于重要的确认行为,如果精确的布尔语义很关键,请将标志与显式的值检查配合使用。 -**Mingling** 为 `usize` 提供了一个特殊的用法:解析类似 `25G`、`32mib` 等字样 - -```rust -// Features: ["parser"] - -#[test] -fn parse_size() { - let vec = vec!["--size".to_string(), "25mib".to_string()]; - let size: usize = vec.pick(["--size", "-S"]).unpack(); - assert_eq!(size, 25 * 1024 * 1024); -} -``` - ## 自定义可解析类型 -你可以使用 `Pickable` trait 使你的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源 +你可以使用 `SinglePickable` trait 使你的类型支持被 `Picker` 解析,这也是 `Picker` 拓展性的来源 ```rust -// Features: ["parser"] +// Features: ["picker"] @@@use mingling::macros::buffer; -@@@use mingling::parser::{Pickable, Argument}; +@@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::Flag; #[derive(Default, Clone)] pub struct Address { @@ -319,23 +277,28 @@ pub struct Address { port: u16, } -impl Pickable for Address { - type Output = Self; - fn pick(args: &mut Argument, flag: Flag) -> Option<Self::Output> { - let raw = args.pick_argument(flag)?; +impl SinglePickable for Address { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + let Some(raw) = str else { + return PickerArgResult::NotFound; + }; let parts: Vec<&str> = raw.split(':').collect(); - let ip = parts.first()?.to_string(); - let port: u16 = parts.get(1)?.parse().ok()?; - Some(Address { ip, port }) + let ip = parts.first().copied().unwrap_or_default().to_string(); + let port: u16 = match parts.get(1).and_then(|p| p.parse().ok()) { + Some(p) => p, + None => return PickerArgResult::NotFound, + }; + PickerArgResult::Parsed(Address { ip, port }) } } -@@@dispatcher!("connect", CMDConnect => EntryConnect); -@@@pack!(ResultConnected = Address); +@@@dispatcher!("connect", EntryConnect); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultConnected(Address); #[chain] fn handle_connect_entry(prev: EntryConnect) -> Next { - let address: Address = prev.pick("--addr").unpack(); - ResultConnected::new(address).into() + let address: Address = prev.pick_or_default(&arg![Address]).unwrap(); + ResultConnected(address).into() } #[renderer(buffer)] @@ -351,14 +314,14 @@ fn render_connected(addr: ResultConnected) { Connected: IP: 127.0.0.1 PORT: 8080 ``` -## 自动为枚举实现 Pickable +## 为枚举实现 Pickable -要为枚举类型实现 `Pickable`,只需该枚举实现了 `EnumTag`,然后为其实现 `PickableEnum` 即可 +要让枚举支持 `Picker` 解析,可以手写 `SinglePickable`,用 match 匹配输入: ```rust -// Features: ["parser"] +// Features: ["picker"] @@@use mingling::macros::buffer; -@@@use mingling::parser::PickableEnum; +@@@use mingling::picker::{PickerArgResult, SinglePickable}; @@@use mingling::EnumTag; #[derive(Debug, Default, EnumTag)] pub enum Fruits { @@ -368,14 +331,28 @@ pub enum Fruits { Orange, } -impl PickableEnum for Fruits {} -@@@dispatcher!("eat", CMDEat => EntryEat); -@@@pack!(ResultFruit = Fruits); +impl SinglePickable for Fruits { + fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { + let Some(str) = str else { + return PickerArgResult::NotFound; + }; + let fruit = match str.to_lowercase().as_str() { + "apple" => Self::Apple, + "banana" => Self::Banana, + "orange" => Self::Orange, + _ => return PickerArgResult::NotFound, + }; + PickerArgResult::Parsed(fruit) + } +} +@@@dispatcher!("eat", EntryEat); +@@@#[derive(Grouped, Wrap)] +@@@pub struct ResultFruit(Fruits); #[chain] fn handle_eat_entry(prev: EntryEat) -> Next { - let fruit: Fruits = prev.pick("--fruit").unpack(); - ResultFruit::new(fruit).into() + let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap(); + ResultFruit(fruit).into() } #[renderer(buffer)] @@ -389,4 +366,3 @@ fn render_fruit(prev: ResultFruit) { <p align="center" style="font-size: 0.85em; color: gray;"> Written by @Weicao-CatilGrass </p> -```` diff --git a/docs/_zh_CN/pages/7-argument-parse-clap.md b/docs/_zh_CN/pages/7-argument-parse-clap.md index 22896d2..e9475ae 100644 --- a/docs/_zh_CN/pages/7-argument-parse-clap.md +++ b/docs/_zh_CN/pages/7-argument-parse-clap.md @@ -27,7 +27,7 @@ features = ["derive", "color"] @@@ use mingling::macros::dispatcher_clap; @@@ use mingling::macros::buffer; #[derive(Default, clap::Parser, Grouped)] -#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreetParsed)] +#[dispatcher_clap("greet", help = true, error = ErrorGreetParsed)] pub struct EntryGreet { #[clap(default_value = "World")] name: String, @@ -63,7 +63,7 @@ fn render_greet_parse_failed(err: ErrorGreetParsed) { @@@use mingling::setup::BasicProgramSetup; @@@use mingling::macros::dispatcher_clap; @@@#[derive(Default, clap::Parser, Grouped)] -@@@#[dispatcher_clap("greet", CMDGreet)] +@@@#[dispatcher_clap("greet", )] @@@pub struct EntryGreet { @@@ name: String, @@@} @@ -76,7 +76,6 @@ fn main() { program.with_setup(BasicProgramSetup); program.stdout_setting.clap_help_print_behaviour = mingling::config::ClapHelpPrintBehaviour::WriteToRenderResult; - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } ``` diff --git a/docs/_zh_CN/pages/9-error-handling.md b/docs/_zh_CN/pages/9-error-handling.md index f5055b8..463b829 100644 --- a/docs/_zh_CN/pages/9-error-handling.md +++ b/docs/_zh_CN/pages/9-error-handling.md @@ -19,18 +19,20 @@ ## 用独立类型区分错误 ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); -pack!(ResultGreeting = String); -pack!(ErrorNameEmpty = String); +@@@dispatcher!("greet", EntryGreet); +#[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() } } ``` @@ -39,10 +41,12 @@ fn handle_greet(args: EntryGreet) -> Next { ```rust @@@use mingling::macros::buffer; -@@@dispatcher!("greet", CMDGreet => 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() } +@@@dispatcher!("greet", EntryGreet); +@@@#[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) { @@ -61,18 +65,20 @@ fn render_error_name_empty(err: ErrorNameEmpty) { ```rust @@@use mingling::macros::buffer; -dispatcher!("greet", CMDGreet => EntryGreet); +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() } } @@ -88,7 +94,6 @@ fn render_error_name_empty(err: ErrorNameEmpty) { fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } @@ -105,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/1-completion.md b/docs/_zh_CN/pages/advanced/1-completion.md index a6500db..290fab0 100644 --- a/docs/_zh_CN/pages/advanced/1-completion.md +++ b/docs/_zh_CN/pages/advanced/1-completion.md @@ -11,21 +11,12 @@ Mingling 的补全是**完全动态**的——没有静态的补全文件,而 # Cargo.toml [dependencies.mingling] features = ["comp"] - -[build-dependencies.mingling] -features = [ - "comp", - # 启用 `build` 特性以提供构建期支持 - "build" -] ``` ## 工作原理 当用户按下 `TAB` 时,补全脚本会调用程序的隐藏子命令 `__comp`,它会根据输入的 `ShellContext` 动态地查询最合适的建议。 -这个隐藏子命令由 `gen_program!()` 在启用 `comp` 特性时自动生成,对应的分发器是 `CMDCompletion`,你需要使用 `with_dispatcher` 添加到程序中。 - 补全流程: 1. 二次匹配用户当前输入的 `Dispatcher` @@ -42,10 +33,10 @@ features = [ @@@use mingling::prelude::*; @@@use mingling::{ShellContext, Suggest, SuggestItem}; @@@use std::collections::BTreeSet; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); #[completion(EntryGreet)] -fn complete_greet(ctx: &ShellContext) -> Suggest { +fn complete_greet(ctx: ShellContext) -> Suggest { if ctx.previous_word == "greet" { let mut items = BTreeSet::new(); items.insert(SuggestItem::new_with_desc("Alice".into(), "Likes to receive messages".into())); @@ -74,8 +65,18 @@ suggest! { ## 生成补全脚本 -在 `build.rs` 中调用 `build_comp_scripts` 生成补全脚本(需要 `builds` + `comp` 特性)。 +开启 `comp` 特性后,`gen_program!()` 会在编译期自动调用 `build_comp!()`,生成以 `CARGO_PKG_NAME` 命名的补全脚本到 `target/mingling/`。 + +如果你的二进制名与 crate 名不同,可以手动调用 `build_comp!()` 并指定二进制名: +```rust +// Features: ["comp"] +@@@use mingling::macros::build_comp; +@@@fn example() { +build_comp!("mybin"); +@@@} +``` + 详见 [example-completion](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-completion)。 <p align="center" style="font-size: 0.85em; color: gray;"> diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md index a498dac..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; -@@@dispatcher!("render", CMDRender => EntryRender); +@@@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"] @@ -72,7 +74,7 @@ fn render_info(r: ResultInfo) { @@@use mingling::setup::StructuralRendererSetup; @@@use mingling::StructuralData; @@@use serde::Serialize; -@@@dispatcher!("render", CMDRender => EntryRender); +@@@dispatcher!("render", EntryRender); #[derive(Serialize, StructuralData, Grouped)] struct Info { @@ -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() } @@ -95,7 +97,6 @@ fn render_info(info: Info) { @@@fn main() { @@@ let mut program = ThisProgram::new(); @@@ program.with_setup(StructuralRendererSetup); -@@@ program.with_dispatcher(CMDRender); @@@ program.exec(); @@@} @@@gen_program!(); 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 7ed3f5f..f8753e5 100644 --- a/docs/_zh_CN/pages/other/features.md +++ b/docs/_zh_CN/pages/other/features.md @@ -25,42 +25,6 @@ Mingling 提供了一系列**预设特性组**,方便用户按需组合启用 **定位:** 完整模式,启用 Mingling 的全部核心功能。在 `advanced` 的基础上额外包含 clap 集成、完整的结构化渲染器(含所有序列化格式)以及实验性的路径分析器,适合大型、功能全面的命令行应用。 -## `build_advanced` - -**启用特性:** `build`、`comp` - -**定位:** 构建期增强配置,用于在项目构建时生成补全脚本等构建辅助材料(`comp` 特性提供补全脚本生成能力)。 - -> [!NOTE] -> -> 此特性组为**构建依赖**专用,需配合 `advanced` 特性使用。请在 `Cargo.toml` 的 `[build-dependencies]` 中启用: - -```toml -[dependencies.mingling] -features = ["advanced"] - -[build-dependencies.mingling] -features = ["build_advanced"] -``` - -## `build_full` - -**启用特性:** `build`、`comp`、`pathf`、`dispatch_tree` - -**定位:** 完整的构建期配置,在 `build_advanced` 的基础上额外包含路径分析器(`pathf`)以自动解析类型模块路径,适合结构复杂、需要自动化构建期分析的项目。 - -> [!NOTE] -> -> 此特性组为**构建依赖**专用,需配合 `full` 特性使用。请在 `Cargo.toml` 的 `[build-dependencies]` 中启用: - -```toml -[dependencies.mingling] -features = ["full"] - -[build-dependencies.mingling] -features = ["build_full"] -``` - # 特性详解 ## 特性 `all_serde_fmt` @@ -80,33 +44,17 @@ 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() } ``` 详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-async-support) -## 特性 `builds` - -**介绍:** - -启用部分需要在 `build.rs` 使用的脚本,目前包含: - -1. `comp` 特性下的补全脚本生成: - -```rust -// BUILD TIME -// Features: ["builds", "comp"] -use mingling::build::build_comp_scripts; - -// 为 `myprogram` 生成补全脚本 -build_comp_scripts("myprogram").unwrap(); -``` - ## 特性 `clap` **介绍:** @@ -151,14 +99,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")` **缩写形式** | 省略 `CMDStruct => EntryStruct`,名字自动推导 | +| 宏 | 说明 | +| -------------------------------------- | -------------------------------------- | +| `empty_result!()` | 链中提前返回空结果的简写 | +| `entry!(Type, ["a", "b"])` | 构造入口类型的测试数据 | +| `group!(Type)` | 将外部类型注册为组成员,无需修改其定义 | +| `#[program_setup]` | 声明程序初始化函数 | +| `dispatcher!("cmd.path")` **缩写形式** | 省略 `EntryStruct`,入口类型名自动推导 | <details> <summary> Details </summary> @@ -168,10 +115,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 +136,7 @@ fn handle_state_prev1(_p: StatePrev1) -> Next { // 当需要 Next 且不需要返回值,便可以使用它 empty_result!() } else { - StateNext::new(()).into() + StateNext(()).into() } } ``` @@ -217,7 +167,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 +182,7 @@ fn handle_hello(args: EntryHello) {} ### `group!` 将外部类型注册为程序组成员,无需修改原始类型的定义。 -类型名会直接作为枚举变体,与 `pack!` 或 `#[derive(Grouped)]` 一致。 +类型名会直接作为枚举变体,与 `#[derive(Grouped)]` 一致。 ```rust // Features: ["extras"] @@ -243,26 +194,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> @@ -317,28 +265,11 @@ pack_err!(ErrorNotDir = PathBuf); # Cargo.toml [dependencies.mingling] features = ["pathf"] - -[build-dependencies.mingling] -features = ["builds", "pathf"] -``` - -```rust -// BUILD TIME -// Features: ["pathf"] -analyze_and_build_type_mapping().unwrap(); ``` -详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-pathfinder) - -## 特性 `parser` - -**介绍:** - -启用参数解析器模块,提供参数解析功能。 - -开启后可以使用 `Picker` 进行简易的参数提取,支持 `pick()` 和 `pick_or()` 等方法。 +开启 `pathf` 特性后,`gen_program!()` 会在编译期自动调用 `build_pathf!()` 执行类型映射分析。 -详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse) +详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-pathfinder) ## 特性 `picker` @@ -346,8 +277,6 @@ analyze_and_build_type_mapping().unwrap(); 引入依赖 `arg-picker`,为 Mingling 提供更高级的参数解析能力。 -它可以与 `parser`、`clap` 特性共存,但建议不要和 `parser` 特性同时启用,因为两者的 API 极为相似。 - `picker` 是独立于 Mingling 的参数解析器,不依赖 `mingling_core` 的内置参数提取 API。 详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-picker) diff --git a/docs/_zh_CN/pages/other/naming_rule.md b/docs/_zh_CN/pages/other/naming_rule.md index 6e65ee4..7694f56 100644 --- a/docs/_zh_CN/pages/other/naming_rule.md +++ b/docs/_zh_CN/pages/other/naming_rule.md @@ -40,19 +40,17 @@ Res + 名称 ### 分发器 -分发器是命令的入口点,与 `Node` 名称一一对应。节点名用 `.` 分隔层级,分发器名用 `CMD` 前缀加 PascalCase。 +分发器是命令的入口点。命令名用 `.` 分隔层级,与用户输入的命令参数一一对应。 ``` -CMD + 命令层级 +命令名 ``` -| 节点 | 分发器 | -| ------------ | ----------------- | -| `greet` | `CMDGreet` | -| `remote.add` | `CMDRemoteAdd` | -| `remote.rm` | `CMDRemoteRemove` | - -即使节点是缩写,分发器的名称也要写全名。例如节点是 `remote.rm`,分发器是 `CMDRemoteRemove`,不是 `CMDRemoteRm`。 +| 命令 | +| ------------ | +| `greet` | +| `remote.add` | +| `remote.rm` | ### 入口 @@ -62,11 +60,11 @@ CMD + 命令层级 Entry + 命令层级 ``` -| 分发器 | 入口 | -| ----------------- | ------------------- | -| `CMDGreet` | `EntryGreet` | -| `CMDRemoteAdd` | `EntryRemoteAdd` | -| `CMDRemoteRemove` | `EntryRemoteRemove` | +| 命令 | 入口 | +| ------------ | ------------------- | +| `greet` | `EntryGreet` | +| `remote.add` | `EntryRemoteAdd` | +| `remote.rm` | `EntryRemoteRemove` | ### 状态 @@ -96,7 +94,7 @@ Result + 内容 | `ResultGreetSomeone` | 问候结果 | | `ResultFruitList` | 水果列表结果 | -结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 代替 `pack!()` 包装,以获得更灵活的字段控制。 +结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 标注结构体,以获得更灵活的字段控制。 ### 错误 @@ -148,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)] @@ -170,11 +169,14 @@ 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", CMDRemoteAdd => EntryRemoteAdd); +dispatcher!("remote.add", EntryRemoteAdd); // 入口 → 状态 #[chain] @@ -185,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); } ``` |
