aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-17 02:29:30 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-17 02:39:56 +0800
commit6980fbf2f9fb4c599d8dc6ff549a8b3288eb24e9 (patch)
treebc7049698955d7a40706ea691fd5bab526a6b5e5
parent4c191b2b46a67a0dda6e9a867b40f45156af4f9c (diff)
refactor!: remove dynamic dispatcher registration API
Dispatchers are now always registered at compile time, removing the `with_dispatcher` / `with_dispatchers` methods and the `PathfinderConfig` API. The `dispatch_tree` feature now only controls the matching strategy (trie vs linear list).
-rw-r--r--CHANGELOG.md51
-rw-r--r--docs/_zh_CN/pages/10-help.md1
-rw-r--r--docs/_zh_CN/pages/13-hook.md1
-rw-r--r--docs/_zh_CN/pages/2-define-a-dispatcher.md17
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md1
-rw-r--r--docs/_zh_CN/pages/4-render-result.md1
-rw-r--r--docs/_zh_CN/pages/5-multiple-commands.md2
-rw-r--r--docs/_zh_CN/pages/7-argument-parse-clap.md1
-rw-r--r--docs/_zh_CN/pages/9-error-handling.md1
-rw-r--r--docs/_zh_CN/pages/advanced/1-completion.md2
-rw-r--r--docs/_zh_CN/pages/advanced/2-structural-renderer.md1
-rw-r--r--docs/pages/10-help.md1
-rw-r--r--docs/pages/13-hook.md1
-rw-r--r--docs/pages/2-define-a-dispatcher.md17
-rw-r--r--docs/pages/3-define-a-chain.md1
-rw-r--r--docs/pages/4-render-result.md1
-rw-r--r--docs/pages/5-multiple-commands.md2
-rw-r--r--docs/pages/7-argument-parse-clap.md1
-rw-r--r--docs/pages/9-error-handling.md1
-rw-r--r--docs/pages/advanced/1-completion.md2
-rw-r--r--docs/pages/advanced/2-structural-renderer.md1
-rw-r--r--examples/example-argument-parse/src/main.rs4
-rw-r--r--examples/example-argument-picker/src/main.rs1
-rw-r--r--examples/example-async-support/src/main.rs2
-rw-r--r--examples/example-basic/src/main.rs5
-rw-r--r--examples/example-clap-binding/src/main.rs1
-rw-r--r--examples/example-combine-pathf-metadata/src/main.rs5
-rw-r--r--examples/example-command-macro/src/main.rs9
-rw-r--r--examples/example-completion/src/main.rs12
-rw-r--r--examples/example-custom-pickable/src/main.rs4
-rw-r--r--examples/example-dispatch-tree/src/main.rs14
-rw-r--r--examples/example-enum-tag/src/main.rs5
-rw-r--r--examples/example-error-handling/src/main.rs4
-rw-r--r--examples/example-exitcode/src/main.rs1
-rw-r--r--examples/example-help/src/main.rs2
-rw-r--r--examples/example-hook/src/main.rs1
-rw-r--r--examples/example-implicit-dispatcher/src/main.rs10
-rw-r--r--examples/example-lazy-resources/src/main.rs1
-rw-r--r--examples/example-metadata/src/main.rs6
-rw-r--r--examples/example-outside-type/src/main.rs5
-rw-r--r--examples/example-pack-err/src/main.rs4
-rw-r--r--examples/example-panic-unwind/src/main.rs1
-rw-r--r--examples/example-pathfinder/src/main.rs5
-rw-r--r--examples/example-repl-basic/src/main.rs6
-rw-r--r--examples/example-resources/src/main.rs3
-rw-r--r--examples/example-setup/src/main.rs67
-rw-r--r--examples/example-setup/test.toml26
-rw-r--r--examples/example-structural-renderer/src/main.rs1
-rw-r--r--examples/example-unit-test/src/main.rs4
-rw-r--r--examples/full-todolist/src/main.rs7
-rw-r--r--mingling/Cargo.toml2
-rw-r--r--mingling/src/docs/lib.md3
-rw-r--r--mingling/src/example_docs.rs183
-rw-r--r--mingling/src/gen_program.rs13
-rw-r--r--mingling_core/Cargo.toml1
-rw-r--r--mingling_core/src/asset/dispatcher.rs415
-rw-r--r--mingling_core/src/build/pathf.rs11
-rw-r--r--mingling_core/src/comp.rs40
-rw-r--r--mingling_core/src/program.rs55
-rw-r--r--mingling_core/src/program/collection.rs20
-rw-r--r--mingling_core/src/program/collection/mock.rs3
-rw-r--r--mingling_core/src/program/exec.rs80
-rw-r--r--mingling_core/src/program/hook.rs10
-rw-r--r--mingling_macros/src/attr/command.rs9
-rw-r--r--mingling_macros/src/attr/dispatcher_clap.rs24
-rw-r--r--mingling_macros/src/func/dispatcher.rs22
-rw-r--r--mingling_macros/src/func/program_comp_gen.rs4
-rw-r--r--mingling_macros/src/func/program_final_gen.rs87
-rw-r--r--mingling_macros/src/func/register_dispatcher.rs20
-rw-r--r--mingling_macros/src/lib.rs162
-rw-r--r--mingling_macros/src/systems.rs2
-rw-r--r--mingling_macros/src/systems/dispatch_list_gen.rs57
-rw-r--r--mingling_macros/src/systems/dispatch_tree_gen.rs32
-rw-r--r--mingling_pathf/src/config.rs27
-rw-r--r--mingling_pathf/src/lib.rs1
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs13
-rw-r--r--mingling_pathf/src/patterns/dispatcher.rs37
-rw-r--r--mingling_pathf/src/patterns/dispatcher_clap.rs32
-rw-r--r--mingling_pathf/src/type_mapping_builder.rs7
-rw-r--r--mingling_pathf/test/src/lib.rs82
80 files changed, 473 insertions, 1304 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 694ce61..e8ce5d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -87,6 +87,57 @@ None
_No behavioral changes — this is a pure rename of the prefix-tree dispatch method. The method's semantics, signature, and dispatch-tree behavior are unchanged; only its name dropped the redundant `_trie` suffix.
+2. **[`macros:dispatcher`]** **[BREAKING]** Dispatchers are now always registered at compile time — the `with_dispatcher` / `with_dispatchers` dynamic registration API on `Program` has been removed entirely.
+
+ ### What changed
+
+ Previously, the `dispatch_tree` feature controlled _whether_ dispatchers were collected at compile time. With the feature enabled, `dispatcher!` (and the related `dispatcher_clap!`, `#[command]`, and completion macros) emitted a `__internal_dispatcher_*` static and registered the node in the global `COMPILE_TIME_DISPATCHERS` registry via `register_dispatcher!`; `gen_program!` then built a `dispatch_args` trie from that registry. Without the feature, users had to manually register dispatchers at runtime via `program.with_dispatcher(CMDGreet)`.
+
+ Now, **all dispatchers are always collected at compile time** regardless of the `dispatch_tree` feature:
+
+ - `dispatcher!`, `dispatcher_clap!`, `#[command]`, and the `comp`-generated completion dispatcher **always** emit the `__internal_dispatcher_*` static and call `register_dispatcher!`.
+ - `gen_program!` always reads `COMPILE_TIME_DISPATCHERS` and generates both `ProgramCollect::dispatch_args` and `ProgramCollect::get_nodes` from it.
+ - The `dispatch_tree` feature now only selects the _internal matching strategy_: a char-level trie when enabled, a linear longest-prefix list otherwise. Both strategies are generated from the same compile-time-collected entries.
+ - The `Program.dispatcher` field and the `with_dispatcher` / `with_dispatchers` methods (and the deprecated `Dispatchers` multi-registration helper) have been **removed**.
+
+ ### Removed API
+ - `Program::with_dispatcher<Disp>(&mut self, dispatcher: Disp) -> &mut Self` — **removed**
+ - `Program::with_dispatchers<D>(&mut self, dispatchers: D) -> &mut Self` — **removed** (already deprecated)
+ - `Dispatchers<G>` struct and all its `From` tuple impls (up to 7 elements), `Deref`, and `Into<Vec<_>>` conversions — **removed** (already deprecated)
+ - `Program::dispatch_args_dynamic(...)` — **removed** (renamed to `dispatch_args` in **BREAKING CHANGE #1**)
+ - `ProgramCollect::dispatch_args` no longer has a fallback default body and is now a required (non-optional) method — any manual `ProgramCollect` impl (tests, mocks, etc.) must implement both `dispatch_args` and `get_nodes`.
+
+ ### New internal module
+
+ A new `dispatch_list_gen` module was added to `mingling_macros` (`systems/dispatch_list_gen.rs`) providing `gen_dispatch_args`, which generates a linear `dispatch_args` body used when the `dispatch_tree` feature is **disabled**. It sorts nodes by display-name length (longest first) so the first matching node is the most specific one, mirroring the old dynamic dispatcher's "longest registered prefix wins" rule:
+
+ ```rust,ignore
+ fn dispatch_args(
+ raw: &[String],
+ ) -> Result<AnyOutput<Self::Enum>, ProgramInternalExecuteError> {
+ let raw_string = format!("{} ", raw.join(" "));
+ // ... linear if-chain over each node, longest prefix first ...
+ Ok(Self::build_entry_fallback(raw.to_vec()))
+ }
+ ```
+
+ `dispatch_tree_gen::gen_dispatch_args_trie` continues to provide the trie strategy (and now also exposes the shared `gen_get_nodes` helper, moved from `dispatch_tree_gen` into `program_final_gen`).
+
+ ### pathf changes
+ - **`mingling_pathf::config::PathfinderConfig`** — **removed** (deleted `config.rs`). The `use_dispatch_tree` flag no longer exists.
+ - **`pattern_analyzer::init_with_config(config)`** — **removed**; `init()` now always registers `DispatcherPattern` / `DispatcherClapPattern` with compile-time collection enabled.
+ - **`DispatcherPattern` / `DispatcherClapPattern`** — no longer carry a `use_dispatch_tree` field (`new()` takes no arguments). Both patterns now always extract the `__internal_dispatcher_*` static for every matched command.
+ - **`mingling_pathf::analyze_and_build_type_mapping_for` / `analyze_and_build_type_mapping`** — signatures no longer take a `&PathfinderConfig` argument.
+ - **`mingling_core::build::pathf`** — the wrappers no longer pass a config (the `config::*` re-export was removed).
+
+ ### Migration guide
+ - **Remove all `program.with_dispatcher(...)` calls.** Dispatchers are now automatically collected by `gen_program!` — no explicit registration is needed. Examples of affected call sites (all updated in this release): `example-basic`, `example-argument-parse`, `example-argument-picker`, `example-async-support`, `example-clap-binding`, `example-command-macro`, `example-completion`, `example-custom-pickable`, `example-dispatch-tree`, `example-enum-tag`, `example-error-handling`, `example-exitcode`, `example-help`, `example-hook`, `example-implicit-dispatcher`, `example-lazy-resources`, `example-metadata`, `example-outside-type`, `example-pack-err`, `example-panic-unwind`, `example-pathfinder`, `example-repl-basic`, `example-resources`, `example-setup`, `example-structural-renderer`, `example-unit-test`, and `full-todolist`.
+ - **Any manual `ProgramCollect` impl** must now implement both required methods `dispatch_args` and `get_nodes`.
+ - **The `dispatch_tree` feature is now purely an internal optimization** (trie vs. linear-list command matching). It no longer changes whether dispatchers are collected at compile time — that behavior is unconditional. Update any documentation/comments that claimed otherwise.
+ - **The `__internal_dispatcher_*` static and the compile-time registration** are now always emitted, so `pathf`-based `use` imports for these types are unconditional.
+
+ _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.
+
---
## Contents
diff --git a/docs/_zh_CN/pages/10-help.md b/docs/_zh_CN/pages/10-help.md
index a399ae3..99a81fd 100644
--- a/docs/_zh_CN/pages/10-help.md
+++ b/docs/_zh_CN/pages/10-help.md
@@ -55,7 +55,6 @@ fn help_root(entry: EntryFallback) {
fn main() {
let mut program = ThisProgram::new();
program.with_setup(BasicProgramSetup);
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
```
diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md
index ad3008a..2aa3ef7 100644
--- a/docs/_zh_CN/pages/13-hook.md
+++ b/docs/_zh_CN/pages/13-hook.md
@@ -75,7 +75,6 @@ fn main() {
}),
);
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
```
diff --git a/docs/_zh_CN/pages/2-define-a-dispatcher.md b/docs/_zh_CN/pages/2-define-a-dispatcher.md
index b238d9f..4ec7c05 100644
--- a/docs/_zh_CN/pages/2-define-a-dispatcher.md
+++ b/docs/_zh_CN/pages/2-define-a-dispatcher.md
@@ -31,23 +31,6 @@ dispatcher!("greet", CMDGreet => EntryGreet);
> [!NOTE]
> 命令名(`"greet"`)会自动转换为 kebab-case。即使你写 `"GreetUser"`,匹配时也会变成 `greet-user`。
-## 注册到 Program
-
-有了分发器之后,需要告诉 Program 它的存在:
-
-```rust
-@@@ dispatcher!("greet", CMDGreet => EntryGreet);
-@@@ fn main() {
-@@@ let mut program = ThisProgram::new();
-// 注册分发器
-program.with_dispatcher(CMDGreet);
-@@@ }
-@@@ gen_program!();
-```
-
-> [!TIP]
-> 如果命令多了,可以用 `with_dispatchers` 一次注册多个:`program.with_dispatchers((CMDGreet, CMDAdd, CMDRemoteRm))`。
-
## 多级命令
如果你的程序有层级结构——比如 `remote add`、`remote rm`——只需要在命令名里加点号分隔:
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md
index f845d11..b1becc9 100644
--- a/docs/_zh_CN/pages/3-define-a-chain.md
+++ b/docs/_zh_CN/pages/3-define-a-chain.md
@@ -117,7 +117,6 @@ fn handle_greet(args: EntryGreet) -> Next {
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/docs/_zh_CN/pages/4-render-result.md b/docs/_zh_CN/pages/4-render-result.md
index 7f66c71..2dbb617 100644
--- a/docs/_zh_CN/pages/4-render-result.md
+++ b/docs/_zh_CN/pages/4-render-result.md
@@ -76,7 +76,6 @@ fn render_name(name: ResultName) {
// 5. 在 main 函数内装配程序并运行
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/docs/_zh_CN/pages/5-multiple-commands.md b/docs/_zh_CN/pages/5-multiple-commands.md
index 681fe0a..b251232 100644
--- a/docs/_zh_CN/pages/5-multiple-commands.md
+++ b/docs/_zh_CN/pages/5-multiple-commands.md
@@ -42,8 +42,6 @@ fn render_sum(result: ResultSum) {
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.with_dispatcher(CMDAdd);
program.exec_and_exit();
}
diff --git a/docs/_zh_CN/pages/7-argument-parse-clap.md b/docs/_zh_CN/pages/7-argument-parse-clap.md
index 22896d2..83dddc3 100644
--- a/docs/_zh_CN/pages/7-argument-parse-clap.md
+++ b/docs/_zh_CN/pages/7-argument-parse-clap.md
@@ -76,7 +76,6 @@ fn main() {
program.with_setup(BasicProgramSetup);
program.stdout_setting.clap_help_print_behaviour =
mingling::config::ClapHelpPrintBehaviour::WriteToRenderResult;
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
```
diff --git a/docs/_zh_CN/pages/9-error-handling.md b/docs/_zh_CN/pages/9-error-handling.md
index f5055b8..222ef44 100644
--- a/docs/_zh_CN/pages/9-error-handling.md
+++ b/docs/_zh_CN/pages/9-error-handling.md
@@ -88,7 +88,6 @@ fn render_error_name_empty(err: ErrorNameEmpty) {
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/docs/_zh_CN/pages/advanced/1-completion.md b/docs/_zh_CN/pages/advanced/1-completion.md
index a6500db..c7dd678 100644
--- a/docs/_zh_CN/pages/advanced/1-completion.md
+++ b/docs/_zh_CN/pages/advanced/1-completion.md
@@ -24,8 +24,6 @@ features = [
当用户按下 `TAB` 时,补全脚本会调用程序的隐藏子命令 `__comp`,它会根据输入的 `ShellContext` 动态地查询最合适的建议。
-这个隐藏子命令由 `gen_program!()` 在启用 `comp` 特性时自动生成,对应的分发器是 `CMDCompletion`,你需要使用 `with_dispatcher` 添加到程序中。
-
补全流程:
1. 二次匹配用户当前输入的 `Dispatcher`
diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
index a498dac..9723247 100644
--- a/docs/_zh_CN/pages/advanced/2-structural-renderer.md
+++ b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
@@ -95,7 +95,6 @@ fn render_info(info: Info) {
@@@fn main() {
@@@ let mut program = ThisProgram::new();
@@@ program.with_setup(StructuralRendererSetup);
-@@@ program.with_dispatcher(CMDRender);
@@@ program.exec();
@@@}
@@@gen_program!();
diff --git a/docs/pages/10-help.md b/docs/pages/10-help.md
index d9cc557..3d4b3b8 100644
--- a/docs/pages/10-help.md
+++ b/docs/pages/10-help.md
@@ -55,7 +55,6 @@ For `--help` to work properly, add `BasicProgramSetup` in `main`:
fn main() {
let mut program = ThisProgram::new();
program.with_setup(BasicProgramSetup);
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
```
diff --git a/docs/pages/13-hook.md b/docs/pages/13-hook.md
index d927a9c..c525998 100644
--- a/docs/pages/13-hook.md
+++ b/docs/pages/13-hook.md
@@ -75,7 +75,6 @@ fn main() {
}),
);
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
```
diff --git a/docs/pages/2-define-a-dispatcher.md b/docs/pages/2-define-a-dispatcher.md
index 1208e64..432d7db 100644
--- a/docs/pages/2-define-a-dispatcher.md
+++ b/docs/pages/2-define-a-dispatcher.md
@@ -31,23 +31,6 @@ dispatcher!("greet", CMDGreet => EntryGreet);
> [!NOTE]
> The command name (`"greet"`) is auto-converted to kebab-case. Even if you write `"GreetUser"`, matching will use `greet-user`.
-## Registering with Program
-
-Once you have a dispatcher, you need to tell Program about it:
-
-```rust
-@@@ dispatcher!("greet", CMDGreet => EntryGreet);
-@@@ fn main() {
-@@@ let mut program = ThisProgram::new();
-// Register the dispatcher
-program.with_dispatcher(CMDGreet);
-@@@ }
-@@@ gen_program!();
-```
-
-> [!TIP]
-> If you have many commands, use `with_dispatchers` to register multiple at once: `program.with_dispatchers((CMDGreet, CMDAdd, CMDRemoteRm))`.
-
## Multi-level Commands
If your program has a hierarchy — e.g., `remote add`, `remote rm` — just separate the command name with dots:
diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md
index 450522b..1134dbf 100644
--- a/docs/pages/3-define-a-chain.md
+++ b/docs/pages/3-define-a-chain.md
@@ -117,7 +117,6 @@ fn handle_greet(args: EntryGreet) -> Next {
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/docs/pages/4-render-result.md b/docs/pages/4-render-result.md
index 7b63b45..a50c509 100644
--- a/docs/pages/4-render-result.md
+++ b/docs/pages/4-render-result.md
@@ -76,7 +76,6 @@ fn render_name(name: ResultName) {
// 5. Assemble and run the program in main
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/docs/pages/5-multiple-commands.md b/docs/pages/5-multiple-commands.md
index 17739e3..b92cb95 100644
--- a/docs/pages/5-multiple-commands.md
+++ b/docs/pages/5-multiple-commands.md
@@ -42,8 +42,6 @@ fn render_sum(result: ResultSum) {
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.with_dispatcher(CMDAdd);
program.exec_and_exit();
}
diff --git a/docs/pages/7-argument-parse-clap.md b/docs/pages/7-argument-parse-clap.md
index 3b8ec8b..ee1e96f 100644
--- a/docs/pages/7-argument-parse-clap.md
+++ b/docs/pages/7-argument-parse-clap.md
@@ -76,7 +76,6 @@ fn main() {
program.with_setup(BasicProgramSetup);
program.stdout_setting.clap_help_print_behaviour =
mingling::config::ClapHelpPrintBehaviour::WriteToRenderResult;
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
```
diff --git a/docs/pages/9-error-handling.md b/docs/pages/9-error-handling.md
index 680328c..389e394 100644
--- a/docs/pages/9-error-handling.md
+++ b/docs/pages/9-error-handling.md
@@ -88,7 +88,6 @@ fn render_error_name_empty(err: ErrorNameEmpty) {
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/docs/pages/advanced/1-completion.md b/docs/pages/advanced/1-completion.md
index 52350db..f70d415 100644
--- a/docs/pages/advanced/1-completion.md
+++ b/docs/pages/advanced/1-completion.md
@@ -24,8 +24,6 @@ features = [
When the user presses `TAB`, the completion script calls the program's hidden subcommand `__comp`, which dynamically queries the best suggestions based on the provided `ShellContext`.
-This hidden subcommand is auto-generated by `gen_program!()` when the `comp` feature is enabled. Its dispatcher is `CMDCompletion` — you need to add it to your program via `with_dispatcher`.
-
Completion flow:
1. Re-match the user's current input to a `Dispatcher`
diff --git a/docs/pages/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md
index b54af22..489baba 100644
--- a/docs/pages/advanced/2-structural-renderer.md
+++ b/docs/pages/advanced/2-structural-renderer.md
@@ -95,7 +95,6 @@ fn render_info(info: Info) {
@@@fn main() {
@@@ let mut program = ThisProgram::new();
@@@ program.with_setup(StructuralRendererSetup);
-@@@ program.with_dispatcher(CMDRender);
@@@ program.exec();
@@@}
@@@gen_program!();
diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs
index eb07672..d7c8681 100644
--- a/examples/example-argument-parse/src/main.rs
+++ b/examples/example-argument-parse/src/main.rs
@@ -97,8 +97,6 @@ fn render_error_no_name_provided(_: ErrorNoNameProvided) -> RenderResult {
gen_program!();
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDTransfer);
- program.with_dispatcher(CMDStrictTransfer);
+ let program = ThisProgram::new();
program.exec_and_exit();
}
diff --git a/examples/example-argument-picker/src/main.rs b/examples/example-argument-picker/src/main.rs
index 7fcc5db..36b552e 100644
--- a/examples/example-argument-picker/src/main.rs
+++ b/examples/example-argument-picker/src/main.rs
@@ -125,7 +125,6 @@ fn main() {
program.with_resource(ResNumberDisplaySetting { round: *round });
// --------- IMPORTANT ---------
- program.with_dispatcher(CMDCalculate);
program.exec_and_exit();
}
diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs
index af86408..1b6c194 100644
--- a/examples/example-async-support/src/main.rs
+++ b/examples/example-async-support/src/main.rs
@@ -30,8 +30,6 @@ use std::io::Write;
async fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDDownload);
-
// Add a hook to display when the download begins
program.with_hook(ProgramHook::empty().on_begin::<_, ()>(|_| println!("Download begin")));
diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs
index 17077e2..418ed95 100644
--- a/examples/example-basic/src/main.rs
+++ b/examples/example-basic/src/main.rs
@@ -26,10 +26,7 @@ dispatcher!("greet", CMDGreet => EntryGreet);
fn main() {
// Create a new ThisProgram
- let mut program = ThisProgram::new();
-
- // Add the CMDGreet dispatcher
- program.with_dispatcher(CMDGreet);
+ let program = ThisProgram::new();
// Run the program, then exit the process
program.exec_and_exit();
diff --git a/examples/example-clap-binding/src/main.rs b/examples/example-clap-binding/src/main.rs
index d25c5f3..449fee3 100644
--- a/examples/example-clap-binding/src/main.rs
+++ b/examples/example-clap-binding/src/main.rs
@@ -60,7 +60,6 @@ fn main() {
// Capture Clap's help information and write to RenderResult
// --------- IMPORTANT ---------
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/examples/example-combine-pathf-metadata/src/main.rs b/examples/example-combine-pathf-metadata/src/main.rs
index a68eac5..894e235 100644
--- a/examples/example-combine-pathf-metadata/src/main.rs
+++ b/examples/example-combine-pathf-metadata/src/main.rs
@@ -24,10 +24,7 @@ mod sub;
use mingling::prelude::*;
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(sub::CMDHello);
- program.with_dispatcher(sub::CMDDescription);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
gen_program!();
diff --git a/examples/example-command-macro/src/main.rs b/examples/example-command-macro/src/main.rs
index 2bf932a..d080043 100644
--- a/examples/example-command-macro/src/main.rs
+++ b/examples/example-command-macro/src/main.rs
@@ -19,14 +19,7 @@
use mingling::{macros::buffer, picker::IntoPicker, prelude::*};
fn main() {
- let mut program = ThisProgram::new();
-
- // Import the dispatchers generated by the `#[command]` macro
- program.with_dispatcher(CMDHelloWorld);
- program.with_dispatcher(CMDGreetSomeone);
- program.with_dispatcher(CMDGoodBye);
-
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
pack!(ResultGreeting = String);
diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs
index d65be49..29d6026 100644
--- a/examples/example-completion/src/main.rs
+++ b/examples/example-completion/src/main.rs
@@ -44,19 +44,11 @@
//! Hello, Alice, Alice, Alice!
//! ```
-use mingling::{macros::suggest, prelude::*, ShellContext, Suggest};
+use mingling::{ShellContext, Suggest, macros::suggest, prelude::*};
use std::io::Write;
fn main() {
- let mut program = ThisProgram::new();
-
- program.with_dispatcher(CMDGreet);
-
- // --------- IMPORTANT ---------
- // The `comp` feature makes `gen_program!()` generate a CMDCompletion automatically
- // It adds a hidden `__comp` subcommand for communication with the completion script
- program.with_dispatcher(crate::CMDCompletion);
- // --------- IMPORTANT ---------
+ let program = ThisProgram::new();
// TIP: Note that the completion script reads stdout,
// so make sure no output is produced before the CMDCompletion is dispatched.
diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs
index b163278..ece5d13 100644
--- a/examples/example-custom-pickable/src/main.rs
+++ b/examples/example-custom-pickable/src/main.rs
@@ -70,9 +70,7 @@ pub fn render_error_parse_address_failed(_: ErrorParseAddressFailed) -> RenderRe
gen_program!();
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDConnect);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
// Address conversion
diff --git a/examples/example-dispatch-tree/src/main.rs b/examples/example-dispatch-tree/src/main.rs
index 9f76f15..a25af2a 100644
--- a/examples/example-dispatch-tree/src/main.rs
+++ b/examples/example-dispatch-tree/src/main.rs
@@ -3,11 +3,9 @@
//! > This example will introduce how to use `dispatch_tree`
//! > to optimize your command line lookup efficiency
//!
-//! When the number of commands in your project increases, you can use `dispatch_tree` to complete command registration at compile time.
-//! It will generate a trie for quickly finding related commands by prefix.
-//!
-//! Therefore, after enabling this feature,
-//! `Program` will no longer store a Dispatcher list internally, and the `with_dispatcher` function will not be compiled.
+//! When the number of commands in your project increases, you can enable
+//! `dispatch_tree` to switch command matching from a linear scan to a
+//! character-level trie.
//!
//! Run:
//! ```bash
@@ -43,12 +41,6 @@ dispatcher!("nested.f", CMDD => EntryD);
fn main() {
let program = ThisProgram::new();
-
- // --------- IMPORTANT ---------
- // // You no longer need to use `with_dispatcher` anymore;
- // // it'll be collected automatically once the `dispatch_tree` feature is enabled
- // program.with_dispatcher(...);
-
program.exec_and_exit();
}
diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs
index b57511d..884bc1d 100644
--- a/examples/example-enum-tag/src/main.rs
+++ b/examples/example-enum-tag/src/main.rs
@@ -100,8 +100,5 @@ fn complete_language_selection(_: &ShellContext) -> Suggest {
gen_program!();
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDCompletion);
- program.with_dispatcher(CMDLanguageSelection);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs
index 0cd973a..e9c0f57 100644
--- a/examples/example-error-handling/src/main.rs
+++ b/examples/example-error-handling/src/main.rs
@@ -108,7 +108,5 @@ fn render_entry_fallback(err: EntryFallback) -> RenderResult {
gen_program!();
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDHello);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs
index 6b22eae..ad712cd 100644
--- a/examples/example-exitcode/src/main.rs
+++ b/examples/example-exitcode/src/main.rs
@@ -31,7 +31,6 @@ fn main() {
program.with_setup(ExitCodeSetup::default());
// --------- IMPORTANT ---------
- program.with_dispatcher(CMDHello);
program.exec_and_exit();
}
diff --git a/examples/example-help/src/main.rs b/examples/example-help/src/main.rs
index 3282683..90619f5 100644
--- a/examples/example-help/src/main.rs
+++ b/examples/example-help/src/main.rs
@@ -36,8 +36,6 @@ fn main() {
program.with_setup(BasicProgramSetup);
// --------- IMPORTANT ---------
- program.with_dispatcher(CMDGreet);
-
program.exec_and_exit();
}
diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs
index da92045..1304f85 100644
--- a/examples/example-hook/src/main.rs
+++ b/examples/example-hook/src/main.rs
@@ -50,7 +50,6 @@ fn main() {
);
// --------- IMPORTANT ---------
- program.with_dispatcher(CMDGreet);
program.exec_and_exit();
}
diff --git a/examples/example-implicit-dispatcher/src/main.rs b/examples/example-implicit-dispatcher/src/main.rs
index 340d585..69adcfe 100644
--- a/examples/example-implicit-dispatcher/src/main.rs
+++ b/examples/example-implicit-dispatcher/src/main.rs
@@ -9,15 +9,7 @@ dispatcher!("remote.add" /*, CMDRemoteAdd => EntryRemoteAdd */);
dispatcher!("remote.remove", CMDRemoteRemove => EntryRemoteRemove);
fn main() {
- let mut program = ThisProgram::new();
-
- // --------- IMPORTANT ---------
- program.with_dispatcher(CMDRemoteAdd);
- // ^^^^^^^^^^^^\_ CMDRemoteAdd is implicitly created
- // --------- IMPORTANT ---------
-
- program.with_dispatcher(CMDRemoteRemove);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
gen_program!();
diff --git a/examples/example-lazy-resources/src/main.rs b/examples/example-lazy-resources/src/main.rs
index 7dda658..199608e 100644
--- a/examples/example-lazy-resources/src/main.rs
+++ b/examples/example-lazy-resources/src/main.rs
@@ -64,7 +64,6 @@ fn main() {
program.with_resource(ResLargeData::lazy_init(init_res_large_data));
// --------- IMPORTANT ---------
- program.with_dispatcher(CMDShow).with_dispatcher(CMDNone);
program.exec_and_exit();
}
diff --git a/examples/example-metadata/src/main.rs b/examples/example-metadata/src/main.rs
index 855241e..5395614 100644
--- a/examples/example-metadata/src/main.rs
+++ b/examples/example-metadata/src/main.rs
@@ -34,11 +34,7 @@ dispatcher!("desc", CMDDescription => EntryDescription);
dispatcher!("nodoc", CMDNoDescription => EntryNoDescription);
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.with_dispatcher(CMDDescription);
- program.with_dispatcher(CMDNoDescription);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
/// The metadata type attached to an entry.
diff --git a/examples/example-outside-type/src/main.rs b/examples/example-outside-type/src/main.rs
index 3159d19..a04727f 100644
--- a/examples/example-outside-type/src/main.rs
+++ b/examples/example-outside-type/src/main.rs
@@ -90,10 +90,7 @@ fn render_error_io(err: ErrorIo) -> RenderResult {
}
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDParse);
- program.with_dispatcher(CMDError);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
gen_program!();
diff --git a/examples/example-pack-err/src/main.rs b/examples/example-pack-err/src/main.rs
index 5bf5066..901072d 100644
--- a/examples/example-pack-err/src/main.rs
+++ b/examples/example-pack-err/src/main.rs
@@ -143,9 +143,9 @@ gen_program!();
fn main() {
let mut program = ThisProgram::new();
+
// Add StructuralRendererSetup to support --json / --yaml flags
program.with_setup(StructuralRendererSetup);
- program.with_dispatcher(CMDFind);
- program.with_dispatcher(CMDFindStructural);
+
let _ = program.exec();
}
diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs
index 2c432eb..867e684 100644
--- a/examples/example-panic-unwind/src/main.rs
+++ b/examples/example-panic-unwind/src/main.rs
@@ -24,7 +24,6 @@ pack!(NotPanic = ());
fn main() {
let mut program = ThisProgram::new();
- program.with_dispatcher(CMDPanic);
// --------- IMPORTANT ---------
// Enable silence_panic to suppress automatic Panic output
diff --git a/examples/example-pathfinder/src/main.rs b/examples/example-pathfinder/src/main.rs
index 0f93a8d..cd7cc7d 100644
--- a/examples/example-pathfinder/src/main.rs
+++ b/examples/example-pathfinder/src/main.rs
@@ -19,12 +19,9 @@
mod sub;
use mingling::macros::gen_program;
-use crate::sub::CMDGreet;
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
gen_program!();
diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs
index 361488d..dc3d2fe 100644
--- a/examples/example-repl-basic/src/main.rs
+++ b/examples/example-repl-basic/src/main.rs
@@ -37,12 +37,6 @@ fn main() {
// Resource
program.with_resource(ResCurrentDir::default());
- // Dispatchers
- program.with_dispatcher(CMDCd);
- program.with_dispatcher(CMDLs);
- program.with_dispatcher(CMDExit);
- program.with_dispatcher(CMDClear);
-
// Setups
// Enable basic std::io::stdin().read_line(&mut input)
program.with_setup(BasicREPLReadlineSetup);
diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs
index 38e4561..262680a 100644
--- a/examples/example-resources/src/main.rs
+++ b/examples/example-resources/src/main.rs
@@ -37,9 +37,6 @@ fn main() {
});
// --------- IMPORTANT ---------
- program
- .with_dispatcher(CMDCurrent)
- .with_dispatcher(CMDModifyCurrent);
program.exec_and_exit();
}
diff --git a/examples/example-setup/src/main.rs b/examples/example-setup/src/main.rs
index bf610ca..b497054 100644
--- a/examples/example-setup/src/main.rs
+++ b/examples/example-setup/src/main.rs
@@ -1,8 +1,28 @@
//! Example Setup
//!
-//! > This example demonstrates how to build a custom Setup for modular management of project components
+//! > This example demonstrates how to build a custom Setup that encapsulates a
+//! > group of related resources and registers them with `with_resource`.
use mingling::{Program, macros::program_setup, prelude::*};
+use std::io::Write;
+
+// A group of related resources — here, the demo app's identity.
+// Resource types are plain structs: any `Default + Clone + Send + Sync` type
+// can be used as a resource, and it is identified by its type.
+#[derive(Default, Clone)]
+struct ResAppName {
+ name: String,
+}
+
+#[derive(Default, Clone)]
+struct ResAppVersion {
+ version: String,
+}
+
+#[derive(Default, Clone)]
+struct ResGreetingPrefix {
+ prefix: String,
+}
fn main() {
let mut program = ThisProgram::new();
@@ -17,21 +37,44 @@ fn main() {
// --------- IMPORTANT ---------
// Define `CustomSetup` (inferred from `custom_setup`)
-// Package part of the program construction logic into this type for modular management
+// Package part of the program construction logic into this type for modular
+// management — e.g. register a group of related resources here.
#[program_setup]
fn custom_setup(program: &mut Program<ThisProgram>) {
- program.with_dispatcher(CMD1);
- program.with_dispatcher(CMD2);
- program.with_dispatcher(CMD3);
- program.with_dispatcher(CMD4);
- program.with_dispatcher(CMD5);
+ program.with_resource(ResAppName {
+ name: "mingling".to_string(),
+ });
+ program.with_resource(ResAppVersion {
+ version: "0.5.0".to_string(),
+ });
+ program.with_resource(ResGreetingPrefix {
+ prefix: "Hello".to_string(),
+ });
}
// --------- IMPORTANT ---------
-dispatcher!("1", CMD1 => Entry1);
-dispatcher!("2", CMD2 => Entry2);
-dispatcher!("3", CMD3 => Entry3);
-dispatcher!("4", CMD4 => Entry4);
-dispatcher!("5", CMD5 => Entry5);
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+pack!(ResultGreeting = String);
+
+/// Chain: reads the `ResAppName` and `ResAppVersion` resources.
+#[chain]
+fn handle_greet(args: EntryGreet, app: &ResAppName, version: &ResAppVersion) -> Next {
+ let who = args
+ .inner
+ .first()
+ .cloned()
+ .unwrap_or_else(|| "World".to_string());
+ let greeting: ResultGreeting = format!("{} from {} v{}", who, app.name, version.version).into();
+ greeting.into()
+}
+
+/// Renderer: injects the `ResGreetingPrefix` resource to decorate the output.
+#[renderer]
+fn render_greet(greeting: ResultGreeting, prefix: &ResGreetingPrefix) -> RenderResult {
+ let mut render_result = RenderResult::new();
+ writeln!(render_result, "{}, {}!", prefix.prefix, *greeting).ok();
+ render_result
+}
gen_program!();
diff --git a/examples/example-setup/test.toml b/examples/example-setup/test.toml
index 811a108..c9b7ed8 100644
--- a/examples/example-setup/test.toml
+++ b/examples/example-setup/test.toml
@@ -1,29 +1,11 @@
[[runs]]
-input = [ "1" ]
+input = [ "greet" ]
expect.exit-code = 0
-expect.result = ""
+expect.result = "Hello, World from mingling v0.5.0!"
[[runs]]
-input = [ "2" ]
+input = [ "greet", "Alice" ]
expect.exit-code = 0
-expect.result = ""
-
-[[runs]]
-input = [ "3" ]
-
-expect.exit-code = 0
-expect.result = ""
-
-[[runs]]
-input = [ "4" ]
-
-expect.exit-code = 0
-expect.result = ""
-
-[[runs]]
-input = [ "5" ]
-
-expect.exit-code = 0
-expect.result = ""
+expect.result = "Hello, Alice from mingling v0.5.0!"
diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs
index 070e75d..8583d46 100644
--- a/examples/example-structural-renderer/src/main.rs
+++ b/examples/example-structural-renderer/src/main.rs
@@ -28,7 +28,6 @@ fn main() {
let mut program = ThisProgram::new();
// Add `StructuralRendererSetup` to receive user input `--json` `--yaml` parameters
program.with_setup(StructuralRendererSetup);
- program.with_dispatcher(CMDRender);
let _ = program.exec();
}
diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs
index 33ddde0..140df90 100644
--- a/examples/example-unit-test/src/main.rs
+++ b/examples/example-unit-test/src/main.rs
@@ -140,7 +140,5 @@ fn render_entry_fallback(err: EntryFallback) -> RenderResult {
gen_program!();
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDHello);
- program.exec_and_exit();
+ ThisProgram::new().exec_and_exit();
}
diff --git a/examples/full-todolist/src/main.rs b/examples/full-todolist/src/main.rs
index 0748832..c0df1df 100644
--- a/examples/full-todolist/src/main.rs
+++ b/examples/full-todolist/src/main.rs
@@ -65,12 +65,7 @@ fn main() {
);
program.with_resource(ResProgramFlags { all });
- // Dispatchers
- program.with_dispatcher(CMDAdd);
- program.with_dispatcher(CMDComplete);
- program.with_dispatcher(CMDList);
- program.with_dispatcher(CMDClean);
-
+ // Execute
program.exec_and_exit();
}
diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml
index 1148f2a..1de0f57 100644
--- a/mingling/Cargo.toml
+++ b/mingling/Cargo.toml
@@ -60,7 +60,7 @@ docs_rs = []
# Features
clap = ["mingling_core/clap", "mingling_macros/clap"]
-dispatch_tree = ["mingling_core/dispatch_tree", "mingling_macros/dispatch_tree"]
+dispatch_tree = ["mingling_macros/dispatch_tree"]
repl = ["mingling_core/repl", "mingling_macros/repl"]
comp = ["mingling_core/comp", "mingling_macros/comp"]
parser = ["dep:size"]
diff --git a/mingling/src/docs/lib.md b/mingling/src/docs/lib.md
index 697f6c5..993358b 100644
--- a/mingling/src/docs/lib.md
+++ b/mingling/src/docs/lib.md
@@ -25,8 +25,7 @@ use mingling::prelude::*;
dispatcher!("greet", CMDGreet => EntryGreet);
fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
+ let program = ThisProgram::new();
program.exec_and_exit();
}
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index c292598..5e4df1b 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -117,9 +117,7 @@
/// gen_program!();
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDTransfer);
-/// program.with_dispatcher(CMDStrictTransfer);
+/// let program = ThisProgram::new();
/// program.exec_and_exit();
/// }
/// ```
@@ -269,7 +267,6 @@ pub mod example_argument_parse {}
/// program.with_resource(ResNumberDisplaySetting { round: *round });
/// // --------- IMPORTANT ---------
///
-/// program.with_dispatcher(CMDCalculate);
/// program.exec_and_exit();
/// }
///
@@ -424,8 +421,6 @@ pub mod example_argument_picker {}
/// async fn main() {
/// let mut program = ThisProgram::new();
///
-/// program.with_dispatcher(CMDDownload);
-///
/// // Add a hook to display when the download begins
/// program.with_hook(ProgramHook::empty().on_begin::<_, ()>(|_| println!("Download begin")));
///
@@ -508,10 +503,7 @@ pub mod example_async_support {}
///
/// fn main() {
/// // Create a new ThisProgram
-/// let mut program = ThisProgram::new();
-///
-/// // Add the CMDGreet dispatcher
-/// program.with_dispatcher(CMDGreet);
+/// let program = ThisProgram::new();
///
/// // Run the program, then exit the process
/// program.exec_and_exit();
@@ -645,7 +637,6 @@ pub mod example_basic {}
/// // Capture Clap's help information and write to RenderResult
/// // --------- IMPORTANT ---------
///
-/// program.with_dispatcher(CMDGreet);
/// program.exec_and_exit();
/// }
///
@@ -824,10 +815,7 @@ pub mod example_combine_pathf_dispatch_tree {}
/// use mingling::prelude::*;
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(sub::CMDHello);
-/// program.with_dispatcher(sub::CMDDescription);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
///
/// gen_program!();
@@ -876,14 +864,7 @@ pub mod example_combine_pathf_metadata {}
/// use mingling::{macros::buffer, picker::IntoPicker, prelude::*};
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-///
-/// // Import the dispatchers generated by the `#[command]` macro
-/// program.with_dispatcher(CMDHelloWorld);
-/// program.with_dispatcher(CMDGreetSomeone);
-/// program.with_dispatcher(CMDGoodBye);
-///
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
///
/// pack!(ResultGreeting = String);
@@ -1002,19 +983,11 @@ pub mod example_command_macro {}
///
/// Source code (./src/main.rs)
/// ```ignore
-/// use mingling::{macros::suggest, prelude::*, ShellContext, Suggest};
+/// use mingling::{ShellContext, Suggest, macros::suggest, prelude::*};
/// use std::io::Write;
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-///
-/// program.with_dispatcher(CMDGreet);
-///
-/// // --------- IMPORTANT ---------
-/// // The `comp` feature makes `gen_program!()` generate a CMDCompletion automatically
-/// // It adds a hidden `__comp` subcommand for communication with the completion script
-/// program.with_dispatcher(crate::CMDCompletion);
-/// // --------- IMPORTANT ---------
+/// let program = ThisProgram::new();
///
/// // TIP: Note that the completion script reads stdout,
/// // so make sure no output is produced before the CMDCompletion is dispatched.
@@ -1180,9 +1153,7 @@ pub mod example_completion {}
/// gen_program!();
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDConnect);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
///
/// // Address conversion
@@ -1247,11 +1218,9 @@ pub mod example_custom_pickable {}
/// > This example will introduce how to use `dispatch_tree`
/// > to optimize your command line lookup efficiency
///
-/// When the number of commands in your project increases, you can use `dispatch_tree` to complete command registration at compile time.
-/// It will generate a trie for quickly finding related commands by prefix.
-///
-/// Therefore, after enabling this feature,
-/// `Program` will no longer store a Dispatcher list internally, and the `with_dispatcher` function will not be compiled.
+/// When the number of commands in your project increases, you can enable
+/// `dispatch_tree` to switch command matching from a linear scan to a
+/// character-level trie.
///
/// Run:
/// ```bash
@@ -1305,12 +1274,6 @@ pub mod example_custom_pickable {}
///
/// fn main() {
/// let program = ThisProgram::new();
-///
-/// // --------- IMPORTANT ---------
-/// // // You no longer need to use `with_dispatcher` anymore;
-/// // // it'll be collected automatically once the `dispatch_tree` feature is enabled
-/// // program.with_dispatcher(...);
-///
/// program.exec_and_exit();
/// }
///
@@ -1447,10 +1410,7 @@ pub mod example_dispatch_tree {}
/// gen_program!();
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDCompletion);
-/// program.with_dispatcher(CMDLanguageSelection);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
/// ```
pub mod example_enum_tag {}
@@ -1579,9 +1539,7 @@ pub mod example_enum_tag {}
/// gen_program!();
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDHello);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
/// ```
pub mod example_error_handling {}
@@ -1633,7 +1591,6 @@ pub mod example_error_handling {}
/// program.with_setup(ExitCodeSetup::default());
/// // --------- IMPORTANT ---------
///
-/// program.with_dispatcher(CMDHello);
/// program.exec_and_exit();
/// }
///
@@ -1739,8 +1696,6 @@ pub mod example_exitcode {}
/// program.with_setup(BasicProgramSetup);
/// // --------- IMPORTANT ---------
///
-/// program.with_dispatcher(CMDGreet);
-///
/// program.exec_and_exit();
/// }
///
@@ -1814,7 +1769,6 @@ pub mod example_help {}
/// );
/// // --------- IMPORTANT ---------
///
-/// program.with_dispatcher(CMDGreet);
/// program.exec_and_exit();
/// }
///
@@ -1870,15 +1824,7 @@ pub mod example_hook {}
/// dispatcher!("remote.remove", CMDRemoteRemove => EntryRemoteRemove);
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-///
-/// // --------- IMPORTANT ---------
-/// program.with_dispatcher(CMDRemoteAdd);
-/// // ^^^^^^^^^^^^\_ CMDRemoteAdd is implicitly created
-/// // --------- IMPORTANT ---------
-///
-/// program.with_dispatcher(CMDRemoteRemove);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
///
/// gen_program!();
@@ -1966,7 +1912,6 @@ pub mod example_implicit_dispatcher {}
/// program.with_resource(ResLargeData::lazy_init(init_res_large_data));
/// // --------- IMPORTANT ---------
///
-/// program.with_dispatcher(CMDShow).with_dispatcher(CMDNone);
/// program.exec_and_exit();
/// }
///
@@ -2051,11 +1996,7 @@ pub mod example_lazy_resources {}
/// dispatcher!("nodoc", CMDNoDescription => EntryNoDescription);
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDGreet);
-/// program.with_dispatcher(CMDDescription);
-/// program.with_dispatcher(CMDNoDescription);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
///
/// /// The metadata type attached to an entry.
@@ -2247,10 +2188,7 @@ pub mod example_metadata {}
/// }
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDParse);
-/// program.with_dispatcher(CMDError);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
///
/// gen_program!();
@@ -2423,10 +2361,10 @@ pub mod example_outside_type {}
///
/// fn main() {
/// let mut program = ThisProgram::new();
+///
/// // Add StructuralRendererSetup to support --json / --yaml flags
/// program.with_setup(StructuralRendererSetup);
-/// program.with_dispatcher(CMDFind);
-/// program.with_dispatcher(CMDFindStructural);
+///
/// let _ = program.exec();
/// }
/// ```
@@ -2481,7 +2419,6 @@ pub mod example_pack_err {}
///
/// fn main() {
/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDPanic);
///
/// // --------- IMPORTANT ---------
/// // Enable silence_panic to suppress automatic Panic output
@@ -2572,12 +2509,9 @@ pub mod example_panic_unwind {}
/// mod sub;
///
/// use mingling::macros::gen_program;
-/// use crate::sub::CMDGreet;
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDGreet);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
///
/// gen_program!();
@@ -2641,12 +2575,6 @@ pub mod example_pathfinder {}
/// // Resource
/// program.with_resource(ResCurrentDir::default());
///
-/// // Dispatchers
-/// program.with_dispatcher(CMDCd);
-/// program.with_dispatcher(CMDLs);
-/// program.with_dispatcher(CMDExit);
-/// program.with_dispatcher(CMDClear);
-///
/// // Setups
/// // Enable basic std::io::stdin().read_line(&mut input)
/// program.with_setup(BasicREPLReadlineSetup);
@@ -2846,9 +2774,6 @@ pub mod example_repl_basic {}
/// });
/// // --------- IMPORTANT ---------
///
-/// program
-/// .with_dispatcher(CMDCurrent)
-/// .with_dispatcher(CMDModifyCurrent);
/// program.exec_and_exit();
/// }
///
@@ -2885,7 +2810,8 @@ pub mod example_repl_basic {}
pub mod example_resources {}
/// Example Setup
///
-/// > This example demonstrates how to build a custom Setup for modular management of project components
+/// > This example demonstrates how to build a custom Setup that encapsulates a
+/// > group of related resources and registers them with `with_resource`.
///
/// Source code (./Cargo.toml)
/// ```toml
@@ -2903,6 +2829,25 @@ pub mod example_resources {}
/// Source code (./src/main.rs)
/// ```ignore
/// use mingling::{Program, macros::program_setup, prelude::*};
+/// use std::io::Write;
+///
+/// // A group of related resources — here, the demo app's identity.
+/// // Resource types are plain structs: any `Default + Clone + Send + Sync` type
+/// // can be used as a resource, and it is identified by its type.
+/// #[derive(Default, Clone)]
+/// struct ResAppName {
+/// name: String,
+/// }
+///
+/// #[derive(Default, Clone)]
+/// struct ResAppVersion {
+/// version: String,
+/// }
+///
+/// #[derive(Default, Clone)]
+/// struct ResGreetingPrefix {
+/// prefix: String,
+/// }
///
/// fn main() {
/// let mut program = ThisProgram::new();
@@ -2917,22 +2862,45 @@ pub mod example_resources {}
///
/// // --------- IMPORTANT ---------
/// // Define `CustomSetup` (inferred from `custom_setup`)
-/// // Package part of the program construction logic into this type for modular management
+/// // Package part of the program construction logic into this type for modular
+/// // management — e.g. register a group of related resources here.
/// #[program_setup]
/// fn custom_setup(program: &mut Program<ThisProgram>) {
-/// program.with_dispatcher(CMD1);
-/// program.with_dispatcher(CMD2);
-/// program.with_dispatcher(CMD3);
-/// program.with_dispatcher(CMD4);
-/// program.with_dispatcher(CMD5);
+/// program.with_resource(ResAppName {
+/// name: "mingling".to_string(),
+/// });
+/// program.with_resource(ResAppVersion {
+/// version: "0.5.0".to_string(),
+/// });
+/// program.with_resource(ResGreetingPrefix {
+/// prefix: "Hello".to_string(),
+/// });
/// }
/// // --------- IMPORTANT ---------
///
-/// dispatcher!("1", CMD1 => Entry1);
-/// dispatcher!("2", CMD2 => Entry2);
-/// dispatcher!("3", CMD3 => Entry3);
-/// dispatcher!("4", CMD4 => Entry4);
-/// dispatcher!("5", CMD5 => Entry5);
+/// dispatcher!("greet", CMDGreet => EntryGreet);
+///
+/// pack!(ResultGreeting = String);
+///
+/// /// Chain: reads the `ResAppName` and `ResAppVersion` resources.
+/// #[chain]
+/// fn handle_greet(args: EntryGreet, app: &ResAppName, version: &ResAppVersion) -> Next {
+/// let who = args
+/// .inner
+/// .first()
+/// .cloned()
+/// .unwrap_or_else(|| "World".to_string());
+/// let greeting: ResultGreeting = format!("{} from {} v{}", who, app.name, version.version).into();
+/// greeting.into()
+/// }
+///
+/// /// Renderer: injects the `ResGreetingPrefix` resource to decorate the output.
+/// #[renderer]
+/// fn render_greet(greeting: ResultGreeting, prefix: &ResGreetingPrefix) -> RenderResult {
+/// let mut render_result = RenderResult::new();
+/// writeln!(render_result, "{}, {}!", prefix.prefix, *greeting).ok();
+/// render_result
+/// }
///
/// gen_program!();
/// ```
@@ -2990,7 +2958,6 @@ pub mod example_setup {}
/// let mut program = ThisProgram::new();
/// // Add `StructuralRendererSetup` to receive user input `--json` `--yaml` parameters
/// program.with_setup(StructuralRendererSetup);
-/// program.with_dispatcher(CMDRender);
/// let _ = program.exec();
/// }
///
@@ -3193,9 +3160,7 @@ pub mod example_structural_renderer {}
/// gen_program!();
///
/// fn main() {
-/// let mut program = ThisProgram::new();
-/// program.with_dispatcher(CMDHello);
-/// program.exec_and_exit();
+/// ThisProgram::new().exec_and_exit();
/// }
/// ```
pub mod example_unit_test {}
diff --git a/mingling/src/gen_program.rs b/mingling/src/gen_program.rs
index 6a0b144..b1fa118 100644
--- a/mingling/src/gen_program.rs
+++ b/mingling/src/gen_program.rs
@@ -193,6 +193,19 @@ impl ProgramCollect for ThisProgram {
type ResultEmpty = ResultEmpty;
+ fn dispatch_args(
+ _raw: &[String],
+ ) -> Result<
+ mingling_core::AnyOutput<Self::Enum>,
+ mingling_core::error::ProgramInternalExecuteError,
+ > {
+ todo!()
+ }
+
+ fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))> {
+ todo!()
+ }
+
fn build_renderer_not_found(_member_id: Self::Enum) -> mingling_core::AnyOutput<Self::Enum> {
todo!()
}
diff --git a/mingling_core/Cargo.toml b/mingling_core/Cargo.toml
index 0b7e1aa..aecf476 100644
--- a/mingling_core/Cargo.toml
+++ b/mingling_core/Cargo.toml
@@ -17,7 +17,6 @@ async = []
build = []
picker = []
-dispatch_tree = []
structural_renderer = ["dep:serde"]
ron_serde_fmt = ["dep:ron"]
json_serde_fmt = ["dep:serde_json"]
diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs
index d700405..1ab7bf6 100644
--- a/mingling_core/src/asset/dispatcher.rs
+++ b/mingling_core/src/asset/dispatcher.rs
@@ -1,6 +1,6 @@
use std::fmt::Display;
-use crate::{ChainProcess, Program, ProgramCollect, asset::node::Node};
+use crate::{ChainProcess, asset::node::Node};
/// The entry logic of the Mingling program
///
@@ -148,307 +148,6 @@ where
}
}
-impl<C> Program<C>
-where
- C: ProgramCollect<Enum = C>,
-{
- /// Add a Dispatcher to the program
- ///
- /// This dynamically registers a Dispatcher into the program, used for command matching at program startup
- ///
- /// ```
- /// # use mingling_core::Program;
- /// # 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>> {
- /// # Box::new(CMDGreet)
- /// # }
- /// # }
- /// let mut program = Program::<ThisProgram>::new();
- /// program.with_dispatcher(CMDGreet);
- /// ```
- #[cfg_attr(
- feature = "dispatch_tree",
- deprecated(
- note = "When the `dispatch_tree` feature is enabled, the `dispatcher` field no longer exists inside Program. All types are collected at compile time by the `gen_program!()` macro, so the `with_dispatcher` function is no longer needed"
- )
- )]
- pub fn with_dispatcher<Disp>(&mut self, dispatcher: Disp) -> &mut Self
- where
- Disp: Dispatcher<C> + Send + Sync + 'static,
- {
- #[cfg(not(feature = "dispatch_tree"))]
- {
- self.dispatcher.push(Box::new(dispatcher));
- }
- #[cfg(feature = "dispatch_tree")]
- {
- let _ = dispatcher;
- }
- self
- }
-
- /// Add a group of Dispatchers to the program
- ///
- /// This dynamically registers a group of Dispatchers into the program, used for command matching at program startup
- ///
- /// ```
- /// # use mingling_core::Program;
- /// # 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>> {
- /// # Box::new(CMDGreet)
- /// # }
- /// # }
- /// let mut program = Program::<ThisProgram>::new();
- /// program.with_dispatchers((CMDGreet, /* Other Dispatchers */));
- /// ```
- #[deprecated(
- note = "with_dispatchers is no longer the recommended way to register Dispatchers, please split into multiple with_dispatcher calls"
- )]
- #[allow(deprecated)]
- pub fn with_dispatchers<D>(&mut self, dispatchers: D) -> &mut Self
- where
- D: Into<Dispatchers<C>>,
- {
- #[cfg(not(feature = "dispatch_tree"))]
- {
- let dispatchers = dispatchers.into();
- self.dispatcher.extend(dispatchers.dispatcher);
- }
- #[cfg(feature = "dispatch_tree")]
- {
- let _ = dispatchers;
- }
- self
- }
-}
-
-/// Represents a group of Dispatchers
-///
-/// It records a group of Dispatchers and implements conversion from tuples `(Disp, ..)` for this type,
-/// allowing for simpler construction syntax when using `with_dispatchers`
-///
-/// # Limits
-///
-/// Dispatchers supports conversion from tuples of up to 7 Dispatchers
-#[deprecated(
- note = "with_dispatchers is no longer the recommended way to register Dispatchers, please split into multiple with_dispatcher calls"
-)]
-pub struct Dispatchers<G> {
- dispatcher: Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>>,
-}
-
-#[allow(deprecated)]
-impl<G> From<Vec<Box<dyn Dispatcher<G> + Send + Sync>>> for Dispatchers<G> {
- fn from(dispatcher: Vec<Box<dyn Dispatcher<G> + Send + Sync>>) -> Self {
- Self { dispatcher }
- }
-}
-
-#[allow(deprecated)]
-impl<G> From<Box<dyn Dispatcher<G> + Send + Sync>> for Dispatchers<G> {
- fn from(dispatcher: Box<dyn Dispatcher<G> + Send + Sync>) -> Self {
- Self {
- dispatcher: vec![dispatcher],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D, G> From<(D,)> for Dispatchers<G>
-where
- D: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatcher: (D,)) -> Self {
- Self {
- dispatcher: vec![Box::new(dispatcher.0)],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, G> From<(D1, D2)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2)) -> Self {
- Self {
- dispatcher: vec![Box::new(dispatchers.0), Box::new(dispatchers.1)],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, G> From<(D1, D2, D3)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, G> From<(D1, D2, D3, D4)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, D5, G> From<(D1, D2, D3, D4, D5)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- D5: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4, D5)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- Box::new(dispatchers.4),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, D5, D6, G> From<(D1, D2, D3, D4, D5, D6)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- D5: Dispatcher<G> + Send + Sync + 'static,
- D6: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4, D5, D6)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- Box::new(dispatchers.4),
- Box::new(dispatchers.5),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, D5, D6, D7, G> From<(D1, D2, D3, D4, D5, D6, D7)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- D5: Dispatcher<G> + Send + Sync + 'static,
- D6: Dispatcher<G> + Send + Sync + 'static,
- D7: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4, D5, D6, D7)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- Box::new(dispatchers.4),
- Box::new(dispatchers.5),
- Box::new(dispatchers.6),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<G> std::ops::Deref for Dispatchers<G> {
- type Target = Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>>;
-
- fn deref(&self) -> &Self::Target {
- &self.dispatcher
- }
-}
-
-#[allow(deprecated)]
-impl<G> From<Dispatchers<G>> for Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>> {
- fn from(val: Dispatchers<G>) -> Self {
- val.dispatcher
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
@@ -485,118 +184,6 @@ mod tests {
}
#[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_single_tuple() {
- let disp = MockDispatcher { name: "foo" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
- assert_eq!(dispatchers.dispatcher.len(), 1);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_two_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2));
- assert_eq!(dispatchers.dispatcher.len(), 2);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_three_tuple() {
- let d1 = MockDispatcher { name: "x" };
- let d2 = MockDispatcher { name: "y" };
- let d3 = MockDispatcher { name: "z" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3));
- assert_eq!(dispatchers.dispatcher.len(), 3);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_four_tuple() {
- let d1 = MockDispatcher { name: "1" };
- let d2 = MockDispatcher { name: "2" };
- let d3 = MockDispatcher { name: "3" };
- let d4 = MockDispatcher { name: "4" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4));
- assert_eq!(dispatchers.dispatcher.len(), 4);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_five_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let d3 = MockDispatcher { name: "c" };
- let d4 = MockDispatcher { name: "d" };
- let d5 = MockDispatcher { name: "e" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4, d5));
- assert_eq!(dispatchers.dispatcher.len(), 5);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_six_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let d3 = MockDispatcher { name: "c" };
- let d4 = MockDispatcher { name: "d" };
- let d5 = MockDispatcher { name: "e" };
- let d6 = MockDispatcher { name: "f" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4, d5, d6));
- assert_eq!(dispatchers.dispatcher.len(), 6);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_seven_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let d3 = MockDispatcher { name: "c" };
- let d4 = MockDispatcher { name: "d" };
- let d5 = MockDispatcher { name: "e" };
- let d6 = MockDispatcher { name: "f" };
- let d7 = MockDispatcher { name: "g" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4, d5, d6, d7));
- assert_eq!(dispatchers.dispatcher.len(), 7);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_vec_of_boxed() {
- let d1: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "a" });
- let d2: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "b" });
- let dispatchers: Dispatchers<MockG> = vec![d1, d2].into();
- assert_eq!(dispatchers.dispatcher.len(), 2);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_single_boxed() {
- let d: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "x" });
- let dispatchers: Dispatchers<MockG> = d.into();
- assert_eq!(dispatchers.dispatcher.len(), 1);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_deref() {
- let disp = MockDispatcher { name: "test" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
- let inner: &Vec<Box<dyn Dispatcher<MockG> + Send + Sync + 'static>> = &dispatchers;
- assert_eq!(inner.len(), 1);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_into_vec() {
- let disp = MockDispatcher { name: "foo" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
- let vec: Vec<Box<dyn Dispatcher<MockG> + Send + Sync + 'static>> = dispatchers.into();
- assert_eq!(vec.len(), 1);
- }
-
- #[test]
fn test_box_clone_dispatcher() {
let disp: Box<dyn Dispatcher<MockG>> = Box::new(MockDispatcher { name: "clonable" });
let cloned = disp.clone_dispatcher();
diff --git a/mingling_core/src/build/pathf.rs b/mingling_core/src/build/pathf.rs
index 23d3910..4b8af1b 100644
--- a/mingling_core/src/build/pathf.rs
+++ b/mingling_core/src/build/pathf.rs
@@ -1,6 +1,5 @@
#![allow(unused_imports)]
-pub use mingling_pathf::config::*;
pub use mingling_pathf::module_pathf::*;
pub use mingling_pathf::pattern_analyzer::*;
pub use mingling_pathf::patterns::*;
@@ -37,10 +36,7 @@ pub fn analyze_and_build_type_mapping_for(
crate_dir: &Path,
output_dir: &Path,
) -> Result<(), crate::error::MinglingPathfinderError> {
- let config = mingling_pathf::config::PathfinderConfig {
- use_dispatch_tree: cfg!(feature = "dispatch_tree"),
- };
- mingling_pathf::analyze_and_build_type_mapping_for(crate_dir, output_dir, &config)
+ mingling_pathf::analyze_and_build_type_mapping_for(crate_dir, output_dir)
}
/// # Analyzes and builds a type mapping
@@ -81,9 +77,6 @@ pub fn analyze_and_build_type_mapping_for(
/// ```
pub fn analyze_and_build_type_mapping() -> Result<(), crate::error::MinglingPathfinderError> {
- let config = mingling_pathf::config::PathfinderConfig {
- use_dispatch_tree: cfg!(feature = "dispatch_tree"),
- };
let crate_dir =
std::env::current_dir().map_err(crate::error::MinglingPathfinderError::IoError)?;
let crate_name = std::env::var("CARGO_PKG_NAME").map_err(|_| {
@@ -99,7 +92,7 @@ pub fn analyze_and_build_type_mapping() -> Result<(), crate::error::MinglingPath
))
})?;
let output_dir = Path::new(&out_dir).join(&crate_name);
- mingling_pathf::analyze_and_build_type_mapping_for(&crate_dir, &output_dir, &config)?;
+ mingling_pathf::analyze_and_build_type_mapping_for(&crate_dir, &output_dir)?;
println!("cargo:rerun-if-changed=src/");
Ok(())
}
diff --git a/mingling_core/src/comp.rs b/mingling_core/src/comp.rs
index aea46e1..399c9bf 100644
--- a/mingling_core/src/comp.rs
+++ b/mingling_core/src/comp.rs
@@ -37,12 +37,6 @@ pub const COMPLETION_SUBCOMMAND: &str = "__comp";
#[cfg(feature = "debug")]
use crate::debug::init_env_logger;
-#[cfg(not(feature = "dispatch_tree"))]
-use crate::ChainProcess;
-
-#[cfg(not(feature = "dispatch_tree"))]
-use crate::exec::match_user_input;
-
/// Mingling Completion Entry Point
///
/// Defines the custom completion logic entry point for the program's shell
@@ -174,30 +168,6 @@ impl CompletionHelper {
let args = first_cmd_match.map_or_else(Vec::new, |start| all_args[start..].to_vec());
trace!("arguments=\"{}\"", args.join(", "));
- #[cfg(not(feature = "dispatch_tree"))]
- let program = this::<P>();
-
- #[cfg(not(feature = "dispatch_tree"))]
- let suggest = if let Ok((dispatcher, args)) = match_user_input(program, &args) {
- trace!(
- "dispatcher matched, dispatcher=\"{}\"",
- dispatcher.node().to_string(),
- );
- let begin = dispatcher.begin(args);
- if let crate::ChainProcess::Ok((any, _)) = begin {
- trace!("entry type: {}", any.member_id);
- let result = P::do_comp(&any, ctx);
- trace!("do_comp result: {:?}", result);
- Some(result)
- } else {
- trace!("begin not Ok");
- None
- }
- } else {
- trace!("no dispatcher matched");
- None
- };
- #[cfg(feature = "dispatch_tree")]
let suggest = if let Ok(any) = P::dispatch_args(&args) {
debug!("dispatch_args OK, member_id = {:?}", any.member_id);
trace!("entry type: {}", any.member_id);
@@ -354,18 +324,8 @@ where
{
let words: Vec<String> = node.split(' ').map(str::to_string).collect();
- #[cfg(feature = "dispatch_tree")]
let lazy_member = P::dispatch_args(&words).ok().map(|any| any.member_id);
- #[cfg(not(feature = "dispatch_tree"))]
- let lazy_member = match match_user_input(this::<P>(), &words) {
- Ok((dispatcher, args)) => match dispatcher.begin(args) {
- ChainProcess::Ok((any, _)) => Some(any.member_id),
- ChainProcess::Err(_) => None,
- },
- Err(_) => None,
- };
-
let member_id = lazy_member?;
P::get_metadata::<Description>(member_id).map(String::from)
}
diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs
index 7bafe72..1c8fb07 100644
--- a/mingling_core/src/program.rs
+++ b/mingling_core/src/program.rs
@@ -53,9 +53,6 @@ where
pub(crate) args: Vec<String>,
- #[cfg(not(feature = "dispatch_tree"))]
- pub(crate) dispatcher: Vec<Box<dyn Dispatcher<C> + Send + Sync>>,
-
/// Program stdout settings.
///
/// This struct controls the program's output behavior, including whether
@@ -116,9 +113,6 @@ where
collect: std::marker::PhantomData,
args: args.into().into(),
- #[cfg(not(feature = "dispatch_tree"))]
- dispatcher: Vec::new(),
-
stdout_setting: ProgramStdoutSetting::default(),
user_context: ProgramUserContext::default(),
@@ -188,25 +182,12 @@ where
get_nodes(self)
}
- /// Dynamically dispatch input arguments to registered entry types
+ /// Dispatch input arguments to an entry
///
/// # Errors
///
/// Returns `Err(ChainProcessError)` if the dispatch fails,
/// e.g., if no dispatcher is found for the given arguments.
- pub fn dispatch_args_dynamic(
- &'static self,
- args: impl Into<StringVec>,
- ) -> Result<AnyOutput<C>, ChainProcessError> {
- let sv: Vec<String> = args.into().into();
- match exec::dispatch_args_dynamic(self, &sv) {
- Ok(ok) => Ok(ok),
- Err(e) => Err(e.into()),
- }
- }
-
- /// Use a prefix tree to quickly match arguments and dispatch to an Entry
- #[cfg(feature = "dispatch_tree")]
pub fn dispatch_args(
&'static self,
args: impl Into<StringVec>,
@@ -225,40 +206,12 @@ where
pub fn get_nodes<C: ProgramCollect<Enum = C>>(
program: &'static Program<C>,
) -> Vec<(String, &'static (dyn Dispatcher<C> + Send + Sync + 'static))> {
- #[cfg(feature = "dispatch_tree")]
let r = C::get_nodes();
- #[cfg(feature = "dispatch_tree")]
- {
- #[cfg(feature = "debug")]
- {
- let node_strs: Vec<String> = r.iter().map(|v| v.0.clone()).collect();
- crate::info!("All Nodes: [{}]", node_strs.join(", "));
- }
- }
-
- #[cfg(not(feature = "dispatch_tree"))]
- let r: Vec<_> = program
- .dispatcher
- .iter()
- .map(|disp| {
- let node_str = disp
- .node()
- .to_string()
- .split('.')
- .collect::<Vec<_>>()
- .join(" ");
- (node_str, &**disp)
- })
- .collect();
-
- #[cfg(not(feature = "dispatch_tree"))]
+ #[cfg(feature = "debug")]
{
- #[cfg(feature = "debug")]
- {
- let node_strs: Vec<String> = r.iter().map(|v| v.0.clone()).collect();
- crate::info!("All Nodes: [{}]", node_strs.join(", "));
- }
+ let node_strs: Vec<String> = r.iter().map(|v| v.0.clone()).collect();
+ crate::info!("All Nodes: [{}]", node_strs.join(", "));
}
r
diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs
index 438b800..c571887 100644
--- a/mingling_core/src/program/collection.rs
+++ b/mingling_core/src/program/collection.rs
@@ -2,7 +2,6 @@
#[cfg(feature = "async")]
use std::pin::Pin;
-#[cfg(feature = "dispatch_tree")]
use crate::Dispatcher;
use crate::{AnyOutput, ChainProcess, Grouped, RenderResult};
@@ -34,26 +33,19 @@ pub trait ProgramCollect {
/// you can use the `empty_result!()` macro to create this
type ResultEmpty: Grouped<Self::Enum>;
- /// Use a prefix tree to quickly match arguments and dispatch to an Entry
- #[cfg(feature = "dispatch_tree")]
- fn dispatch_args(
- raw: &[String],
- ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError>;
-
- #[cfg(not(feature = "dispatch_tree"))]
- /// Use a prefix tree to quickly match arguments and dispatch to an Entry
+ /// Dispatch the raw user arguments to an Entry.
+ ///
+ /// The concrete matching strategy (trie or linear list) is generated by
+ /// `gen_program!` and selected by the `dispatch_tree` feature.
///
/// # Errors
///
/// Returns an error if the program fails to execute the given arguments.
fn dispatch_args(
- _raw: &[String],
- ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError> {
- unreachable!()
- }
+ raw: &[String],
+ ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError>;
/// Get all registered dispatcher names from the program
- #[cfg(feature = "dispatch_tree")]
fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))>;
/// Build an [`AnyOutput`](./struct.AnyOutput.html) to indicate that a renderer was not found
diff --git a/mingling_core/src/program/collection/mock.rs b/mingling_core/src/program/collection/mock.rs
index 662d8f2..d256cc1 100644
--- a/mingling_core/src/program/collection/mock.rs
+++ b/mingling_core/src/program/collection/mock.rs
@@ -3,7 +3,6 @@ use crate::{AnyOutput, ChainProcess, Grouped, ProgramCollect, RenderResult};
#[cfg(feature = "async")]
use std::pin::Pin;
-#[cfg(feature = "dispatch_tree")]
use crate::Dispatcher;
#[cfg(feature = "comp")]
@@ -74,14 +73,12 @@ impl ProgramCollect for MockProgramCollect {
type ErrorRendererNotFound = Self;
type ResultEmpty = Self;
- #[cfg(feature = "dispatch_tree")]
fn dispatch_args(
_raw: &[String],
) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError> {
unreachable!()
}
- #[cfg(feature = "dispatch_tree")]
fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))> {
unreachable!()
}
diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs
index 3ad5ba8..4980d40 100644
--- a/mingling_core/src/program/exec.rs
+++ b/mingling_core/src/program/exec.rs
@@ -3,7 +3,7 @@
#![allow(clippy::too_many_lines)]
use crate::{
- AnyOutput, ChainProcess, Dispatcher, NextProcess, Program, ProgramCollect, RenderResult,
+ AnyOutput, ChainProcess, NextProcess, Program, ProgramCollect, RenderResult,
error::ProgramInternalExecuteError, hook::ProgramControls,
};
@@ -58,12 +58,8 @@ where
current
);
- // Dispatch args - either via dynamic dispatch or trie dispatch based on feature flag
- let mut current = if cfg!(not(feature = "dispatch_tree")) {
- dispatch_args_dynamic(program, args)?
- } else {
- C::dispatch_args(args)?
- };
+ // Dispatch args
+ let mut current = C::dispatch_args(args)?;
// Run hook
control!(
@@ -180,76 +176,6 @@ where
Ok(render_result)
}
-/// Dynamically dispatch input arguments to registered entry types
-pub(crate) fn dispatch_args_dynamic<C>(
- program: &'static Program<C>,
- args: &[String],
-) -> Result<AnyOutput<C>, ProgramInternalExecuteError>
-where
- C: ProgramCollect<Enum = C>,
-{
- let next = match match_user_input(program, args) {
- Ok((dispatcher, args)) => {
- // Entry point
- match dispatcher.begin(args) {
- ChainProcess::Ok((any, _)) => any,
- ChainProcess::Err(e) => return Err(e.into()),
- }
- }
- Err(ProgramInternalExecuteError::DispatcherNotFound) => {
- // No matching Dispatcher is found
- C::build_entry_fallback(args.to_vec())
- }
- Err(e) => return Err(e),
- };
- Ok(next)
-}
-
-/// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments.
-#[allow(clippy::type_complexity)]
-pub(crate) fn match_user_input<C>(
- program: &'static Program<C>,
- args: &[String],
-) -> Result<(&'static (dyn Dispatcher<C> + Send + Sync), Vec<String>), ProgramInternalExecuteError>
-where
- C: ProgramCollect<Enum = C>,
-{
- let nodes = program.get_nodes();
- let command = format!("{} ", args.join(" "));
-
- // Find all nodes that match the command prefix
- let matching_nodes: Vec<&(String, &(dyn Dispatcher<C> + Send + Sync))> = nodes
- .iter()
- // Also add a space to the node string to ensure consistent matching logic
- .filter(|(node_str, _)| command.starts_with(&format!("{node_str} ")))
- .collect();
-
- match matching_nodes.len() {
- 0 => {
- // No matching node found
- Err(ProgramInternalExecuteError::DispatcherNotFound)
- }
- 1 => {
- let matched_prefix = matching_nodes[0];
- let prefix_len = matched_prefix.0.split_whitespace().count();
- let trimmed_args: Vec<String> = args.iter().skip(prefix_len).cloned().collect();
- Ok((matched_prefix.1, trimmed_args))
- }
- _ => {
- // Multiple matching nodes found
- // Find the node with the longest length (most specific match)
- let matched_prefix = matching_nodes
- .iter()
- .max_by_key(|node| node.0.len())
- .unwrap();
-
- let prefix_len = matched_prefix.0.split_whitespace().count();
- let trimmed_args: Vec<String> = args.iter().skip(prefix_len).cloned().collect();
- Ok((matched_prefix.1, trimmed_args))
- }
- }
-}
-
#[inline]
pub(crate) fn handle_program_control<C: ProgramCollect<Enum = C>>(
program: &Program<C>,
diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs
index 92106f9..a5cd3a7 100644
--- a/mingling_core/src/program/hook.rs
+++ b/mingling_core/src/program/hook.rs
@@ -722,6 +722,16 @@ mod tests {
type ErrorRendererNotFound = Self;
type ResultEmpty = Self;
+ fn dispatch_args(
+ _raw: &[String],
+ ) -> Result<crate::AnyOutput<Self>, crate::error::ProgramInternalExecuteError> {
+ unreachable!()
+ }
+
+ fn get_nodes() -> Vec<(String, &'static (dyn crate::Dispatcher<Self> + Send + Sync))> {
+ unreachable!()
+ }
+
fn build_renderer_not_found(_member_id: Self) -> crate::AnyOutput<Self> {
unreachable!()
}
diff --git a/mingling_macros/src/attr/command.rs b/mingling_macros/src/attr/command.rs
index c2ddde3..4542bd7 100644
--- a/mingling_macros/src/attr/command.rs
+++ b/mingling_macros/src/attr/command.rs
@@ -321,19 +321,16 @@ pub(crate) fn command_attr(attr: TokenStream, item: TokenStream) -> TokenStream
fn_name.span(),
);
- // dispatcher internal static (only exists with dispatch_tree feature)
- #[cfg(feature = "dispatch_tree")]
+ // dispatcher internal static (always exists now that dispatchers are
+ // collected at compile time regardless of the `dispatch_tree` feature)
let snaked_node = just_fmt::snake_case!(names.node_lit.value());
- #[cfg(feature = "dispatch_tree")]
let dispatcher_internal = {
let ident = Ident::new(
- &format!("__internal_dispatcher_{}", snaked_node),
+ &format!("__internal_dispatcher_{snaked_node}"),
fn_name.span(),
);
quote! { #vis use super::#ident; }
};
- #[cfg(not(feature = "dispatch_tree"))]
- let dispatcher_internal = quote! {};
let cmd_name = &names.cmd_name;
let entry_type = &names.entry_type;
diff --git a/mingling_macros/src/attr/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs
index 9aa7779..2f45b14 100644
--- a/mingling_macros/src/attr/dispatcher_clap.rs
+++ b/mingling_macros/src/attr/dispatcher_clap.rs
@@ -174,8 +174,8 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
None
};
- let dispatch_tree_entry =
- get_dispatch_tree_entry(&command_name_str, dispatcher_struct, struct_name);
+ let compile_time_registration =
+ get_compile_time_registration(&command_name_str, dispatcher_struct, struct_name);
let expanded = quote! {
// Keep the original struct definition
@@ -187,8 +187,8 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
// Generate the help block if enabled
#help_gen
- // Dispatch tree registration (if feature enabled)
- #dispatch_tree_entry
+ // Compile-time registration
+ #compile_time_registration
// Generate the dispatcher struct
#[doc(hidden)]
@@ -223,8 +223,11 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
expanded.into()
}
-#[cfg(feature = "dispatch_tree")]
-fn get_dispatch_tree_entry(
+/// Registers the dispatcher at compile time (collects its node into the
+/// global `COMPILE_TIME_DISPATCHERS` registry and emits the
+/// `__internal_dispatcher_*` static), regardless of the `dispatch_tree`
+/// feature.
+fn get_compile_time_registration(
command_name_str: &str,
dispatcher_struct: &Ident,
entry_name: &Ident,
@@ -234,12 +237,3 @@ fn get_dispatch_tree_entry(
::mingling::macros::register_dispatcher!(#node_name_lit, #dispatcher_struct, #entry_name);
}
}
-
-#[cfg(not(feature = "dispatch_tree"))]
-fn get_dispatch_tree_entry(
- _command_name_str: &str,
- _dispatcher_struct: &Ident,
- _entry_name: &Ident,
-) -> proc_macro2::TokenStream {
- quote! {}
-}
diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs
index b5e711e..6183993 100644
--- a/mingling_macros/src/func/dispatcher.rs
+++ b/mingling_macros/src/func/dispatcher.rs
@@ -111,7 +111,8 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
let comp_entry = get_comp_entry(&pack);
- let dispatch_tree_entry = get_dispatch_tree_entry(&command_name_str, &command_struct, &pack);
+ let compile_time_registration =
+ get_compile_time_registration(&command_name_str, &command_struct, &pack);
let program_type = crate::default_program_path();
@@ -129,7 +130,7 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
}
#comp_entry
- #dispatch_tree_entry
+ #compile_time_registration
impl ::mingling::Dispatcher<#program_type> for #command_struct {
fn node(&self) -> ::mingling::Node {
@@ -165,8 +166,12 @@ fn get_comp_entry(_entry_name: &Ident) -> TokenStream2 {
quote! {}
}
-#[cfg(feature = "dispatch_tree")]
-fn get_dispatch_tree_entry(
+/// Registers the dispatcher at compile time (collects its node into the
+/// global `COMPILE_TIME_DISPATCHERS` registry and emits the
+/// `__internal_dispatcher_*` static), regardless of the `dispatch_tree`
+/// feature. The feature only selects which matching strategy
+/// (trie vs. linear list) is generated later by `gen_program!`.
+fn get_compile_time_registration(
command_name_str: &str,
command_struct: &Ident,
entry_name: &Ident,
@@ -176,12 +181,3 @@ fn get_dispatch_tree_entry(
::mingling::macros::register_dispatcher!(#node_name_lit, #command_struct, #entry_name);
}
}
-
-#[cfg(not(feature = "dispatch_tree"))]
-fn get_dispatch_tree_entry(
- _command_name_str: &str,
- _command_struct: &Ident,
- _entry_name: &Ident,
-) -> TokenStream2 {
- quote! {}
-}
diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs
index d9001ad..7f77d46 100644
--- a/mingling_macros/src/func/program_comp_gen.rs
+++ b/mingling_macros/src/func/program_comp_gen.rs
@@ -40,14 +40,10 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
}
};
- #[cfg(feature = "dispatch_tree")]
let internal_dispatcher_comp = quote! {
use __internal_completion_mod::__internal_dispatcher_comp;
};
- #[cfg(not(feature = "dispatch_tree"))]
- let internal_dispatcher_comp = quote! {};
-
let comp_dispatcher = quote! {
#[doc(hidden)]
mod __internal_completion_mod {
diff --git a/mingling_macros/src/func/program_final_gen.rs b/mingling_macros/src/func/program_final_gen.rs
index 25bca5e..d549a2b 100644
--- a/mingling_macros/src/func/program_final_gen.rs
+++ b/mingling_macros/src/func/program_final_gen.rs
@@ -4,7 +4,6 @@ use quote::quote;
use crate::CHAINS;
use crate::CHAINS_EXIST;
-#[cfg(feature = "dispatch_tree")]
use crate::COMPILE_TIME_DISPATCHERS;
#[cfg(feature = "comp")]
use crate::COMPLETIONS;
@@ -16,6 +15,8 @@ use crate::RENDERERS_EXIST;
#[cfg(feature = "structural_renderer")]
use crate::STRUCTURAL_RENDERERS;
use crate::get_global_set;
+#[cfg(not(feature = "dispatch_tree"))]
+use crate::systems::dispatch_list_gen;
#[cfg(feature = "dispatch_tree")]
use crate::systems::dispatch_tree_gen;
@@ -24,6 +25,34 @@ const ASYNC_ENABLED: bool = true;
#[cfg(not(feature = "async"))]
const ASYNC_ENABLED: bool = false;
+/// Generate the `get_nodes()` function body for a `ProgramCollect` impl.
+///
+/// Shared by both dispatch strategies (trie and linear list); it only depends
+/// on the compile-time-collected `__internal_dispatcher_*` statics.
+fn gen_get_nodes(entries: &[(String, String, String)]) -> proc_macro2::TokenStream {
+ let mut node_entries = Vec::new();
+
+ for (node_name, _disp_type, _entry_name) in entries {
+ let static_name_str = format!("__internal_dispatcher_{}", just_fmt::snake_case!(node_name));
+ let static_ident =
+ proc_macro2::Ident::new(&static_name_str, proc_macro2::Span::call_site());
+ let node_display_name = node_name.replace('.', " ");
+ let node_display_lit = syn::LitStr::new(&node_display_name, proc_macro2::Span::call_site());
+
+ node_entries.push(quote! {
+ (#node_display_lit.to_string(), &#static_ident)
+ });
+ }
+
+ quote! {
+ fn get_nodes() -> Vec<(String, &'static (dyn ::mingling::Dispatcher<Self::Enum> + Send + Sync))> {
+ vec![
+ #(#node_entries),*
+ ]
+ }
+ }
+}
+
/// Parses an entry of the format `StructName => EnumVariant,` into a pair of idents.
fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, proc_macro2::Ident) {
let s = entry.to_string();
@@ -117,7 +146,6 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream {
#[cfg(not(feature = "structural_renderer"))]
let structural_render = quote! {};
- #[cfg(feature = "dispatch_tree")]
let compile_time_dispatchers: Vec<String> = get_global_set(&COMPILE_TIME_DISPATCHERS)
.lock()
.unwrap()
@@ -126,35 +154,45 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream {
.cloned()
.collect();
- #[cfg(feature = "dispatch_tree")]
- let dispatch_tree_nodes = {
- let entries: Vec<(String, String, String)> = compile_time_dispatchers
- .iter()
- .filter_map(|entry| {
- let parts: Vec<&str> = entry.split(':').collect();
- if parts.len() == 3 {
- Some((
- parts[0].to_string(),
- parts[1].to_string(),
- parts[2].to_string(),
- ))
- } else {
- None
- }
- })
- .collect();
+ let entries: Vec<(String, String, String)> = compile_time_dispatchers
+ .iter()
+ .filter_map(|entry| {
+ let parts: Vec<&str> = entry.split(':').collect();
+ if parts.len() == 3 {
+ Some((
+ parts[0].to_string(),
+ parts[1].to_string(),
+ parts[2].to_string(),
+ ))
+ } else {
+ None
+ }
+ })
+ .collect();
- let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries);
- let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries);
+ // The `dispatch_tree` feature only selects the internal matching strategy:
+ // a char-level trie when enabled, a linear longest-prefix list otherwise.
+ #[cfg(feature = "dispatch_tree")]
+ let dispatch_gen = {
+ let get_nodes_fn = gen_get_nodes(&entries);
+ let dispatch_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries);
quote! {
#get_nodes_fn
- #dispatch_trie_fn
+ #dispatch_fn
}
};
#[cfg(not(feature = "dispatch_tree"))]
- let dispatch_tree_nodes = quote! {};
+ let dispatch_gen = {
+ let get_nodes_fn = gen_get_nodes(&entries);
+ let dispatch_fn = dispatch_list_gen::gen_dispatch_args(&entries);
+
+ quote! {
+ #get_nodes_fn
+ #dispatch_fn
+ }
+ };
#[cfg(feature = "comp")]
let completion_tokens: Vec<proc_macro2::TokenStream> = completions
@@ -367,7 +405,7 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream {
_ => false
}
}
- #dispatch_tree_nodes
+ #dispatch_gen
#structural_render
#comp
}
@@ -395,7 +433,6 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream {
get_global_set(&METADATA).lock().unwrap().clear();
#[cfg(feature = "comp")]
get_global_set(&COMPLETIONS).lock().unwrap().clear();
- #[cfg(feature = "dispatch_tree")]
get_global_set(&COMPILE_TIME_DISPATCHERS)
.lock()
.unwrap()
diff --git a/mingling_macros/src/func/register_dispatcher.rs b/mingling_macros/src/func/register_dispatcher.rs
index eb4a6e4..dc9a31a 100644
--- a/mingling_macros/src/func/register_dispatcher.rs
+++ b/mingling_macros/src/func/register_dispatcher.rs
@@ -1,26 +1,19 @@
// Doc Not Optimize
-#[cfg(feature = "dispatch_tree")]
use just_fmt::snake_case;
use proc_macro::TokenStream;
use quote::quote;
-#[cfg(feature = "dispatch_tree")]
use syn::parse::{Parse, ParseStream};
-#[cfg(feature = "dispatch_tree")]
use syn::{Ident, LitStr, Result as SynResult, Token};
-#[cfg(feature = "dispatch_tree")]
use crate::COMPILE_TIME_DISPATCHERS;
-#[cfg(feature = "dispatch_tree")]
use crate::get_global_set;
-#[cfg(feature = "dispatch_tree")]
struct RegisterDispatcherInput {
node_name: LitStr,
dispatcher_type: Ident,
entry_name: Ident,
}
-#[cfg(feature = "dispatch_tree")]
impl Parse for RegisterDispatcherInput {
fn parse(input: ParseStream) -> SynResult<Self> {
let node_name: LitStr = input.parse()?;
@@ -28,7 +21,7 @@ impl Parse for RegisterDispatcherInput {
let dispatcher_type: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let entry_name: Ident = input.parse()?;
- Ok(RegisterDispatcherInput {
+ Ok(Self {
node_name,
dispatcher_type,
entry_name,
@@ -36,7 +29,6 @@ impl Parse for RegisterDispatcherInput {
}
}
-#[cfg(feature = "dispatch_tree")]
pub(crate) fn register_dispatcher(input: TokenStream) -> TokenStream {
let RegisterDispatcherInput {
node_name,
@@ -56,10 +48,7 @@ pub(crate) fn register_dispatcher(input: TokenStream) -> TokenStream {
get_global_set(&COMPILE_TIME_DISPATCHERS)
.lock()
.unwrap()
- .insert(format!(
- "{}:{}:{}",
- node_name_str, dispatcher_type, entry_name
- ));
+ .insert(format!("{node_name_str}:{dispatcher_type}:{entry_name}"));
let expanded = quote! {
#[doc(hidden)]
@@ -69,8 +58,3 @@ pub(crate) fn register_dispatcher(input: TokenStream) -> TokenStream {
expanded.into()
}
-
-#[cfg(not(feature = "dispatch_tree"))]
-pub(crate) fn register_dispatcher(_input: TokenStream) -> TokenStream {
- quote! {}.into()
-}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index bf3e650..ad84548 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -1,146 +1,9 @@
-// Doc Not Optimize
//! Proc-macro engine of the Mingling CLI framework.
//!
//! This crate is the **macro layer** of Mingling. Each `#[attribute]` or `!`-callable
//! macro collects metadata into **compile-time global registries** (`OnceLock<Mutex<BTreeSet>>`).
//! At the end, `gen_program!` reads all registries and generates the final program struct
//! with all dispatchers, chains, renderers, and completions wired together.
-//!
-//! # How Macros Work Together
-//!
-//! The Mingling macro pipeline has three phases:
-//!
-//! ```text
-//! ┌──────────────────────────────────────────────────────────────────┐
-//! │ Phase 1: Declaration │
-//! │ │
-//! │ dispatcher! pack! node! #[derive(Grouped)] │
-//! │ │ │ │ │ │
-//! │ V V V V │
-//! │ Declares Wraps a Builds Makes a type │
-//! │ a command type in a command recognizable │
-//! │ entry a new path Node by the │
-//! │ type framework │
-//! ├──────────────────────────────────────────────────────────────────┤
-//! │ Phase 2: Registration (at compile time, in statics) │
-//! │ │
-//! │ #[chain] #[renderer] #[help] #[completion] │
-//! │ │ │ │ │ │
-//! │ V V V V │
-//! │ Registers Registers Registers Registers │
-//! │ type → chain type → renderer type → help completion logic │
-//! ├──────────────────────────────────────────────────────────────────┤
-//! │ Phase 3: Code Generation │
-//! │ │
-//! │ gen_program!() │
-//! │ │ │
-//! │ V │
-//! │ Reads all registries → generates ThisProgram with: │
-//! │ • ProgramCollect impl (dispatch/render/chain dispatch tree) │
-//! │ • Fallback types (EntryFallback, etc.) │
-//! │ • Completion logic (if `comp` feature enabled) │
-//! └──────────────────────────────────────────────────────────────────┘
-//! ```
-//!
-//! # Macro Categories
-//!
-//! ## Phase 1: Command & Type Declaration
-//!
-//! | Macro | What it does |
-//! |-------|-------------|
-//! | `dispatcher!` | Declares a command entry point and its argument type |
-//! | `dispatcher_clap!` | Like `dispatcher!` but powered by `clap::Parser` |
-//! | `node!` | Builds a [`Node`](https://docs.rs/mingling/latest/mingling/struct.Node.html) from a dot-separated path string |
-//! | `pack!` | Creates a newtype wrapper around an inner type for use in Chain/Renderer |
-//! | `pack_structural!` | Like `pack!` but also derives `StructuralData` for structured output |
-//! | `pack_err!` | Creates an error struct with automatic `name` field |
-//! | `pack_err_structural!` | Like `pack_err!` but also derives `StructuralData` for structured output |
-//! | `entry!` | Creates a packed entry from string literals |
-//! | [`#[derive(Grouped)]`](derive@Grouped) | Makes a type recognizable by the framework's type registry |
-//! | `#[derive(StructuralData)]` | Marks a type as eligible for structured output (JSON/YAML/etc.) |
-//! | [`#[derive(EnumTag)]`](derive@EnumTag) | Adds enum variant metadata (name, description) |
-//!
-//! ## Phase 2: Processing & Rendering Registration
-//!
-//! | Macro | What it does |
-//! |-------|-------------|
-//! | [`#[chain]`](attr.chain.html) | Transforms a function into a chain processing step |
-//! | [`#[renderer]`](attr.renderer.html) | Transforms a function into a renderer for a type |
-//! | [`#[help]`](attr.help.html) | Defines help output for a command entry type |
-//! | `route!` | Routes execution depending on a condition |
-//! | `empty_result!` | Returns an empty result for early termination |
-//! | [`#[completion]`](attr.completion.html) | Registers a shell completion handler |
-//!
-//! ## Phase 3: Program Generation
-//!
-//! | Macro | What it does |
-//! |-------|-------------|
-//! | `gen_program!` | **Final step**: reads all registries and generates the full program |
-//! | `suggest!` | Generates suggestion logic for a dispatcher |
-//! | `suggest_enum!` | Generates suggestion logic for an enum dispatcher |
-//!
-//! ## Internal (used by the macros above)
-//!
-//! | Macro | What it does |
-//! |-------|-------------|
-//! | `register_type!` | Registers a type in the packed-type registry |
-//! | `register_chain!` | Registers a chain mapping in the chain registry |
-//! | `register_renderer!` | Registers a renderer mapping in the renderer registry |
-//! | `register_dispatcher!` | Registers a dispatcher for the `dispatch_tree` feature |
-//! | `register_help!` | Registers a help request handler |
-//! | `program_fallback_gen!` | Generates fallback error types |
-//! | `program_final_gen!` | Generates the `ProgramCollect` impl and `ThisProgram` struct |
-//! | `program_comp_gen!` | Generates completion logic |
-//! | [`#[program_setup]`](attr.program_setup.html) | Declares a custom program setup step |
-//!
-//! # Feature Gates
-//!
-//! Some macros are only available when certain Cargo features are enabled:
-//!
-//! | Feature | Macros enabled |
-//! |---------|---------------|
-//! | `clap` | `dispatcher_clap!` |
-//! | `comp` | [`#[completion]`](attr.completion.html), `suggest!`, `suggest_enum!` |
-//! | `extras` | `entry!`, `empty_result!`, `route!`, [`#[program_setup]`](attr.program_setup.html), `group!` |
-//! | `dispatch_tree` | `register_dispatcher!` (enables trie-based command dispatch) |
-//! | `structural_renderer` | `#[derive(StructuralData)]`, `pack_structural!`, `pack_err_structural!`, `group_structural!` |
-//! | `structural_renderer` + `extras` | `group_structural!`, `pack_err_structural!` |
-//! | `async` | Enables async `#[chain]` functions |
-//! | `repl` | Enables REPL execution loop |
-//!
-//! # The Compile-Time Registry System
-//!
-//! Macros in this crate do **not** generate all code immediately. Instead, they
-//! store entries into `OnceLock<Mutex<BTreeSet<String>>>` statics. These string
-//! entries contain the **token-stream representation** of match arms, type mappings,
-//! and struct definitions.
-//!
-//! When `gen_program!` is called, it reads all registries, concatenates their
-//! entries, and emits the complete program:
-//!
-//! ```rust,ignore
-//! // Example of what gen_program! generates (simplified):
-//! impl ProgramCollect for ThisProgram {
-//! fn build_entry_fallback(args: Vec<String>) -> AnyOutput {
-//! AnyOutput::new(EntryFallback::new(args))
-//! }
-//! fn has_chain(any: &AnyOutput) -> bool {
-//! match any.member_id() {
-//! MyType => true, // ← collected from #[chain] macros
-//! _ => false,
-//! }
-//! }
-//! fn has_renderer(any: &AnyOutput) -> bool {
-//! match any.member_id() {
-//! MyType => true, // ← collected from #[renderer] macros
-//! // When `structural_renderer` is enabled, ALL registered types
-//! // return true — non-structural types fall through to render
-//! // a `ResultEmpty` value (via structural_render fallback).
-//! _ => false,
-//! }
-//! }
-//! }
-//! ```
#![deny(missing_docs)]
#![deny(clippy::pedantic)]
@@ -204,7 +67,6 @@ pub(crate) static STRUCTURED_TYPES: Registry = OnceLock::new();
#[cfg(feature = "comp")]
pub(crate) static COMPLETIONS: Registry = OnceLock::new();
-#[cfg(feature = "dispatch_tree")]
pub(crate) static COMPILE_TIME_DISPATCHERS: Registry = OnceLock::new();
pub(crate) static PACKED_TYPES: Registry = OnceLock::new();
@@ -740,8 +602,9 @@ pub fn empty_result(input: TokenStream) -> TokenStream {
/// - `node()` returns the [`Node`] hierarchy for the command path.
/// - `begin(args)` wraps `args` into the entry type and routes to chain.
/// - `clone_dispatcher()` returns a boxed clone.
-/// 3. **Registration** — If the `dispatch_tree` feature is enabled, also calls
-/// `register_dispatcher!` for compile-time trie construction.
+/// 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!`).
///
/// With the `comp` feature, the entry type also implements `CompletionEntry`
/// for providing shell completion suggestions.
@@ -1322,13 +1185,14 @@ pub fn register_metadata(input: TokenStream) -> TokenStream {
func::register_metadata::register_metadata_impl(input)
}
-/// Registers a dispatcher at compile time for the `dispatch_tree` feature.
+/// Registers a dispatcher at compile time.
///
-/// This macro is called internally by `dispatcher!` when the `dispatch_tree`
-/// feature is enabled. Each call stores the node name into the global
-/// `COMPILE_TIME_DISPATCHERS` registry and generates a static variable for the
-/// dispatcher instance. This data is later consumed by `gen_program!` to
-/// generate a character-level **Trie** for efficient command dispatch.
+/// This macro is called internally by `dispatcher!` and `dispatcher_clap!`.
+/// Each call stores the node name into the global `COMPILE_TIME_DISPATCHERS`
+/// registry and generates a static variable for the dispatcher instance. This
+/// data is later consumed by `gen_program!` to generate command matching: a
+/// character-level **trie** when the `dispatch_tree` feature is enabled, or a
+/// linear longest-prefix list otherwise.
///
/// The trie dispatch works by grouping commands by their character prefix,
/// enabling O(n) lookup (where n is input length) instead of linear iteration
@@ -1345,7 +1209,7 @@ pub fn register_metadata(input: TokenStream) -> TokenStream {
/// # See also
///
/// - `dispatcher!` — The primary way to declare dispatchers (calls this internally).
-/// - `dispatch_tree_gen` module — The trie generation logic.
+/// - `dispatch_tree_gen` / `dispatch_list_gen` modules — The matching-strategy generators.
#[proc_macro]
pub fn register_dispatcher(input: TokenStream) -> TokenStream {
func::register_dispatcher::register_dispatcher(input)
@@ -1935,8 +1799,8 @@ pub fn gen_program(input: TokenStream) -> TokenStream {
/// 3. An internal renderer `__render_completion` that renders the suggestions via
/// `CompletionHelper::render_suggest`.
///
-/// When the `dispatch_tree` feature is enabled, it also imports the internal dispatcher
-/// from the generated module into the parent scope for trie-based dispatch.
+/// It also imports the internal dispatcher from the generated module into the
+/// parent scope for compile-time collection.
///
/// This macro is called automatically by `gen_program!` and should not be called
/// directly by user code.
diff --git a/mingling_macros/src/systems.rs b/mingling_macros/src/systems.rs
index 44478a0..ef3624e 100644
--- a/mingling_macros/src/systems.rs
+++ b/mingling_macros/src/systems.rs
@@ -1,4 +1,6 @@
// Doc Not Optimize
+#[cfg(not(feature = "dispatch_tree"))]
+pub(crate) mod dispatch_list_gen;
#[cfg(feature = "dispatch_tree")]
pub(crate) mod dispatch_tree_gen;
pub(crate) mod res_injection;
diff --git a/mingling_macros/src/systems/dispatch_list_gen.rs b/mingling_macros/src/systems/dispatch_list_gen.rs
new file mode 100644
index 0000000..52b0e86
--- /dev/null
+++ b/mingling_macros/src/systems/dispatch_list_gen.rs
@@ -0,0 +1,57 @@
+// Doc Not Optimize
+use std::cmp::Reverse;
+
+use proc_macro2::TokenStream;
+use quote::quote;
+
+/// Generate the `dispatch_args()` function body for a `ProgramCollect` impl
+/// using linear matching over the compile-time-collected dispatchers.
+///
+/// Nodes are sorted by display-name length (longest first) so the first
+/// matching node is the most specific one, mirroring the "longest registered
+/// prefix wins" rule of the old dynamic dispatcher.
+pub(crate) fn gen_dispatch_args(entries: &[(String, String, String)]) -> TokenStream {
+ let mut nodes: Vec<(String, String)> = entries
+ .iter()
+ .map(|(name, disp, _)| (name.replace('.', " "), disp.clone()))
+ .collect();
+ nodes.sort_by_key(|(name, _)| Reverse(name.len()));
+
+ let arms: Vec<TokenStream> = nodes
+ .iter()
+ .map(|(name, disp_type)| {
+ let name_space = format!("{name} ");
+ let name_lit = syn::LitStr::new(&name_space, proc_macro2::Span::call_site());
+ let disp_ident = proc_macro2::Ident::new(disp_type, proc_macro2::Span::call_site());
+ let prefix_word_count = name.split_whitespace().count();
+ quote! {
+ if raw_str.starts_with(#name_lit) {
+ let prefix_len = #prefix_word_count;
+ let trimmed_args: Vec<String> = raw.iter().skip(prefix_len).cloned().collect();
+ let __cp = <#disp_ident as ::mingling::Dispatcher<Self::Enum>>::begin(
+ &#disp_ident::default(),
+ trimmed_args,
+ );
+ return match __cp {
+ ::mingling::ChainProcess::Ok(any_output) => Ok(any_output.0),
+ ::mingling::ChainProcess::Err(chain_process_error) => {
+ Err(chain_process_error.into())
+ }
+ };
+ }
+ }
+ })
+ .collect();
+
+ quote! {
+ fn dispatch_args(
+ raw: &[String],
+ ) -> Result<::mingling::AnyOutput<Self::Enum>, ::mingling::error::ProgramInternalExecuteError>
+ {
+ let raw_string = format!("{} ", raw.join(" "));
+ let raw_str = raw_string.as_str();
+ #(#arms)*
+ Ok(Self::build_entry_fallback(raw.to_vec()))
+ }
+ }
+}
diff --git a/mingling_macros/src/systems/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs
index d157feb..2b264f7 100644
--- a/mingling_macros/src/systems/dispatch_tree_gen.rs
+++ b/mingling_macros/src/systems/dispatch_tree_gen.rs
@@ -1,36 +1,10 @@
// Doc Not Optimize
use std::collections::BTreeMap;
-use just_fmt::snake_case;
use proc_macro2::TokenStream;
use quote::quote;
-/// Generate the `get_nodes()` function body for a ProgramCollect impl.
-pub(crate) fn gen_get_nodes(entries: &[(String, String, String)]) -> TokenStream {
- let mut node_entries = Vec::new();
-
- for (node_name, _disp_type, _entry_name) in entries {
- let static_name_str = format!("__internal_dispatcher_{}", snake_case!(node_name));
- let static_ident =
- proc_macro2::Ident::new(&static_name_str, proc_macro2::Span::call_site());
- let node_display_name = node_name.replace('.', " ");
- let node_display_lit = syn::LitStr::new(&node_display_name, proc_macro2::Span::call_site());
-
- node_entries.push(quote! {
- (#node_display_lit.to_string(), &#static_ident)
- });
- }
-
- quote! {
- fn get_nodes() -> Vec<(String, &'static (dyn ::mingling::Dispatcher<Self::Enum> + Send + Sync))> {
- vec![
- #(#node_entries),*
- ]
- }
- }
-}
-
-/// Generate the `dispatch_args()` function body for a ProgramCollect impl.
+/// Generate the `dispatch_args()` function body for a `ProgramCollect` impl.
///
/// Builds a hardcoded match tree: at each depth, group nodes by character.
/// Single-node groups use `starts_with`; multi-node groups recurse with `nth()` match.
@@ -63,7 +37,7 @@ pub(crate) fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> To
/// Recursively build the trie match body.
///
-/// `nodes`: slice of (display_name, disp_type) for commands that share the same prefix so far.
+/// `nodes`: slice of (`display_name`, `disp_type`) for commands that share the same prefix so far.
/// `depth`: The character index currently being matched.
/// `no_match`: fallback code to run when no node in this subtree matches the input.
///
@@ -95,7 +69,7 @@ fn build_dispatch_body(
}
let make_starts_with_arm = |name: &str, disp_type: &str| -> TokenStream {
- let name_space = format!("{} ", name);
+ let name_space = format!("{name} ");
let name_lit = syn::LitStr::new(&name_space, proc_macro2::Span::call_site());
let disp_ident = proc_macro2::Ident::new(disp_type, proc_macro2::Span::call_site());
let prefix_word_count = name.split_whitespace().count();
diff --git a/mingling_pathf/src/config.rs b/mingling_pathf/src/config.rs
deleted file mode 100644
index 10ef002..0000000
--- a/mingling_pathf/src/config.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-// Doc Not Optimize
-//! Configuration for the module pathfinder analysis.
-//!
-//! This module defines [`PathfinderConfig`], which controls behavior such as
-//! whether dispatch-tree related types (`__internal_dispatcher_*`) should be
-//! extracted.
-
-/// Configuration for the module pathfinder analysis.
-///
-/// Controls behavior such as whether dispatch-tree related types
-/// (`__internal_dispatcher_*`) should be extracted.
-#[derive(Debug, Clone, Default)]
-pub struct PathfinderConfig {
- /// Whether to also extract `__internal_dispatcher_*` static types
- /// generated by the `dispatch_tree` feature in Mingling.
- pub use_dispatch_tree: bool,
-}
-
-impl PathfinderConfig {
- /// Create a config with `use_dispatch_tree` enabled.
- #[must_use]
- pub const fn with_dispatch_tree() -> Self {
- Self {
- use_dispatch_tree: true,
- }
- }
-}
diff --git a/mingling_pathf/src/lib.rs b/mingling_pathf/src/lib.rs
index 93a80a3..492bfc7 100644
--- a/mingling_pathf/src/lib.rs
+++ b/mingling_pathf/src/lib.rs
@@ -5,7 +5,6 @@
#![deny(clippy::pedantic)]
#![deny(clippy::nursery)]
-pub mod config;
pub mod error;
pub mod module_pathf;
pub mod pattern_analyzer;
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
index c2dd25f..b7f923c 100644
--- a/mingling_pathf/src/pattern_analyzer.rs
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -9,14 +9,12 @@
//!
//! The entry points are:
//! - [`init()`] — creates a default `PatternAnalyzer` with all built-in patterns.
-//! - [`init_with_config()`] — creates a `PatternAnalyzer` with a given `PathfinderConfig`.
//! - [`PatternAnalyzer::analyze_file()`] / [`PatternAnalyzer::analyze_file_items()`] — run
//! analysis on a single file.
use std::collections::HashSet;
use std::path::Path;
-use crate::config::PathfinderConfig;
use crate::error::MinglingPathfinderError;
use crate::patterns::{
ChainPattern, CommandPattern, CompletionPattern, DispatcherClapPattern, DispatcherPattern,
@@ -26,13 +24,6 @@ use crate::patterns::{
/// Creates a default `PatternAnalyzer` with all built-in patterns pre-registered.
#[must_use]
pub fn init() -> PatternAnalyzer {
- init_with_config(&PathfinderConfig::default())
-}
-
-/// Creates a `PatternAnalyzer` with the given config, used by `mingling_core`'s pathf wrapper
-/// to inject feature-dependent settings (e.g., `dispatch_tree`).
-#[must_use]
-pub fn init_with_config(config: &PathfinderConfig) -> PatternAnalyzer {
let mut analyzer = PatternAnalyzer::new();
analyzer.add_pattern(PackPattern);
analyzer.add_pattern(GroupPattern);
@@ -43,8 +34,8 @@ pub fn init_with_config(config: &PathfinderConfig) -> PatternAnalyzer {
analyzer.add_pattern(HelpPattern);
analyzer.add_pattern(MetadataPattern);
analyzer.add_pattern(CompletionPattern);
- analyzer.add_pattern(DispatcherPattern::new(config.use_dispatch_tree));
- analyzer.add_pattern(DispatcherClapPattern::new(config.use_dispatch_tree));
+ analyzer.add_pattern(DispatcherPattern::new());
+ analyzer.add_pattern(DispatcherClapPattern::new());
analyzer
}
diff --git a/mingling_pathf/src/patterns/dispatcher.rs b/mingling_pathf/src/patterns/dispatcher.rs
index 56d2b97..5090052 100644
--- a/mingling_pathf/src/patterns/dispatcher.rs
+++ b/mingling_pathf/src/patterns/dispatcher.rs
@@ -3,7 +3,7 @@
//! extracts the generated type names from its arguments. It supports:
//! - `Entry*` — the entry type (always generated)
//! - `CMD*` — the dispatcher struct (always generated)
-//! - `__internal_dispatcher_*` — the dispatch tree static (when `use_dispatch_tree` is `true`)
+//! - `__internal_dispatcher_*` — the compile-time collected static (always generated)
//!
//! Supported forms:
//! - Explicit: `dispatcher!("greet", CMDGreet => EntryGreet)`
@@ -19,23 +19,15 @@ use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
/// Matches the `dispatcher!` macro, extracts:
/// - `Entry*` — the entry type (always)
/// - `CMD*` — the dispatcher struct (always)
-/// - `__internal_dispatcher_*` — dispatch tree static (when `use_dispatch_tree` is true)
-pub struct DispatcherPattern {
- /// Whether the dispatcher generates a dispatch tree static (`__internal_dispatcher_*`).
- pub use_dispatch_tree: bool,
-}
+/// - `__internal_dispatcher_*` — the compile-time collected static (always)
+#[derive(Default)]
+pub struct DispatcherPattern;
impl DispatcherPattern {
/// Creates a new `DispatcherPattern`.
- ///
- /// # Arguments
- ///
- /// * `use_dispatch_tree` — when `true`, the generated dispatcher also produces a
- /// `__internal_dispatcher_*` static dispatch tree item. Set this based on whether
- /// your macro invocation includes the `use_dispatch_tree` configuration.
#[must_use]
- pub const fn new(use_dispatch_tree: bool) -> Self {
- Self { use_dispatch_tree }
+ pub const fn new() -> Self {
+ Self
}
}
@@ -62,7 +54,7 @@ impl AnalyzePattern for DispatcherPattern {
if macro_name != "dispatcher" {
continue;
}
- items.extend(extract_all_types(&m.mac.tokens, "", self.use_dispatch_tree));
+ items.extend(extract_all_types(&m.mac.tokens, ""));
}
Item::Mod(item_mod) => {
if let Some((_, nested)) = &item_mod.content {
@@ -74,7 +66,6 @@ impl AnalyzePattern for DispatcherPattern {
items.extend(extract_all_types(
&m.mac.tokens,
&item_mod.ident.to_string(),
- self.use_dispatch_tree,
));
}
}
@@ -98,11 +89,7 @@ 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,
- use_dispatch_tree: bool,
-) -> Vec<AnalyzeItem> {
+fn extract_all_types(tokens: &proc_macro2::TokenStream, module: &str) -> Vec<AnalyzeItem> {
let (cmd_name, cmd_struct, entry_struct) = parse_dispatcher_args(tokens);
let Some(cmd_name) = cmd_name else {
return Vec::new();
@@ -120,11 +107,9 @@ fn extract_all_types(
items.push(AnalyzeItem::local(module.to_string(), cmd.clone()));
}
- // __internal_dispatcher_* — when configured
- if use_dispatch_tree {
- let internal_name = format!("__internal_dispatcher_{}", snake_case(&cmd_name));
- items.push(AnalyzeItem::local(module.to_string(), internal_name));
- }
+ // __internal_dispatcher_* — the compile-time collected static
+ let internal_name = format!("__internal_dispatcher_{}", snake_case(&cmd_name));
+ items.push(AnalyzeItem::local(module.to_string(), internal_name));
items
}
diff --git a/mingling_pathf/src/patterns/dispatcher_clap.rs b/mingling_pathf/src/patterns/dispatcher_clap.rs
index 55001a6..da9da3e 100644
--- a/mingling_pathf/src/patterns/dispatcher_clap.rs
+++ b/mingling_pathf/src/patterns/dispatcher_clap.rs
@@ -5,7 +5,7 @@
//! - The dispatcher command struct (`CMD*`, always)
//! - The error type, if `error = ErrorType` is specified
//! - The help internal struct, if `help = true` is specified
-//! - The `__internal_dispatcher_*` dispatch tree static, if `use_dispatch_tree` is enabled
+//! - The `__internal_dispatcher_*` compile-time collected static (always)
//!
//! Supported forms:
//! - `#[dispatcher_clap("greet", CMDGreet)] struct EntryGreet { ... }`
@@ -22,27 +22,21 @@ use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
/// - The dispatcher struct (`CMD*`, always)
/// - The error type, if `error = ErrorType` is specified
/// - The help internal struct, if `help = true` is specified
-/// - `__internal_dispatcher_*` — dispatch tree static (when `use_dispatch_tree` is true)
+/// - `__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 { ... }`
-pub struct DispatcherClapPattern {
- /// Whether to include the `__internal_dispatcher_*` dispatch tree static in the analysis.
- pub use_dispatch_tree: bool,
-}
+#[derive(Default)]
+pub struct DispatcherClapPattern;
impl DispatcherClapPattern {
- /// Creates a new `DispatcherClapPattern` with the given configuration.
- ///
- /// # Parameters
- /// - `use_dispatch_tree`: When `true`, enables analysis of the `__internal_dispatcher_*`
- /// static dispatch tree for each matched command.
+ /// Creates a new `DispatcherClapPattern`.
#[must_use]
- pub const fn new(use_dispatch_tree: bool) -> Self {
- Self { use_dispatch_tree }
+ pub const fn new() -> Self {
+ Self
}
}
@@ -61,7 +55,7 @@ impl AnalyzePattern for DispatcherClapPattern {
for item in &syntax.items {
match item {
Item::Struct(s) if has_attr(&s.attrs, "dispatcher_clap") => {
- items.extend(self.analyze_struct(s, ""));
+ items.extend(Self::analyze_struct(s, ""));
}
Item::Mod(item_mod) => {
if let Some((_, nested)) = &item_mod.content {
@@ -69,7 +63,7 @@ impl AnalyzePattern for DispatcherClapPattern {
if let Item::Struct(s) = n
&& has_attr(&s.attrs, "dispatcher_clap")
{
- items.extend(self.analyze_struct(s, &item_mod.ident.to_string()));
+ items.extend(Self::analyze_struct(s, &item_mod.ident.to_string()));
}
}
}
@@ -83,7 +77,7 @@ impl AnalyzePattern for DispatcherClapPattern {
}
impl DispatcherClapPattern {
- fn analyze_struct(&self, s: &syn::ItemStruct, module: &str) -> Vec<AnalyzeItem> {
+ fn analyze_struct(s: &syn::ItemStruct, module: &str) -> Vec<AnalyzeItem> {
let mut items = Vec::new();
// Entry type (struct name) — always
@@ -120,10 +114,8 @@ impl DispatcherClapPattern {
items.push(AnalyzeItem::local(module.to_string(), help_struct));
}
- // __internal_dispatcher_* — when configured
- if self.use_dispatch_tree
- && let Some(ref cmd_name) = parsed.cmd_name
- {
+ // __internal_dispatcher_* — the compile-time collected static
+ if let Some(ref cmd_name) = parsed.cmd_name {
let internal_name =
format!("__internal_dispatcher_{}", just_fmt::snake_case!(cmd_name));
items.push(AnalyzeItem::local(module.to_string(), internal_name));
diff --git a/mingling_pathf/src/type_mapping_builder.rs b/mingling_pathf/src/type_mapping_builder.rs
index 5341578..1ccd267 100644
--- a/mingling_pathf/src/type_mapping_builder.rs
+++ b/mingling_pathf/src/type_mapping_builder.rs
@@ -7,7 +7,6 @@ use std::collections::HashSet;
use std::fmt::Write as FmtWrite;
use std::path::Path;
-use crate::config::PathfinderConfig;
use crate::error::MinglingPathfinderError;
use crate::module_pathf;
use crate::pattern_analyzer;
@@ -16,7 +15,6 @@ use crate::pattern_analyzer;
///
/// `crate_dir` — crate root directory (i.e., the directory containing Cargo.toml)
/// `output_dir` — directory where mapping files will be written
-/// `config` — pathfinder configuration (e.g., [`dispatch_tree`] detection)
///
/// Mapping file format per line: `TypeName = crate::module::path::TypeName`
///
@@ -27,10 +25,9 @@ use crate::pattern_analyzer;
pub fn analyze_and_build_type_mapping_for(
crate_dir: &Path,
output_dir: &Path,
- config: &PathfinderConfig,
) -> Result<(), MinglingPathfinderError> {
let module_mapping = module_pathf::analyze(crate_dir)?;
- let analyzer = pattern_analyzer::init_with_config(config);
+ let analyzer = pattern_analyzer::init();
let mut type_mappings: Vec<(String, String, bool)> = Vec::new();
@@ -118,7 +115,7 @@ pub fn analyze_and_build_type_mapping() -> Result<(), MinglingPathfinderError> {
let crate_dir = std::env::current_dir()?;
let output_dir = Path::new(&out_dir).join(&crate_name);
- analyze_and_build_type_mapping_for(&crate_dir, &output_dir, &PathfinderConfig::default())?;
+ analyze_and_build_type_mapping_for(&crate_dir, &output_dir)?;
// Notify Cargo to re-run build.rs when source files change
println!("cargo:rerun-if-changed=src/");
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index 95d7410..81f3b3f 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -269,6 +269,13 @@ fn test_dispatcher_analyze() {
"::sub::CMDGreet",
"::sub::EntryDelete",
"::sub::CMDDelete",
+ // Dispatchers are always collected at compile time:
+ "::__internal_dispatcher_greet",
+ "::__internal_dispatcher_remote_add",
+ "::__internal_dispatcher_delete",
+ "::__internal_dispatcher_remote_rm",
+ "::sub::__internal_dispatcher_greet",
+ "::sub::__internal_dispatcher_delete",
];
assert_eq!(r.len(), required.len());
@@ -279,36 +286,27 @@ fn test_dispatcher_analyze() {
#[test]
fn test_dispatcher_dispatch_tree() {
- use mingling_pathf::config::PathfinderConfig;
use mingling_pathf::pattern_analyzer;
let file = current_dir()
.unwrap()
.join("src/test_files/test_dispatcher_dispatch_tree.rs");
- // Without dispatch_tree: only Entry + CMD types
- let r1 = pattern_analyzer::init().analyze_file(&file).unwrap();
- // 4 root (EntryGreet, CMDGreet, EntryDelete, CMDDelete)
- // + 4 sub (sub::EntryGreet, sub::CMDGreet, sub::EntryDelete, sub::CMDDelete)
- // = 8
- assert_eq!(r1.len(), 8);
- assert!(r1.contains("::EntryGreet"));
- assert!(r1.contains("::CMDGreet"));
- assert!(r1.contains("::EntryDelete"));
- assert!(r1.contains("::CMDDelete"));
- assert!(r1.contains("::sub::EntryGreet"));
- assert!(r1.contains("::sub::CMDGreet"));
-
- // With dispatch_tree: Entry + CMD + __internal_dispatcher
- let r2 = pattern_analyzer::init_with_config(&PathfinderConfig::with_dispatch_tree())
- .analyze_file(&file)
- .unwrap();
- // 8 (from above) + 2 __internal (root) + 2 __internal (sub) = 12
- assert_eq!(r2.len(), 12);
- assert!(r2.contains("::__internal_dispatcher_greet"));
- assert!(r2.contains("::__internal_dispatcher_delete"));
- assert!(r2.contains("::sub::__internal_dispatcher_greet"));
- assert!(r2.contains("::sub::__internal_dispatcher_delete"));
+ // 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
+ 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("::EntryDelete"));
+ assert!(r.contains("::CMDDelete"));
+ assert!(r.contains("::sub::EntryGreet"));
+ assert!(r.contains("::sub::CMDGreet"));
+ assert!(r.contains("::__internal_dispatcher_greet"));
+ assert!(r.contains("::__internal_dispatcher_delete"));
+ assert!(r.contains("::sub::__internal_dispatcher_greet"));
+ assert!(r.contains("::sub::__internal_dispatcher_delete"));
}
#[test]
@@ -355,6 +353,14 @@ fn test_dispatcher_clap_analyze() {
"::sub::EntryWithHelp",
"::sub::CMDHelp",
"::sub::__internal_help_cmdhelp_help",
+ // Dispatchers are always collected at compile time:
+ "::__internal_dispatcher_greet",
+ "::__internal_dispatcher_delete",
+ "::__internal_dispatcher_helpcmd",
+ "::__internal_dispatcher_full",
+ "::sub::__internal_dispatcher_greet",
+ "::sub::__internal_dispatcher_delete",
+ "::sub::__internal_dispatcher_helpcmd",
];
assert_eq!(r.len(), required.len());
@@ -365,29 +371,23 @@ fn test_dispatcher_clap_analyze() {
#[test]
fn test_dispatcher_clap_dispatch_tree() {
- use mingling_pathf::config::PathfinderConfig;
use mingling_pathf::pattern_analyzer;
let file = current_dir()
.unwrap()
.join("src/test_files/test_dispatcher_clap.rs");
- // Without dispatch_tree: 26 items (same set as test_dispatcher_clap_analyze)
- let r1 = pattern_analyzer::init().analyze_file(&file).unwrap();
- assert_eq!(r1.len(), 26);
-
- // With dispatch_tree: 26 + 4 __internal (root) + 3 __internal (sub, no "full") = 33
- let r2 = pattern_analyzer::init_with_config(&PathfinderConfig::with_dispatch_tree())
- .analyze_file(&file)
- .unwrap();
- assert_eq!(r2.len(), 33);
- assert!(r2.contains("::__internal_dispatcher_greet"));
- assert!(r2.contains("::__internal_dispatcher_delete"));
- assert!(r2.contains("::__internal_dispatcher_helpcmd"));
- assert!(r2.contains("::__internal_dispatcher_full"));
- assert!(r2.contains("::sub::__internal_dispatcher_greet"));
- assert!(r2.contains("::sub::__internal_dispatcher_delete"));
- assert!(r2.contains("::sub::__internal_dispatcher_helpcmd"));
+ // Dispatchers are always collected at compile time:
+ // 26 (Entry/CMD/error/help items) + 4 __internal (root) + 3 __internal (sub, no "full") = 33
+ let r = pattern_analyzer::init().analyze_file(&file).unwrap();
+ assert_eq!(r.len(), 33);
+ assert!(r.contains("::__internal_dispatcher_greet"));
+ assert!(r.contains("::__internal_dispatcher_delete"));
+ assert!(r.contains("::__internal_dispatcher_helpcmd"));
+ assert!(r.contains("::__internal_dispatcher_full"));
+ assert!(r.contains("::sub::__internal_dispatcher_greet"));
+ assert!(r.contains("::sub::__internal_dispatcher_delete"));
+ assert!(r.contains("::sub::__internal_dispatcher_helpcmd"));
}
#[test]