From 6980fbf2f9fb4c599d8dc6ff549a8b3288eb24e9 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Mon, 17 Aug 2026 02:29:30 +0800 Subject: 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). --- CHANGELOG.md | 51 +++ docs/_zh_CN/pages/10-help.md | 1 - docs/_zh_CN/pages/13-hook.md | 1 - docs/_zh_CN/pages/2-define-a-dispatcher.md | 17 - docs/_zh_CN/pages/3-define-a-chain.md | 1 - docs/_zh_CN/pages/4-render-result.md | 1 - docs/_zh_CN/pages/5-multiple-commands.md | 2 - docs/_zh_CN/pages/7-argument-parse-clap.md | 1 - docs/_zh_CN/pages/9-error-handling.md | 1 - docs/_zh_CN/pages/advanced/1-completion.md | 2 - .../_zh_CN/pages/advanced/2-structural-renderer.md | 1 - docs/pages/10-help.md | 1 - docs/pages/13-hook.md | 1 - docs/pages/2-define-a-dispatcher.md | 17 - docs/pages/3-define-a-chain.md | 1 - docs/pages/4-render-result.md | 1 - docs/pages/5-multiple-commands.md | 2 - docs/pages/7-argument-parse-clap.md | 1 - docs/pages/9-error-handling.md | 1 - docs/pages/advanced/1-completion.md | 2 - docs/pages/advanced/2-structural-renderer.md | 1 - examples/example-argument-parse/src/main.rs | 4 +- examples/example-argument-picker/src/main.rs | 1 - examples/example-async-support/src/main.rs | 2 - examples/example-basic/src/main.rs | 5 +- examples/example-clap-binding/src/main.rs | 1 - .../example-combine-pathf-metadata/src/main.rs | 5 +- examples/example-command-macro/src/main.rs | 9 +- examples/example-completion/src/main.rs | 12 +- examples/example-custom-pickable/src/main.rs | 4 +- examples/example-dispatch-tree/src/main.rs | 14 +- examples/example-enum-tag/src/main.rs | 5 +- examples/example-error-handling/src/main.rs | 4 +- examples/example-exitcode/src/main.rs | 1 - examples/example-help/src/main.rs | 2 - examples/example-hook/src/main.rs | 1 - examples/example-implicit-dispatcher/src/main.rs | 10 +- examples/example-lazy-resources/src/main.rs | 1 - examples/example-metadata/src/main.rs | 6 +- examples/example-outside-type/src/main.rs | 5 +- examples/example-pack-err/src/main.rs | 4 +- examples/example-panic-unwind/src/main.rs | 1 - examples/example-pathfinder/src/main.rs | 5 +- examples/example-repl-basic/src/main.rs | 6 - examples/example-resources/src/main.rs | 3 - examples/example-setup/src/main.rs | 67 +++- examples/example-setup/test.toml | 26 +- examples/example-structural-renderer/src/main.rs | 1 - examples/example-unit-test/src/main.rs | 4 +- examples/full-todolist/src/main.rs | 7 +- mingling/Cargo.toml | 2 +- mingling/src/docs/lib.md | 3 +- mingling/src/example_docs.rs | 183 ++++----- mingling/src/gen_program.rs | 13 + mingling_core/Cargo.toml | 1 - mingling_core/src/asset/dispatcher.rs | 415 +-------------------- mingling_core/src/build/pathf.rs | 11 +- mingling_core/src/comp.rs | 40 -- mingling_core/src/program.rs | 55 +-- mingling_core/src/program/collection.rs | 20 +- mingling_core/src/program/collection/mock.rs | 3 - mingling_core/src/program/exec.rs | 80 +--- mingling_core/src/program/hook.rs | 10 + mingling_macros/src/attr/command.rs | 9 +- mingling_macros/src/attr/dispatcher_clap.rs | 24 +- mingling_macros/src/func/dispatcher.rs | 22 +- mingling_macros/src/func/program_comp_gen.rs | 4 - mingling_macros/src/func/program_final_gen.rs | 87 +++-- mingling_macros/src/func/register_dispatcher.rs | 20 +- mingling_macros/src/lib.rs | 162 +------- mingling_macros/src/systems.rs | 2 + mingling_macros/src/systems/dispatch_list_gen.rs | 57 +++ mingling_macros/src/systems/dispatch_tree_gen.rs | 32 +- mingling_pathf/src/config.rs | 27 -- mingling_pathf/src/lib.rs | 1 - mingling_pathf/src/pattern_analyzer.rs | 13 +- mingling_pathf/src/patterns/dispatcher.rs | 37 +- mingling_pathf/src/patterns/dispatcher_clap.rs | 32 +- mingling_pathf/src/type_mapping_builder.rs | 7 +- mingling_pathf/test/src/lib.rs | 82 ++-- 80 files changed, 473 insertions(+), 1304 deletions(-) create mode 100644 mingling_macros/src/systems/dispatch_list_gen.rs delete mode 100644 mingling_pathf/src/config.rs 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(&mut self, dispatcher: Disp) -> &mut Self` — **removed** + - `Program::with_dispatchers(&mut self, dispatchers: D) -> &mut Self` — **removed** (already deprecated) + - `Dispatchers` struct and all its `From` tuple impls (up to 7 elements), `Deref`, and `Into>` 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, 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) { - 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) { -/// 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, + mingling_core::error::ProgramInternalExecuteError, + > { + todo!() + } + + fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher + Send + Sync))> { + todo!() + } + fn build_renderer_not_found(_member_id: Self::Enum) -> mingling_core::AnyOutput { 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 Program -where - C: ProgramCollect, -{ - /// 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 for Foo { - /// # fn member_id() -> ThisProgram { ThisProgram::Foo } - /// # } - /// # struct CMDGreet; - /// # struct Foo { - /// # args: Vec - /// # } - /// # impl Dispatcher for CMDGreet { - /// # fn node(&self) -> Node { - /// # Node::default().join("greet") - /// # } - /// # fn begin(&self, args: Vec) -> ChainProcess { - /// # Routable::to_chain(Foo { args }) - /// # } - /// # fn clone_dispatcher(&self) -> Box> { - /// # Box::new(CMDGreet) - /// # } - /// # } - /// let mut program = Program::::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(&mut self, dispatcher: Disp) -> &mut Self - where - Disp: Dispatcher + 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 for Foo { - /// # fn member_id() -> ThisProgram { ThisProgram::Foo } - /// # } - /// # struct CMDGreet; - /// # struct Foo { - /// # args: Vec - /// # } - /// # impl Dispatcher for CMDGreet { - /// # fn node(&self) -> Node { - /// # Node::default().join("greet") - /// # } - /// # fn begin(&self, args: Vec) -> ChainProcess { - /// # Routable::to_chain(Foo { args }) - /// # } - /// # fn clone_dispatcher(&self) -> Box> { - /// # Box::new(CMDGreet) - /// # } - /// # } - /// let mut program = Program::::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(&mut self, dispatchers: D) -> &mut Self - where - D: Into>, - { - #[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 { - dispatcher: Vec + Send + Sync + 'static>>, -} - -#[allow(deprecated)] -impl From + Send + Sync>>> for Dispatchers { - fn from(dispatcher: Vec + Send + Sync>>) -> Self { - Self { dispatcher } - } -} - -#[allow(deprecated)] -impl From + Send + Sync>> for Dispatchers { - fn from(dispatcher: Box + Send + Sync>) -> Self { - Self { - dispatcher: vec![dispatcher], - } - } -} - -#[allow(deprecated)] -impl From<(D,)> for Dispatchers -where - D: Dispatcher + Send + Sync + 'static, - G: Display, -{ - fn from(dispatcher: (D,)) -> Self { - Self { - dispatcher: vec![Box::new(dispatcher.0)], - } - } -} - -#[allow(deprecated)] -impl From<(D1, D2)> for Dispatchers -where - D1: Dispatcher + Send + Sync + 'static, - D2: Dispatcher + 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 From<(D1, D2, D3)> for Dispatchers -where - D1: Dispatcher + Send + Sync + 'static, - D2: Dispatcher + Send + Sync + 'static, - D3: Dispatcher + 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 From<(D1, D2, D3, D4)> for Dispatchers -where - D1: Dispatcher + Send + Sync + 'static, - D2: Dispatcher + Send + Sync + 'static, - D3: Dispatcher + Send + Sync + 'static, - D4: Dispatcher + 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 From<(D1, D2, D3, D4, D5)> for Dispatchers -where - D1: Dispatcher + Send + Sync + 'static, - D2: Dispatcher + Send + Sync + 'static, - D3: Dispatcher + Send + Sync + 'static, - D4: Dispatcher + Send + Sync + 'static, - D5: Dispatcher + 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 From<(D1, D2, D3, D4, D5, D6)> for Dispatchers -where - D1: Dispatcher + Send + Sync + 'static, - D2: Dispatcher + Send + Sync + 'static, - D3: Dispatcher + Send + Sync + 'static, - D4: Dispatcher + Send + Sync + 'static, - D5: Dispatcher + Send + Sync + 'static, - D6: Dispatcher + 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 From<(D1, D2, D3, D4, D5, D6, D7)> for Dispatchers -where - D1: Dispatcher + Send + Sync + 'static, - D2: Dispatcher + Send + Sync + 'static, - D3: Dispatcher + Send + Sync + 'static, - D4: Dispatcher + Send + Sync + 'static, - D5: Dispatcher + Send + Sync + 'static, - D6: Dispatcher + Send + Sync + 'static, - D7: Dispatcher + 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 std::ops::Deref for Dispatchers { - type Target = Vec + Send + Sync + 'static>>; - - fn deref(&self) -> &Self::Target { - &self.dispatcher - } -} - -#[allow(deprecated)] -impl From> for Vec + Send + Sync + 'static>> { - fn from(val: Dispatchers) -> Self { - val.dispatcher - } -} - #[cfg(test)] mod tests { use super::*; @@ -484,118 +183,6 @@ mod tests { } } - #[test] - #[allow(deprecated)] - fn test_dispatchers_from_single_tuple() { - let disp = MockDispatcher { name: "foo" }; - let dispatchers: Dispatchers = 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 = 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 = 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 = 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 = 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 = 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 = 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 + Send + Sync> = Box::new(MockDispatcher { name: "a" }); - let d2: Box + Send + Sync> = Box::new(MockDispatcher { name: "b" }); - let dispatchers: Dispatchers = vec![d1, d2].into(); - assert_eq!(dispatchers.dispatcher.len(), 2); - } - - #[test] - #[allow(deprecated)] - fn test_dispatchers_from_single_boxed() { - let d: Box + Send + Sync> = Box::new(MockDispatcher { name: "x" }); - let dispatchers: Dispatchers = d.into(); - assert_eq!(dispatchers.dispatcher.len(), 1); - } - - #[test] - #[allow(deprecated)] - fn test_dispatchers_deref() { - let disp = MockDispatcher { name: "test" }; - let dispatchers: Dispatchers = Dispatchers::from((disp,)); - let inner: &Vec + 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 = Dispatchers::from((disp,)); - let vec: Vec + Send + Sync + 'static>> = dispatchers.into(); - assert_eq!(vec.len(), 1); - } - #[test] fn test_box_clone_dispatcher() { let disp: Box> = Box::new(MockDispatcher { name: "clonable" }); 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::

(); - - #[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 = 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::

(), &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::(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, - #[cfg(not(feature = "dispatch_tree"))] - pub(crate) dispatcher: Vec + 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, - ) -> Result, ChainProcessError> { - let sv: Vec = 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, @@ -225,40 +206,12 @@ where pub fn get_nodes>( program: &'static Program, ) -> Vec<(String, &'static (dyn Dispatcher + Send + Sync + 'static))> { - #[cfg(feature = "dispatch_tree")] let r = C::get_nodes(); - #[cfg(feature = "dispatch_tree")] - { - #[cfg(feature = "debug")] - { - let node_strs: Vec = 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::>() - .join(" "); - (node_str, &**disp) - }) - .collect(); - - #[cfg(not(feature = "dispatch_tree"))] + #[cfg(feature = "debug")] { - #[cfg(feature = "debug")] - { - let node_strs: Vec = r.iter().map(|v| v.0.clone()).collect(); - crate::info!("All Nodes: [{}]", node_strs.join(", ")); - } + let node_strs: Vec = 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; - /// Use a prefix tree to quickly match arguments and dispatch to an Entry - #[cfg(feature = "dispatch_tree")] - fn dispatch_args( - raw: &[String], - ) -> Result, 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, crate::error::ProgramInternalExecuteError> { - unreachable!() - } + raw: &[String], + ) -> Result, crate::error::ProgramInternalExecuteError>; /// Get all registered dispatcher names from the program - #[cfg(feature = "dispatch_tree")] fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher + 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, crate::error::ProgramInternalExecuteError> { unreachable!() } - #[cfg(feature = "dispatch_tree")] fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher + 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( - program: &'static Program, - args: &[String], -) -> Result, ProgramInternalExecuteError> -where - C: ProgramCollect, -{ - 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( - program: &'static Program, - args: &[String], -) -> Result<(&'static (dyn Dispatcher + Send + Sync), Vec), ProgramInternalExecuteError> -where - C: ProgramCollect, -{ - 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 + 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 = 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 = args.iter().skip(prefix_len).cloned().collect(); - Ok((matched_prefix.1, trimmed_args)) - } - } -} - #[inline] pub(crate) fn handle_program_control>( program: &Program, 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::error::ProgramInternalExecuteError> { + unreachable!() + } + + fn get_nodes() -> Vec<(String, &'static (dyn crate::Dispatcher + Send + Sync))> { + unreachable!() + } + fn build_renderer_not_found(_member_id: Self) -> crate::AnyOutput { 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 + 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 = 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 = 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 { let node_name: LitStr = input.parse()?; @@ -28,7 +21,7 @@ impl Parse for RegisterDispatcherInput { let dispatcher_type: Ident = input.parse()?; input.parse::()?; 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>`). //! 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>>` 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) -> 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 = 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 = raw.iter().skip(prefix_len).cloned().collect(); + let __cp = <#disp_ident as ::mingling::Dispatcher>::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, ::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 + 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 { +fn extract_all_types(tokens: &proc_macro2::TokenStream, module: &str) -> Vec { 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 { + fn analyze_struct(s: &syn::ItemStruct, module: &str) -> Vec { 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] -- cgit