aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-17 03:27:46 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-17 03:27:46 +0800
commitc23c590330af83afb6e146bcd9b0a274b3689d22 (patch)
tree34599cf2737ce4de22653c9ccae2d60a38a15842
parent6980fbf2f9fb4c599d8dc6ff549a8b3288eb24e9 (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.
-rw-r--r--.config/verified-docs.toml2
-rw-r--r--CHANGELOG.md64
-rw-r--r--GETTING-STARTED.md67
-rw-r--r--README.md4
-rw-r--r--docs/_zh_CN/pages/10-help.md4
-rw-r--r--docs/_zh_CN/pages/11-resource-system.md6
-rw-r--r--docs/_zh_CN/pages/13-hook.md2
-rw-r--r--docs/_zh_CN/pages/14-testing.md6
-rw-r--r--docs/_zh_CN/pages/2-define-a-dispatcher.md10
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md8
-rw-r--r--docs/_zh_CN/pages/4-render-result.md2
-rw-r--r--docs/_zh_CN/pages/5-multiple-commands.md8
-rw-r--r--docs/_zh_CN/pages/6-argument-parse-picker.md24
-rw-r--r--docs/_zh_CN/pages/7-argument-parse-clap.md4
-rw-r--r--docs/_zh_CN/pages/9-error-handling.md6
-rw-r--r--docs/_zh_CN/pages/advanced/1-completion.md2
-rw-r--r--docs/_zh_CN/pages/advanced/2-structural-renderer.md4
-rw-r--r--docs/_zh_CN/pages/other/features.md16
-rw-r--r--docs/_zh_CN/pages/other/naming_rule.md28
-rw-r--r--docs/pages/10-help.md4
-rw-r--r--docs/pages/11-resource-system.md6
-rw-r--r--docs/pages/13-hook.md2
-rw-r--r--docs/pages/14-testing.md6
-rw-r--r--docs/pages/2-define-a-dispatcher.md10
-rw-r--r--docs/pages/3-define-a-chain.md8
-rw-r--r--docs/pages/4-render-result.md2
-rw-r--r--docs/pages/5-multiple-commands.md8
-rw-r--r--docs/pages/6-argument-parse-picker.md24
-rw-r--r--docs/pages/7-argument-parse-clap.md4
-rw-r--r--docs/pages/9-error-handling.md6
-rw-r--r--docs/pages/advanced/1-completion.md2
-rw-r--r--docs/pages/advanced/2-structural-renderer.md4
-rw-r--r--docs/pages/other/features.md2
-rw-r--r--docs/pages/other/naming_rule.md28
-rw-r--r--docs/res/changlog_examples/feat_program_res.rs2
-rw-r--r--examples/example-argument-parse/src/main.rs4
-rw-r--r--examples/example-argument-picker/src/main.rs2
-rw-r--r--examples/example-async-support/src/main.rs2
-rw-r--r--examples/example-basic/src/main.rs2
-rw-r--r--examples/example-clap-binding/src/main.rs2
-rw-r--r--examples/example-combine-pathf-metadata/src/sub/mod.rs2
-rw-r--r--examples/example-command-macro/src/main.rs8
-rw-r--r--examples/example-completion/src/main.rs2
-rw-r--r--examples/example-custom-pickable/src/main.rs2
-rw-r--r--examples/example-dispatch-tree/src/main.rs28
-rw-r--r--examples/example-enum-tag/src/main.rs2
-rw-r--r--examples/example-error-handling/src/main.rs2
-rw-r--r--examples/example-exitcode/src/main.rs2
-rw-r--r--examples/example-help/src/main.rs2
-rw-r--r--examples/example-hook/src/main.rs2
-rw-r--r--examples/example-implicit-dispatcher/src/main.rs7
-rw-r--r--examples/example-lazy-resources/src/main.rs4
-rw-r--r--examples/example-metadata/src/main.rs6
-rw-r--r--examples/example-pack-err/src/main.rs4
-rw-r--r--examples/example-panic-unwind/src/main.rs2
-rw-r--r--examples/example-pathfinder/src/sub/mod.rs2
-rw-r--r--examples/example-repl-basic/src/main.rs8
-rw-r--r--examples/example-resources/src/main.rs4
-rw-r--r--examples/example-setup/src/main.rs2
-rw-r--r--examples/example-structural-renderer/src/main.rs2
-rw-r--r--examples/example-unit-test/src/main.rs2
-rw-r--r--mingling/src/docs/lib.md2
-rw-r--r--mingling/src/example_docs.rs103
-rw-r--r--mingling/src/gen_program.rs9
-rw-r--r--mingling/src/lib.rs1
-rw-r--r--mingling_cli/src/linter/cmd_lint.rs2
-rw-r--r--mingling_cli/src/linter/cmd_ra_lints.rs6
-rw-r--r--mingling_core/src/asset.rs1
-rw-r--r--mingling_core/src/asset/dispatcher.rs141
-rw-r--r--mingling_core/src/asset/node.rs157
-rw-r--r--mingling_core/src/lib.rs1
-rw-r--r--mingling_core/tests/test-all/tests/integration.rs15
-rw-r--r--mingling_core/tests/test-basic/tests/integration.rs28
-rw-r--r--mingling_core/tests/test-dispatch-tree/tests/integration.rs28
-rw-r--r--mingling_core/tests/test-repl/tests/integration.rs15
-rw-r--r--mingling_macros/src/attr/command.rs53
-rw-r--r--mingling_macros/src/attr/dispatcher_clap.rs39
-rw-r--r--mingling_macros/src/func.rs1
-rw-r--r--mingling_macros/src/func/dispatcher.rs123
-rw-r--r--mingling_macros/src/func/node.rs60
-rw-r--r--mingling_macros/src/func/program_comp_gen.rs4
-rw-r--r--mingling_macros/src/lib.rs49
-rw-r--r--mingling_pathf/src/patterns/dispatcher.rs85
-rw-r--r--mingling_pathf/src/patterns/dispatcher_clap.rs56
-rw-r--r--mingling_pathf/test/src/lib.rs69
-rw-r--r--mingling_pathf/test/src/test_files/test_dispatcher.rs12
-rw-r--r--mingling_pathf/test/src/test_files/test_dispatcher_clap.rs14
-rw-r--r--mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs8
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();
}
diff --git a/README.md b/README.md
index 480510c..91aa166 100644
--- a/README.md
+++ b/README.md
@@ -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);
}