diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-17 03:27:46 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-17 03:27:46 +0800 |
| commit | c23c590330af83afb6e146bcd9b0a274b3689d22 (patch) | |
| tree | 34599cf2737ce4de22653c9ccae2d60a38a15842 | |
| parent | 6980fbf2f9fb4c599d8dc6ff549a8b3288eb24e9 (diff) | |
refactor!: remove Node type and simplify dispatcher macro syntax0.5.0-rd
The `dispatcher!` macro no longer requires a `CMD*` dispatcher type
argument; the dispatcher struct is now generated internally as
`__Dispatcher{Pascal}`. The `Node` type, `node!` macro, and
`Dispatcher::node()` / `clone_dispatcher()` methods are removed.
88 files changed, 548 insertions, 1026 deletions
diff --git a/.config/verified-docs.toml b/.config/verified-docs.toml index f3b1f85..df2469b 100644 --- a/.config/verified-docs.toml +++ b/.config/verified-docs.toml @@ -3,6 +3,6 @@ [verified] readme = "./README.md" -getting_started = "./GETTING_STARTED.md" +getting_started = "./GETTING-STARTED.md" documents_en_us = "./docs/pages/**" documents_zh_cn = "./docs/_zh_CN/pages/**" diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ce5d2..7094c3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,6 +138,70 @@ None _Behavioral note:_ the runtime behavior of programs is unchanged — all dispatchers registered via `with_dispatcher` are now simply gathered automatically, and the "longest registered prefix wins" matching rule is preserved by both the trie and the linear-list strategies. +3. **[`core`]** **[`macros`]** **[BREAKING REMOVAL]** Removed the `Node` type, the `node!` macro, and the `Dispatcher::node()` / `Dispatcher::clone_dispatcher()` methods. Command path matching is now handled entirely by the compile-time-collected string command names, and dispatchers are identified by a hidden internally-generated `__Dispatcher*` struct. + + ### What changed + + The `Node` struct (in `mingling_core::asset::node`) was a path hierarchy of kebab-cased string segments used by the old dynamic dispatcher to match user input. With dispatchers now always collected at compile time (see **BREAKING CHANGE #2** above), the `Node` type and its supporting machinery became dead code. Command names are now stored and matched as plain string literals during compile-time registration. + + **Removed API:** + + - **`mingling_core::Node`** — Removed the entire `node` module and its public re-export. This includes the struct itself, its `From<&str>` / `From<String>` impls, `join()`, `PartialEq` / `Eq`, `PartialOrd` / `Ord`, `Display`, and `Default`. + - **`mingling::macros::node!`** — Removed the `node!` procedural macro. It was only used internally by `dispatcher!` / `dispatcher_clap!` / `#[command]` to construct a `Node` from a dot-separated string; now that `Node` is gone, the macro is obsolete. + - **`Dispatcher::node(&self) -> Node`** — Removed from the `Dispatcher` trait. Dispatchers no longer expose a `Node` hierarchy; the command path is embedded in the compile-time registration via `register_dispatcher!("name", ...)`. + - **`Dispatcher::clone_dispatcher(&self) -> Box<dyn Dispatcher<C>>`** — Removed from the `Dispatcher` trait, along with the blanket `Clone for Box<dyn Dispatcher<G>>` impl (which relied on `clone_dispatcher`). Dynamic dispatch / boxing of dispatchers is no longer supported. + - **`mingling::macros::node` re-export** — Removed from `mingling_macros/src/lib.rs` and `mingling/src/lib.rs`. + + **Dispatcher trait now requires only `begin`:** + + ```rust + pub trait Dispatcher<C> { + fn begin(&self, args: Vec<String>) -> ChainProcess<C>; + } + ``` + + **Generated dispatcher struct renamed:** + + The `dispatcher!` / `dispatcher_clap!` / `#[command]` macros now generate a **hidden** dispatcher struct named `__Dispatcher{Pascal}` (e.g., `__DispatcherGreet`) instead of the user-facing `CMD*` struct. The struct is marked `#[doc(hidden)]` and `#[allow(nonstandard_style)]`. Users never reference it directly — it is registered at compile time via `register_dispatcher!` and matched purely by its string command name. + + **`Dispatcher` trait example** (from `mingling_core/src/asset/dispatcher.rs` docs): + + ```rust,ignore + impl Dispatcher<ThisProgram> for CMDGreet { + fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> { + Routable::to_chain(Foo { args }) + } + } + ``` + + **`dispatcher!` syntax change** (reflected in **BREAKING CHANGE #1**'s rename context): + + - Old: `dispatcher!("greet", CMDGreet => EntryGreet)` + - New: `dispatcher!("greet", EntryGreet)` + + The `CMD*` is no longer part of the user-facing syntax — the dispatcher struct is generated internally as `__Dispatcher*`. The old form produces a compile error with a migration hint. + + **`dispatcher_clap!` syntax change:** + + - Old: `#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreet)]` + - New: `#[dispatcher_clap("greet", help = true, error = ErrorGreet)]` + + `#[dispatcher_clap("greet")]` (bare, no options) is still valid. + + **`#[command]` syntax change:** + + - The `name = CMDName` attribute argument was removed. `#[command(entry = EntryGreet)]` still works; the dispatcher struct is generated internally as `__DispatcherGreet`. + + ### Migration guide + - **Replace all `dispatcher!("name", CMDType => EntryType)` calls** with `dispatcher!("name", EntryType)`. The `CMD*` identifier is no longer generated or referenced. + - **Replace all `#[dispatcher_clap("name", CMDType, ...)]` attributes** with `#[dispatcher_clap("name", ...)]` (drop the `CMDType` argument). + - **Remove `name = CMDName` from `#[command(...)]`** attributes. If you still reference the old generated `CMDName` type (e.g., in `program.with_dispatcher(...)`), remove that call entirely — dispatchers are auto-collected (see **BREAKING CHANGE #2**). + - **Remove any `use mingling::Node;` imports** and any code constructing / manipulating `Node` values. If you had custom `Dispatcher` implementations, they must (a) drop the `node()` and `clone_dispatcher()` methods and (b) rely on the compile-time registration (via `dispatcher!` / `dispatcher_clap!` / `#[command]`) rather than manual `with_dispatcher` dynamic dispatch. + - **Remove any `node!("...")` macro invocations.** The `node!` macro no longer exists. + - **Any manual `Dispatcher` impls** now only need `begin()`. If you previously implemented `node()` for a custom dispatcher used with `program.with_dispatcher(...)`, that whole registration model is removed — see **BREAKING CHANGE #2** for the compile-time-only registration approach. + + _No behavioral changes to command matching — the semantics of dot-separated command paths, kebab-case normalization, and "longest registered prefix wins" are all preserved by the compiled-in string-based dispatch trie / linear list. The removal is purely an API simplification: the `Node` intermediate abstraction and the dispatcher-boxing machinery are gone._ + --- ## Contents diff --git a/GETTING-STARTED.md b/GETTING-STARTED.md index add49eb..c1b8e25 100644 --- a/GETTING-STARTED.md +++ b/GETTING-STARTED.md @@ -29,22 +29,21 @@ The entry point for every subcommand is the `dispatcher!` macro. It generates tw ```rust use mingling::prelude::*; -// command.name Dispatcher EntryType -// │ │ │ -dispatcher!("greet", CMDGreet => EntryGreet); +// command.name EntryType +// │ │ +dispatcher!("greet", EntryGreet); // Nested subcommand: `remote add` -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); +dispatcher!("remote.add", EntryRemoteAdd); ``` Then in `main()`, register the dispatcher with the program: ```rust -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } ``` @@ -65,7 +64,7 @@ dispatcher!("greet"); The `#[chain]` attribute turns a plain function into an execution step. Think of it as "the logic that transforms one typed value into another." ```rust -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); pack!(ResultGreeting = String); @@ -132,7 +131,7 @@ Mingling provides a **Picker** for argument extraction. You can use `pick()` or ```rust // Features: ["picker"] -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); pack!(ResultGreeting = String); #[chain] @@ -159,7 +158,7 @@ Enable it by adding `BasicProgramSetup`: use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); #[help] fn help_greet(_prev: EntryGreet) -> RenderResult { @@ -172,7 +171,6 @@ fn help_greet(_prev: EntryGreet) -> RenderResult { fn main() { let mut program = ThisProgram::new(); program.with_setup(BasicProgramSetup); // enables --help / -h - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } @@ -196,7 +194,7 @@ With the `comp` feature, Mingling provides a fully dynamic completion system. Yo use mingling::{macros::suggest, ShellContext, Suggest}; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); pack!(ResultName = (u8, String)); #[completion(EntryGreet)] @@ -230,7 +228,6 @@ You also need to register the built-in completion dispatcher: fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(crate::CMDCompletion); program.exec_and_exit(); } ``` @@ -252,7 +249,7 @@ use mingling::{ShellContext, Suggest}; use mingling::macros::suggest_enum; use mingling::EnumTag; -dispatcher!("lang.select", CMDLang => EntryLang); +dispatcher!("lang.select", EntryLang); #[derive(EnumTag)] pub enum ProgrammingLanguages { @@ -278,7 +275,7 @@ use mingling::macros::pack; use mingling::prelude::*; use std::io::Write; -dispatcher!("hello", CMDHello => EntryHello); +dispatcher!("hello", EntryHello); pack!(ResultName = String); pack!(ErrorNoNameProvided = ()); pack!(ErrorNameTooLong = u16); @@ -327,8 +324,8 @@ Chain and renderer functions can accept **additional parameters** for the progra use std::path::PathBuf; -dispatcher!("current", CMDCurrent => EntryCurrent); -dispatcher!("cd", CMDCd => EntryCd); +dispatcher!("current", EntryCurrent); +dispatcher!("cd", EntryCd); #[derive(Default, Clone)] struct ResCurrentDir { @@ -340,8 +337,6 @@ fn main() { program.with_resource(ResCurrentDir { current_dir: std::env::current_dir().unwrap(), }); - program.with_dispatcher(CMDCurrent); - program.with_dispatcher(CMDCd); program.exec_and_exit(); } @@ -367,7 +362,7 @@ Resources can also be injected into `#[renderer]`: use mingling::prelude::*; use std::io::Write; -dispatcher!("current", CMDCurrent => EntryCurrent); +dispatcher!("current", EntryCurrent); #[derive(Default, Clone)] struct ResCurrentDir { @@ -391,11 +386,11 @@ As your program grows to dozens or hundreds of subcommands, linear dispatcher lo ```rust // Features: ["dispatch_tree"] -dispatcher!("cmd1", CMD1 => Entry1); -dispatcher!("cmd2.sub1", CMD2Sub1 => Entry2Sub1); -dispatcher!("cmd2.sub2", CMD2Sub2 => Entry2Sub2); -dispatcher!("cmd3.sub1.leaf1", CMD3Sub1Leaf1 => Entry3Sub1Leaf1); -dispatcher!("cmd3.sub1.leaf2", CMD3Sub1Leaf2 => Entry3Sub1Leaf2); +dispatcher!("cmd1", Entry1); +dispatcher!("cmd2.sub1", Entry2Sub1); +dispatcher!("cmd2.sub2", Entry2Sub2); +dispatcher!("cmd3.sub1.leaf1", Entry3Sub1Leaf1); +dispatcher!("cmd3.sub1.leaf2", Entry3Sub1Leaf2); // ... dozens more fn main() { @@ -429,7 +424,7 @@ use std::io::Write; #[derive(Default, clap::Parser, Grouped)] #[dispatcher_clap( - "greet", CMDGreet, + "greet", help = true, // auto-generate #[help] from clap error = ErrorGreetParsed, // capture parse errors as a renderable type )] @@ -463,13 +458,12 @@ You can control how clap help is displayed: ```rust // Features: ["clap"] -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); program.stdout_setting.clap_help_print_behaviour = - mingling::ClapHelpPrintBehaviour::WriteToRenderResult; + mingling::config::ClapHelpPrintBehaviour::WriteToRenderResult; // or: PrintDirectly — writes clap help straight to stdout program.exec_and_exit(); } @@ -499,14 +493,12 @@ use mingling::{ setup::{BasicREPLReadlineSetup, BasicREPLOutputSetup, BasicREPLPromptSetup}, }; -dispatcher!("cd", CMDCd => EntryCd); -dispatcher!("exit", CMDExit => EntryExit); +dispatcher!("cd", EntryCd); +dispatcher!("exit", EntryExit); fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDCd); - program.with_dispatcher(CMDExit); // Enable line reading from stdin program.with_setup(BasicREPLReadlineSetup); @@ -538,7 +530,7 @@ use mingling::{ hook::{ProgramControlUnit, ProgramHook}, }; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { let mut program = ThisProgram::new(); @@ -560,7 +552,6 @@ fn main() { .on_post_render(|_| println!("[DEBUG] Post render")), ); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } ``` @@ -584,7 +575,7 @@ use mingling::StructuralData; use serde::Serialize; use std::io::Write; -dispatcher!("render", CMDRender => EntryRender); +dispatcher!("render", EntryRender); #[derive(Default, StructuralData, Serialize, Grouped)] struct ResultInfo { @@ -609,7 +600,6 @@ fn render_info_result(info: ResultInfo) { fn main() { let mut program = ThisProgram::new(); program.with_setup(StructuralRendererSetup); // enables --json / --yaml - program.with_dispatcher(CMDRender); let _ = program.exec(); } ``` @@ -642,7 +632,7 @@ Enable the `async` feature to use `async fn` inside `#[chain]`: use std::io::Write; use std::time::Duration; -dispatcher!("download", CMDDownload => EntryDownload); +dispatcher!("download", EntryDownload); pack!(ResultDownloaded = String); #[chain] @@ -693,11 +683,10 @@ use mingling::macros::pack; use mingling::prelude::*; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { let mut program = ThisProgram::new(); - program.with_dispatcher(CMDGreet); program.exec_and_exit(); } @@ -38,7 +38,7 @@ Additionally, the project is currently developed by me alone ([Weicao-CatilGrass Mingling abstracts the behavior of a program's lifecycle into three phases: **Dispatch**, **Execution**, and **Rendering**. Each phase is connected by types — the output of the current phase becomes the input of the next phase. For example: ```rust -dispatcher!("current", CMDCurrent => EntryCurrent); +dispatcher!("current", EntryCurrent); pack!(StateNext = ()); #[chain] @@ -65,7 +65,7 @@ You can use this approach to separate computation from result rendering, like th use mingling::macros::buffer; use mingling::prelude::*; -dispatcher!("calc", CMDCalculate => EntryCalculate); +dispatcher!("calc", EntryCalculate); pack!(StateSumNumbers = Vec<i32>); pack!(ResultNumber = i32); diff --git a/docs/_zh_CN/pages/10-help.md b/docs/_zh_CN/pages/10-help.md index 99a81fd..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,7 +51,7 @@ 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); diff --git a/docs/_zh_CN/pages/11-resource-system.md b/docs/_zh_CN/pages/11-resource-system.md index b127a3a..40d8cd9 100644 --- a/docs/_zh_CN/pages/11-resource-system.md +++ b/docs/_zh_CN/pages/11-resource-system.md @@ -30,7 +30,7 @@ fn main() { @@@use mingling::macros::buffer; @@@#[derive(Default, Clone)] @@@struct ResCurrentDir(String); -@@@dispatcher!("pwd", CMDPrintWorkingDir => EntryPrintWorkingDir); +@@@dispatcher!("pwd", EntryPrintWorkingDir); @@@pack!(ResultPath = String); // 通过 &T 注入只读资源 #[chain] @@ -52,7 +52,7 @@ fn render_path(result: ResultPath) { @@@use mingling::macros::buffer; @@@#[derive(Default, Clone)] @@@struct ResVisitCount(u32); -@@@dispatcher!("visit", CMDVisit => EntryVisit); +@@@dispatcher!("visit", EntryVisit); @@@pack!(ResultDone = ()); #[chain] fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next { @@ -73,7 +73,7 @@ Chain 可以同时注入任意多个资源,框架按类型自动匹配: ```rust @@@#[derive(Default, Clone)] struct ResConfig(String); @@@#[derive(Default, Clone)] struct ResCounter(u32); -@@@dispatcher!("test", CMDTest => EntryTest); +@@@dispatcher!("test", EntryTest); @@@pack!(ResultDone = ()); // 同时注入只读 + 可修改 #[chain] diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md index 2aa3ef7..811957c 100644 --- a/docs/_zh_CN/pages/13-hook.md +++ b/docs/_zh_CN/pages/13-hook.md @@ -54,7 +54,7 @@ Hook 覆盖了管线的完整生命周期: @@@use mingling::prelude::*; @@@use mingling::hook::ProgramHook; @@@ -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@ @@@#[chain] fn handle_greet(args: EntryGreet) -> Next { diff --git a/docs/_zh_CN/pages/14-testing.md b/docs/_zh_CN/pages/14-testing.md index 3567aef..30f3fc4 100644 --- a/docs/_zh_CN/pages/14-testing.md +++ b/docs/_zh_CN/pages/14-testing.md @@ -35,7 +35,7 @@ fn test_render_name() { ```rust @@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process}; -@@@dispatcher!("hello", CMDHello => EntryHello); +@@@dispatcher!("hello", EntryHello); @@@pack!(ResultName = String); @@@pack!(ErrorNoName = ()); @@@#[chain] @@ -77,7 +77,7 @@ fn test_handle_hello_with_name() { @@@use mingling::{assert_member_id, unpack_chain_process}; @@@use mingling::macros::entry; -@@@dispatcher!("hello", CMDHello => EntryHello); +@@@dispatcher!("hello", EntryHello); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_hello(args: EntryHello) -> Next { @@ -102,7 +102,7 @@ fn test_with_entry_macro() { @@@use mingling::{assert_render_result, unpack_chain_process}; @@@#[derive(Default, Clone)] @@@struct ResPrefix(String); -@@@dispatcher!("hello", CMDHello => EntryHello); +@@@dispatcher!("hello", EntryHello); @@@pack!(ResultGreeting = String); @@@ #[chain] diff --git a/docs/_zh_CN/pages/2-define-a-dispatcher.md b/docs/_zh_CN/pages/2-define-a-dispatcher.md index 4ec7c05..7ae26f1 100644 --- a/docs/_zh_CN/pages/2-define-a-dispatcher.md +++ b/docs/_zh_CN/pages/2-define-a-dispatcher.md @@ -19,13 +19,13 @@ Mingling 的管线从 Dispatcher 开始。 写法是固定的三个部分: ```rust -dispatcher!("命令路径", 分发器类型 => 入口类型); +dispatcher!("命令路径", 入口类型); ``` 看一个具体的例子: ```rust -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); ``` > [!NOTE] @@ -36,8 +36,8 @@ dispatcher!("greet", CMDGreet => EntryGreet); 如果你的程序有层级结构——比如 `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` 两个层级。 @@ -68,7 +68,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 b1becc9..bf30b91 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,7 +16,7 @@ `#[chain]` 用来标记一个处理函数,格式非常直接: ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); pack!(ResultName = String); #[chain] @@ -77,7 +77,7 @@ pub struct ResultName { `EntryGreet` 的 `inner` 是一个 `Vec<String>`,你可以在 Chain 里自由地处理它: ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet(args: EntryGreet) -> Next { @@ -100,7 +100,7 @@ fn handle_greet(args: EntryGreet) -> Next { ```rust // 1. 声明命令 -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // 2. 声明管线中的数据类型 pack!(ResultName = String); diff --git a/docs/_zh_CN/pages/4-render-result.md b/docs/_zh_CN/pages/4-render-result.md index 2dbb617..e3e49f5 100644 --- a/docs/_zh_CN/pages/4-render-result.md +++ b/docs/_zh_CN/pages/4-render-result.md @@ -52,7 +52,7 @@ fn render_name(name: ResultName) { use mingling::macros::buffer; // 1. 用 Dispatcher 声明命令 -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // 2. 用 pack! 声明结果数据 pack!(ResultName = String); diff --git a/docs/_zh_CN/pages/5-multiple-commands.md b/docs/_zh_CN/pages/5-multiple-commands.md index b251232..e0a58e7 100644 --- a/docs/_zh_CN/pages/5-multiple-commands.md +++ b/docs/_zh_CN/pages/5-multiple-commands.md @@ -12,8 +12,8 @@ ```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); @@ -62,8 +62,8 @@ Sum: 6 多层级的命令也是同理——每个点号分隔的层级都只是名字的一部分: ```rust -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); -dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm); +dispatcher!("remote.add", EntryRemoteAdd); +dispatcher!("remote.rm", EntryRemoteRm); ``` 每个子命令的 Entry、Chain、Renderer 完全独立,互不干扰。 diff --git a/docs/_zh_CN/pages/6-argument-parse-picker.md b/docs/_zh_CN/pages/6-argument-parse-picker.md index 462a912..33f1f0d 100644 --- a/docs/_zh_CN/pages/6-argument-parse-picker.md +++ b/docs/_zh_CN/pages/6-argument-parse-picker.md @@ -26,7 +26,7 @@ features = ["parser"] ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] @@ -42,7 +42,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next { ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) -> Next { @@ -55,7 +55,7 @@ let name = prev.pick_or((), "World").unpack(); ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@ -76,7 +76,7 @@ let name = prev.pick_or((), "World").unpack(); ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] @@ -90,7 +90,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next { ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@ -113,7 +113,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next { ```rust // Features: ["parser"] -@@@dispatcher!("test", CMDTest => EntryTest); +@@@dispatcher!("test", EntryTest); @@@pack!(ResultInfo = (String, u8, u32)); #[chain] @@ -141,7 +141,7 @@ fn handle_test_entry(prev: EntryTest) -> Next { // Features: ["parser", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@pack!(ErrorNoName = ()); @@ -200,7 +200,7 @@ let name = match pick_result { ````rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] @@ -226,7 +226,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next { // Features: ["parser", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@pack!(ErrorNameTooLong = usize); @@ -276,7 +276,7 @@ fn render_name(prev: ResultName) { ```rust // Features: ["parser"] @@@use mingling::parser::Yes; -@@@dispatcher!("test", CMDTest => EntryTest); +@@@dispatcher!("test", EntryTest); @@@pack!(ResultDone = ()); #[chain] @@ -329,7 +329,7 @@ impl Pickable for Address { Some(Address { ip, port }) } } -@@@dispatcher!("connect", CMDConnect => EntryConnect); +@@@dispatcher!("connect", EntryConnect); @@@pack!(ResultConnected = Address); #[chain] @@ -369,7 +369,7 @@ pub enum Fruits { } impl PickableEnum for Fruits {} -@@@dispatcher!("eat", CMDEat => EntryEat); +@@@dispatcher!("eat", EntryEat); @@@pack!(ResultFruit = Fruits); #[chain] diff --git a/docs/_zh_CN/pages/7-argument-parse-clap.md b/docs/_zh_CN/pages/7-argument-parse-clap.md index 83dddc3..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, @@@} diff --git a/docs/_zh_CN/pages/9-error-handling.md b/docs/_zh_CN/pages/9-error-handling.md index 222ef44..dce48d1 100644 --- a/docs/_zh_CN/pages/9-error-handling.md +++ b/docs/_zh_CN/pages/9-error-handling.md @@ -19,7 +19,7 @@ ## 用独立类型区分错误 ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); pack!(ResultGreeting = String); pack!(ErrorNameEmpty = String); @@ -39,7 +39,7 @@ fn handle_greet(args: EntryGreet) -> Next { ```rust @@@use mingling::macros::buffer; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@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() } @@ -61,7 +61,7 @@ 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); diff --git a/docs/_zh_CN/pages/advanced/1-completion.md b/docs/_zh_CN/pages/advanced/1-completion.md index c7dd678..28587b3 100644 --- a/docs/_zh_CN/pages/advanced/1-completion.md +++ b/docs/_zh_CN/pages/advanced/1-completion.md @@ -40,7 +40,7 @@ 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 { diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md index 9723247..f2e1717 100644 --- a/docs/_zh_CN/pages/advanced/2-structural-renderer.md +++ b/docs/_zh_CN/pages/advanced/2-structural-renderer.md @@ -29,7 +29,7 @@ features = ["structural_renderer"] // serde = "1" @@@use mingling::macros::buffer; @@@use mingling::setup::StructuralRendererSetup; -@@@dispatcher!("render", CMDRender => EntryRender); +@@@dispatcher!("render", EntryRender); // pack_structural! 等价于 pack! + StructuralData pack_structural!(ResultInfo = (String, i32)); @@ -72,7 +72,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 { diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md index 7ed3f5f..4913945 100644 --- a/docs/_zh_CN/pages/other/features.md +++ b/docs/_zh_CN/pages/other/features.md @@ -151,14 +151,14 @@ 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)` | 将外部类型注册为组成员,无需修改其定义 | +| `pack_err!(ErrorType)` / `pack_err!(ErrorType = Inner)` | 创建带自动 `name` 字段的错误类型 | +| `#[program_setup]` | 声明程序初始化函数 | +| `dispatcher!("cmd.path")` **缩写形式** | 省略 `EntryStruct`,入口类型名自动推导 | <details> <summary> Details </summary> diff --git a/docs/_zh_CN/pages/other/naming_rule.md b/docs/_zh_CN/pages/other/naming_rule.md index 6e65ee4..1bca8f6 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` | ### 状态 @@ -174,7 +172,7 @@ fn handle_remote_add(args: EntryRemoteAdd, cwd: &ResCurrentDir, db: &mut ResData @@@ pack!(ResultRemoteAdded = String); @@@ pack!(ErrorRepositoryNotFound = String); // 分发器 -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); +dispatcher!("remote.add", EntryRemoteAdd); // 入口 → 状态 #[chain] diff --git a/docs/pages/10-help.md b/docs/pages/10-help.md index 3d4b3b8..b378a61 100644 --- a/docs/pages/10-help.md +++ b/docs/pages/10-help.md @@ -14,7 +14,7 @@ Write a help function directly for an Entry: ```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,7 +51,7 @@ For `--help` to work properly, add `BasicProgramSetup` in `main`: ```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); diff --git a/docs/pages/11-resource-system.md b/docs/pages/11-resource-system.md index 0e2bd19..3a4dc36 100644 --- a/docs/pages/11-resource-system.md +++ b/docs/pages/11-resource-system.md @@ -30,7 +30,7 @@ In a Chain or Renderer, simply declare the resource in the parameter list: @@@use mingling::macros::buffer; @@@#[derive(Default, Clone)] @@@struct ResCurrentDir(String); -@@@dispatcher!("pwd", CMDPrintWorkingDir => EntryPrintWorkingDir); +@@@dispatcher!("pwd", EntryPrintWorkingDir); @@@pack!(ResultPath = String); // Inject read-only resource via &T #[chain] @@ -52,7 +52,7 @@ Use `&mut T` to inject a mutable resource: @@@use mingling::macros::buffer; @@@#[derive(Default, Clone)] @@@struct ResVisitCount(u32); -@@@dispatcher!("visit", CMDVisit => EntryVisit); +@@@dispatcher!("visit", EntryVisit); @@@pack!(ResultDone = ()); #[chain] fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next { @@ -73,7 +73,7 @@ A Chain can inject any number of resources at once — the framework matches the ```rust @@@#[derive(Default, Clone)] struct ResConfig(String); @@@#[derive(Default, Clone)] struct ResCounter(u32); -@@@dispatcher!("test", CMDTest => EntryTest); +@@@dispatcher!("test", EntryTest); @@@pack!(ResultDone = ()); // Inject both read-only and mutable resources #[chain] diff --git a/docs/pages/13-hook.md b/docs/pages/13-hook.md index c525998..90df379 100644 --- a/docs/pages/13-hook.md +++ b/docs/pages/13-hook.md @@ -54,7 +54,7 @@ Each hook callback receives a corresponding `Hook*Info` struct containing contex @@@use mingling::prelude::*; @@@use mingling::hook::ProgramHook; @@@ -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@ @@@#[chain] fn handle_greet(args: EntryGreet) -> Next { diff --git a/docs/pages/14-testing.md b/docs/pages/14-testing.md index 86171bf..9f6b6ed 100644 --- a/docs/pages/14-testing.md +++ b/docs/pages/14-testing.md @@ -35,7 +35,7 @@ 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", CMDHello => EntryHello); +@@@dispatcher!("hello", EntryHello); @@@pack!(ResultName = String); @@@pack!(ErrorNoName = ()); @@@#[chain] @@ -77,7 +77,7 @@ 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", CMDHello => EntryHello); +@@@dispatcher!("hello", EntryHello); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_hello(args: EntryHello) -> Next { @@ -102,7 +102,7 @@ If a Chain uses resources, you need to provide resource instances in the test: @@@use mingling::{assert_render_result, unpack_chain_process}; @@@#[derive(Default, Clone)] @@@struct ResPrefix(String); -@@@dispatcher!("hello", CMDHello => EntryHello); +@@@dispatcher!("hello", EntryHello); @@@pack!(ResultGreeting = String); @@@ #[chain] diff --git a/docs/pages/2-define-a-dispatcher.md b/docs/pages/2-define-a-dispatcher.md index 432d7db..efd744d 100644 --- a/docs/pages/2-define-a-dispatcher.md +++ b/docs/pages/2-define-a-dispatcher.md @@ -19,13 +19,13 @@ The `dispatcher!` macro generates two types at once: The syntax is a fixed three-part pattern: ```rust -dispatcher!("command path", DispatcherType => EntryType); +dispatcher!("command path", EntryType); ``` Here's a concrete example: ```rust -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); ``` > [!NOTE] @@ -36,8 +36,8 @@ dispatcher!("greet", CMDGreet => EntryGreet); If your program has a hierarchy — e.g., `remote add`, `remote rm` — just separate the command name with dots: ```rust -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); -dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm); +dispatcher!("remote.add", EntryRemoteAdd); +dispatcher!("remote.rm", EntryRemoteRm); ``` When the user types `remote add` in the terminal, Mingling matches `remote` and `add` as two levels in sequence. @@ -68,7 +68,7 @@ The above is the standard syntax. If you enable the `extras` feature, you can be // Features: ["extras"] // Omit CMDType and EntryType, names are auto-derived dispatcher!("greet"); -// dispatcher!("greet", CMDGreet => EntryGreet); +// dispatcher!("greet", EntryGreet); ``` This syntax auto-generates `CMDGreet` and `EntryGreet`, with the same effect as the explicit declaration. diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md index 1134dbf..dca299e 100644 --- a/docs/pages/3-define-a-chain.md +++ b/docs/pages/3-define-a-chain.md @@ -3,7 +3,7 @@ Use the <code>chain</code> macro to declare a chain and handle Entry input </p> -In the previous section, we declared `dispatcher!("greet", CMDGreet => EntryGreet)`. +In the previous section, we declared `dispatcher!("greet", EntryGreet)`. Now when a user types `greet`, it gets matched and wrapped into `EntryGreet`. @@ -16,7 +16,7 @@ We need a Chain to process it. `#[chain]` marks a handler function. The format is straightforward: ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); pack!(ResultName = String); #[chain] @@ -77,7 +77,7 @@ See [Naming Convention](pages/other/naming_rule) for details, but for now just r `EntryGreet`'s `inner` is a `Vec<String>`, which you can freely process inside a Chain: ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet(args: EntryGreet) -> Next { @@ -100,7 +100,7 @@ Now let's connect the Dispatcher and Chain: ```rust // 1. Declare the command -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // 2. Declare the pipeline data type pack!(ResultName = String); diff --git a/docs/pages/4-render-result.md b/docs/pages/4-render-result.md index a50c509..fdf8b12 100644 --- a/docs/pages/4-render-result.md +++ b/docs/pages/4-render-result.md @@ -52,7 +52,7 @@ Putting all three tutorials together, here's your first complete Mingling progra use mingling::macros::buffer; // 1. Declare commands with a Dispatcher -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // 2. Declare result data with pack! pack!(ResultName = String); diff --git a/docs/pages/5-multiple-commands.md b/docs/pages/5-multiple-commands.md index b92cb95..a5c09b0 100644 --- a/docs/pages/5-multiple-commands.md +++ b/docs/pages/5-multiple-commands.md @@ -12,8 +12,8 @@ Work in the same project: ```rust @@@use mingling::macros::buffer; // Declare two commands -dispatcher!("greet", CMDGreet => EntryGreet); -dispatcher!("add", CMDAdd => EntryAdd); +dispatcher!("greet", EntryGreet); +dispatcher!("add", EntryAdd); pack!(ResultGreeting = String); pack!(ResultSum = i32); @@ -62,8 +62,8 @@ Sum: 6 Multi-level commands work the same way—each dot-separated level is just part of the name: ```rust -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); -dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm); +dispatcher!("remote.add", EntryRemoteAdd); +dispatcher!("remote.rm", EntryRemoteRm); ``` Each subcommand's Entry, Chain, and Renderer are completely independent and don't interfere. diff --git a/docs/pages/6-argument-parse-picker.md b/docs/pages/6-argument-parse-picker.md index d7d38af..9da56d5 100644 --- a/docs/pages/6-argument-parse-picker.md +++ b/docs/pages/6-argument-parse-picker.md @@ -26,7 +26,7 @@ Now let's see how `Picker` is written: ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] @@ -42,7 +42,7 @@ For the code above: ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) -> Next { @@ -55,7 +55,7 @@ Its semantics are: ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@ -76,7 +76,7 @@ If your program needs to parse flag arguments (e.g. `greet --name Alice`), do th ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] @@ -90,7 +90,7 @@ Its semantics: ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@#[chain] @@@fn handle_greet_entry(prev: EntryGreet) { @@ -113,7 +113,7 @@ For a single pick, `.unpack()` returns the value directly; for multiple picks, i ```rust // Features: ["parser"] -@@@dispatcher!("test", CMDTest => EntryTest); +@@@dispatcher!("test", EntryTest); @@@pack!(ResultInfo = (String, u8, u32)); #[chain] @@ -141,7 +141,7 @@ Here's a simple example: // Features: ["parser", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@pack!(ErrorNoName = ()); @@ -200,7 +200,7 @@ After picking user input with `pick`, you can use `after` to process it immediat ```rust // Features: ["parser"] -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); #[chain] @@ -226,7 +226,7 @@ Similarly, you can use `after_or_route` to handle input format errors: // Features: ["parser", "extras"] @@@use mingling::macros::buffer; @@@use mingling::macros::route; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); @@@pack!(ResultName = String); @@@pack!(ErrorNameTooLong = usize); @@ -276,7 +276,7 @@ Implicit mode is generally sufficient, but for important confirmations, explicit ```rust // Features: ["parser"] @@@use mingling::parser::Yes; -@@@dispatcher!("test", CMDTest => EntryTest); +@@@dispatcher!("test", EntryTest); @@@pack!(ResultDone = ()); #[chain] @@ -329,7 +329,7 @@ impl Pickable for Address { Some(Address { ip, port }) } } -@@@dispatcher!("connect", CMDConnect => EntryConnect); +@@@dispatcher!("connect", EntryConnect); @@@pack!(ResultConnected = Address); #[chain] @@ -369,7 +369,7 @@ pub enum Fruits { } impl PickableEnum for Fruits {} -@@@dispatcher!("eat", CMDEat => EntryEat); +@@@dispatcher!("eat", EntryEat); @@@pack!(ResultFruit = Fruits); #[chain] diff --git a/docs/pages/7-argument-parse-clap.md b/docs/pages/7-argument-parse-clap.md index ee1e96f..8b2ad0c 100644 --- a/docs/pages/7-argument-parse-clap.md +++ b/docs/pages/7-argument-parse-clap.md @@ -27,7 +27,7 @@ Add `#[dispatcher_clap]` on a `clap::Parser` struct to auto-generate a Dispatche @@@ 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 @@ If you need `--help` support, register `BasicProgramSetup` in main and set the c @@@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, @@@} diff --git a/docs/pages/9-error-handling.md b/docs/pages/9-error-handling.md index 389e394..eefc0f0 100644 --- a/docs/pages/9-error-handling.md +++ b/docs/pages/9-error-handling.md @@ -19,7 +19,7 @@ Error values can also take either path—you can render the error msg directly, ## Distinguish Errors with Dedicated Types ```rust -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@dispatcher!("greet", EntryGreet); pack!(ResultGreeting = String); pack!(ErrorNameEmpty = String); @@ -39,7 +39,7 @@ Then write separate Renderers: ```rust @@@use mingling::macros::buffer; -@@@dispatcher!("greet", CMDGreet => EntryGreet); +@@@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() } @@ -61,7 +61,7 @@ Each Renderer does its own job; what the user sees depends on what the Chain ret ```rust @@@use mingling::macros::buffer; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); pack!(ResultGreeting = String); pack!(ErrorNameEmpty = String); diff --git a/docs/pages/advanced/1-completion.md b/docs/pages/advanced/1-completion.md index f70d415..55b8b1d 100644 --- a/docs/pages/advanced/1-completion.md +++ b/docs/pages/advanced/1-completion.md @@ -40,7 +40,7 @@ Use `#[completion(EntryType)]` to define completion logic for an Entry: @@@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 { diff --git a/docs/pages/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md index 489baba..e444ee6 100644 --- a/docs/pages/advanced/2-structural-renderer.md +++ b/docs/pages/advanced/2-structural-renderer.md @@ -29,7 +29,7 @@ After enabling `StructuralRendererSetup`, use `pack_structural!` instead of `pac // serde = "1" @@@use mingling::macros::buffer; @@@use mingling::setup::StructuralRendererSetup; -@@@dispatcher!("render", CMDRender => EntryRender); +@@@dispatcher!("render", EntryRender); // pack_structural! is equivalent to pack! + StructuralData pack_structural!(ResultInfo = (String, i32)); @@ -72,7 +72,7 @@ The default output from `pack_structural!` includes an `inner` field. For full c @@@use mingling::setup::StructuralRendererSetup; @@@use mingling::StructuralData; @@@use serde::Serialize; -@@@dispatcher!("render", CMDRender => EntryRender); +@@@dispatcher!("render", EntryRender); #[derive(Serialize, StructuralData, Grouped)] struct Info { diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md index 7615118..4552d30 100644 --- a/docs/pages/other/features.md +++ b/docs/pages/other/features.md @@ -158,7 +158,7 @@ For example, allows the shorthand form `dispatcher!("greet")`, which auto-genera | `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 `CMDStruct => EntryStruct`, names are auto-derived | +| `dispatcher!("cmd.path")` **shorthand** | Omit `EntryStruct`, the entry name is auto-derived | <details> <summary> Details </summary> diff --git a/docs/pages/other/naming_rule.md b/docs/pages/other/naming_rule.md index 1175ae0..770fd10 100644 --- a/docs/pages/other/naming_rule.md +++ b/docs/pages/other/naming_rule.md @@ -40,19 +40,17 @@ Name + Setup ### Dispatcher -Dispatchers are the entry points of commands, corresponding one-to-one with `Node` names. Node names use `.` to separate levels, dispatcher names use the `CMD` prefix with PascalCase. +Dispatchers are the entry points of commands. The command name uses `.` to separate hierarchy levels and matches the arguments typed by the user. ``` -CMD + Command Hierarchy +command name ``` -| Node | Dispatcher | -| ------------ | ----------------- | -| `greet` | `CMDGreet` | -| `remote.add` | `CMDRemoteAdd` | -| `remote.rm` | `CMDRemoteRemove` | - -Even if a node is an abbreviation, the dispatcher name should use the full name. For example, the node is `remote.rm`, but the dispatcher is `CMDRemoteRemove`, not `CMDRemoteRm`. +| Command | +| ------------ | +| `greet` | +| `remote.add` | +| `remote.rm` | ### Entry @@ -62,11 +60,11 @@ Entries are the pipeline starting types created by dispatchers, wrapping `Vec<St Entry + Command Hierarchy ``` -| Dispatcher | Entry | -| ----------------- | ------------------- | -| `CMDGreet` | `EntryGreet` | -| `CMDRemoteAdd` | `EntryRemoteAdd` | -| `CMDRemoteRemove` | `EntryRemoteRemove` | +| Command | Entry | +| ------------ | ------------------- | +| `greet` | `EntryGreet` | +| `remote.add` | `EntryRemoteAdd` | +| `remote.rm` | `EntryRemoteRemove` | ### State @@ -174,7 +172,7 @@ fn handle_remote_add(args: EntryRemoteAdd, cwd: &ResCurrentDir, db: &mut ResData @@@ pack!(ResultRemoteAdded = String); @@@ pack!(ErrorRepositoryNotFound = String); // Dispatcher -dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); +dispatcher!("remote.add", EntryRemoteAdd); // Entry → State #[chain] diff --git a/docs/res/changlog_examples/feat_program_res.rs b/docs/res/changlog_examples/feat_program_res.rs index 11a1471..5133000 100644 --- a/docs/res/changlog_examples/feat_program_res.rs +++ b/docs/res/changlog_examples/feat_program_res.rs @@ -20,7 +20,7 @@ fn main() { program.exec(); } -dispatcher!("modify", CMDModify => EntryModify); +dispatcher!("modify", EntryModify); pack!(DisplayGlobal = ()); diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs index d7c8681..d45c4af 100644 --- a/examples/example-argument-parse/src/main.rs +++ b/examples/example-argument-parse/src/main.rs @@ -21,8 +21,8 @@ use mingling::{macros::route, prelude::*}; use std::io::Write; -dispatcher!("transfer", CMDTransfer => EntryTransfer); -dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer); +dispatcher!("transfer", EntryTransfer); +dispatcher!("strict-transfer", EntryStrictTransfer); pack!(ResultFile = (bool, usize, String)); // (IsDir, Size, Name) diff --git a/examples/example-argument-picker/src/main.rs b/examples/example-argument-picker/src/main.rs index 36b552e..99eee30 100644 --- a/examples/example-argument-picker/src/main.rs +++ b/examples/example-argument-picker/src/main.rs @@ -42,7 +42,7 @@ use mingling::setup::picker::BasicProgramSetup; // --------- IMPORTANT --------- -dispatcher!("calc", CMDCalculate => EntryCalculate); +dispatcher!("calc", EntryCalculate); pack_err!(ErrorNumberANotProvided); pack_err!(ErrorNumberBNotProvided); diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs index 1b6c194..ac1bcc1 100644 --- a/examples/example-async-support/src/main.rs +++ b/examples/example-async-support/src/main.rs @@ -39,7 +39,7 @@ async fn main() { // --------- IMPORTANT --------- } -dispatcher!("download", CMDDownload => EntryDownload); +dispatcher!("download", EntryDownload); pack!(ResultDownloaded = String); diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs index 418ed95..44736ad 100644 --- a/examples/example-basic/src/main.rs +++ b/examples/example-basic/src/main.rs @@ -22,7 +22,7 @@ use std::io::Write; // | / _________ entry, records raw arguments // | | / ^^^^^^^^^^^^^ // vvvvv vvvvvvvv vvvvvvvvvv \_ equivalent to pack!(EntryGreet = Vec<String>) -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { // Create a new ThisProgram diff --git a/examples/example-clap-binding/src/main.rs b/examples/example-clap-binding/src/main.rs index 449fee3..6ff963b 100644 --- a/examples/example-clap-binding/src/main.rs +++ b/examples/example-clap-binding/src/main.rs @@ -71,7 +71,7 @@ fn main() { // vvvvvvv vvvvvvvvvvvv vvvvvvv #[derive(Default, clap::Parser, Grouped)] #[dispatcher_clap( - "greet", CMDGreet, // Bind EntryGreet to "greet" command + "greet", // Bind EntryGreet to "greet" command help = true, // Generate clap help for EntryGreet error = ErrorGreetParsed, // Generate and bind error type for parse failure // ^^^^^\__ Using `error` intercepts parse failure information into the specified type, diff --git a/examples/example-combine-pathf-metadata/src/sub/mod.rs b/examples/example-combine-pathf-metadata/src/sub/mod.rs index 2e6776e..ba56285 100644 --- a/examples/example-combine-pathf-metadata/src/sub/mod.rs +++ b/examples/example-combine-pathf-metadata/src/sub/mod.rs @@ -8,7 +8,7 @@ use std::io::Write; // Implicit dispatcher form — creates `CMDHello` / `EntryHello` in this module dispatcher!("hello"); // Creates `CMDDescription` / `EntryDescription` in this module -dispatcher!("desc", CMDDescription => EntryDescription); +dispatcher!("desc", EntryDescription); /// The metadata type attached to an entry (`DataType`). #[derive(Debug, PartialEq, Eq)] diff --git a/examples/example-command-macro/src/main.rs b/examples/example-command-macro/src/main.rs index d080043..e525098 100644 --- a/examples/example-command-macro/src/main.rs +++ b/examples/example-command-macro/src/main.rs @@ -26,21 +26,21 @@ pack!(ResultGreeting = String); pack!(ResultGoodbye = ()); // --------- IMPORTANT --------- -// Auto-generates dispatcher!("hello.world", CMDHelloWorld => EntryHelloWorld); +// Auto-generates dispatcher!("hello.world", EntryHelloWorld); #[command] fn hello_world() -> ResultGreeting { ResultGreeting::new("World".to_string()) } -// Auto-generates dispatcher!("hello-world", CMDGreetSomeone => EntryGreetSomeone); +// Auto-generates dispatcher!("hello-world", EntryGreetSomeone); #[command(node = "greet-someone")] fn greet_someone(args: Vec<String>) -> ResultGreeting { let name = args.pick_or(&arg![String], || "World".to_string()).unwrap(); ResultGreeting::new(name) } -// Auto-generates dispatcher!("goodbye", CMDGoodBye => EntryGoodBye); -#[command(name = CMDGoodBye, entry = EntryGoodBye)] +// Auto-generates dispatcher!("goodbye", EntryGoodBye); +#[command(entry = EntryGoodBye)] fn goodbye() -> ResultGoodbye { ResultGoodbye::default() } diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index 29d6026..7de41f6 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -96,7 +96,7 @@ fn complete_greet_entry(ctx: &ShellContext) -> Suggest { } // --------- IMPORTANT --------- -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); pack!(ResultName = (u8, String)); #[chain] diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs index ece5d13..8265b09 100644 --- a/examples/example-custom-pickable/src/main.rs +++ b/examples/example-custom-pickable/src/main.rs @@ -41,7 +41,7 @@ impl Pickable for Address { } // --------- IMPORTANT --------- -dispatcher!("connect", CMDConnect => EntryConnect); +dispatcher!("connect", EntryConnect); pack!(ErrorParseAddressFailed = ()); #[chain] diff --git a/examples/example-dispatch-tree/src/main.rs b/examples/example-dispatch-tree/src/main.rs index a25af2a..bba9c2e 100644 --- a/examples/example-dispatch-tree/src/main.rs +++ b/examples/example-dispatch-tree/src/main.rs @@ -23,20 +23,20 @@ use std::io::Write; // --------- IMPORTANT --------- // You have a large number of subcommands -dispatcher!("cmd1", CMD1 => Entry1); -dispatcher!("cmd2.sub1", CMD2Sub1 => Entry2Sub1); -dispatcher!("cmd2.sub2", CMD2Sub2 => Entry2Sub2); -dispatcher!("cmd3.sub1.leaf1", CMD3Sub1Leaf1 => Entry3Sub1Leaf1); -dispatcher!("cmd3.sub1.leaf2", CMD3Sub1Leaf2 => Entry3Sub1Leaf2); -dispatcher!("cmd3.sub2", CMD3Sub2 => Entry3Sub2); -dispatcher!("cmd4.sub1.subsub1.deep", CMD4Deep => Entry4Deep); -dispatcher!("cmd4.sub1.subsub2", CMD4SubSub2 => Entry4SubSub2); -dispatcher!("cmd5", CMD5 => Entry5); -dispatcher!("cmd5.extra", CMD5Extra => Entry5Extra); -dispatcher!("nested.a.b.c", CMDA => EntryA); -dispatcher!("nested.a.b.d", CMDB => EntryB); -dispatcher!("nested.a.e", CMDC => EntryC); -dispatcher!("nested.f", CMDD => EntryD); +dispatcher!("cmd1", Entry1); +dispatcher!("cmd2.sub1", Entry2Sub1); +dispatcher!("cmd2.sub2", Entry2Sub2); +dispatcher!("cmd3.sub1.leaf1", Entry3Sub1Leaf1); +dispatcher!("cmd3.sub1.leaf2", Entry3Sub1Leaf2); +dispatcher!("cmd3.sub2", Entry3Sub2); +dispatcher!("cmd4.sub1.subsub1.deep", Entry4Deep); +dispatcher!("cmd4.sub1.subsub2", Entry4SubSub2); +dispatcher!("cmd5", Entry5); +dispatcher!("cmd5.extra", Entry5Extra); +dispatcher!("nested.a.b.c", EntryA); +dispatcher!("nested.a.b.d", EntryB); +dispatcher!("nested.a.e", EntryC); +dispatcher!("nested.f", EntryD); // --------- IMPORTANT --------- fn main() { diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs index 884bc1d..2966036 100644 --- a/examples/example-enum-tag/src/main.rs +++ b/examples/example-enum-tag/src/main.rs @@ -73,7 +73,7 @@ pub enum ProgrammingLanguages { impl PickableEnum for ProgrammingLanguages {} // --------- IMPORTANT --------- -dispatcher!("lang-select", CMDLanguageSelection => EntryLanguageSelection); +dispatcher!("lang-select", EntryLanguageSelection); #[chain] fn handle_language_selection(args: EntryLanguageSelection) -> Next { diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs index e9c0f57..05b451b 100644 --- a/examples/example-error-handling/src/main.rs +++ b/examples/example-error-handling/src/main.rs @@ -26,7 +26,7 @@ use std::io::Write; // In Mingling, instead of using ? to propagate errors upward, // errors are treated as branches that continue execution. -dispatcher!("hello", CMDHello => EntryHello); +dispatcher!("hello", EntryHello); // Define error types pack!(ErrorNoNameProvided = ()); diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs index ad712cd..d3e035e 100644 --- a/examples/example-exitcode/src/main.rs +++ b/examples/example-exitcode/src/main.rs @@ -34,7 +34,7 @@ fn main() { program.exec_and_exit(); } -dispatcher!("hello", CMDHello => EntryHello); +dispatcher!("hello", EntryHello); pack!(ErrorNoNameProvided = ()); pack!(ResultName = String); diff --git a/examples/example-help/src/main.rs b/examples/example-help/src/main.rs index 90619f5..75170c7 100644 --- a/examples/example-help/src/main.rs +++ b/examples/example-help/src/main.rs @@ -16,7 +16,7 @@ use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // Define help _________ When `program.user_context.help` is `true` // / the command will not enter `#[chain]` / `#[renderer]` diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index 1304f85..1807e5e 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -26,7 +26,7 @@ use mingling::{ }; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { let mut program = ThisProgram::new(); diff --git a/examples/example-implicit-dispatcher/src/main.rs b/examples/example-implicit-dispatcher/src/main.rs index 69adcfe..89a5ec3 100644 --- a/examples/example-implicit-dispatcher/src/main.rs +++ b/examples/example-implicit-dispatcher/src/main.rs @@ -4,9 +4,10 @@ use mingling::prelude::*; -// When using implicit syntax, the entry and dispatcher names will be automatically derived -dispatcher!("remote.add" /*, CMDRemoteAdd => EntryRemoteAdd */); -dispatcher!("remote.remove", CMDRemoteRemove => EntryRemoteRemove); +// When using implicit syntax, the entry name will be automatically derived +// from the command name (the dispatcher struct is generated internally) +dispatcher!("remote.add" /* => EntryRemoteAdd */); +dispatcher!("remote.remove", EntryRemoteRemove); fn main() { ThisProgram::new().exec_and_exit(); diff --git a/examples/example-lazy-resources/src/main.rs b/examples/example-lazy-resources/src/main.rs index 199608e..cc1604a 100644 --- a/examples/example-lazy-resources/src/main.rs +++ b/examples/example-lazy-resources/src/main.rs @@ -49,8 +49,8 @@ fn init_res_large_data() -> ResLargeData { ResLargeData { data } } -dispatcher!("show", CMDShow => EntryShow); -dispatcher!("none", CMDNone => EntryNone); +dispatcher!("show", EntryShow); +dispatcher!("none", EntryNone); pack!(ResultShow = BTreeMap<Key, Value>); diff --git a/examples/example-metadata/src/main.rs b/examples/example-metadata/src/main.rs index 5395614..94facb4 100644 --- a/examples/example-metadata/src/main.rs +++ b/examples/example-metadata/src/main.rs @@ -25,13 +25,13 @@ use mingling::{macros::metadata, prelude::*}; use std::io::Write; // Define the `greet` subcommand -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); // Define the `desc` subcommand, which queries metadata bound to EntryGreet -dispatcher!("desc", CMDDescription => EntryDescription); +dispatcher!("desc", EntryDescription); // Define the `nodoc` subcommand, which queries metadata for an entry that has none -dispatcher!("nodoc", CMDNoDescription => EntryNoDescription); +dispatcher!("nodoc", EntryNoDescription); fn main() { ThisProgram::new().exec_and_exit(); diff --git a/examples/example-pack-err/src/main.rs b/examples/example-pack-err/src/main.rs index 901072d..e30e4cb 100644 --- a/examples/example-pack-err/src/main.rs +++ b/examples/example-pack-err/src/main.rs @@ -31,8 +31,8 @@ use mingling::setup::StructuralRendererSetup; use std::io::Write; use std::path::PathBuf; -dispatcher!("find", CMDFind => EntryFind); -dispatcher!("find-structural", CMDFindStructural => EntryFindStructural); +dispatcher!("find", EntryFind); +dispatcher!("find-structural", EntryFindStructural); // --------- IMPORTANT --------- // `pack_err!` is a convenient macro for defining error types. diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs index 867e684..e1aa15c 100644 --- a/examples/example-panic-unwind/src/main.rs +++ b/examples/example-panic-unwind/src/main.rs @@ -19,7 +19,7 @@ use mingling::config::PanicSilence; use mingling::{hook::ProgramHook, prelude::*}; use std::io::Write; -dispatcher!("panic", CMDPanic => EntryPanic); +dispatcher!("panic", EntryPanic); pack!(NotPanic = ()); fn main() { diff --git a/examples/example-pathfinder/src/sub/mod.rs b/examples/example-pathfinder/src/sub/mod.rs index 8cbc1c5..cf0a97a 100644 --- a/examples/example-pathfinder/src/sub/mod.rs +++ b/examples/example-pathfinder/src/sub/mod.rs @@ -2,7 +2,7 @@ use crate::Next; use mingling::prelude::*; use std::io::Write; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); pack!(ResultName = String); #[chain] diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs index dc3d2fe..325619e 100644 --- a/examples/example-repl-basic/src/main.rs +++ b/examples/example-repl-basic/src/main.rs @@ -73,10 +73,10 @@ fn main() { pack!(ErrorDirectoryNotExist = PathBuf); // Create commands: cd ls exit -dispatcher!("cd", CMDCd => EntryCd); -dispatcher!("ls", CMDLs => EntryLs); -dispatcher!("exit", CMDExit => EntryExit); -dispatcher!("clear", CMDClear => EntryClear); +dispatcher!("cd", EntryCd); +dispatcher!("ls", EntryLs); +dispatcher!("exit", EntryExit); +dispatcher!("clear", EntryClear); // Define data needed for the cd command's execution phase pack!(StateChangeDirectory = String); diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs index 262680a..bb07d74 100644 --- a/examples/example-resources/src/main.rs +++ b/examples/example-resources/src/main.rs @@ -40,8 +40,8 @@ fn main() { program.exec_and_exit(); } -dispatcher!("current", CMDCurrent => EntryCurrent); -dispatcher!("modify-current", CMDModifyCurrent => EntryModifyCurrent); +dispatcher!("current", EntryCurrent); +dispatcher!("modify-current", EntryModifyCurrent); // Define chain for modifying current directory _________________ Injected muttable resource // / diff --git a/examples/example-setup/src/main.rs b/examples/example-setup/src/main.rs index b497054..523a567 100644 --- a/examples/example-setup/src/main.rs +++ b/examples/example-setup/src/main.rs @@ -53,7 +53,7 @@ fn custom_setup(program: &mut Program<ThisProgram>) { } // --------- IMPORTANT --------- -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); pack!(ResultGreeting = String); diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs index 8583d46..eca87fc 100644 --- a/examples/example-structural-renderer/src/main.rs +++ b/examples/example-structural-renderer/src/main.rs @@ -22,7 +22,7 @@ use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, Structur use serde::Serialize; use std::io::Write; -dispatcher!("render", CMDRender => EntryRender); +dispatcher!("render", EntryRender); fn main() { let mut program = ThisProgram::new(); diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs index 140df90..29ff9da 100644 --- a/examples/example-unit-test/src/main.rs +++ b/examples/example-unit-test/src/main.rs @@ -65,7 +65,7 @@ mod tests { // --------- IMPORTANT --------- } -dispatcher!("hello", CMDHello => EntryHello); +dispatcher!("hello", EntryHello); pack!(ErrorNoNameProvided = ()); pack!(ErrorNameTooLong = u16); diff --git a/mingling/src/docs/lib.md b/mingling/src/docs/lib.md index 993358b..91858d0 100644 --- a/mingling/src/docs/lib.md +++ b/mingling/src/docs/lib.md @@ -22,7 +22,7 @@ Here is a basic project written using **Mingling**: ```rust use mingling::prelude::*; -dispatcher!("greet", CMDGreet => EntryGreet); +dispatcher!("greet", EntryGreet); fn main() { let program = ThisProgram::new(); diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index 5e4df1b..5688793 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -41,8 +41,8 @@ /// use mingling::{macros::route, prelude::*}; /// use std::io::Write; /// -/// dispatcher!("transfer", CMDTransfer => EntryTransfer); -/// dispatcher!("strict-transfer", CMDStrictTransfer => EntryStrictTransfer); +/// dispatcher!("transfer", EntryTransfer); +/// dispatcher!("strict-transfer", EntryStrictTransfer); /// /// pack!(ResultFile = (bool, usize, String)); // (IsDir, Size, Name) /// @@ -184,7 +184,7 @@ pub mod example_argument_parse {} /// /// // --------- IMPORTANT --------- /// -/// dispatcher!("calc", CMDCalculate => EntryCalculate); +/// dispatcher!("calc", EntryCalculate); /// /// pack_err!(ErrorNumberANotProvided); /// pack_err!(ErrorNumberBNotProvided); @@ -430,7 +430,7 @@ pub mod example_argument_picker {} /// // --------- IMPORTANT --------- /// } /// -/// dispatcher!("download", CMDDownload => EntryDownload); +/// dispatcher!("download", EntryDownload); /// /// pack!(ResultDownloaded = String); /// @@ -499,7 +499,7 @@ pub mod example_async_support {} /// // | / _________ entry, records raw arguments /// // | | / ^^^^^^^^^^^^^ /// // vvvvv vvvvvvvv vvvvvvvvvv \_ equivalent to pack!(EntryGreet = Vec<String>) -/// dispatcher!("greet", CMDGreet => EntryGreet); +/// dispatcher!("greet", EntryGreet); /// /// fn main() { /// // Create a new ThisProgram @@ -648,7 +648,7 @@ pub mod example_basic {} /// // vvvvvvv vvvvvvvvvvvv vvvvvvv /// #[derive(Default, clap::Parser, Grouped)] /// #[dispatcher_clap( -/// "greet", CMDGreet, // Bind EntryGreet to "greet" command +/// "greet", // Bind EntryGreet to "greet" command /// help = true, // Generate clap help for EntryGreet /// error = ErrorGreetParsed, // Generate and bind error type for parse failure /// // ^^^^^\__ Using `error` intercepts parse failure information into the specified type, @@ -871,21 +871,21 @@ pub mod example_combine_pathf_metadata {} /// pack!(ResultGoodbye = ()); /// /// // --------- IMPORTANT --------- -/// // Auto-generates dispatcher!("hello.world", CMDHelloWorld => EntryHelloWorld); +/// // Auto-generates dispatcher!("hello.world", EntryHelloWorld); /// #[command] /// fn hello_world() -> ResultGreeting { /// ResultGreeting::new("World".to_string()) /// } /// -/// // Auto-generates dispatcher!("hello-world", CMDGreetSomeone => EntryGreetSomeone); +/// // Auto-generates dispatcher!("hello-world", EntryGreetSomeone); /// #[command(node = "greet-someone")] /// fn greet_someone(args: Vec<String>) -> ResultGreeting { /// let name = args.pick_or(&arg![String], || "World".to_string()).unwrap(); /// ResultGreeting::new(name) /// } /// -/// // Auto-generates dispatcher!("goodbye", CMDGoodBye => EntryGoodBye); -/// #[command(name = CMDGoodBye, entry = EntryGoodBye)] +/// // Auto-generates dispatcher!("goodbye", EntryGoodBye); +/// #[command(entry = EntryGoodBye)] /// fn goodbye() -> ResultGoodbye { /// ResultGoodbye::default() /// } @@ -1035,7 +1035,7 @@ pub mod example_command_macro {} /// } /// // --------- IMPORTANT --------- /// -/// dispatcher!("greet", CMDGreet => EntryGreet); +/// dispatcher!("greet", EntryGreet); /// pack!(ResultName = (u8, String)); /// /// #[chain] @@ -1124,7 +1124,7 @@ pub mod example_completion {} /// } /// // --------- IMPORTANT --------- /// -/// dispatcher!("connect", CMDConnect => EntryConnect); +/// dispatcher!("connect", EntryConnect); /// pack!(ErrorParseAddressFailed = ()); /// /// #[chain] @@ -1256,20 +1256,20 @@ pub mod example_custom_pickable {} /// /// // --------- IMPORTANT --------- /// // You have a large number of subcommands -/// dispatcher!("cmd1", CMD1 => Entry1); -/// dispatcher!("cmd2.sub1", CMD2Sub1 => Entry2Sub1); -/// dispatcher!("cmd2.sub2", CMD2Sub2 => Entry2Sub2); -/// dispatcher!("cmd3.sub1.leaf1", CMD3Sub1Leaf1 => Entry3Sub1Leaf1); -/// dispatcher!("cmd3.sub1.leaf2", CMD3Sub1Leaf2 => Entry3Sub1Leaf2); -/// dispatcher!("cmd3.sub2", CMD3Sub2 => Entry3Sub2); -/// dispatcher!("cmd4.sub1.subsub1.deep", CMD4Deep => Entry4Deep); -/// dispatcher!("cmd4.sub1.subsub2", CMD4SubSub2 => Entry4SubSub2); -/// dispatcher!("cmd5", CMD5 => Entry5); -/// dispatcher!("cmd5.extra", CMD5Extra => Entry5Extra); -/// dispatcher!("nested.a.b.c", CMDA => EntryA); -/// dispatcher!("nested.a.b.d", CMDB => EntryB); -/// dispatcher!("nested.a.e", CMDC => EntryC); -/// dispatcher!("nested.f", CMDD => EntryD); +/// dispatcher!("cmd1", Entry1); +/// dispatcher!("cmd2.sub1", Entry2Sub1); +/// dispatcher!("cmd2.sub2", Entry2Sub2); +/// dispatcher!("cmd3.sub1.leaf1", Entry3Sub1Leaf1); +/// dispatcher!("cmd3.sub1.leaf2", Entry3Sub1Leaf2); +/// dispatcher!("cmd3.sub2", Entry3Sub2); +/// dispatcher!("cmd4.sub1.subsub1.deep", Entry4Deep); +/// dispatcher!("cmd4.sub1.subsub2", Entry4SubSub2); +/// dispatcher!("cmd5", Entry5); +/// dispatcher!("cmd5.extra", Entry5Extra); +/// dispatcher!("nested.a.b.c", EntryA); +/// dispatcher!("nested.a.b.d", EntryB); +/// dispatcher!("nested.a.e", EntryC); +/// dispatcher!("nested.f", EntryD); /// // --------- IMPORTANT --------- /// /// fn main() { @@ -1383,7 +1383,7 @@ pub mod example_dispatch_tree {} /// impl PickableEnum for ProgrammingLanguages {} /// // --------- IMPORTANT --------- /// -/// dispatcher!("lang-select", CMDLanguageSelection => EntryLanguageSelection); +/// dispatcher!("lang-select", EntryLanguageSelection); /// /// #[chain] /// fn handle_language_selection(args: EntryLanguageSelection) -> Next { @@ -1457,7 +1457,7 @@ pub mod example_enum_tag {} /// // In Mingling, instead of using ? to propagate errors upward, /// // errors are treated as branches that continue execution. /// -/// dispatcher!("hello", CMDHello => EntryHello); +/// dispatcher!("hello", EntryHello); /// /// // Define error types /// pack!(ErrorNoNameProvided = ()); @@ -1594,7 +1594,7 @@ pub mod example_error_handling {} /// program.exec_and_exit(); /// } /// -/// dispatcher!("hello", CMDHello => EntryHello); +/// dispatcher!("hello", EntryHello); /// /// pack!(ErrorNoNameProvided = ()); /// pack!(ResultName = String); @@ -1676,7 +1676,7 @@ pub mod example_exitcode {} /// use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; /// use std::io::Write; /// -/// dispatcher!("greet", CMDGreet => EntryGreet); +/// dispatcher!("greet", EntryGreet); /// /// // Define help _________ When `program.user_context.help` is `true` /// // / the command will not enter `#[chain]` / `#[renderer]` @@ -1745,7 +1745,7 @@ pub mod example_help {} /// }; /// use std::io::Write; /// -/// dispatcher!("greet", CMDGreet => EntryGreet); +/// dispatcher!("greet", EntryGreet); /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -1819,9 +1819,10 @@ pub mod example_hook {} /// ```ignore /// use mingling::prelude::*; /// -/// // When using implicit syntax, the entry and dispatcher names will be automatically derived -/// dispatcher!("remote.add" /*, CMDRemoteAdd => EntryRemoteAdd */); -/// dispatcher!("remote.remove", CMDRemoteRemove => EntryRemoteRemove); +/// // When using implicit syntax, the entry name will be automatically derived +/// // from the command name (the dispatcher struct is generated internally) +/// dispatcher!("remote.add" /* => EntryRemoteAdd */); +/// dispatcher!("remote.remove", EntryRemoteRemove); /// /// fn main() { /// ThisProgram::new().exec_and_exit(); @@ -1897,8 +1898,8 @@ pub mod example_implicit_dispatcher {} /// ResLargeData { data } /// } /// -/// dispatcher!("show", CMDShow => EntryShow); -/// dispatcher!("none", CMDNone => EntryNone); +/// dispatcher!("show", EntryShow); +/// dispatcher!("none", EntryNone); /// /// pack!(ResultShow = BTreeMap<Key, Value>); /// @@ -1987,13 +1988,13 @@ pub mod example_lazy_resources {} /// use std::io::Write; /// /// // Define the `greet` subcommand -/// dispatcher!("greet", CMDGreet => EntryGreet); +/// dispatcher!("greet", EntryGreet); /// /// // Define the `desc` subcommand, which queries metadata bound to EntryGreet -/// dispatcher!("desc", CMDDescription => EntryDescription); +/// dispatcher!("desc", EntryDescription); /// /// // Define the `nodoc` subcommand, which queries metadata for an entry that has none -/// dispatcher!("nodoc", CMDNoDescription => EntryNoDescription); +/// dispatcher!("nodoc", EntryNoDescription); /// /// fn main() { /// ThisProgram::new().exec_and_exit(); @@ -2249,8 +2250,8 @@ pub mod example_outside_type {} /// use std::io::Write; /// use std::path::PathBuf; /// -/// dispatcher!("find", CMDFind => EntryFind); -/// dispatcher!("find-structural", CMDFindStructural => EntryFindStructural); +/// dispatcher!("find", EntryFind); +/// dispatcher!("find-structural", EntryFindStructural); /// /// // --------- IMPORTANT --------- /// // `pack_err!` is a convenient macro for defining error types. @@ -2414,7 +2415,7 @@ pub mod example_pack_err {} /// use mingling::{hook::ProgramHook, prelude::*}; /// use std::io::Write; /// -/// dispatcher!("panic", CMDPanic => EntryPanic); +/// dispatcher!("panic", EntryPanic); /// pack!(NotPanic = ()); /// /// fn main() { @@ -2611,10 +2612,10 @@ pub mod example_pathfinder {} /// pack!(ErrorDirectoryNotExist = PathBuf); /// /// // Create commands: cd ls exit -/// dispatcher!("cd", CMDCd => EntryCd); -/// dispatcher!("ls", CMDLs => EntryLs); -/// dispatcher!("exit", CMDExit => EntryExit); -/// dispatcher!("clear", CMDClear => EntryClear); +/// dispatcher!("cd", EntryCd); +/// dispatcher!("ls", EntryLs); +/// dispatcher!("exit", EntryExit); +/// dispatcher!("clear", EntryClear); /// /// // Define data needed for the cd command's execution phase /// pack!(StateChangeDirectory = String); @@ -2777,8 +2778,8 @@ pub mod example_repl_basic {} /// program.exec_and_exit(); /// } /// -/// dispatcher!("current", CMDCurrent => EntryCurrent); -/// dispatcher!("modify-current", CMDModifyCurrent => EntryModifyCurrent); +/// dispatcher!("current", EntryCurrent); +/// dispatcher!("modify-current", EntryModifyCurrent); /// /// // Define chain for modifying current directory _________________ Injected muttable resource /// // / @@ -2878,7 +2879,7 @@ pub mod example_resources {} /// } /// // --------- IMPORTANT --------- /// -/// dispatcher!("greet", CMDGreet => EntryGreet); +/// dispatcher!("greet", EntryGreet); /// /// pack!(ResultGreeting = String); /// @@ -2952,7 +2953,7 @@ pub mod example_setup {} /// use serde::Serialize; /// use std::io::Write; /// -/// dispatcher!("render", CMDRender => EntryRender); +/// dispatcher!("render", EntryRender); /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -3085,7 +3086,7 @@ pub mod example_structural_renderer {} /// // --------- IMPORTANT --------- /// } /// -/// dispatcher!("hello", CMDHello => EntryHello); +/// dispatcher!("hello", EntryHello); /// /// pack!(ErrorNoNameProvided = ()); /// pack!(ErrorNameTooLong = u16); diff --git a/mingling/src/gen_program.rs b/mingling/src/gen_program.rs index b1fa118..2c476a2 100644 --- a/mingling/src/gen_program.rs +++ b/mingling/src/gen_program.rs @@ -4,7 +4,6 @@ use mingling_core::ChainProcess; use mingling_core::Dispatcher; use mingling_core::Grouped; -use mingling_core::Node; use mingling_core::Program; use mingling_core::ProgramCollect; @@ -96,18 +95,10 @@ pub struct CompletionSuggest { #[cfg(feature = "comp")] impl Dispatcher<ThisProgram> for CMDCompletion { - fn node(&self) -> mingling_core::Node { - Node::default().join(mingling_core::COMPLETION_SUBCOMMAND) - } - fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> { use mingling_core::AnyOutput; AnyOutput::new(CompletionContext { inner: args }).route_chain() } - - fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> { - todo!() - } } // SAFETY: These implementations are provided for demonstration purposes only. diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index 9d38a2a..3707289 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -88,7 +88,6 @@ pub mod macros { pub use mingling_macros::help; pub use mingling_macros::metadata; pub use mingling_macros::mlint; - pub use mingling_macros::node; pub use mingling_macros::pack; #[cfg(feature = "extras")] pub use mingling_macros::pack_err; diff --git a/mingling_cli/src/linter/cmd_lint.rs b/mingling_cli/src/linter/cmd_lint.rs index 38f2973..07c18c5 100644 --- a/mingling_cli/src/linter/cmd_lint.rs +++ b/mingling_cli/src/linter/cmd_lint.rs @@ -8,7 +8,7 @@ use mingling::picker::{EntryPicker, PickerArg}; use mingling::{LazyRes, ShellContext, Suggest}; use tokio::task::JoinSet; -dispatcher!("lint", CMDLint => EntryLint); +dispatcher!("lint", EntryLint); const ARG_WITH_CHECKER: PickerArg<Option<String>> = arg![with_checker: Option<String>]; diff --git a/mingling_cli/src/linter/cmd_ra_lints.rs b/mingling_cli/src/linter/cmd_ra_lints.rs index 733d611..60b3e88 100644 --- a/mingling_cli/src/linter/cmd_ra_lints.rs +++ b/mingling_cli/src/linter/cmd_ra_lints.rs @@ -8,15 +8,15 @@ use crate::{linter::cmd_lint::EntryLint, metadata::setup::ResUsingJson}; // Aliases dispatcher!("ra-lint-clippy", - CMDLinterSupportRustAnalyzerWithClippy => EntryLinterSupportRustAnalyzerWithClippy + EntryLinterSupportRustAnalyzerWithClippy ); dispatcher!("ra-lint-check", - CMDLinterSupportRustAnalyzerWithCheck => EntryLinterSupportRustAnalyzerWithCheck + EntryLinterSupportRustAnalyzerWithCheck ); dispatcher!("ra-lint", - CMDLinterSupportRustAnalyzer => EntryLinterSupportRustAnalyzer + EntryLinterSupportRustAnalyzer ); #[chain] diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs index 527607a..217aa6d 100644 --- a/mingling_core/src/asset.rs +++ b/mingling_core/src/asset.rs @@ -7,6 +7,5 @@ pub(crate) mod global_resource; pub(crate) mod help; pub(crate) mod lazy_resource; pub(crate) mod metadata; -pub(crate) mod node; pub(crate) mod renderer; pub(crate) mod routable; diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs index 1ab7bf6..79ebee0 100644 --- a/mingling_core/src/asset/dispatcher.rs +++ b/mingling_core/src/asset/dispatcher.rs @@ -1,6 +1,4 @@ -use std::fmt::Display; - -use crate::{ChainProcess, asset::node::Node}; +use crate::ChainProcess; /// The entry logic of the Mingling program /// @@ -14,7 +12,6 @@ use crate::{ChainProcess, asset::node::Node}; /// # use mingling_core::Dispatcher; /// # use mingling_core::Grouped; /// # use mingling_core::Routable; -/// # use mingling_core::Node; /// # use mingling_core::MockProgramCollect as ThisProgram; /// # unsafe impl Grouped<ThisProgram> for Foo { /// # fn member_id() -> ThisProgram { ThisProgram::Foo } @@ -25,54 +22,14 @@ use crate::{ChainProcess, asset::node::Node}; /// } /// /// impl Dispatcher<ThisProgram> for CMDGreet { -/// fn node(&self) -> Node { -/// Node::default().join("greet") -/// } -/// /// fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> { /// Routable::to_chain(Foo { args }) /// } -/// -/// fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> { -/// Box::new(CMDGreet) -/// } /// } /// ``` pub trait Dispatcher<C> { - /// Get the node of this Dispatcher, used to tell the program loop which arguments should be handled by this Dispatcher - /// - /// Example: - /// - /// ``` - /// # use mingling_core::ChainProcess; - /// # use mingling_core::Dispatcher; - /// # use mingling_core::Grouped; - /// # use mingling_core::Routable; - /// # use mingling_core::Node; - /// # use mingling_core::MockProgramCollect as ThisProgram; - /// # unsafe impl Grouped<ThisProgram> for Foo { - /// # fn member_id() -> ThisProgram { ThisProgram::Foo } - /// # } - /// # struct CMDGreet; - /// # struct Foo { - /// # args: Vec<String> - /// # } - /// # impl Dispatcher<ThisProgram> for CMDGreet { - /// fn node(&self) -> Node { - /// // Construct the node - /// Node::default().join("greet") - /// } - /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> { - /// # Routable::to_chain(Foo { args }) - /// # } - /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> { - /// # Box::new(CMDGreet) - /// # } - /// # } - /// ``` - fn node(&self) -> Node; - - /// Begin logic, receives the remaining arguments after the prefix has been stripped + /// Begin logic, receives the remaining arguments after the command prefix + /// has been stripped /// /// Example: /// @@ -81,7 +38,6 @@ pub trait Dispatcher<C> { /// # use mingling_core::Dispatcher; /// # use mingling_core::Grouped; /// # use mingling_core::Routable; - /// # use mingling_core::Node; /// # use mingling_core::MockProgramCollect as ThisProgram; /// # unsafe impl Grouped<ThisProgram> for Foo { /// # fn member_id() -> ThisProgram { ThisProgram::Foo } @@ -91,102 +47,11 @@ pub trait Dispatcher<C> { /// # args: Vec<String> /// # } /// # impl Dispatcher<ThisProgram> for CMDGreet { - /// # fn node(&self) -> Node { - /// # Node::default().join("greet") - /// # } /// fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> { /// // Create Foo from args and route it to the next chain /// Routable::to_chain(Foo { args }) /// } - /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> { - /// # Box::new(CMDGreet) - /// # } /// # } /// ``` fn begin(&self, args: Vec<String>) -> ChainProcess<C>; - - /// Clone the dispatcher's Box for dynamic dispatch - /// - /// Example: - /// - /// ``` - /// # use mingling_core::ChainProcess; - /// # use mingling_core::Dispatcher; - /// # use mingling_core::Grouped; - /// # use mingling_core::Routable; - /// # use mingling_core::Node; - /// # use mingling_core::MockProgramCollect as ThisProgram; - /// # unsafe impl Grouped<ThisProgram> for Foo { - /// # fn member_id() -> ThisProgram { ThisProgram::Foo } - /// # } - /// # struct CMDGreet; - /// # struct Foo { - /// # args: Vec<String> - /// # } - /// # impl Dispatcher<ThisProgram> for CMDGreet { - /// # fn node(&self) -> Node { - /// # Node::default().join("greet") - /// # } - /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> { - /// # Routable::to_chain(Foo { args }) - /// # } - /// fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> { - /// // Create a new Box - /// Box::new(CMDGreet) - /// } - /// # } - /// ``` - fn clone_dispatcher(&self) -> Box<dyn Dispatcher<C>>; -} - -impl<G> Clone for Box<dyn Dispatcher<G>> -where - G: Display, -{ - fn clone(&self) -> Self { - self.clone_dispatcher() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ChainProcess; - use std::fmt::Display; - #[derive(Clone)] - struct MockDispatcher { - name: &'static str, - } - - impl<C: Display> Dispatcher<C> for MockDispatcher { - fn node(&self) -> crate::asset::node::Node { - self.name.into() - } - - fn begin(&self, _args: Vec<String>) -> ChainProcess<C> { - unimplemented!("not used in these tests") - } - - fn clone_dispatcher(&self) -> Box<dyn Dispatcher<C>> { - Box::new(self.clone()) - } - } - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[allow(dead_code)] - enum MockG { - A, - } - - impl Display for MockG { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "A") - } - } - - #[test] - fn test_box_clone_dispatcher() { - let disp: Box<dyn Dispatcher<MockG>> = Box::new(MockDispatcher { name: "clonable" }); - let cloned = disp.clone_dispatcher(); - assert_eq!(cloned.node().to_string(), "clonable"); - } } diff --git a/mingling_core/src/asset/node.rs b/mingling_core/src/asset/node.rs deleted file mode 100644 index ee28836..0000000 --- a/mingling_core/src/asset/node.rs +++ /dev/null @@ -1,157 +0,0 @@ -use just_fmt::kebab_case; - -/// Represents a command node used for matching user-input command paths. -/// -/// The node consists of multiple segments, separated by dots (`.`), which are automatically -/// converted to kebab-case. For example, the input string `"node.subnode"` would be converted -/// to the node representation `["node", "subnode"]`. -/// -/// # Examples -/// -/// ``` -/// use mingling_core::Node; -/// -/// // Create a node and append child segments -/// let node = Node::from("base").join("sub").join("leaf"); -/// assert_eq!(node.to_string(), "base.sub.leaf"); -/// ``` -#[derive(Debug, Default)] -pub struct Node { - node: Vec<String>, -} - -impl Node { - /// Appends a new segment to the node path. - /// - /// This method consumes the current node and returns a new node with the new - /// segment appended to the end of the path. - /// - /// # Examples - /// - /// ``` - /// use mingling_core::Node; - /// - /// let node = Node::from("base").join("sub"); - /// assert_eq!(node.to_string(), "base.sub"); - /// ``` - #[must_use] - pub fn join(self, node: impl Into<String>) -> Self { - let mut new_node = self.node; - new_node.push(node.into()); - Self { node: new_node } - } -} - -impl From<&str> for Node { - fn from(s: &str) -> Self { - let node = s.split('.').map(|part| kebab_case!(part)).collect(); - Self { node } - } -} - -impl From<String> for Node { - fn from(s: String) -> Self { - let node = s.split('.').map(|part| kebab_case!(part)).collect(); - Self { node } - } -} - -impl PartialEq for Node { - fn eq(&self, other: &Self) -> bool { - self.node == other.node - } -} - -impl Eq for Node {} - -impl PartialOrd for Node { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for Node { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.node.cmp(&other.node) - } -} - -impl std::fmt::Display for Node { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.node.join(".")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_node_from_single_str() { - let node = Node::from("hello"); - assert_eq!(node.node, vec!["hello"]); - assert_eq!(node.to_string(), "hello"); - } - - #[test] - fn test_node_from_dotted_str() { - let node = Node::from("a.b.c"); - assert_eq!(node.node, vec!["a", "b", "c"]); - assert_eq!(node.to_string(), "a.b.c"); - } - - #[test] - fn test_node_kebab_case_conversion() { - let node = Node::from("HelloWorld.FooBar"); - assert_eq!(node.node, vec!["hello-world", "foo-bar"]); - } - - #[test] - fn test_node_from_string() { - let s = String::from("x.y"); - let node = Node::from(s); - assert_eq!(node.node, vec!["x", "y"]); - } - - #[test] - fn test_node_join() { - let node = Node::from("base").join("sub"); - assert_eq!(node.node, vec!["base", "sub"]); - } - - #[test] - fn test_node_join_multiple() { - let node = Node::from("a").join("b").join("c"); - assert_eq!(node.to_string(), "a.b.c"); - } - - #[test] - fn test_node_default_empty() { - let node = Node::default(); - assert!(node.node.is_empty()); - assert_eq!(node.to_string(), ""); - } - - #[test] - fn test_node_partial_eq() { - let a = Node::from("a.b"); - let b = Node::from("a.b"); - let c = Node::from("a.c"); - assert_eq!(a, b); - assert_ne!(a, c); - } - - #[test] - fn test_node_ord() { - let a = Node::from("a"); - let b = Node::from("b"); - assert!(a < b); - } - - #[test] - fn test_node_join_appends_part() { - let node = Node::from("existing"); - let joined = node.join("new-part"); - assert_eq!(joined.to_string(), "existing.new-part"); - } -} diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs index 641118c..2aa2ce1 100644 --- a/mingling_core/src/lib.rs +++ b/mingling_core/src/lib.rs @@ -75,7 +75,6 @@ pub use crate::asset::global_resource::*; pub use crate::asset::help::*; pub use crate::asset::lazy_resource::*; pub use crate::asset::metadata::*; -pub use crate::asset::node::*; pub use crate::asset::renderer::*; pub use crate::asset::routable::*; #[cfg(feature = "comp")] diff --git a/mingling_core/tests/test-all/tests/integration.rs b/mingling_core/tests/test-all/tests/integration.rs index 936ca52..29cc910 100644 --- a/mingling_core/tests/test-all/tests/integration.rs +++ b/mingling_core/tests/test-all/tests/integration.rs @@ -1,6 +1,5 @@ use mingling::Flag; use mingling::NextProcess; -use mingling::Node; use mingling::Program; use mingling::RenderResult; use mingling::StringVec; @@ -44,20 +43,6 @@ fn test_res_repl_default() { assert!(!res.exit); } -// Node - -#[test] -fn test_node_creation() { - let node = Node::from("a.b.c"); - assert_eq!(node.to_string(), "a.b.c"); -} - -#[test] -fn test_node_kebab() { - let node = Node::from("HelloWorld.FooBar"); - assert_eq!(node.to_string(), "hello-world.foo-bar"); -} - // Flag #[test] diff --git a/mingling_core/tests/test-basic/tests/integration.rs b/mingling_core/tests/test-basic/tests/integration.rs index e51992c..767ef00 100644 --- a/mingling_core/tests/test-basic/tests/integration.rs +++ b/mingling_core/tests/test-basic/tests/integration.rs @@ -1,37 +1,9 @@ use mingling::Flag; use mingling::NextProcess; -use mingling::Node; use mingling::RenderResult; use mingling::StringVec; #[test] -fn test_node_from_str() { - let node = Node::from("a.b.c"); - assert_eq!(node.to_string(), "a.b.c"); -} - -#[test] -fn test_node_kebab_case() { - let node = Node::from("HelloWorld.FooBar"); - assert_eq!(node.to_string(), "hello-world.foo-bar"); -} - -#[test] -fn test_node_join() { - let node = Node::from("base").join("sub"); - assert_eq!(node.to_string(), "base.sub"); -} - -#[test] -fn test_node_eq() { - let a = Node::from("x.y"); - let b = Node::from("x.y"); - let c = Node::from("x.z"); - assert_eq!(a, b); - assert_ne!(a, c); -} - -#[test] fn test_flag_from_static_str() { let flag = Flag::from("-h"); assert_eq!(flag.as_ref(), &["-h"]); diff --git a/mingling_core/tests/test-dispatch-tree/tests/integration.rs b/mingling_core/tests/test-dispatch-tree/tests/integration.rs index e51992c..767ef00 100644 --- a/mingling_core/tests/test-dispatch-tree/tests/integration.rs +++ b/mingling_core/tests/test-dispatch-tree/tests/integration.rs @@ -1,37 +1,9 @@ use mingling::Flag; use mingling::NextProcess; -use mingling::Node; use mingling::RenderResult; use mingling::StringVec; #[test] -fn test_node_from_str() { - let node = Node::from("a.b.c"); - assert_eq!(node.to_string(), "a.b.c"); -} - -#[test] -fn test_node_kebab_case() { - let node = Node::from("HelloWorld.FooBar"); - assert_eq!(node.to_string(), "hello-world.foo-bar"); -} - -#[test] -fn test_node_join() { - let node = Node::from("base").join("sub"); - assert_eq!(node.to_string(), "base.sub"); -} - -#[test] -fn test_node_eq() { - let a = Node::from("x.y"); - let b = Node::from("x.y"); - let c = Node::from("x.z"); - assert_eq!(a, b); - assert_ne!(a, c); -} - -#[test] fn test_flag_from_static_str() { let flag = Flag::from("-h"); assert_eq!(flag.as_ref(), &["-h"]); diff --git a/mingling_core/tests/test-repl/tests/integration.rs b/mingling_core/tests/test-repl/tests/integration.rs index 4de0e8f..3e1ca7c 100644 --- a/mingling_core/tests/test-repl/tests/integration.rs +++ b/mingling_core/tests/test-repl/tests/integration.rs @@ -1,5 +1,4 @@ use mingling::Flag; -use mingling::Node; use mingling::RenderResult; use mingling::core_res::ResREPL; @@ -18,20 +17,6 @@ fn test_res_repl_exit_true() { assert!(res.exit); } -// Node tests - -#[test] -fn test_node_from_str() { - let node = Node::from("a.b.c"); - assert_eq!(node.to_string(), "a.b.c"); -} - -#[test] -fn test_node_kebab_case() { - let node = Node::from("HelloWorld.FooBar"); - assert_eq!(node.to_string(), "hello-world.foo-bar"); -} - // Flag tests #[test] diff --git a/mingling_macros/src/attr/command.rs b/mingling_macros/src/attr/command.rs index 4542bd7..6f2409c 100644 --- a/mingling_macros/src/attr/command.rs +++ b/mingling_macros/src/attr/command.rs @@ -12,12 +12,10 @@ use syn::{FnArg, Ident, ItemFn, LitStr, PatType, Token, Type}; /// /// Supports: /// - `node = "dot.separated.path"` — explicit command path -/// - `name = CMDName` — explicit CMD struct name /// - `entry = EntryName` — explicit Entry struct name /// - bare paths like `routeify`, `::mingling::macros::routeify` — extension attrs for the original fn struct CommandArgs { node: Option<LitStr>, - name: Option<Ident>, entry: Option<Ident>, exts: Vec<syn::Path>, } @@ -25,7 +23,6 @@ struct CommandArgs { impl Parse for CommandArgs { fn parse(input: ParseStream) -> syn::Result<Self> { let mut node = None; - let mut name = None; let mut entry = None; let mut exts = Vec::new(); @@ -41,19 +38,18 @@ impl Parse for CommandArgs { } node = Some(input.parse()?); } else if key == "name" { - if name.is_some() { - return Err(input.error("duplicate `name` argument")); - } - name = Some(input.parse()?); + return Err(input.error( + "`name = ...` was removed in 0.5.0; the dispatcher struct is generated internally", + )); } else if key == "entry" { if entry.is_some() { return Err(input.error("duplicate `entry` argument")); } entry = Some(input.parse()?); } else { - return Err(input.error(format!( - "unknown key `{key}`; expected `node`, `name`, or `entry`" - ))); + return Err( + input.error(format!("unknown key `{key}`; expected `node` or `entry`")) + ); } } else { // Extension path (e.g. `routeify` or `::mingling::macros::routeify`) @@ -67,12 +63,7 @@ impl Parse for CommandArgs { } } - Ok(Self { - node, - name, - entry, - exts, - }) + Ok(Self { node, entry, exts }) } } @@ -127,17 +118,15 @@ fn handle_async(f: &ItemFn) -> Result<(TokenStream2, TokenStream2), TokenStream2 struct ResolvedNames { /// `node_str` as a string literal token node_lit: LitStr, - /// Whether the user supplied any explicit override (node/name/entry) + /// Whether the user supplied any explicit override (node/entry) has_overrides: bool, - /// CMD struct name (e.g. `CMDGreet`) - cmd_name: Ident, /// Entry struct name (e.g. `EntryGreet`) entry_type: Ident, /// Chain wrapper function name (e.g. `__command_chain_greet`) chain_fn_name: Ident, } -/// Resolves `node`, `cmd_name`, `entry_type`, and `chain_fn_name` from +/// Resolves `node`, `entry_type`, and `chain_fn_name` from /// the attribute args and the original function name. fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames { let fn_name_str = fn_name.to_string(); @@ -148,12 +137,7 @@ fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames { .map_or_else(|| default_node_from_fn(fn_name), syn::LitStr::value); let node_lit = syn::LitStr::new(&node_str, fn_name.span()); - let has_overrides = args.node.is_some() || args.name.is_some() || args.entry.is_some(); - - let cmd_name = args.name.clone().unwrap_or_else(|| { - let pascal = just_fmt::pascal_case!(&node_str); - Ident::new(&format!("CMD{pascal}"), fn_name.span()) - }); + let has_overrides = args.node.is_some() || args.entry.is_some(); let entry_type = args.entry.clone().unwrap_or_else(|| { let pascal = just_fmt::pascal_case!(&node_str); @@ -165,7 +149,6 @@ fn resolve_names(fn_name: &Ident, args: &CommandArgs) -> ResolvedNames { ResolvedNames { node_lit, has_overrides, - cmd_name, entry_type, chain_fn_name, } @@ -247,13 +230,12 @@ fn build_call_args(sig: &syn::Signature) -> Vec<TokenStream2> { /// Generates the `dispatcher!(...)` call. /// /// - No overrides → abbreviated form: `dispatcher!("node")` -/// - Any override → explicit form: `dispatcher!("node", CMDName => EntryName)` +/// - Any override → explicit form: `dispatcher!("node", EntryType)` fn build_dispatcher_invoke(names: &ResolvedNames) -> TokenStream2 { let node_lit = &names.node_lit; if names.has_overrides { - let cmd_name = &names.cmd_name; let entry_type = &names.entry_type; - quote! { ::mingling::macros::dispatcher!(#node_lit, #cmd_name => #entry_type); } + quote! { ::mingling::macros::dispatcher!(#node_lit, #entry_type); } } else { quote! { ::mingling::macros::dispatcher!(#node_lit); } } @@ -271,7 +253,6 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream let args: CommandArgs = if attr.is_empty() { CommandArgs { node: None, - name: None, entry: None, exts: Vec::new(), } @@ -332,7 +313,13 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream quote! { #vis use super::#ident; } }; - let cmd_name = &names.cmd_name; + // hidden dispatcher struct generated by the internal `dispatcher!` call + let hidden_dispatcher = { + let node_str = names.node_lit.value(); + let pascal = just_fmt::pascal_case!(&node_str); + Ident::new(&format!("__Dispatcher{pascal}"), fn_name.span()) + }; + let entry_type = &names.entry_type; // assemble output @@ -351,7 +338,7 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream // hidden module gathering all generated types for pathf / external access #[doc(hidden)] #vis mod #mod_name { - #vis use super::#cmd_name; + #vis use super::#hidden_dispatcher; #vis use super::#entry_type; #vis use super::#chain_internal; #dispatcher_internal diff --git a/mingling_macros/src/attr/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs index 2f45b14..46238d6 100644 --- a/mingling_macros/src/attr/dispatcher_clap.rs +++ b/mingling_macros/src/attr/dispatcher_clap.rs @@ -30,7 +30,13 @@ impl Parse for ClapOptions { } let key: Ident = input.parse()?; - input.parse::<Token![=]>()?; + if input.parse::<Token![=]>().is_err() { + return Err(syn::Error::new( + key.span(), + "expected `key = value`; note: the explicit CMD struct argument \ + was removed in 0.5.0, use `dispatcher_clap!(\"name\", help = ..., error = ...)`", + )); + } if key == "error" { let value: Ident = input.parse()?; @@ -63,18 +69,15 @@ impl Parse for ClapOptions { /// Input for the `dispatcher_clap` attribute struct DispatcherClapInput { - /// `("cmd", Disp, ...)` + /// `("cmd", options...)` command_name: LitStr, - dispatcher_struct: Ident, options: ClapOptions, } impl Parse for DispatcherClapInput { fn parse(input: ParseStream) -> syn::Result<Self> { - // Format: "cmd", Disp, ... + // Format: "cmd", options... let command_name: LitStr = input.parse()?; - input.parse::<Token![,]>()?; - let dispatcher_struct: Ident = input.parse()?; let options = if input.is_empty() { ClapOptions { @@ -87,13 +90,13 @@ impl Parse for DispatcherClapInput { Ok(Self { command_name, - dispatcher_struct, options, }) } } #[cfg(feature = "clap")] +#[allow(clippy::too_many_lines)] pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let attr_input = parse_macro_input!(attr as DispatcherClapInput); let input_struct = parse_macro_input!(item as ItemStruct); @@ -102,7 +105,12 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke let program_path = crate::default_program_path(); let command_name_str = attr_input.command_name.value(); - let dispatcher_struct = &attr_input.dispatcher_struct; + + // The dispatcher struct is now generated internally. + let dispatcher_struct = Ident::new( + &format!("__Dispatcher{}", just_fmt::pascal_case!(&command_name_str)), + attr_input.command_name.span(), + ); let options = &attr_input.options; // Generate the `begin` method body @@ -141,8 +149,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke // Generate the #[help] block if help = true let help_gen = if options.help_enabled { - let dispatcher_name_str = dispatcher_struct.to_string(); - let help_fn_name_str = format!("__{}_help", just_fmt::snake_case!(&dispatcher_name_str)); + let help_fn_name_str = format!("__{}_help", just_fmt::snake_case!(&command_name_str)); let help_fn_name = Ident::new(&help_fn_name_str, proc_macro2::Span::call_site()); Some(quote! { @@ -175,7 +182,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke }; let compile_time_registration = - get_compile_time_registration(&command_name_str, dispatcher_struct, struct_name); + get_compile_time_registration(&command_name_str, &dispatcher_struct, struct_name); let expanded = quote! { // Keep the original struct definition @@ -196,10 +203,6 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke pub(crate) struct #dispatcher_struct; impl ::mingling::Dispatcher<#program_path> for #dispatcher_struct { - fn node(&self) -> ::mingling::Node { - ::mingling::macros::node!(#command_name_str) - } - fn begin( &self, args: Vec<String>, @@ -211,12 +214,6 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke #begin_body } - - fn clone_dispatcher( - &self, - ) -> Box<dyn ::mingling::Dispatcher<#program_path>> { - Box::new(#dispatcher_struct) - } } }; diff --git a/mingling_macros/src/func.rs b/mingling_macros/src/func.rs index 834fa7a..57d0ec5 100644 --- a/mingling_macros/src/func.rs +++ b/mingling_macros/src/func.rs @@ -9,7 +9,6 @@ pub(crate) mod gen_program; pub(crate) mod group; #[cfg(all(feature = "structural_renderer", feature = "extras"))] pub(crate) mod group_structural; -pub(crate) mod node; pub(crate) mod pack; #[cfg(feature = "extras")] pub(crate) mod pack_err; diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs index 6183993..c4d67a9 100644 --- a/mingling_macros/src/func/dispatcher.rs +++ b/mingling_macros/src/func/dispatcher.rs @@ -6,13 +6,14 @@ use syn::parse::{Parse, ParseStream}; use syn::{Attribute, Ident, LitStr, Token}; enum DispatcherChainInput { + /// `dispatcher!("name", EntryType)` — explicit entry type Default { cmd_attrs: Vec<Attribute>, entry_attrs: Vec<Attribute>, command_name: syn::LitStr, - command_struct: Ident, pack: Ident, }, + /// `dispatcher!("name")` — entry type derived from the command name #[cfg(feature = "extras")] Auto { cmd_attrs: Vec<Attribute>, @@ -22,106 +23,107 @@ enum DispatcherChainInput { impl Parse for DispatcherChainInput { fn parse(input: ParseStream) -> syn::Result<Self> { - // Collect outer attributes for the CMD struct + // Collect outer attributes for the hidden dispatcher struct let cmd_attrs = input.call(Attribute::parse_outer)?; - if input.peek(syn::LitStr) { - // Parse the command name string first - let command_name: LitStr = input.parse()?; - - // Check if this is the abbreviated form: just "command_name" without ", CMD => Entry" - if input.is_empty() { - #[cfg(feature = "extras")] - { - return Ok(Self::Auto { - cmd_attrs, - command_name, - }); - } - #[cfg(not(feature = "extras"))] - { - return Err(syn::Error::new( - command_name.span(), - "expected `, CommandStruct => EntryStruct` after command name", - )); - } + let command_name: LitStr = input.parse()?; + + if input.is_empty() { + // Abbreviated form: just "command_name" + #[cfg(feature = "extras")] + { + return Ok(Self::Auto { + cmd_attrs, + command_name, + }); + } + #[cfg(not(feature = "extras"))] + { + return Err(syn::Error::new( + command_name.span(), + "expected `, EntryType` after command name", + )); } + } - // Default format: "command_name", CommandStruct => ChainStruct - input.parse::<Token![,]>()?; - let command_struct = input.parse()?; - input.parse::<Token![=>]>()?; - let entry_attrs = input.call(Attribute::parse_outer)?; - let pack = input.parse()?; - - Ok(Self::Default { - cmd_attrs, - entry_attrs, - command_name, - command_struct, - pack, - }) - } else { - Err(input.lookahead1().error()) + // Explicit form: "command_name", EntryType + input.parse::<Token![,]>()?; + let entry_attrs = input.call(Attribute::parse_outer)?; + let pack: Ident = input.parse()?; + + // The old `"name", CMD => Entry` form was removed in 0.5.0. + if input.peek(Token![=>]) { + return Err(syn::Error::new( + pack.span(), + "the `dispatcher!(\"name\", CMD => Entry)` form was removed in 0.5.0; \ + use `dispatcher!(\"name\", Entry)` — the dispatcher struct is generated internally", + )); } + + Ok(Self::Default { + cmd_attrs, + entry_attrs, + command_name, + pack, + }) } } -// NOTICE: The token stream generation patterns in `dispatcher_chain` and `dispatcher_render` -// are nearly identical and could benefit from refactoring into common helper functions. - -#[allow(clippy::too_many_lines)] pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { - // Parse the input let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput); #[cfg(not(feature = "extras"))] - let (command_name, command_struct, pack, cmd_attrs, entry_attrs) = match dispatcher_input { + let (command_name, pack, cmd_attrs, entry_attrs) = match dispatcher_input { DispatcherChainInput::Default { cmd_attrs, entry_attrs, command_name, - command_struct, pack, - } => (command_name, command_struct, pack, cmd_attrs, entry_attrs), + } => (command_name, pack, cmd_attrs, entry_attrs), }; #[cfg(feature = "extras")] - let (command_name, command_struct, pack, cmd_attrs, entry_attrs) = match dispatcher_input { + let (command_name, pack, cmd_attrs, entry_attrs) = match dispatcher_input { DispatcherChainInput::Default { cmd_attrs, entry_attrs, command_name, - command_struct, pack, - } => (command_name, command_struct, pack, cmd_attrs, entry_attrs), + } => (command_name, pack, cmd_attrs, entry_attrs), DispatcherChainInput::Auto { cmd_attrs, command_name, } => { let command_name_str = command_name.value(); let pascal = just_fmt::pascal_case!(&command_name_str); - let command_struct = Ident::new(&format!("CMD{pascal}"), command_name.span()); let pack = Ident::new(&format!("Entry{pascal}"), command_name.span()); - (command_name, command_struct, pack, cmd_attrs, Vec::new()) + (command_name, pack, cmd_attrs, Vec::new()) } }; let command_name_str = command_name.value(); + let hidden_dispatcher = Ident::new( + &format!("__Dispatcher{}", just_fmt::pascal_case!(&command_name_str)), + command_name.span(), + ); let comp_entry = get_comp_entry(&pack); let compile_time_registration = - get_compile_time_registration(&command_name_str, &command_struct, &pack); + get_compile_time_registration(&command_name_str, &hidden_dispatcher, &pack); let program_type = crate::default_program_path(); let expanded = quote! { + ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec<String>); + #(#cmd_attrs)* + #[doc(hidden)] #[derive(Debug, Default)] - pub struct #command_struct; + #[allow(nonstandard_style)] + pub struct #hidden_dispatcher; - ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec<String>); + #compile_time_registration impl From<#pack> for crate::Entry { fn from(value: #pack) -> Self { @@ -130,19 +132,12 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { } #comp_entry - #compile_time_registration - impl ::mingling::Dispatcher<#program_type> for #command_struct { - fn node(&self) -> ::mingling::Node { - ::mingling::macros::node!(#command_name_str) - } + impl ::mingling::Dispatcher<#program_type> for #hidden_dispatcher { fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess<#program_type> { use ::mingling::Grouped; ::mingling::Routable::to_chain(#pack::new(args)) } - fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> { - Box::new(#command_struct) - } } }; @@ -173,11 +168,11 @@ fn get_comp_entry(_entry_name: &Ident) -> TokenStream2 { /// (trie vs. linear list) is generated later by `gen_program!`. fn get_compile_time_registration( command_name_str: &str, - command_struct: &Ident, + dispatcher_struct: &Ident, entry_name: &Ident, ) -> TokenStream2 { let node_name_lit = syn::LitStr::new(command_name_str, proc_macro2::Span::call_site()); quote! { - ::mingling::macros::register_dispatcher!(#node_name_lit, #command_struct, #entry_name); + ::mingling::macros::register_dispatcher!(#node_name_lit, #dispatcher_struct, #entry_name); } } diff --git a/mingling_macros/src/func/node.rs b/mingling_macros/src/func/node.rs deleted file mode 100644 index 9963037..0000000 --- a/mingling_macros/src/func/node.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Doc Not Optimize -use just_fmt::kebab_case; -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::{LitStr, Result as SynResult}; - -/// Parses a string literal input for the node macro -struct NodeInput { - path: LitStr, -} - -impl Parse for NodeInput { - fn parse(input: ParseStream) -> SynResult<Self> { - Ok(Self { - path: input.parse()?, - }) - } -} - -pub(crate) fn node(input: TokenStream) -> TokenStream { - // Parse the input as a string literal - let input_parsed = syn::parse_macro_input!(input as NodeInput); - let path_str = input_parsed.path.value(); - - // If the input string is empty, return an empty Node - if path_str.is_empty() { - return quote! { - mingling::Node::default() - } - .into(); - } - - // Split the path by dots - let parts: Vec<String> = path_str - .split('.') - .map(|s| { - if s.starts_with('_') { - s.to_string() - } else { - kebab_case!(s) - } - }) - .collect(); - - // Build the expression starting from Node::default() - let mut expr: TokenStream2 = quote! { - mingling::Node::default() - }; - - // Add .join() calls for each part of the path - for part in parts { - expr = quote! { - #expr.join(#part) - }; - } - - expr.into() -} diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs index 7f77d46..ed04379 100644 --- a/mingling_macros/src/func/program_comp_gen.rs +++ b/mingling_macros/src/func/program_comp_gen.rs @@ -42,13 +42,14 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { let internal_dispatcher_comp = quote! { use __internal_completion_mod::__internal_dispatcher_comp; + use __internal_completion_mod::__DispatcherComp; }; let comp_dispatcher = quote! { #[doc(hidden)] mod __internal_completion_mod { use ::mingling::Grouped; - ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext); + ::mingling::macros::dispatcher!("__comp", CompletionContext); ::mingling::macros::pack!( CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) ); @@ -56,7 +57,6 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { #internal_dispatcher_comp use __internal_completion_mod::CompletionContext; use __internal_completion_mod::CompletionSuggest; - pub use __internal_completion_mod::CMDCompletion; #fn_exec_comp diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index ad84548..607ba9f 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -42,7 +42,7 @@ pub(crate) use func::group as group_impl; use func::pack_err; #[cfg(feature = "comp")] use func::suggest; -use func::{dispatcher, node, pack}; +use func::{dispatcher, pack}; use systems::res_injection; pub(crate) fn default_program_path() -> proc_macro2::TokenStream { quote::quote! { crate::ThisProgram } @@ -183,47 +183,6 @@ pub fn group_structural(input: TokenStream) -> TokenStream { func::group_structural::group_structural(input) } -/// Creates a `Node` from a dot-separated path string. -/// -/// Each segment is converted to kebab-case (unless it starts with `_`). -/// Segments are joined via `.join()` calls, building a path hierarchy for -/// command matching. -/// -/// # Syntax -/// -/// ```rust,ignore -/// node!("subcommand") -/// node!("sub.subsub") -/// node!("") // empty → Node::default() -/// ``` -/// -/// # Example -/// -/// ```rust,ignore -/// use mingling::macros::node; -/// -/// // Creates a single-level node for "hello" -/// let n = node!("hello"); -/// -/// // Creates a two-level node for "remote control" -/// let n = node!("remote.control"); -/// ``` -/// -/// # Internals -/// -/// The generated code is equivalent to: -/// ```rust,ignore -/// Node::default().join("hello") -/// Node::default().join("remote").join("control") -/// ``` -/// -/// This macro is typically used internally by `dispatcher!` and should rarely -/// need to be called directly. -#[proc_macro] -pub fn node(input: TokenStream) -> TokenStream { - node::node(input) -} - /// Creates a type-safe wrapper struct around an inner type, with automatic /// trait implementations for use in the Mingling chain/render pipeline. /// @@ -598,10 +557,8 @@ pub fn empty_result(input: TokenStream) -> TokenStream { /// /// 1. **Entry struct** — A `pack!`-style wrapper around `Vec<String>` (the raw args). /// Registered in the program enum via `register_type!`. -/// 2. **Dispatcher struct** — A zero-sized struct implementing [`Dispatcher<Program>`]: -/// - `node()` returns the [`Node`] hierarchy for the command path. +/// 2. **Dispatcher struct** — A hidden zero-sized struct implementing [`Dispatcher<Program>`]: /// - `begin(args)` wraps `args` into the entry type and routes to chain. -/// - `clone_dispatcher()` returns a boxed clone. /// 3. **Registration** — Calls `register_dispatcher!` to collect the command /// at compile time (the `dispatch_tree` feature only selects the matching /// strategy generated later by `gen_program!`). @@ -612,12 +569,10 @@ pub fn empty_result(input: TokenStream) -> TokenStream { /// # See also /// /// - `dispatcher_clap!` — For clap-powered argument parsing. -/// - `node!` — For building custom [`Node`] paths. /// - [`#[chain]`](attr.chain.html) — For processing the dispatched entry. /// /// [`ChainProcess`]: https://docs.rs/mingling/latest/mingling/enum.ChainProcess.html /// [`Dispatcher<Program>`]: https://docs.rs/mingling/latest/mingling/trait.Dispatcher.html -/// [`Node`]: https://docs.rs/mingling/latest/mingling/struct.Node.html #[proc_macro] pub fn dispatcher(input: TokenStream) -> TokenStream { dispatcher::dispatcher(input) diff --git a/mingling_pathf/src/patterns/dispatcher.rs b/mingling_pathf/src/patterns/dispatcher.rs index 5090052..859f198 100644 --- a/mingling_pathf/src/patterns/dispatcher.rs +++ b/mingling_pathf/src/patterns/dispatcher.rs @@ -2,12 +2,12 @@ //! The `DispatcherPattern` matches invocations of the `dispatcher!` macro and //! extracts the generated type names from its arguments. It supports: //! - `Entry*` — the entry type (always generated) -//! - `CMD*` — the dispatcher struct (always generated) +//! - `__Dispatcher*` — the hidden dispatcher struct (always generated) //! - `__internal_dispatcher_*` — the compile-time collected static (always generated) //! //! Supported forms: -//! - Explicit: `dispatcher!("greet", CMDGreet => EntryGreet)` -//! - Implicit: `dispatcher!("greet")` — infers `CMDGreet` and `EntryGreet` +//! - Explicit: `dispatcher!("greet", EntryGreet)` +//! - Implicit: `dispatcher!("greet")` — infers `EntryGreet` //! - With braces: `dispatcher! { ... }` //! //! This pattern is used to track dispatcher types for code generation or analysis. @@ -18,7 +18,7 @@ use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern}; /// Matches the `dispatcher!` macro, extracts: /// - `Entry*` — the entry type (always) -/// - `CMD*` — the dispatcher struct (always) +/// - `__Dispatcher*` — the hidden dispatcher struct (always) /// - `__internal_dispatcher_*` — the compile-time collected static (always) #[derive(Default)] pub struct DispatcherPattern; @@ -90,22 +90,20 @@ fn macro_simple_name(m: &syn::ItemMacro) -> String { /// Extracts all types generated by a `dispatcher!` call. fn extract_all_types(tokens: &proc_macro2::TokenStream, module: &str) -> Vec<AnalyzeItem> { - let (cmd_name, cmd_struct, entry_struct) = parse_dispatcher_args(tokens); + let (cmd_name, entry_struct) = parse_dispatcher_args(tokens); let Some(cmd_name) = cmd_name else { return Vec::new(); }; let mut items = Vec::new(); - // Entry type — always - if let Some(ref entry) = entry_struct { - items.push(AnalyzeItem::local(module.to_string(), entry.clone())); - } + // Entry type — always (derived from the command name in the implicit form) + let entry = entry_struct.unwrap_or_else(|| format!("Entry{}", to_pascal_case(&cmd_name))); + items.push(AnalyzeItem::local(module.to_string(), entry)); - // CMD type — always - if let Some(ref cmd) = cmd_struct { - items.push(AnalyzeItem::local(module.to_string(), cmd.clone())); - } + // Hidden dispatcher struct — always + let hidden_name = format!("__Dispatcher{}", to_pascal_case(&cmd_name)); + items.push(AnalyzeItem::local(module.to_string(), hidden_name)); // __internal_dispatcher_* — the compile-time collected static let internal_name = format!("__internal_dispatcher_{}", snake_case(&cmd_name)); @@ -114,53 +112,28 @@ fn extract_all_types(tokens: &proc_macro2::TokenStream, module: &str) -> Vec<Ana items } -/// Parses dispatcher arguments and returns (`command_name`, `cmd_struct`, `entry_struct`). -fn parse_dispatcher_args( - tokens: &proc_macro2::TokenStream, -) -> (Option<String>, Option<String>, Option<String>) { +/// Parses dispatcher arguments and returns (`command_name`, `entry_struct`). +fn parse_dispatcher_args(tokens: &proc_macro2::TokenStream) -> (Option<String>, Option<String>) { let stream = tokens.to_string(); - // Explicit form: "name", CMDType => EntryType - if let Some(arrow_idx) = stream.find("=>") { - // Extract command name - let before_arrow = &stream[..arrow_idx]; - let cmd_name = extract_string_literal(before_arrow); - - // Extract CMD type: the ident before `=>` - let before_arrow_trimmed = before_arrow.trim(); - let cmd_type = before_arrow_trimmed - .split(|c: char| c.is_whitespace() || c == ',') - .filter_map(|s| { - let s = s.trim(); - if s.starts_with('"') || s.is_empty() { - None - } else { - Some(s.to_string()) - } - }) - .next_back(); - - // Extract entry type: after `=>` - let after_arrow = stream[arrow_idx + 2..].trim(); - let entry_type = after_arrow - .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '}') - .next() - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()); - - return (cmd_name, cmd_type, entry_type); - } - - // Implicit form: "name" let Some(cmd_name) = extract_string_literal(&stream) else { - return (None, None, None); + return (None, None); }; - let pascal = to_pascal_case(&cmd_name); - ( - Some(cmd_name), - Some(format!("CMD{pascal}")), - Some(format!("Entry{pascal}")), - ) + + // Explicit form: "name", EntryType — the entry is the first bare + // identifier after the string literal. (The old `CMD => Entry` form is + // no longer supported.) + let after_lit = { + let start = stream.find('"').unwrap_or_default(); + &stream[start + cmd_name.len() + 2..] + }; + let entry_type = after_lit + .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '}' || c == '=') + .map(str::trim) + .find(|s| !s.is_empty() && !s.starts_with('"')) + .map(str::to_string); + + (Some(cmd_name), entry_type) } /// Extracts the first string literal from a token string. diff --git a/mingling_pathf/src/patterns/dispatcher_clap.rs b/mingling_pathf/src/patterns/dispatcher_clap.rs index da9da3e..cd7bf67 100644 --- a/mingling_pathf/src/patterns/dispatcher_clap.rs +++ b/mingling_pathf/src/patterns/dispatcher_clap.rs @@ -2,16 +2,16 @@ //! The `DispatcherClapPattern` matches structs annotated with `#[dispatcher_clap(...)]` and //! extracts key items for code generation or analysis: //! - The entry struct name (always) -//! - The dispatcher command struct (`CMD*`, always) +//! - The hidden dispatcher struct (`__Dispatcher*`, always) //! - The error type, if `error = ErrorType` is specified //! - The help internal struct, if `help = true` is specified //! - The `__internal_dispatcher_*` compile-time collected static (always) //! //! Supported forms: -//! - `#[dispatcher_clap("greet", CMDGreet)] struct EntryGreet { ... }` -//! - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet)] struct EntryGreet { ... }` -//! - `#[dispatcher_clap("greet", CMDGreet, help = true)] struct EntryGreet { ... }` -//! - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet, help = true)] struct EntryGreet { ... }` +//! - `#[dispatcher_clap("greet")] struct EntryGreet { ... }` +//! - `#[dispatcher_clap("greet", error = ErrorGreet)] struct EntryGreet { ... }` +//! - `#[dispatcher_clap("greet", help = true)] struct EntryGreet { ... }` +//! - `#[dispatcher_clap("greet", error = ErrorGreet, help = true)] struct EntryGreet { ... }` use syn::Item; @@ -19,16 +19,16 @@ use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern}; /// Match structs annotated with `#[dispatcher_clap(...)]`, extracting: /// - The entry type (struct name, always) -/// - The dispatcher struct (`CMD*`, always) +/// - The hidden dispatcher struct (`__Dispatcher*`, always) /// - The error type, if `error = ErrorType` is specified /// - The help internal struct, if `help = true` is specified /// - `__internal_dispatcher_*` — compile-time collected static (always) /// /// Covers forms: -/// - `#[dispatcher_clap("greet", CMDGreet)] struct EntryGreet { ... }` -/// - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet)] struct EntryGreet { ... }` -/// - `#[dispatcher_clap("greet", CMDGreet, help = true)] struct EntryGreet { ... }` -/// - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet, help = true)] struct EntryGreet { ... }` +/// - `#[dispatcher_clap("greet")] struct EntryGreet { ... }` +/// - `#[dispatcher_clap("greet", error = ErrorGreet)] struct EntryGreet { ... }` +/// - `#[dispatcher_clap("greet", help = true)] struct EntryGreet { ... }` +/// - `#[dispatcher_clap("greet", error = ErrorGreet, help = true)] struct EntryGreet { ... }` #[derive(Default)] pub struct DispatcherClapPattern; @@ -95,9 +95,10 @@ impl DispatcherClapPattern { let args_str = args.map(|l| l.tokens.to_string()).unwrap_or_default(); let parsed = parse_dispatcher_clap_args(&args_str); - // CMD type — always - if let Some(ref cmd) = parsed.cmd_type { - items.push(AnalyzeItem::local(module.to_string(), cmd.clone())); + // Hidden dispatcher struct — always (if the command name is given) + if let Some(ref cmd_name) = parsed.cmd_name { + let hidden_name = format!("__Dispatcher{}", to_pascal_case(cmd_name)); + items.push(AnalyzeItem::local(module.to_string(), hidden_name)); } // Error type — if error = TypeName @@ -107,9 +108,9 @@ impl DispatcherClapPattern { // Help internal struct — if help = true if parsed.help_enabled - && let Some(ref cmd) = parsed.cmd_type + && let Some(ref cmd_name) = parsed.cmd_name { - let help_fn = format!("__{}_help", just_fmt::snake_case!(cmd)); + let help_fn = format!("__{}_help", just_fmt::snake_case!(cmd_name)); let help_struct = format!("__internal_help_{}", just_fmt::snake_case!(&help_fn)); items.push(AnalyzeItem::local(module.to_string(), help_struct)); } @@ -128,15 +129,13 @@ impl DispatcherClapPattern { struct ParsedClapArgs { cmd_name: Option<String>, - cmd_type: Option<String>, error_type: Option<String>, help_enabled: bool, } -/// Parse `#[dispatcher_clap("cmd", CMDType, error = ErrorType, help = true)]` arguments. +/// Parse `#[dispatcher_clap("cmd", error = ErrorType, help = true)]` arguments. fn parse_dispatcher_clap_args(args: &str) -> ParsedClapArgs { let mut cmd_name = None; - let mut cmd_type = None; let mut error_type = None; let mut help_enabled = false; @@ -178,23 +177,30 @@ fn parse_dispatcher_clap_args(args: &str) -> ParsedClapArgs { } _ => {} } - } else { - // Bare ident — the CMD type - let clean = part.trim_end_matches([')', ']']).trim(); - if !clean.is_empty() && cmd_type.is_none() { - cmd_type = Some(clean.to_string()); - } } + // Bare idents (e.g. the old CMD struct argument) are ignored. } ParsedClapArgs { cmd_name, - cmd_type, error_type, help_enabled, } } +/// Simple `pascal_case` conversion for deriving the hidden dispatcher name. +fn to_pascal_case(s: &str) -> String { + s.split(['-', '_', '.']) + .filter(|s| !s.is_empty()) + .map(|s| { + let mut c = s.chars(); + c.next().map_or_else(String::new, |f| { + f.to_uppercase().collect::<String>() + c.as_str() + }) + }) + .collect() +} + fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool { attrs .iter() diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs index 81f3b3f..fff2597 100644 --- a/mingling_pathf/test/src/lib.rs +++ b/mingling_pathf/test/src/lib.rs @@ -258,17 +258,17 @@ fn test_dispatcher_analyze() { let r = analyzer.analyze_file(file).unwrap(); let required: Vec<&str> = vec![ "::EntryGreet", - "::CMDGreet", + "::__DispatcherGreet", "::EntryRemoteAdd", - "::CMDRemoteAdd", + "::__DispatcherRemoteAdd", "::EntryDelete", - "::CMDDelete", + "::__DispatcherDelete", "::EntryRemoteRm", - "::CMDRemoteRm", + "::__DispatcherRemoteRm", "::sub::EntryGreet", - "::sub::CMDGreet", + "::sub::__DispatcherGreet", "::sub::EntryDelete", - "::sub::CMDDelete", + "::sub::__DispatcherDelete", // Dispatchers are always collected at compile time: "::__internal_dispatcher_greet", "::__internal_dispatcher_remote_add", @@ -293,16 +293,18 @@ fn test_dispatcher_dispatch_tree() { .join("src/test_files/test_dispatcher_dispatch_tree.rs"); // Dispatchers are always collected at compile time, so the analyzer - // always extracts the `__internal_dispatcher_*` statics too: - // 8 (Entry + CMD, root + sub) + 4 __internal (root + sub) = 12 + // always extracts the hidden dispatcher structs and statics too: + // 8 (Entry + __Dispatcher, root + sub) + 4 __internal (root + sub) = 12 let r = pattern_analyzer::init().analyze_file(&file).unwrap(); assert_eq!(r.len(), 12); assert!(r.contains("::EntryGreet")); - assert!(r.contains("::CMDGreet")); + assert!(r.contains("::__DispatcherGreet")); assert!(r.contains("::EntryDelete")); - assert!(r.contains("::CMDDelete")); + assert!(r.contains("::__DispatcherDelete")); assert!(r.contains("::sub::EntryGreet")); - assert!(r.contains("::sub::CMDGreet")); + assert!(r.contains("::sub::__DispatcherGreet")); + assert!(r.contains("::sub::EntryDelete")); + assert!(r.contains("::sub::__DispatcherDelete")); assert!(r.contains("::__internal_dispatcher_greet")); assert!(r.contains("::__internal_dispatcher_delete")); assert!(r.contains("::sub::__internal_dispatcher_greet")); @@ -323,43 +325,43 @@ fn test_dispatcher_clap_analyze() { "::EntryClap2", "::EntryClap3", "::EntryClap4", - // Root: with CMD type + // Root: with command name only "::EntryWithCmd", - "::CMDGreet", - // Root: with CMD + error + // Root: with error "::EntryWithError", - "::CMDDelete", "::ErrorDelete", - // Root: with CMD + help + // Root: with help "::EntryWithHelp", - "::CMDHelp", - "::__internal_help_cmdhelp_help", - // Root: with CMD + error + help + "::__internal_help_helpcmd_help", + // Root: with error + help "::EntryFull", - "::CMDFull", "::ErrorFull", - "::__internal_help_cmdfull_help", + "::__internal_help_full_help", // Sub: entry types (bare dispatcher_clap) "::sub::EntryClap1", "::sub::EntryClap3", - // Sub: with CMD type + // Sub: with command name only "::sub::EntryWithCmd", - "::sub::CMDGreet", - // Sub: with CMD + error + // Sub: with error "::sub::EntryWithError", - "::sub::CMDDelete", "::sub::ErrorDelete", - // Sub: with CMD + help + // Sub: with help "::sub::EntryWithHelp", - "::sub::CMDHelp", - "::sub::__internal_help_cmdhelp_help", - // Dispatchers are always collected at compile time: + "::sub::__internal_help_helpcmd_help", + // Hidden dispatcher structs + statics are always collected: + "::__DispatcherGreet", "::__internal_dispatcher_greet", + "::__DispatcherDelete", "::__internal_dispatcher_delete", + "::__DispatcherHelpcmd", "::__internal_dispatcher_helpcmd", + "::__DispatcherFull", "::__internal_dispatcher_full", + "::sub::__DispatcherGreet", "::sub::__internal_dispatcher_greet", + "::sub::__DispatcherDelete", "::sub::__internal_dispatcher_delete", + "::sub::__DispatcherHelpcmd", "::sub::__internal_dispatcher_helpcmd", ]; @@ -378,15 +380,22 @@ fn test_dispatcher_clap_dispatch_tree() { .join("src/test_files/test_dispatcher_clap.rs"); // Dispatchers are always collected at compile time: - // 26 (Entry/CMD/error/help items) + 4 __internal (root) + 3 __internal (sub, no "full") = 33 + // 19 (entry/error/help items) + 14 (hidden structs + statics, root + sub) = 33 let r = pattern_analyzer::init().analyze_file(&file).unwrap(); assert_eq!(r.len(), 33); + assert!(r.contains("::__DispatcherGreet")); assert!(r.contains("::__internal_dispatcher_greet")); + assert!(r.contains("::__DispatcherDelete")); assert!(r.contains("::__internal_dispatcher_delete")); + assert!(r.contains("::__DispatcherHelpcmd")); assert!(r.contains("::__internal_dispatcher_helpcmd")); + assert!(r.contains("::__DispatcherFull")); assert!(r.contains("::__internal_dispatcher_full")); + assert!(r.contains("::sub::__DispatcherGreet")); assert!(r.contains("::sub::__internal_dispatcher_greet")); + assert!(r.contains("::sub::__DispatcherDelete")); assert!(r.contains("::sub::__internal_dispatcher_delete")); + assert!(r.contains("::sub::__DispatcherHelpcmd")); assert!(r.contains("::sub::__internal_dispatcher_helpcmd")); } diff --git a/mingling_pathf/test/src/test_files/test_dispatcher.rs b/mingling_pathf/test/src/test_files/test_dispatcher.rs index 48f5e4d..66e6a74 100644 --- a/mingling_pathf/test/src/test_files/test_dispatcher.rs +++ b/mingling_pathf/test/src/test_files/test_dispatcher.rs @@ -1,17 +1,17 @@ -mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet); +mingling::macros::dispatcher!("greet", EntryGreet); mingling::macros::dispatcher!("greet"); -mingling::macros::dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd); +mingling::macros::dispatcher!("remote.add", EntryRemoteAdd); mingling::macros::dispatcher!("remote.add"); -dispatcher!("delete", CMDDelete => EntryDelete); +dispatcher!("delete", EntryDelete); dispatcher!("delete"); -dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm); +dispatcher!("remote.rm", EntryRemoteRm); dispatcher!("remote.rm"); pub mod sub { - mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet); + mingling::macros::dispatcher!("greet", EntryGreet); mingling::macros::dispatcher!("greet"); - dispatcher!("delete", CMDDelete => EntryDelete); + dispatcher!("delete", EntryDelete); dispatcher!("delete"); } diff --git a/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs b/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs index 33d86e0..c1d5ef0 100644 --- a/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs +++ b/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs @@ -22,25 +22,25 @@ pub struct EntryClap4 { } // With CMD type -#[dispatcher_clap("greet", CMDGreet)] +#[dispatcher_clap("greet", )] struct EntryWithCmd { name: String, } // With CMD + error -#[dispatcher_clap("delete", CMDDelete, error = ErrorDelete)] +#[dispatcher_clap("delete", error = ErrorDelete)] struct EntryWithError { id: u64, } // With CMD + help -#[dispatcher_clap("helpcmd", CMDHelp, help = true)] +#[dispatcher_clap("helpcmd", help = true)] struct EntryWithHelp { verbose: bool, } // With CMD + error + help -#[dispatcher_clap("full", CMDFull, error = ErrorFull, help = true)] +#[dispatcher_clap("full", error = ErrorFull, help = true)] struct EntryFull { all: bool, } @@ -56,17 +56,17 @@ pub mod sub { value: String, } - #[dispatcher_clap("greet", CMDGreet)] + #[dispatcher_clap("greet", )] struct EntryWithCmd { name: String, } - #[dispatcher_clap("delete", CMDDelete, error = ErrorDelete)] + #[dispatcher_clap("delete", error = ErrorDelete)] struct EntryWithError { id: u64, } - #[dispatcher_clap("helpcmd", CMDHelp, help = true)] + #[dispatcher_clap("helpcmd", help = true)] struct EntryWithHelp { verbose: bool, } diff --git a/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs b/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs index ac321ca..e319242 100644 --- a/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs +++ b/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs @@ -1,7 +1,7 @@ -mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet); -dispatcher!("delete", CMDDelete => EntryDelete); +mingling::macros::dispatcher!("greet", EntryGreet); +dispatcher!("delete", EntryDelete); pub mod sub { - mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet); - dispatcher!("delete", CMDDelete => EntryDelete); + mingling::macros::dispatcher!("greet", EntryGreet); + dispatcher!("delete", EntryDelete); } |
