aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md66
-rw-r--r--GETTING_STARTED.md10
-rw-r--r--README.md20
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md2
-rw-r--r--docs/_zh_CN/pages/7-argument-parse-clap.md4
-rw-r--r--docs/_zh_CN/pages/advanced/2-structural-renderer.md4
-rw-r--r--docs/_zh_CN/pages/concepts/3-any-output.md10
-rw-r--r--docs/_zh_CN/pages/other/features.md18
-rw-r--r--docs/_zh_CN/pages/other/naming_rule.md2
-rw-r--r--docs/dev/pages/issues/add-picker2.md16
-rw-r--r--docs/dev/pages/issues/remove-r-print-macro.md12
-rw-r--r--docs/example-pages/examples.json15
-rw-r--r--docs/pages/3-define-a-chain.md2
-rw-r--r--docs/pages/7-argument-parse-clap.md4
-rw-r--r--docs/pages/advanced/2-structural-renderer.md4
-rw-r--r--docs/pages/concepts/3-any-output.md10
-rw-r--r--docs/pages/other/features.md18
-rw-r--r--docs/pages/other/naming_rule.md2
-rw-r--r--examples/example-argument-picker/Cargo.lock94
-rw-r--r--examples/example-argument-picker/Cargo.toml12
-rw-r--r--examples/example-argument-picker/page.toml10
-rw-r--r--examples/example-argument-picker/src/main.rs225
-rw-r--r--examples/example-clap-binding/src/main.rs8
-rw-r--r--examples/example-custom-pickable/src/main.rs8
-rw-r--r--examples/example-enum-tag/src/main.rs4
-rw-r--r--examples/example-outside-type/src/main.rs2
-rw-r--r--examples/example-structural-renderer/src/main.rs14
-rw-r--r--examples/full-todolist/src/todolist.rs4
-rw-r--r--examples/test-examples.toml35
-rw-r--r--mingling/Cargo.toml2
-rw-r--r--mingling/src/example_docs.rs281
-rw-r--r--mingling/src/lib.rs12
-rw-r--r--mingling/src/picker/entry_picker.rs8
-rw-r--r--mingling_core/Cargo.toml1
-rw-r--r--mingling_core/src/any.rs22
-rw-r--r--mingling_core/src/any/group.rs20
-rw-r--r--mingling_core/src/asset.rs3
-rw-r--r--mingling_core/src/asset/routable.rs22
-rw-r--r--mingling_core/src/comp.rs2
-rw-r--r--mingling_core/src/comp/shell_ctx.rs34
-rw-r--r--mingling_core/src/comp/suggest.rs8
-rw-r--r--mingling_core/src/lib.rs1
-rw-r--r--mingling_core/src/program/collection.rs8
-rw-r--r--mingling_core/src/program/collection/mock.rs4
-rw-r--r--mingling_core/src/program/hook.rs4
-rw-r--r--mingling_macros/src/attr.rs9
-rw-r--r--mingling_macros/src/attr/chain.rs (renamed from mingling_macros/src/chain.rs)15
-rw-r--r--mingling_macros/src/attr/completion.rs (renamed from mingling_macros/src/completion.rs)2
-rw-r--r--mingling_macros/src/attr/dispatcher_clap.rs (renamed from mingling_macros/src/dispatcher_clap.rs)6
-rw-r--r--mingling_macros/src/attr/help.rs (renamed from mingling_macros/src/help.rs)4
-rw-r--r--mingling_macros/src/attr/program_setup.rs (renamed from mingling_macros/src/program_setup.rs)2
-rw-r--r--mingling_macros/src/attr/renderer.rs (renamed from mingling_macros/src/renderer.rs)12
-rw-r--r--mingling_macros/src/derive.rs2
-rw-r--r--mingling_macros/src/derive/enum_tag.rs (renamed from mingling_macros/src/enum_tag.rs)2
-rw-r--r--mingling_macros/src/derive/grouped.rs (renamed from mingling_macros/src/groupped.rs)12
-rw-r--r--mingling_macros/src/extensions.rs116
-rw-r--r--mingling_macros/src/extensions/routeify.rs39
-rw-r--r--mingling_macros/src/func.rs12
-rw-r--r--mingling_macros/src/func/dispatcher.rs (renamed from mingling_macros/src/dispatcher.rs)10
-rw-r--r--mingling_macros/src/func/entry.rs (renamed from mingling_macros/src/entry.rs)2
-rw-r--r--mingling_macros/src/func/gen_program.rs602
-rw-r--r--mingling_macros/src/func/group.rs (renamed from mingling_macros/src/group_impl.rs)6
-rw-r--r--mingling_macros/src/func/node.rs (renamed from mingling_macros/src/node.rs)2
-rw-r--r--mingling_macros/src/func/pack.rs (renamed from mingling_macros/src/pack.rs)4
-rw-r--r--mingling_macros/src/func/pack_err.rs (renamed from mingling_macros/src/pack_err.rs)12
-rw-r--r--mingling_macros/src/func/suggest.rs (renamed from mingling_macros/src/suggest.rs)4
-rw-r--r--mingling_macros/src/lib.rs834
-rw-r--r--mingling_macros/src/systems.rs5
-rw-r--r--mingling_macros/src/systems/dispatch_tree_gen.rs (renamed from mingling_macros/src/dispatch_tree_gen.rs)6
-rw-r--r--mingling_macros/src/systems/res_injection.rs (renamed from mingling_macros/src/res_injection.rs)0
-rw-r--r--mingling_macros/src/systems/structural_data.rs (renamed from mingling_macros/src/structural_data.rs)4
-rw-r--r--mingling_macros/src/utils.rs1
-rw-r--r--mingling_pathf/README.md2
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs2
-rw-r--r--mingling_pathf/src/patterns.rs4
-rw-r--r--mingling_pathf/src/patterns/grouped_derive.rs (renamed from mingling_pathf/src/patterns/groupped_derive.rs)34
-rw-r--r--mingling_pathf/test/src/lib.rs4
-rw-r--r--mingling_pathf/test/src/test_files/test_grouped_derive.rs (renamed from mingling_pathf/test/src/test_files/test_groupped_derive.rs)16
-rw-r--r--mling/src/cli.rs2
-rw-r--r--mling/src/proj_mgr/generator.rs2
-rw-r--r--mling/src/proj_mgr/show_binaries.rs4
-rw-r--r--mling/src/proj_mgr/show_directories.rs6
82 files changed, 1934 insertions, 934 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c70d3d..54cffcf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,7 +58,26 @@ None
#### Optimizations:
-None
+1. **[`macros`]** Updated `route!` macro to use `Routable` trait instead of `Grouped` trait for error conversion, making the semantics clearer. The `route!` macro now calls `::mingling::Routable::to_chain(e)` on the error branch instead of `::mingling::Grouped::to_chain(e)`.
+
+ Additionally, `ChainProcess<ThisProgram>` now implements `Routable<ThisProgram>`, allowing `route!` to work with `Result<Ok, ChainProcess<ThisProgram>>` patterns — where the error side is already a `ChainProcess` value that should be routed directly:
+
+ When `ChainProcess` implements `Routable`, `to_chain()` re-routes the inner `AnyOutput` to the chain pipeline (preserving the existing `NextProcess::Chain`/`Renderer` flag), while `to_render()` re-routes it to the render pipeline. This enables seamless propagation of already-routed chain process values through the `route!` macro without double-wrapping.
+
+ The `Routable` trait is defined in `mingling_core::asset::routable` and provides unified routing capabilities (`to_chain` / `to_render`) for any type that can be dispatched into the program's pipeline. A blanket implementation is provided for all `T: Grouped<C> + Send`, ensuring backward compatibility — existing types that implement `Grouped` automatically implement `Routable`.
+
+2. **[`macros`]** Restructured the `mingling_macros` crate's internal module hierarchy. The previously flat module structure has been reorganized into a logical directory-based layout:
+
+ - **`attr/`** — Attribute macro implementations (e.g., `#[chain]`, `#[renderer]`, `#[help]`, `#[completion]`, `#[dispatcher_clap]`, `#[program_setup]`)
+ - **`derive/`** — Derive macro implementations (e.g., `#[derive(Grouped)]`, `#[derive(EnumTag)]`)
+ - **`func/`** — Function-like macro implementations (e.g., `pack!`, `group!`, `dispatcher!`, `suggest!`, `entry!`, `node!`, `gen_program!` and its sub-macros)
+ - **`systems/`** — Cross-cutting systems (e.g., resource injection, dispatch tree generation, structural data derive support)
+ - **`extensions/`** — Extension point mechanism for attribute macros (unchanged)
+ - **`utils.rs`** — Shared utility module for future common helpers
+
+ All public API items (`#[proc_macro]`, `#[proc_macro_derive]`, `#[proc_macro_attribute]`) remain at the crate root (`lib.rs`) with identical signatures. Internal function visibility has been tightened from `pub fn` to `pub(crate) fn` for all module-internal functions that were previously publicly accessible only within the crate.
+
+ _No migration is required for downstream code — all macros are re-exported with the same names and signatures as before._
#### Features:
@@ -206,11 +225,43 @@ None
The generated `proc` function now wraps the body result in `<UserReturnType as Into<ChainProcess<ThisProgram>>>::into(...)`, which:
- Works for `Next` / `ChainProcess` via the identity `From<T> for T` implementation.
- - Works for any pack type (`ResultGreeting`, etc.) via the `.into()` conversion generated by `pack!` / `#[derive(Groupped)]`.
+ - Works for any pack type (`ResultGreeting`, etc.) via the `.into()` conversion generated by `pack!` / `#[derive(Grouped)]`.
- Works for `()` via the `From<()>` implementation on `ChainProcess`.
The return type validation has been removed entirely — any valid Rust return type is accepted. If the type does not implement `Into<ChainProcess<ThisProgram>>`, a standard Rust compilation error will be produced at the call site.
+10. **[`macros`]** **[`extensions`]** Added the `extensions` module to `mingling_macros`, providing an extension point mechanism for attribute macros (`#[chain]`, `#[renderer]`, `#[help]`, `#[completion]`). The extension system allows identifiers in the attribute argument to be extracted and processed before the main macro logic runs.
+
+ Each attribute macro now attempts to redispatch through `extensions::try_redispatch_simple()` (or `try_redispatch_completion` for `#[completion]`) before executing its standard logic. If extension identifiers are detected, the call is re-routed so that extensions are applied via additional `#[...]` attributes stacked on top of the inner core attribute. New extensions can be added without modifying the attribute macros themselves — only the `extensions` module needs to be updated to register new identifiers.
+
+ This system is designed for future extensibility: as new cross-cutting concerns (e.g., logging, metrics, validation) are identified, they can be added as simple extension identifiers without touching the core macro logic.
+
+11. **[`extensions`]** **[`macros`]** Added the `#[routeify]` extension attribute macro that transforms `expr?` into `route!(expr)`, enabling concise error routing in chain functions using the `?` operator syntax.
+
+ The `#[routeify]` macro can be used:
+ - **Standalone** — as a direct attribute: `#[routeify] fn handle(...) { ... }`
+ - **As an extension** — via the extension point system: `#[chain(routeify)] fn handle(...) { ... }`
+
+ When used as a `#[chain]` extension, the `routeify` identifier is detected by the extension point mechanism, stripped from the `#[chain]` attribute arguments, and `#[routeify]` is applied as an outer attribute on top of `#[chain]`. The re-dispatch token stream now correctly generates `#[#exts]*` (i.e., `#[routeify] #[chain]`) instead of the previous bare `#exts` — fixing a bug where extension identifiers were emitted without the `#[...]` attribute delimiter, producing invalid token streams.
+
+ ```rust
+ use mingling::macros::routeify;
+
+ #[chain(routeify)]
+ fn handle_calc(args: EntryCalculate) -> Next {
+ let a = args.pick(&arg![f32]).to_result()?;
+ let op = args.pick(&arg![Operator]).to_result()?;
+ StateCalculate { number_a: a, operator: op, ... }.to_chain()
+ }
+ ```
+
+ The `#[routeify]` macro is feature-gated behind `extra_macros` and re-exported as `mingling::macros::routeify`.
+
+ Internal changes:
+ - Added `mingling_macros/src/extensions/routeify.rs` with `routeify_impl` implementation.
+ - Updated `try_redispatch_simple` and `try_redispatch_completion` to emit `#[#exts]` instead of bare `#exts`, ensuring proper attribute syntax in the re-dispatched token stream.
+ - Registered `#[proc_macro_attribute] pub fn routeify` in `mingling_macros/src/lib.rs`.
+
#### **BREAKING CHANGES** (API CHANGES):
1. **[`macros:renderer`]** **[`macros:help`]** Removed `r_println!` and `r_print!` macros. The `#[renderer]` and `#[help]` macros no longer implicitly inject an internal `RenderResult` variable or provide `r_println!` / `r_print!` macros.
@@ -263,6 +314,17 @@ None
3. **[`core`]** **[`ExitCodeSetup`]** Updated `ExitCodeSetup` to only override the exit code when `ResExitCode` has been modified (i.e., `exit_code != 0`). Previously, it unconditionally overrode the exit code, which could interfere with exit codes set by other hooks or the program's default exit flow. The `on_finish` hook now returns `ProgramControlUnit::OverrideExitCode(...)` only when the exit code is non-zero, and `ProgramControls::Empty` otherwise. The import of `ProgramControls` has been added accordingly.
+4. **[`core`]** **[`macros`]** Renamed `Groupped` (typo) to `Grouped`. All references to the trait, derive macro, module files, and related types have been corrected throughout the codebase:
+
+ - Trait: `Groupped<Group>` → `Grouped<Group>`
+ - Derive macro: `#[derive(Groupped)]` → `#[derive(Grouped)]`
+ - Serialize variant: `GrouppedSerialize` → `GroupedSerialize`
+ - Source files: `groupped.rs` → `grouped.rs`
+ - Pattern matcher: `GrouppedDerivePattern` → `GroupedDerivePattern`
+ - All `use` imports, type annotations, and trait bound references updated accordingly.
+
+ This is a pure rename — no behavioral changes. All functionality remains identical. Downstream code using the old `Groupped` name must migrate to `Grouped`.
+
---
## Release 0.2.2 (2026-07-10)
diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md
index dbea3d2..e08d17b 100644
--- a/GETTING_STARTED.md
+++ b/GETTING_STARTED.md
@@ -171,12 +171,12 @@ For enums, derive `EnumTag` and implement `PickableEnum` to parse enum variants
```rust
// Features: ["parser", "extra_macros"]
-use mingling::{EnumTag, Groupped};
+use mingling::{EnumTag, Grouped};
use mingling::parser::PickableEnum;
dispatcher!("lang.select", CMDLang => EntryLang);
-#[derive(Debug, Default, EnumTag, Groupped)]
+#[derive(Debug, Default, EnumTag, Grouped)]
pub enum Language {
#[default]
Rust,
@@ -473,7 +473,7 @@ use mingling::macros::dispatcher_clap;
use mingling::prelude::*;
use std::io::Write;
-#[derive(Default, clap::Parser, Groupped)]
+#[derive(Default, clap::Parser, Grouped)]
#[dispatcher_clap(
"greet", CMDGreet,
help = true, // auto-generate #[help] from clap
@@ -623,14 +623,14 @@ With the `structural_renderer` feature, users can add `--json` or `--yaml` flags
// serde = "1"
use mingling::{prelude::*, setup::StructuralRendererSetup};
-use mingling::Groupped;
+use mingling::Grouped;
use mingling::StructuralData;
use serde::Serialize;
use std::io::Write;
dispatcher!("render", CMDRender => EntryRender);
-#[derive(Default, StructuralData, Serialize, Groupped)]
+#[derive(Default, StructuralData, Serialize, Grouped)]
struct ResultInfo {
name: String,
age: i32,
diff --git a/README.md b/README.md
index 3c04a48..e24fb7e 100644
--- a/README.md
+++ b/README.md
@@ -38,8 +38,8 @@ Additionally, the project is currently developed by me alone ([Weicao-CatilGrass
Mingling abstracts the behavior of a program's lifecycle into three phases: **Dispatch**, **Execution**, and **Rendering**. Each phase is connected by types — the output of the current phase becomes the input of the next phase. For example:
```rust
-dispatcher!("current", CMDCurrent => EntryGreet);
-pack!(StateNext => ());
+dispatcher!("current", CMDCurrent => EntryCurrent);
+pack!(StateNext = ());
#[chain]
fn handle_current(_: EntryCurrent) -> StateNext {
@@ -128,15 +128,15 @@ features = []
- [x] [[0.1.9](https://docs.rs/mingling/0.1.9/mingling/)] [`core`] [`repl`] Provides REPL capability (`program.exec_repl();`)
- [x] [[0.2.0](https://docs.rs/mingling/0.2.0/mingling/)] Complete documentation, tests, and examples
- [ ] Milestone.2 "More Comfortable Dev and User Experience"
- - [ ] [`mling` / `mingling-cli`]
- - [ ] **Mingling** Linter
- - [ ] **Mingling** Project Generator
- - [ ] **Mingling** Program Installer & Manager (For development)
- - [ ] Helpdoc Editor
- - [ ] [`picker`] A more efficient and intelligent argument parser
- - [x] [`macros`] Remove `r_print!` / `r_println!` macros
+ - [ ] [`mling` / `mingling-cli`]
+ - [ ] **Mingling** Linter
+ - [ ] **Mingling** Project Generator
+ - [ ] **Mingling** Program Installer & Manager (For development)
+ - [ ] Helpdoc Editor
+ - [ ] [`picker`] A more efficient and intelligent argument parser
+ - [x] [`macros`] Remove `r_print!` / `r_println!` macros
- [ ] Milestone.3 "Unplanned"
- - [ ] ...
+ - [ ] ...
## Unplanned Features
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md
index b2850c5..f845d11 100644
--- a/docs/_zh_CN/pages/3-define-a-chain.md
+++ b/docs/_zh_CN/pages/3-define-a-chain.md
@@ -48,7 +48,7 @@ Chain 函数签名里写着它需要什么——`args: EntryGreet`
```rust
// pack!(ResultName = String) 大概生成了这样的代码
-#[derive(Groupped)]
+#[derive(Grouped)]
pub struct ResultName {
pub inner: String,
}
diff --git a/docs/_zh_CN/pages/7-argument-parse-clap.md b/docs/_zh_CN/pages/7-argument-parse-clap.md
index 7dce301..21f886c 100644
--- a/docs/_zh_CN/pages/7-argument-parse-clap.md
+++ b/docs/_zh_CN/pages/7-argument-parse-clap.md
@@ -25,7 +25,7 @@ features = ["derive", "color"]
// Dependencies:
// clap = "4"
@@@ use mingling::macros::dispatcher_clap;
-#[derive(Default, clap::Parser, Groupped)]
+#[derive(Default, clap::Parser, Grouped)]
#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreetParsed)]
pub struct EntryGreet {
#[clap(default_value = "World")]
@@ -64,7 +64,7 @@ fn render_greet_parse_failed(err: ErrorGreetParsed) -> RenderResult {
// clap = "4"
@@@use mingling::setup::BasicProgramSetup;
@@@use mingling::macros::dispatcher_clap;
-@@@#[derive(Default, clap::Parser, Groupped)]
+@@@#[derive(Default, clap::Parser, Grouped)]
@@@#[dispatcher_clap("greet", CMDGreet)]
@@@pub struct EntryGreet {
@@@ name: String,
diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
index 6b6b0f9..70bed79 100644
--- a/docs/_zh_CN/pages/advanced/2-structural-renderer.md
+++ b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
@@ -62,7 +62,7 @@ fn render_info(r: ResultInfo) -> RenderResult {
## 自定义输出结构
-`pack_structural!` 的默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Groupped)]` 手动定义类型:
+`pack_structural!` 的默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Grouped)]` 手动定义类型:
```rust
// Features: ["structural_renderer"]
@@ -74,7 +74,7 @@ fn render_info(r: ResultInfo) -> RenderResult {
@@@use serde::Serialize;
@@@dispatcher!("render", CMDRender => EntryRender);
-#[derive(Serialize, StructuralData, Groupped)]
+#[derive(Serialize, StructuralData, Grouped)]
struct Info {
name: String,
age: i32,
diff --git a/docs/_zh_CN/pages/concepts/3-any-output.md b/docs/_zh_CN/pages/concepts/3-any-output.md
index 9b820da..118e856 100644
--- a/docs/_zh_CN/pages/concepts/3-any-output.md
+++ b/docs/_zh_CN/pages/concepts/3-any-output.md
@@ -20,7 +20,7 @@ AnyOutput<G>
这里的 `G` 就是 `gen_program!()` 生成的程序枚举(也就是你熟知的 `ThisProgram`)。
-每个被 `pack!` 或 `#[derive(Groupped)]` 标记的类型都被分配到这个枚举的一个变体。
+每个被 `pack!` 或 `#[derive(Grouped)]` 标记的类型都被分配到这个枚举的一个变体。
## ChainProcess:数据 + 路由
@@ -38,17 +38,17 @@ ChainProcess<G>
调度器根据 `NextProcess` 决定是继续循环还是退出渲染。
-## Groupped:谁是谁
+## Grouped:谁是谁
-调度器如何知道 `AnyOutput` 里装的是 `ResultName` 还是 `ErrorUserBlocked`?答案是 `Groupped` trait:
+调度器如何知道 `AnyOutput` 里装的是 `ResultName` 还是 `ErrorUserBlocked`?答案是 `Grouped` trait:
```
-trait Groupped<G> {
+trait Grouped<G> {
fn member_id() -> G;
}
```
-当你用 `pack!(ResultName = String)` 时,宏自动为 `ResultName` 实现 `Groupped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
+当你用 `pack!(ResultName = String)` 时,宏自动为 `ResultName` 实现 `Grouped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
`to_chain()` 和 `to_render()` 本质上是 `AnyOutput` 的快捷方法,分别构造 `ChainProcess::Ok(any, Chain)` 和 `ChainProcess::Ok(any, Renderer)`。
diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md
index d92e597..8bd386c 100644
--- a/docs/_zh_CN/pages/other/features.md
+++ b/docs/_zh_CN/pages/other/features.md
@@ -171,7 +171,7 @@ fn handle_hello(args: EntryHello) {}
### `group!`
将外部类型注册为程序组成员,无需修改原始类型的定义。
-类型名会直接作为枚举变体,与 `pack!` 或 `#[derive(Groupped)]` 一致。
+类型名会直接作为枚举变体,与 `pack!` 或 `#[derive(Grouped)]` 一致。
```rust
// Features: ["extra_macros"]
@@ -274,12 +274,24 @@ analyze_and_build_type_mapping().unwrap();
**介绍:**
-启用解析器模块,提供文本解析和语法分析功能。
+启用参数解析器模块,提供参数解析功能。
-开启后可以使用 `Picker` 进行零成本的参数提取,支持 `pick()` 和 `pick_or()` 等方法。
+开启后可以使用 `Picker` 进行简易的参数提取,支持 `pick()` 和 `pick_or()` 等方法。
详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse)
+## 特性 `picker`
+
+**介绍:**
+
+引入依赖 `arg-picker`,为 Mingling 提供更高级的参数解析能力。
+
+它可以与 `parser`、`clap` 特性共存,但建议不要和 `parser` 特性同时启用,因为两者的 API 极为相似。
+
+`picker` 是独立于 Mingling 的参数解析器,不依赖 `mingling_core` 的内置参数提取 API。
+
+详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-picker)
+
## 特性 `repl`
**介绍:**
diff --git a/docs/_zh_CN/pages/other/naming_rule.md b/docs/_zh_CN/pages/other/naming_rule.md
index 5f2ac34..c854ba1 100644
--- a/docs/_zh_CN/pages/other/naming_rule.md
+++ b/docs/_zh_CN/pages/other/naming_rule.md
@@ -96,7 +96,7 @@ Result + 内容
| `ResultGreetSomeone` | 问候结果 |
| `ResultFruitList` | 水果列表结果 |
-结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Groupped)]` 代替 `pack!()` 包装,以获得更灵活的字段控制。
+结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 代替 `pack!()` 包装,以获得更灵活的字段控制。
### 错误
diff --git a/docs/dev/pages/issues/add-picker2.md b/docs/dev/pages/issues/add-picker2.md
index f5c3ca7..a2b5a10 100644
--- a/docs/dev/pages/issues/add-picker2.md
+++ b/docs/dev/pages/issues/add-picker2.md
@@ -40,7 +40,7 @@ fn handle_hello(args: EntryHello) {
```rust
#[chain]
fn handle_hello(args: EntryHello) -> Next {
- // requires impl Groupped<_>
+ // requires impl Grouped<_>
// |
let parsed = args // vvvvvvvvvvvvvvvvvvv
.pick_route::<String, _>(positional!(), ErrorNoNameProvided)
@@ -90,11 +90,11 @@ pub struct PickerResult<Tuple> {
## 🕘 Progress
- [x] In Progress
- - [x] Added `Picker` struct and related call chain
- - [x] Added `parselib` providing parsing logic
- - [x] Added `Pickable` for extensibility
- - [ ] Comprehensive testing!
- - [ ] Improve documentation
- - [ ] Add examples
- - [ ] Update README
+ - [x] Added `Picker` struct and related call chain
+ - [x] Added `parselib` providing parsing logic
+ - [x] Added `Pickable` for extensibility
+ - [x] Comprehensive testing!
+ - [ ] Improve documentation
+ - [x] Add examples
+ - [ ] Update README
- [ ] Complete
diff --git a/docs/dev/pages/issues/remove-r-print-macro.md b/docs/dev/pages/issues/remove-r-print-macro.md
index d6f6bff..43d2740 100644
--- a/docs/dev/pages/issues/remove-r-print-macro.md
+++ b/docs/dev/pages/issues/remove-r-print-macro.md
@@ -89,9 +89,9 @@ As for rendering in logic functions like `#[chain]`, that should be handled by a
## 🕘 Progress
- [x] In Progress
- - [x] Remove `r_println!` and `r_print!` macros
- - [x] Modify `#[renderer]` and `#[help]` macros, remove implicit injection
- - [x] Provide **no-return-value mode** and **RenderResult return value mode** for `#[renderer]` and `#[help]` macros
- - [ ] Add new simplified syntax
- - [x] Update documentation and test cases, ensure **all pass**
-- [ ] Complete
+ - [x] Remove `r_println!` and `r_print!` macros
+ - [x] Modify `#[renderer]` and `#[help]` macros, remove implicit injection
+ - [x] Provide **no-return-value mode** and **RenderResult return value mode** for `#[renderer]` and `#[help]` macros
+ - [x] Add new simplified syntax
+ - [x] Update documentation and test cases, ensure **all pass**
+- [x] Complete
diff --git a/docs/example-pages/examples.json b/docs/example-pages/examples.json
index a539011..2a07365 100644
--- a/docs/example-pages/examples.json
+++ b/docs/example-pages/examples.json
@@ -31,6 +31,21 @@
]
},
{
+ "id": "example-argument-picker",
+ "name": "Argument Picker",
+ "icon": "📋",
+ "category": "parsing",
+ "desc": "Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.\n",
+ "tags": [
+ "arg-picker",
+ "SinglePickable"
+ ],
+ "files": [
+ "src/main.rs",
+ "Cargo.toml"
+ ]
+ },
+ {
"id": "example-async-support",
"name": "Async Support",
"icon": "⚡",
diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md
index b2822bc..450522b 100644
--- a/docs/pages/3-define-a-chain.md
+++ b/docs/pages/3-define-a-chain.md
@@ -48,7 +48,7 @@ You've probably guessed it — `pack!(ResultName = String)` defines a type that
```rust
// pack!(ResultName = String) generates code roughly like this
-#[derive(Groupped)]
+#[derive(Grouped)]
pub struct ResultName {
pub inner: String,
}
diff --git a/docs/pages/7-argument-parse-clap.md b/docs/pages/7-argument-parse-clap.md
index b11e00e..86fce35 100644
--- a/docs/pages/7-argument-parse-clap.md
+++ b/docs/pages/7-argument-parse-clap.md
@@ -25,7 +25,7 @@ Add `#[dispatcher_clap]` on a `clap::Parser` struct to auto-generate a Dispatche
// Dependencies:
// clap = "4"
@@@ use mingling::macros::dispatcher_clap;
-#[derive(Default, clap::Parser, Groupped)]
+#[derive(Default, clap::Parser, Grouped)]
#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreetParsed)]
pub struct EntryGreet {
#[clap(default_value = "World")]
@@ -64,7 +64,7 @@ If you need `--help` support, register `BasicProgramSetup` in main and set the c
// clap = "4"
@@@use mingling::setup::BasicProgramSetup;
@@@use mingling::macros::dispatcher_clap;
-@@@#[derive(Default, clap::Parser, Groupped)]
+@@@#[derive(Default, clap::Parser, Grouped)]
@@@#[dispatcher_clap("greet", CMDGreet)]
@@@pub struct EntryGreet {
@@@ name: String,
diff --git a/docs/pages/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md
index fab7530..910b197 100644
--- a/docs/pages/advanced/2-structural-renderer.md
+++ b/docs/pages/advanced/2-structural-renderer.md
@@ -62,7 +62,7 @@ When the user passes `--json`, the framework automatically serializes the render
## Customizing Output Structure
-The default output from `pack_structural!` includes an `inner` field. For full control over the output structure, define the type manually with `#[derive(StructuralData, Serialize, Groupped)]`:
+The default output from `pack_structural!` includes an `inner` field. For full control over the output structure, define the type manually with `#[derive(StructuralData, Serialize, Grouped)]`:
```rust
// Features: ["structural_renderer"]
@@ -74,7 +74,7 @@ The default output from `pack_structural!` includes an `inner` field. For full c
@@@use serde::Serialize;
@@@dispatcher!("render", CMDRender => EntryRender);
-#[derive(Serialize, StructuralData, Groupped)]
+#[derive(Serialize, StructuralData, Grouped)]
struct Info {
name: String,
age: i32,
diff --git a/docs/pages/concepts/3-any-output.md b/docs/pages/concepts/3-any-output.md
index f780377..f02805f 100644
--- a/docs/pages/concepts/3-any-output.md
+++ b/docs/pages/concepts/3-any-output.md
@@ -20,7 +20,7 @@ AnyOutput<G>
Here `G` is the program enum generated by `gen_program!()` (i.e., `ThisProgram` as you know it).
-Each type annotated with `pack!` or `#[derive(Groupped)]` is assigned to one variant of this enum.
+Each type annotated with `pack!` or `#[derive(Grouped)]` is assigned to one variant of this enum.
## ChainProcess: Data + Routing
@@ -38,17 +38,17 @@ This is why a Chain function returns `ChainProcess` instead of raw data—it bun
The dispatcher reads `NextProcess` to decide whether to continue the loop or exit to rendering.
-## Groupped: Who Is Who
+## Grouped: Who Is Who
-How does the dispatcher know whether an `AnyOutput` holds a `ResultName` or an `ErrorUserBlocked`? The answer is the `Groupped` trait:
+How does the dispatcher know whether an `AnyOutput` holds a `ResultName` or an `ErrorUserBlocked`? The answer is the `Grouped` trait:
```
-trait Groupped<G> {
+trait Grouped<G> {
fn member_id() -> G;
}
```
-When you use `pack!(ResultName = String)`, the macro automatically implements `Groupped` for `ResultName`, and `member_id()` returns the corresponding enum variant. The dispatcher looks at `member_id` and finds the matching Chain or Renderer.
+When you use `pack!(ResultName = String)`, the macro automatically implements `Grouped` for `ResultName`, and `member_id()` returns the corresponding enum variant. The dispatcher looks at `member_id` and finds the matching Chain or Renderer.
`to_chain()` and `to_render()` are essentially convenience methods on `AnyOutput` that construct `ChainProcess::Ok(any, Chain)` and `ChainProcess::Ok(any, Renderer)` respectively.
diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md
index 325bb75..7994d4c 100644
--- a/docs/pages/other/features.md
+++ b/docs/pages/other/features.md
@@ -171,7 +171,7 @@ fn handle_hello(args: EntryHello) {}
### `group!`
Registers an external type as a member of the program group without modifying its definition.
-The type's simple name is used as the enum variant, just like `pack!` or `#[derive(Groupped)]`.
+The type's simple name is used as the enum variant, just like `pack!` or `#[derive(Grouped)]`.
```rust
// Features: ["extra_macros"]
@@ -274,12 +274,24 @@ See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?na
**Description:**
-Enables the parser module, providing text parsing and analysis capabilities.
+Enables the argument parser module, providing argument parsing functionality.
-When enabled, you can use `Picker` for zero-cost argument extraction, supporting methods like `pick()` and `pick_or()`.
+When enabled, you can use `Picker` for simple argument extraction, supporting methods like `pick()` and `pick_or()`.
See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse)
+## Feature `picker`
+
+**Description:**
+
+Introduces the `arg-picker` dependency, providing more advanced argument parsing capabilities for Mingling.
+
+It can coexist with the `parser` and `clap` features, but it is recommended not to enable it alongside the `parser` feature, as their APIs are very similar.
+
+`picker` is an argument parser independent of Mingling and does not rely on the built-in argument extraction API of `mingling_core`.
+
+See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-picker)
+
## Feature `repl`
**Description:**
diff --git a/docs/pages/other/naming_rule.md b/docs/pages/other/naming_rule.md
index 2ede61f..089a711 100644
--- a/docs/pages/other/naming_rule.md
+++ b/docs/pages/other/naming_rule.md
@@ -96,7 +96,7 @@ Result + Content
| `ResultGreetSomeone` | Greeting result |
| `ResultFruitList` | Fruit list result |
-Result structs are expected to be consumed by the Renderer, and their internal structure should be designed for rendering aesthetics. Generally use `#[derive(Groupped)]` instead of `pack!()` wrapping for more flexible field control.
+Result structs are expected to be consumed by the Renderer, and their internal structure should be designed for rendering aesthetics. Generally use `#[derive(Grouped)]` instead of `pack!()` wrapping for more flexible field control.
### Error
diff --git a/examples/example-argument-picker/Cargo.lock b/examples/example-argument-picker/Cargo.lock
new file mode 100644
index 0000000..9a9baa4
--- /dev/null
+++ b/examples/example-argument-picker/Cargo.lock
@@ -0,0 +1,94 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "arg-picker"
+version = "0.1.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "example-argument-picker"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[package]]
+name = "just_fmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
+
+[[package]]
+name = "mingling"
+version = "0.3.0"
+dependencies = [
+ "arg-picker",
+ "mingling_core",
+ "mingling_macros",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.3.0"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.3.0"
+dependencies = [
+ "just_fmt",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-argument-picker/Cargo.toml b/examples/example-argument-picker/Cargo.toml
new file mode 100644
index 0000000..686b95b
--- /dev/null
+++ b/examples/example-argument-picker/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "example-argument-picker"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+
+# Enable `picker` features
+features = ["picker", "extra_macros"]
+
+[workspace]
diff --git a/examples/example-argument-picker/page.toml b/examples/example-argument-picker/page.toml
new file mode 100644
index 0000000..563bccc
--- /dev/null
+++ b/examples/example-argument-picker/page.toml
@@ -0,0 +1,10 @@
+[example]
+id = "example-argument-picker"
+name = "Argument Picker"
+icon = "📋"
+category = "parsing"
+desc = """
+Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.
+"""
+tags = ["arg-picker", "SinglePickable"]
+files = ["src/main.rs", "Cargo.toml"]
diff --git a/examples/example-argument-picker/src/main.rs b/examples/example-argument-picker/src/main.rs
new file mode 100644
index 0000000..7fcc5db
--- /dev/null
+++ b/examples/example-argument-picker/src/main.rs
@@ -0,0 +1,225 @@
+//! Example Argument Picker
+//!
+//! > Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 + 1
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 7 * 7
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 +
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3 --round
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Result: 2
+//! Result: 49
+//! Error: First number (number_a) was not provided.
+//! Error: Operator was not provided.
+//! Error: Second number (number_b) was not provided.
+//! Result: 1.3333334
+//! Result: 1
+//! ```
+
+use mingling::{
+ consts::REMAINS,
+ macros::route,
+ picker::{
+ IntoPicker, PickerArgResult, SinglePickable,
+ parselib::{ParserStyle, UNIX_STYLE},
+ value::Flag,
+ },
+ prelude::*,
+};
+
+// --------- IMPORTANT ---------
+// Use picker::BasicProgramSetup instead of the original BasicProgramSetup
+// It uses arg-picker to rewrite the logic of the original BasicProgramSetup
+use mingling::setup::picker::BasicProgramSetup;
+
+// --------- IMPORTANT ---------
+
+dispatcher!("calc", CMDCalculate => EntryCalculate);
+
+pack_err!(ErrorNumberANotProvided);
+pack_err!(ErrorNumberBNotProvided);
+pack_err!(ErrorNumberOperatorNotProvided);
+pack_err!(ErrorDivisionByZero);
+
+pack!(StateAdd = (f32, f32));
+pack!(StateSubtract = (f32, f32));
+pack!(StateMultiply = (f32, f32));
+pack!(StateDivide = (f32, f32));
+
+pack!(ResultNumber = f32);
+
+#[derive(Grouped)]
+struct StateCalculate {
+ number_a: f32,
+ operator: Operator,
+ number_b: f32,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+enum Operator {
+ Plus,
+ Dash,
+ Slash,
+ Star,
+}
+
+// --------- IMPORTANT ---------
+// Define SinglePickable for type Operator
+// This allows the type to be picked as an argument
+impl SinglePickable for Operator {
+ fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+ let Some(str) = str else {
+ return PickerArgResult::NotFound;
+ };
+ let op = match str.chars().next() {
+ Some('+') => Operator::Plus,
+ Some('-') => Operator::Dash,
+ Some('*') => Operator::Star,
+ Some('/') => Operator::Slash,
+ _ => return PickerArgResult::NotFound,
+ };
+ PickerArgResult::Parsed(op)
+ }
+}
+// --------- IMPORTANT ---------
+
+#[derive(Default, Clone)]
+struct ResNumberDisplaySetting {
+ round: bool,
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // Use ParserStyle to manage the arg-picker theme
+ ParserStyle::set_global_style(&UNIX_STYLE);
+
+ // Enable picker::BasicProgramSetup
+ program.with_setup(BasicProgramSetup);
+
+ // --------- IMPORTANT ---------
+ // Pre-process global arguments before executing commands
+ let (round, args) = program
+ .take_args()
+ // Use arg![round: Flag] to indicate the `--round` | `-R` flag
+ // |
+ // vvvvvvvvvvvvvvvv
+ .pick(&arg![round: Flag, 'R'])
+ // Use REMAINS to extract remaining arguments
+ // |
+ // vvvvvvvv
+ .pick(&REMAINS)
+ // Since Flag and REMAINS will not fail to parse,
+ // we can safely unwrap here
+ .unwrap();
+ program.replace_args(args.into());
+
+ program.with_resource(ResNumberDisplaySetting { round: *round });
+ // --------- IMPORTANT ---------
+
+ program.with_dispatcher(CMDCalculate);
+ program.exec_and_exit();
+}
+
+#[chain]
+fn handle_calc(args: EntryCalculate) -> Next {
+ // --------- IMPORTANT ---------
+ let (number_a, operator, number_b) = route!(
+ // Use the arg! macro to define a positional argument of type f32
+ // |
+ // vvvvvvvvvv
+ args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain())
+ .pick_or_route(&arg![Operator], || {
+ ErrorNumberOperatorNotProvided::default().to_chain()
+ }) // Returns a routable type when not found or fails to parse
+ // |
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+ .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain())
+ // Use `to_result` to parse arguments
+ // and convert to Result<(Tuple, ...), Route> type
+ .to_result()
+ );
+ // --------- IMPORTANT ---------
+
+ if operator == Operator::Slash && number_b == 0. {
+ return ErrorDivisionByZero::default().to_chain();
+ }
+
+ StateCalculate {
+ number_a,
+ operator,
+ number_b,
+ }
+ .to_chain()
+}
+
+#[chain]
+fn handle_state_calculate(state: StateCalculate) -> Next {
+ match (state.operator, state.number_a, state.number_b) {
+ (Operator::Plus, a, b) => StateAdd::new((a, b)).to_chain(),
+ (Operator::Dash, a, b) => StateSubtract::new((a, b)).to_chain(),
+ (Operator::Slash, a, b) => StateDivide::new((a, b)).to_chain(),
+ (Operator::Star, a, b) => StateMultiply::new((a, b)).to_chain(),
+ }
+}
+
+#[chain]
+fn handle_state_add(state_add: StateAdd) -> ResultNumber {
+ let (a, b) = state_add.inner;
+ ResultNumber::new(a + b)
+}
+
+#[chain]
+fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber {
+ let (a, b) = state_subtract.inner;
+ ResultNumber::new(a - b)
+}
+
+#[chain]
+fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber {
+ let (a, b) = state_multiply.inner;
+ ResultNumber::new(a * b)
+}
+
+#[chain]
+fn handle_state_divide(state_divide: StateDivide) -> ResultNumber {
+ let (a, b) = state_divide.inner;
+ ResultNumber::new(a / b)
+}
+
+#[renderer]
+fn render_result_number(result: ResultNumber, setting: &ResNumberDisplaySetting) -> String {
+ let round = setting.round;
+ let result = if round { result.round() } else { result.inner };
+ format!("Result: {}", result)
+}
+
+#[renderer]
+fn render_error_division_by_zero(_: ErrorDivisionByZero) -> String {
+ "Error: Division by zero is not allowed!".to_string()
+}
+
+#[renderer]
+fn render_error_number_a_not_provided(_: ErrorNumberANotProvided) -> String {
+ "Error: First number (number_a) was not provided.".to_string()
+}
+
+#[renderer]
+fn render_error_number_b_not_provided(_: ErrorNumberBNotProvided) -> String {
+ "Error: Second number (number_b) was not provided.".to_string()
+}
+
+#[renderer]
+fn render_error_number_operator_not_provided(_: ErrorNumberOperatorNotProvided) -> String {
+ "Error: Operator was not provided.".to_string()
+}
+
+gen_program!();
diff --git a/examples/example-clap-binding/src/main.rs b/examples/example-clap-binding/src/main.rs
index d99f8d1..0f839c8 100644
--- a/examples/example-clap-binding/src/main.rs
+++ b/examples/example-clap-binding/src/main.rs
@@ -38,7 +38,7 @@
//! For more information, try '--help'.
//! ```
-use mingling::{Groupped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
+use mingling::{Grouped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
use std::io::Write;
fn main() {
@@ -67,10 +67,10 @@ fn main() {
// Implement Clap Parser, and bind to Dispatcher
// _______________________________ Default trait, provides fallback on parse failure
// / ______________________ clap::Parser, parsing logic implemented by Clap
-// | / ________ Implement mingling::Groupped
+// | / ________ Implement mingling::Grouped
// | | / to ensure Mingling can recognize the type
-// vvvvvvv vvvvvvvvvvvv vvvvvvvv
-#[derive(Default, clap::Parser, Groupped)]
+// vvvvvvv vvvvvvvvvvvv vvvvvvv
+#[derive(Default, clap::Parser, Grouped)]
#[dispatcher_clap(
"greet", CMDGreet, // Bind EntryGreet to "greet" command
help = true, // Generate clap help for EntryGreet
diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs
index d203815..b163278 100644
--- a/examples/example-custom-pickable/src/main.rs
+++ b/examples/example-custom-pickable/src/main.rs
@@ -14,15 +14,15 @@
//! Failed to parse address
//! ```
-use mingling::{macros::route, parser::Pickable, prelude::*, Groupped};
+use mingling::{macros::route, parser::Pickable, prelude::*, Grouped};
use std::io::Write;
// Define types that can be recognized by Mingling
// ________________________ `Pickable` trait needs to implement Default
-// / ________ The Groupped derive macro registers an ID for this type
+// / ________ The Grouped derive macro registers an ID for this type
// | / Mingling uses this ID to identify the type
-// vvvvvvv vvvvvvvv
-#[derive(Debug, Default, Clone, Groupped)]
+// vvvvvvv vvvvvvv
+#[derive(Debug, Default, Clone, Grouped)]
pub struct Address {
pub ip: [u8; 4],
pub port: u16,
diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs
index 91c5358..b57511d 100644
--- a/examples/example-enum-tag/src/main.rs
+++ b/examples/example-enum-tag/src/main.rs
@@ -16,7 +16,7 @@
//! ```
use mingling::{
- macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Groupped, ShellContext,
+ macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext,
Suggest,
};
use std::io::Write;
@@ -25,7 +25,7 @@ use std::io::Write;
// ________ adds metadata to the enum, enabling it to:
// / 1. Be used by the `suggest_enum!(Enum)` macro under the `comp` feature for autocompletion
// vvvvvvv 2. Implement the `PickableEnum` trait
-#[derive(Debug, Default, EnumTag, Groupped)]
+#[derive(Debug, Default, EnumTag, Grouped)]
pub enum ProgrammingLanguages {
#[enum_desc("An efficient and flexible compiled language widely used for system programming")]
C,
diff --git a/examples/example-outside-type/src/main.rs b/examples/example-outside-type/src/main.rs
index 925878a..3159d19 100644
--- a/examples/example-outside-type/src/main.rs
+++ b/examples/example-outside-type/src/main.rs
@@ -71,7 +71,7 @@ fn render_number(num: ParsedNumber) -> RenderResult {
/// Renderer for parse errors — using the outside `ParseIntError` type.
///
/// The `ParseIntError` type is registered via `group!` above, so it implements
-/// `Groupped<ThisProgram>` and can be used directly in a `#[renderer]` function.
+/// `Grouped<ThisProgram>` and can be used directly in a `#[renderer]` function.
#[renderer]
fn render_parse_error(err: ParseIntError) -> RenderResult {
let mut render_result = RenderResult::new();
diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs
index 13c4c84..070e75d 100644
--- a/examples/example-structural-renderer/src/main.rs
+++ b/examples/example-structural-renderer/src/main.rs
@@ -18,7 +18,7 @@
//! ```
use mingling::prelude::*;
-use mingling::{Groupped, StructuralData, parser::Picker, setup::StructuralRendererSetup};
+use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData};
use serde::Serialize;
use std::io::Write;
@@ -35,12 +35,12 @@ fn main() {
// --------- IMPORTANT ---------
// For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
// Instead, manually implement
-// __________________________________ Mark as structured data so it can be rendered
-// / ____________________ Implement serde::Serialize
-// | / _________ Implement mingling::Groupped
-// | | / to ensure Mingling can recognize the type
-// vvvvvvvvvvvv vvvvvvvvv vvvvvvvv
-#[derive(StructuralData, Serialize, Groupped)]
+// ____________________________________ Mark as structured data so it can be rendered
+// / ____________________ Implement serde::Serialize
+// | / _________ Implement mingling::Grouped
+// | | / to ensure Mingling can recognize the type
+// vvvvvvvvvvvv vvvvvvvvv vvvvvvv
+#[derive(StructuralData, Serialize, Grouped)]
struct Info {
#[serde(rename = "member_name")]
name: String,
diff --git a/examples/full-todolist/src/todolist.rs b/examples/full-todolist/src/todolist.rs
index 71338c3..d3582e3 100644
--- a/examples/full-todolist/src/todolist.rs
+++ b/examples/full-todolist/src/todolist.rs
@@ -1,13 +1,13 @@
//! Data structures, read and write logic for the todo list
-use mingling::{Groupped, RenderResult, macros::renderer};
+use mingling::{Grouped, RenderResult, macros::renderer};
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::{env::current_dir, path::PathBuf};
use crate::ResProgramFlags;
-#[derive(Debug, Serialize, Deserialize, Clone, Default, Groupped)]
+#[derive(Debug, Serialize, Deserialize, Clone, Default, Grouped)]
pub struct ResTodoList {
pub items: Vec<Todo>,
}
diff --git a/examples/test-examples.toml b/examples/test-examples.toml
index d2fe602..e450919 100644
--- a/examples/test-examples.toml
+++ b/examples/test-examples.toml
@@ -277,3 +277,38 @@ expect.result = "Hello, Alice!"
command = "hello"
expect.exit-code = 0
expect.result = "Hello, World!"
+
+[[test.example-argument-picker]]
+command = "calc 1 + 1"
+expect.exit-code = 0
+expect.result = "Result: 2"
+
+[[test.example-argument-picker]]
+command = "calc 7 * 7"
+expect.exit-code = 0
+expect.result = "Result: 49"
+
+[[test.example-argument-picker]]
+command = "calc"
+expect.exit-code = 0
+expect.result = "Error: First number (number_a) was not provided."
+
+[[test.example-argument-picker]]
+command = "calc 1"
+expect.exit-code = 0
+expect.result = "Error: Operator was not provided."
+
+[[test.example-argument-picker]]
+command = "calc 1 +"
+expect.exit-code = 0
+expect.result = "Error: Second number (number_b) was not provided."
+
+[[test.example-argument-picker]]
+command = "calc 4 / 3"
+expect.exit-code = 0
+expect.result = "Result: 1.3333334"
+
+[[test.example-argument-picker]]
+command = "calc 4 / 3 --round"
+expect.exit-code = 0
+expect.result = "Result: 1"
diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml
index adf900f..2509029 100644
--- a/mingling/Cargo.toml
+++ b/mingling/Cargo.toml
@@ -50,7 +50,7 @@ dispatch_tree = ["mingling_core/dispatch_tree", "mingling_macros/dispatch_tree"]
repl = ["mingling_core/repl", "mingling_macros/repl"]
comp = ["mingling_core/comp", "mingling_macros/comp"]
parser = ["dep:size"]
-picker = ["dep:arg-picker", "arg-picker/mingling_support"]
+picker = ["mingling_core/picker", "dep:arg-picker", "arg-picker/mingling_support"]
pathf = ["mingling_core/pathf", "mingling_macros/pathf"]
structural_renderer = [
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index c4f59c9..bdefb5a 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -124,6 +124,251 @@
/// }
/// ```
pub mod example_argument_parse {}
+/// Example Argument Picker
+///
+/// > Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.
+///
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 + 1
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 7 * 7
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 +
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3 --round
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Result: 2
+/// Result: 49
+/// Error: First number (number_a) was not provided.
+/// Error: Operator was not provided.
+/// Error: Second number (number_b) was not provided.
+/// Result: 1.3333334
+/// Result: 1
+/// ```
+///
+/// Source code (./Cargo.toml)
+/// ```toml
+/// [package]
+/// name = "example-argument-picker"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// # Enable `picker` features
+/// features = ["picker", "extra_macros"]
+///
+/// [workspace]
+/// ```
+///
+/// Source code (./src/main.rs)
+/// ```ignore
+/// use mingling::{
+/// consts::REMAINS,
+/// macros::route,
+/// picker::{
+/// IntoPicker, PickerArgResult, SinglePickable,
+/// parselib::{ParserStyle, UNIX_STYLE},
+/// value::Flag,
+/// },
+/// prelude::*,
+/// };
+///
+/// // --------- IMPORTANT ---------
+/// // Use picker::BasicProgramSetup instead of the original BasicProgramSetup
+/// // It uses arg-picker to rewrite the logic of the original BasicProgramSetup
+/// use mingling::setup::picker::BasicProgramSetup;
+///
+/// // --------- IMPORTANT ---------
+///
+/// dispatcher!("calc", CMDCalculate => EntryCalculate);
+///
+/// pack_err!(ErrorNumberANotProvided);
+/// pack_err!(ErrorNumberBNotProvided);
+/// pack_err!(ErrorNumberOperatorNotProvided);
+/// pack_err!(ErrorDivisionByZero);
+///
+/// pack!(StateAdd = (f32, f32));
+/// pack!(StateSubtract = (f32, f32));
+/// pack!(StateMultiply = (f32, f32));
+/// pack!(StateDivide = (f32, f32));
+///
+/// pack!(ResultNumber = f32);
+///
+/// #[derive(Grouped)]
+/// struct StateCalculate {
+/// number_a: f32,
+/// operator: Operator,
+/// number_b: f32,
+/// }
+///
+/// #[derive(Debug, PartialEq, Eq)]
+/// enum Operator {
+/// Plus,
+/// Dash,
+/// Slash,
+/// Star,
+/// }
+///
+/// // --------- IMPORTANT ---------
+/// // Define SinglePickable for type Operator
+/// // This allows the type to be picked as an argument
+/// impl SinglePickable for Operator {
+/// fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+/// let Some(str) = str else {
+/// return PickerArgResult::NotFound;
+/// };
+/// let op = match str.chars().next() {
+/// Some('+') => Operator::Plus,
+/// Some('-') => Operator::Dash,
+/// Some('*') => Operator::Star,
+/// Some('/') => Operator::Slash,
+/// _ => return PickerArgResult::NotFound,
+/// };
+/// PickerArgResult::Parsed(op)
+/// }
+/// }
+/// // --------- IMPORTANT ---------
+///
+/// #[derive(Default, Clone)]
+/// struct ResNumberDisplaySetting {
+/// round: bool,
+/// }
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+///
+/// // Use ParserStyle to manage the arg-picker theme
+/// ParserStyle::set_global_style(&UNIX_STYLE);
+///
+/// // Enable picker::BasicProgramSetup
+/// program.with_setup(BasicProgramSetup);
+///
+/// // --------- IMPORTANT ---------
+/// // Pre-process global arguments before executing commands
+/// let (round, args) = program
+/// .take_args()
+/// // Use arg![round: Flag] to indicate the `--round` | `-R` flag
+/// // |
+/// // vvvvvvvvvvvvvvvv
+/// .pick(&arg![round: Flag, 'R'])
+/// // Use REMAINS to extract remaining arguments
+/// // |
+/// // vvvvvvvv
+/// .pick(&REMAINS)
+/// // Since Flag and REMAINS will not fail to parse,
+/// // we can safely unwrap here
+/// .unwrap();
+/// program.replace_args(args.into());
+///
+/// program.with_resource(ResNumberDisplaySetting { round: *round });
+/// // --------- IMPORTANT ---------
+///
+/// program.with_dispatcher(CMDCalculate);
+/// program.exec_and_exit();
+/// }
+///
+/// #[chain]
+/// fn handle_calc(args: EntryCalculate) -> Next {
+/// // --------- IMPORTANT ---------
+/// let (number_a, operator, number_b) = route!(
+/// // Use the arg! macro to define a positional argument of type f32
+/// // |
+/// // vvvvvvvvvv
+/// args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain())
+/// .pick_or_route(&arg![Operator], || {
+/// ErrorNumberOperatorNotProvided::default().to_chain()
+/// }) // Returns a routable type when not found or fails to parse
+/// // |
+/// // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+/// .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain())
+/// // Use `to_result` to parse arguments
+/// // and convert to Result<(Tuple, ...), Route> type
+/// .to_result()
+/// );
+/// // --------- IMPORTANT ---------
+///
+/// if operator == Operator::Slash && number_b == 0. {
+/// return ErrorDivisionByZero::default().to_chain();
+/// }
+///
+/// StateCalculate {
+/// number_a,
+/// operator,
+/// number_b,
+/// }
+/// .to_chain()
+/// }
+///
+/// #[chain]
+/// fn handle_state_calculate(state: StateCalculate) -> Next {
+/// match (state.operator, state.number_a, state.number_b) {
+/// (Operator::Plus, a, b) => StateAdd::new((a, b)).to_chain(),
+/// (Operator::Dash, a, b) => StateSubtract::new((a, b)).to_chain(),
+/// (Operator::Slash, a, b) => StateDivide::new((a, b)).to_chain(),
+/// (Operator::Star, a, b) => StateMultiply::new((a, b)).to_chain(),
+/// }
+/// }
+///
+/// #[chain]
+/// fn handle_state_add(state_add: StateAdd) -> ResultNumber {
+/// let (a, b) = state_add.inner;
+/// ResultNumber::new(a + b)
+/// }
+///
+/// #[chain]
+/// fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber {
+/// let (a, b) = state_subtract.inner;
+/// ResultNumber::new(a - b)
+/// }
+///
+/// #[chain]
+/// fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber {
+/// let (a, b) = state_multiply.inner;
+/// ResultNumber::new(a * b)
+/// }
+///
+/// #[chain]
+/// fn handle_state_divide(state_divide: StateDivide) -> ResultNumber {
+/// let (a, b) = state_divide.inner;
+/// ResultNumber::new(a / b)
+/// }
+///
+/// #[renderer]
+/// fn render_result_number(result: ResultNumber, setting: &ResNumberDisplaySetting) -> String {
+/// let round = setting.round;
+/// let result = if round { result.round() } else { result.inner };
+/// format!("Result: {}", result)
+/// }
+///
+/// #[renderer]
+/// fn render_error_division_by_zero(_: ErrorDivisionByZero) -> String {
+/// "Error: Division by zero is not allowed!".to_string()
+/// }
+///
+/// #[renderer]
+/// fn render_error_number_a_not_provided(_: ErrorNumberANotProvided) -> String {
+/// "Error: First number (number_a) was not provided.".to_string()
+/// }
+///
+/// #[renderer]
+/// fn render_error_number_b_not_provided(_: ErrorNumberBNotProvided) -> String {
+/// "Error: Second number (number_b) was not provided.".to_string()
+/// }
+///
+/// #[renderer]
+/// fn render_error_number_operator_not_provided(_: ErrorNumberOperatorNotProvided) -> String {
+/// "Error: Operator was not provided.".to_string()
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_argument_picker {}
/// Example Async Runtime Support
///
/// > This example shows how to drive an async runtime using the `async` feature
@@ -378,7 +623,7 @@ pub mod example_basic {}
///
/// Source code (./src/main.rs)
/// ```ignore
-/// use mingling::{Groupped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
+/// use mingling::{Grouped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
/// use std::io::Write;
///
/// fn main() {
@@ -407,10 +652,10 @@ pub mod example_basic {}
/// // Implement Clap Parser, and bind to Dispatcher
/// // _______________________________ Default trait, provides fallback on parse failure
/// // / ______________________ clap::Parser, parsing logic implemented by Clap
-/// // | / ________ Implement mingling::Groupped
+/// // | / ________ Implement mingling::Grouped
/// // | | / to ensure Mingling can recognize the type
-/// // vvvvvvv vvvvvvvvvvvv vvvvvvvv
-/// #[derive(Default, clap::Parser, Groupped)]
+/// // vvvvvvv vvvvvvvvvvvv vvvvvvv
+/// #[derive(Default, clap::Parser, Grouped)]
/// #[dispatcher_clap(
/// "greet", CMDGreet, // Bind EntryGreet to "greet" command
/// help = true, // Generate clap help for EntryGreet
@@ -724,15 +969,15 @@ pub mod example_completion {}
///
/// Source code (./src/main.rs)
/// ```ignore
-/// use mingling::{macros::route, parser::Pickable, prelude::*, Groupped};
+/// use mingling::{macros::route, parser::Pickable, prelude::*, Grouped};
/// use std::io::Write;
///
/// // Define types that can be recognized by Mingling
/// // ________________________ `Pickable` trait needs to implement Default
-/// // / ________ The Groupped derive macro registers an ID for this type
+/// // / ________ The Grouped derive macro registers an ID for this type
/// // | / Mingling uses this ID to identify the type
-/// // vvvvvvv vvvvvvvv
-/// #[derive(Debug, Default, Clone, Groupped)]
+/// // vvvvvvv vvvvvvv
+/// #[derive(Debug, Default, Clone, Grouped)]
/// pub struct Address {
/// pub ip: [u8; 4],
/// pub port: u16,
@@ -963,7 +1208,7 @@ pub mod example_dispatch_tree {}
/// Source code (./src/main.rs)
/// ```ignore
/// use mingling::{
-/// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Groupped, ShellContext,
+/// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext,
/// Suggest,
/// };
/// use std::io::Write;
@@ -972,7 +1217,7 @@ pub mod example_dispatch_tree {}
/// // ________ adds metadata to the enum, enabling it to:
/// // / 1. Be used by the `suggest_enum!(Enum)` macro under the `comp` feature for autocompletion
/// // vvvvvvv 2. Implement the `PickableEnum` trait
-/// #[derive(Debug, Default, EnumTag, Groupped)]
+/// #[derive(Debug, Default, EnumTag, Grouped)]
/// pub enum ProgrammingLanguages {
/// #[enum_desc("An efficient and flexible compiled language widely used for system programming")]
/// C,
@@ -1691,7 +1936,7 @@ pub mod example_lazy_resources {}
/// /// Renderer for parse errors — using the outside `ParseIntError` type.
/// ///
/// /// The `ParseIntError` type is registered via `group!` above, so it implements
-/// /// `Groupped<ThisProgram>` and can be used directly in a `#[renderer]` function.
+/// /// `Grouped<ThisProgram>` and can be used directly in a `#[renderer]` function.
/// #[renderer]
/// fn render_parse_error(err: ParseIntError) -> RenderResult {
/// let mut render_result = RenderResult::new();
@@ -2436,7 +2681,7 @@ pub mod example_setup {}
/// Source code (./src/main.rs)
/// ```ignore
/// use mingling::prelude::*;
-/// use mingling::{Groupped, StructuralData, parser::Picker, setup::StructuralRendererSetup};
+/// use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData};
/// use serde::Serialize;
/// use std::io::Write;
///
@@ -2453,12 +2698,12 @@ pub mod example_setup {}
/// // --------- IMPORTANT ---------
/// // For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
/// // Instead, manually implement
-/// // __________________________________ Mark as structured data so it can be rendered
-/// // / ____________________ Implement serde::Serialize
-/// // | / _________ Implement mingling::Groupped
-/// // | | / to ensure Mingling can recognize the type
-/// // vvvvvvvvvvvv vvvvvvvvv vvvvvvvv
-/// #[derive(StructuralData, Serialize, Groupped)]
+/// // ____________________________________ Mark as structured data so it can be rendered
+/// // / ____________________ Implement serde::Serialize
+/// // | / _________ Implement mingling::Grouped
+/// // | | / to ensure Mingling can recognize the type
+/// // vvvvvvvvvvvv vvvvvvvvv vvvvvvv
+/// #[derive(StructuralData, Serialize, Grouped)]
/// struct Info {
/// #[serde(rename = "member_name")]
/// name: String,
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 4b3ced6..5cd53db 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -106,6 +106,10 @@ pub mod macros {
/// `route! { /* ... */ }` - Used to generate a route that either returns a successful result or early returns an error.
#[cfg(feature = "extra_macros")]
pub use mingling_macros::route;
+ /// `#[routeify]` - An extension attribute macro that transforms `expr?` into `route!(expr)`.
+ /// Can be used standalone or as a chain/renderer extension: `#[chain(routeify, ...)]`.
+ #[cfg(feature = "extra_macros")]
+ pub use mingling_macros::routeify;
/// `suggest! { "hello", "bye" }` - Used to generate suggestions
#[cfg(feature = "comp")]
pub use mingling_macros::suggest;
@@ -118,9 +122,9 @@ pub mod macros {
#[cfg(feature = "macros")]
pub use mingling_macros::EnumTag;
-/// derive macro Groupped
+/// derive macro Grouped
#[cfg(feature = "macros")]
-pub use mingling_macros::Groupped;
+pub use mingling_macros::Grouped;
/// derive macro `StructuralData` — marks a type as supporting structured output
#[cfg(feature = "structural_renderer")]
@@ -171,9 +175,9 @@ pub mod res;
/// use mingling::prelude::*;
/// ```
pub mod prelude {
- /// Re-export of the `Groupped` derive macro for grouping types.
+ /// Re-export of the `Grouped` derive macro for grouping types.
#[cfg(feature = "core")]
- pub use crate::Groupped;
+ pub use crate::Grouped;
/// Re-export of the `RenderResult` struct for outputting rendering result
#[cfg(feature = "core")]
pub use crate::RenderResult;
diff --git a/mingling/src/picker/entry_picker.rs b/mingling/src/picker/entry_picker.rs
index 7a980c6..7364c50 100644
--- a/mingling/src/picker/entry_picker.rs
+++ b/mingling/src/picker/entry_picker.rs
@@ -1,8 +1,8 @@
-use mingling_core::{ChainProcess, Groupped, ProgramCollect};
+use mingling_core::{ChainProcess, Grouped, ProgramCollect};
use crate::{picker::Pickable, picker::Picker, picker::PickerArg, picker::PickerPattern1};
-/// Trait for converting Mingling entry types (types that implement `Groupped<R>` and `Into<Vec<String>>`)
+/// Trait for converting Mingling entry types (types that implement `Grouped<R>` and `Into<Vec<String>>`)
/// into [`Picker`] instances for a given route type `R`.
///
/// This trait provides a bridge between entry definitions created with the `mingling` framework
@@ -12,7 +12,7 @@ use crate::{picker::Pickable, picker::Picker, picker::PickerArg, picker::PickerP
///
/// * `'a` — The lifetime of the picker and its references to argument definitions.
/// * `This` — The program type used for dispatching runtime routes; must implement [`ProgramCollect`].
-/// * `Route` — The route type used for dispatching; must implement [`Groupped<This>`].
+/// * `Route` — The route type used for dispatching; must implement [`Grouped<This>`].
pub trait EntryPicker<'a, This> {
/// Converts `self` into a [`Picker`] for the given route type `Route`.
fn to_picker(self) -> Picker<'a, ChainProcess<This>>;
@@ -117,7 +117,7 @@ pub trait EntryPicker<'a, This> {
impl<'a, This, Bind> EntryPicker<'a, This> for Bind
where
This: ProgramCollect<Enum = This>,
- Bind: Groupped<This> + Into<Vec<String>>,
+ Bind: Grouped<This> + Into<Vec<String>>,
{
fn to_picker(self) -> Picker<'a, ChainProcess<This>> {
let args = self.into();
diff --git a/mingling_core/Cargo.toml b/mingling_core/Cargo.toml
index 85afd55..14140c6 100644
--- a/mingling_core/Cargo.toml
+++ b/mingling_core/Cargo.toml
@@ -15,6 +15,7 @@ nightly = []
default = []
async = []
builds = []
+picker = []
dispatch_tree = []
structural_renderer = ["dep:serde"]
diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs
index 2680f43..e6b7406 100644
--- a/mingling_core/src/any.rs
+++ b/mingling_core/src/any.rs
@@ -1,12 +1,12 @@
use crate::error::ChainProcessError;
-use crate::{Groupped, ProgramCollect};
+use crate::{Grouped, ProgramCollect};
#[doc(hidden)]
pub mod group;
/// Any type output
///
-/// Accepts any type that implements `Send + Groupped<G>`
+/// Accepts any type that implements `Send + Grouped<G>`
/// After being passed into `AnyOutput`, it will be converted to `Box<dyn Any + Send + 'static>`
///
/// Note:
@@ -22,10 +22,10 @@ pub struct AnyOutput<G> {
}
impl<G> AnyOutput<G> {
- /// Create an `AnyOutput` from a `Send + Groupped<G>` type
+ /// Create an `AnyOutput` from a `Send + Grouped<G>` type
pub fn new<T>(value: T) -> Self
where
- T: Send + Groupped<G> + 'static,
+ T: Send + Grouped<G> + 'static,
{
Self {
inner: Box::new(value),
@@ -148,7 +148,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
- use crate::Groupped;
+ use crate::Grouped;
/// Mock enum for testing AnyOutput
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -175,7 +175,7 @@ mod tests {
value: i32,
}
- impl Groupped<MockGroup> for AlphaData {
+ impl Grouped<MockGroup> for AlphaData {
fn member_id() -> MockGroup {
MockGroup::Alpha
}
@@ -187,7 +187,7 @@ mod tests {
name: String,
}
- impl Groupped<MockGroup> for BetaData {
+ impl Grouped<MockGroup> for BetaData {
fn member_id() -> MockGroup {
MockGroup::Beta
}
@@ -198,7 +198,7 @@ mod tests {
#[cfg_attr(feature = "structural_renderer", derive(serde::Serialize))]
struct GammaData;
- impl Groupped<MockGroup> for GammaData {
+ impl Grouped<MockGroup> for GammaData {
fn member_id() -> MockGroup {
MockGroup::Gamma
}
@@ -354,7 +354,7 @@ mod tests {
x: i32,
}
- impl Groupped<MockGroup> for SerData {
+ impl Grouped<MockGroup> for SerData {
fn member_id() -> MockGroup {
MockGroup::Gamma
}
@@ -381,13 +381,13 @@ mod tests {
b: String,
}
- impl Groupped<MockGroup> for SerA {
+ impl Grouped<MockGroup> for SerA {
fn member_id() -> MockGroup {
MockGroup::Alpha
}
}
- impl Groupped<MockGroup> for SerB {
+ impl Grouped<MockGroup> for SerB {
fn member_id() -> MockGroup {
MockGroup::Beta
}
diff --git a/mingling_core/src/any/group.rs b/mingling_core/src/any/group.rs
index cb847d9..90ef9fc 100644
--- a/mingling_core/src/any/group.rs
+++ b/mingling_core/src/any/group.rs
@@ -1,11 +1,11 @@
-use crate::{AnyOutput, ChainProcess};
+use crate::{AnyOutput, ChainProcess, ProgramCollect, Routable};
/// Used to mark a type with a unique enum ID, assisting dynamic dispatch
///
-/// **Note:** Unlike earlier versions, `Groupped` no longer requires `Serialize`
+/// **Note:** Unlike earlier versions, `Grouped` no longer requires `Serialize`
/// even when the `structural_renderer` feature is enabled. Structured output is
/// controlled separately via the \[`StructuralData`\] trait.
-pub trait Groupped<Group>
+pub trait Grouped<Group>
where
Self: Sized + 'static,
{
@@ -32,3 +32,17 @@ where
AnyOutput::new(self).route_renderer()
}
}
+
+impl<T, C> Routable<C> for T
+where
+ C: ProgramCollect<Enum = C>,
+ T: Grouped<C> + Send,
+{
+ fn to_chain(self) -> ChainProcess<C> {
+ T::to_chain(self)
+ }
+
+ fn to_render(self) -> ChainProcess<C> {
+ T::to_render(self)
+ }
+}
diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs
index b1fd617..9b9a5d4 100644
--- a/mingling_core/src/asset.rs
+++ b/mingling_core/src/asset.rs
@@ -21,3 +21,6 @@ pub mod node;
#[doc(hidden)]
pub mod renderer;
+
+#[doc(hidden)]
+pub mod routable;
diff --git a/mingling_core/src/asset/routable.rs b/mingling_core/src/asset/routable.rs
new file mode 100644
index 0000000..24b7bb1
--- /dev/null
+++ b/mingling_core/src/asset/routable.rs
@@ -0,0 +1,22 @@
+use crate::ChainProcess;
+
+/// Provides routing capabilities for converting an item into a `ChainProcess`
+/// directed to either the chain or render processing pipeline.
+///
+/// This trait enables items to be dispatched to different processing routes
+/// (chain or render) by wrapping them into an `AnyOutput` and routing them
+/// through the appropriate pipeline.
+pub trait Routable<Group>
+where
+ Self: Sized + 'static,
+{
+ /// Converts the routable item into a `ChainProcess` directed to the chain route.
+ ///
+ /// This wraps the item into an `AnyOutput` and routes it to the chain processing pipeline.
+ fn to_chain(self) -> ChainProcess<Group>;
+
+ /// Converts the routable item into a `ChainProcess` directed to the render route.
+ ///
+ /// This wraps the item into an `AnyOutput` and routes it to the render processing pipeline.
+ fn to_render(self) -> ChainProcess<Group>;
+}
diff --git a/mingling_core/src/comp.rs b/mingling_core/src/comp.rs
index f6fecd1..55e9952 100644
--- a/mingling_core/src/comp.rs
+++ b/mingling_core/src/comp.rs
@@ -96,7 +96,7 @@ impl CompletionHelper {
trace!("entry type: {}", any.member_id);
let dispatcher_not_found =
- <P::ErrorDispatcherNotFound as crate::Groupped<P>>::member_id();
+ <P::ErrorDispatcherNotFound as crate::Grouped<P>>::member_id();
if dispatcher_not_found == any.member_id {
debug!("dispatcher_not_found matched");
diff --git a/mingling_core/src/comp/shell_ctx.rs b/mingling_core/src/comp/shell_ctx.rs
index cfa4700..1fca325 100644
--- a/mingling_core/src/comp/shell_ctx.rs
+++ b/mingling_core/src/comp/shell_ctx.rs
@@ -1,3 +1,5 @@
+#![allow(deprecated)]
+
use std::collections::HashSet;
use crate::{Flag, ShellFlag, Suggest};
@@ -115,6 +117,13 @@ impl ShellContext {
/// // }
/// }
/// ```
+ #[must_use]
+ #[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When using the `picker` feature, this method does not work under all ParserStyle settings"
+ )
+ )]
pub fn filling_argument_first(&self, flag: impl Into<Flag>) -> bool {
let flag = flag.into();
if self.filling_argument(&flag) {
@@ -153,6 +162,13 @@ impl ShellContext {
/// // }
/// }
/// ```
+ #[must_use]
+ #[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When using the `picker` feature, this method does not work under all ParserStyle settings"
+ )
+ )]
pub fn filling_argument(&self, flag: impl Into<Flag>) -> bool {
for f in flag.into().iter() {
if self.previous_word == **f {
@@ -190,6 +206,12 @@ impl ShellContext {
/// }
/// ```
#[must_use]
+ #[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When using the `picker` feature, this method does not work under all ParserStyle settings"
+ )
+ )]
pub fn typing_argument(&self) -> bool {
#[cfg(target_os = "windows")]
{
@@ -208,6 +230,12 @@ impl ShellContext {
/// when the user has already typed certain flags. The method processes both
/// regular suggestion sets and file completion suggestions differently.
#[must_use]
+ #[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When using the `picker` feature, this method does not work under all ParserStyle settings"
+ )
+ )]
pub fn strip_typed_argument(&self, suggest: Suggest) -> Suggest {
let typed = Self::get_typed_arguments(self);
match suggest {
@@ -225,6 +253,12 @@ impl ShellContext {
/// which typically represent command-line flags or options. It returns a vector
/// containing these flag strings, converted to owned `String` values.
#[must_use]
+ #[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When using the `picker` feature, this method does not work under all ParserStyle settings"
+ )
+ )]
pub fn get_typed_arguments(&self) -> HashSet<String> {
self.all_words
.iter()
diff --git a/mingling_core/src/comp/suggest.rs b/mingling_core/src/comp/suggest.rs
index a81de64..99afc54 100644
--- a/mingling_core/src/comp/suggest.rs
+++ b/mingling_core/src/comp/suggest.rs
@@ -1,3 +1,5 @@
+#![allow(deprecated)]
+
use std::collections::BTreeSet;
use crate::ShellContext;
@@ -30,6 +32,12 @@ impl Suggest {
/// Filters out already typed flag arguments from suggestion results.
#[must_use]
+ #[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When using the `picker` feature, this method does not work under all ParserStyle settings"
+ )
+ )]
pub fn strip_typed_argument(self, ctx: &ShellContext) -> Self {
ctx.strip_typed_argument(self)
}
diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs
index 3c2cf9b..4996b19 100644
--- a/mingling_core/src/lib.rs
+++ b/mingling_core/src/lib.rs
@@ -36,6 +36,7 @@ pub use crate::asset::help::*;
pub use crate::asset::lazy_resource::*;
pub use crate::asset::node::*;
pub use crate::asset::renderer::*;
+pub use crate::asset::routable::*;
/// All error types of `Mingling`
pub mod error {
diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs
index d5aab4b..fe78979 100644
--- a/mingling_core/src/program/collection.rs
+++ b/mingling_core/src/program/collection.rs
@@ -4,7 +4,7 @@ use std::pin::Pin;
#[cfg(feature = "dispatch_tree")]
use crate::Dispatcher;
-use crate::{AnyOutput, ChainProcess, Groupped, RenderResult};
+use crate::{AnyOutput, ChainProcess, Grouped, RenderResult};
#[cfg(feature = "structural_renderer")]
use crate::{StructuralRendererSetting, error::StructuralRendererSerializeError};
@@ -21,9 +21,9 @@ pub use mock::*;
pub trait ProgramCollect {
/// Enum type representing internal IDs for the program
type Enum;
- type ErrorDispatcherNotFound: Groupped<Self::Enum>;
- type ErrorRendererNotFound: Groupped<Self::Enum>;
- type ResultEmpty: Groupped<Self::Enum>;
+ type ErrorDispatcherNotFound: Grouped<Self::Enum>;
+ type ErrorRendererNotFound: Grouped<Self::Enum>;
+ type ResultEmpty: Grouped<Self::Enum>;
/// Use a prefix tree to quickly match arguments and dispatch to an Entry
#[cfg(feature = "dispatch_tree")]
diff --git a/mingling_core/src/program/collection/mock.rs b/mingling_core/src/program/collection/mock.rs
index 9b2e7af..5847f10 100644
--- a/mingling_core/src/program/collection/mock.rs
+++ b/mingling_core/src/program/collection/mock.rs
@@ -4,7 +4,7 @@ use std::pin::Pin;
#[cfg(feature = "dispatch_tree")]
use crate::Dispatcher;
-use crate::{AnyOutput, ChainProcess, Groupped, ProgramCollect, RenderResult};
+use crate::{AnyOutput, ChainProcess, Grouped, ProgramCollect, RenderResult};
#[cfg(feature = "structural_renderer")]
use crate::{StructuralRendererSetting, error::StructuralRendererSerializeError};
@@ -23,7 +23,7 @@ pub enum MockProgramCollect {
Bar,
}
-impl Groupped<MockProgramCollect> for MockProgramCollect {
+impl Grouped<MockProgramCollect> for MockProgramCollect {
fn member_id() -> MockProgramCollect {
MockProgramCollect::Foo
}
diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs
index 56d8e0e..db1691b 100644
--- a/mingling_core/src/program/hook.rs
+++ b/mingling_core/src/program/hook.rs
@@ -678,7 +678,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
- use crate::Groupped;
+ use crate::Grouped;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -694,7 +694,7 @@ mod tests {
}
}
- impl Groupped<MockHookEnum> for MockHookEnum {
+ impl Grouped<MockHookEnum> for MockHookEnum {
fn member_id() -> MockHookEnum {
MockHookEnum::A
}
diff --git a/mingling_macros/src/attr.rs b/mingling_macros/src/attr.rs
new file mode 100644
index 0000000..e4cd826
--- /dev/null
+++ b/mingling_macros/src/attr.rs
@@ -0,0 +1,9 @@
+pub(crate) mod chain;
+#[cfg(feature = "comp")]
+pub(crate) mod completion;
+#[cfg(feature = "clap")]
+pub(crate) mod dispatcher_clap;
+pub(crate) mod help;
+#[cfg(feature = "extra_macros")]
+pub(crate) mod program_setup;
+pub(crate) mod renderer;
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/attr/chain.rs
index 566e782..120e65d 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/attr/chain.rs
@@ -62,13 +62,13 @@ fn generate_proc_fn(
quote! {
#(#immut_resource_stmts)*
#wrapped_body;
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>
+ <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>>
::to_chain(crate::ResultEmpty)
}
} else {
quote! {
#wrapped_body;
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>
+ <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>>
::to_chain(crate::ResultEmpty)
}
};
@@ -158,7 +158,7 @@ fn reject_async(sig: &Signature) -> Result<(), proc_macro2::TokenStream> {
Ok(())
}
-pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
+pub(crate) fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// Reject non-empty attribute arguments; #[chain] must be bare
if !attr.is_empty() {
return syn::Error::new(
@@ -263,7 +263,10 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
}
/// Builds a match arm for chain mapping
-pub fn build_chain_arm(struct_name: &Ident, previous_type: &TypePath) -> proc_macro2::TokenStream {
+pub(crate) fn build_chain_arm(
+ struct_name: &Ident,
+ previous_type: &TypePath,
+) -> proc_macro2::TokenStream {
let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
#struct_name => #enum_variant,
@@ -271,14 +274,14 @@ pub fn build_chain_arm(struct_name: &Ident, previous_type: &TypePath) -> proc_ma
}
/// Builds a match arm for chain existence check
-pub fn build_chain_exist_arm(previous_type: &TypePath) -> proc_macro2::TokenStream {
+pub(crate) fn build_chain_exist_arm(previous_type: &TypePath) -> proc_macro2::TokenStream {
let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
Self::#enum_variant => true,
}
}
-pub fn register_chain(input: TokenStream) -> TokenStream {
+pub(crate) fn register_chain(input: TokenStream) -> TokenStream {
// Parse the input as a comma-separated list of arguments
let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/attr/completion.rs
index ae01462..e917d7d 100644
--- a/mingling_macros/src/completion.rs
+++ b/mingling_macros/src/attr/completion.rs
@@ -5,7 +5,7 @@ use syn::spanned::Spanned;
use syn::{FnArg, Ident, ItemFn, Pat, PatType, Type, TypePath, parse_macro_input};
#[cfg(feature = "comp")]
-pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
+pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse the attribute arguments such as HelloEntry or crate::EntryFine from #[completion(crate::EntryFine)]
use crate::get_global_set;
let previous_type_path: TypePath = if attr.is_empty() {
diff --git a/mingling_macros/src/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs
index 0945e31..6083a52 100644
--- a/mingling_macros/src/dispatcher_clap.rs
+++ b/mingling_macros/src/attr/dispatcher_clap.rs
@@ -93,7 +93,7 @@ impl Parse for DispatcherClapInput {
}
#[cfg(feature = "clap")]
-pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
+pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr_input = parse_macro_input!(attr as DispatcherClapInput);
let input_struct = parse_macro_input!(item as ItemStruct);
let struct_name = &input_struct.ident;
@@ -144,7 +144,7 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream
Some(quote! {
#[allow(non_snake_case)]
#[::mingling::macros::help]
- pub fn #help_fn_name(_prev: #struct_name) -> ::mingling::RenderResult {
+ pub(crate) fn #help_fn_name(_prev: #struct_name) -> ::mingling::RenderResult {
use std::io::Write;
use clap::ColorChoice;
@@ -189,7 +189,7 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream
// Generate the dispatcher struct
#[doc(hidden)]
#[derive(Default)]
- pub struct #dispatcher_struct;
+ pub(crate) struct #dispatcher_struct;
impl ::mingling::Dispatcher<#program_path> for #dispatcher_struct {
fn node(&self) -> ::mingling::Node {
diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/attr/help.rs
index 6e0d12e..6defae4 100644
--- a/mingling_macros/src/help.rs
+++ b/mingling_macros/src/attr/help.rs
@@ -14,7 +14,7 @@ fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream>
}
}
-pub fn help_attr(item: TokenStream) -> TokenStream {
+pub(crate) fn help_attr(item: TokenStream) -> TokenStream {
// Parse the function item
let input_fn = parse_macro_input!(item as ItemFn);
@@ -160,7 +160,7 @@ fn build_help_entry(struct_name: &Ident, entry_type: &TypePath) -> proc_macro2::
}
}
-pub fn register_help(input: TokenStream) -> TokenStream {
+pub(crate) fn register_help(input: TokenStream) -> TokenStream {
// Parse the input as a comma-separated list of arguments
let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
diff --git a/mingling_macros/src/program_setup.rs b/mingling_macros/src/attr/program_setup.rs
index 7fd9d16..dee5a1c 100644
--- a/mingling_macros/src/program_setup.rs
+++ b/mingling_macros/src/attr/program_setup.rs
@@ -47,7 +47,7 @@ fn extract_return_type(sig: &Signature) -> syn::Result<()> {
}
}
-pub fn setup_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
+pub(crate) fn setup_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// #[program_setup] takes no arguments; always use the default program path
let _ = attr;
let program_path = crate::default_program_path();
diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/attr/renderer.rs
index d124ec9..c7cbb0b 100644
--- a/mingling_macros/src/renderer.rs
+++ b/mingling_macros/src/attr/renderer.rs
@@ -15,7 +15,7 @@ fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream>
}
#[allow(clippy::too_many_lines)]
-pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
+pub(crate) fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// #[renderer] takes no arguments; always use the default program path
let _ = attr;
let program_path = crate::default_program_path();
@@ -130,7 +130,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
}
/// Builds the renderer entry for the global renderers list
-pub fn build_renderer_entry(
+pub(crate) fn build_renderer_entry(
struct_name: &syn::Ident,
previous_type: &TypePath,
) -> proc_macro2::TokenStream {
@@ -141,7 +141,7 @@ pub fn build_renderer_entry(
}
/// Builds the renderer existence check entry
-pub fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
+pub(crate) fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
Self::#enum_variant => true,
@@ -150,7 +150,9 @@ pub fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::Toke
/// Builds the structural renderer entry
#[cfg(feature = "structural_renderer")]
-pub fn build_structural_renderer_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
+pub(crate) fn build_structural_renderer_entry(
+ previous_type: &TypePath,
+) -> proc_macro2::TokenStream {
let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
Self::#enum_variant => {
@@ -164,7 +166,7 @@ pub fn build_structural_renderer_entry(previous_type: &TypePath) -> proc_macro2:
}
}
-pub fn register_renderer(input: TokenStream) -> TokenStream {
+pub(crate) fn register_renderer(input: TokenStream) -> TokenStream {
// Parse the input as a comma-separated list of arguments
let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
diff --git a/mingling_macros/src/derive.rs b/mingling_macros/src/derive.rs
new file mode 100644
index 0000000..ffa405b
--- /dev/null
+++ b/mingling_macros/src/derive.rs
@@ -0,0 +1,2 @@
+pub(crate) mod enum_tag;
+pub(crate) mod grouped;
diff --git a/mingling_macros/src/enum_tag.rs b/mingling_macros/src/derive/enum_tag.rs
index 6277b69..a7f71f0 100644
--- a/mingling_macros/src/enum_tag.rs
+++ b/mingling_macros/src/derive/enum_tag.rs
@@ -4,7 +4,7 @@ use syn::{
Attribute, Data, DeriveInput, Error, Fields, Ident, LitStr, Result, Variant, parse_macro_input,
};
-pub fn derive_enum_tag(input: TokenStream) -> TokenStream {
+pub(crate) fn derive_enum_tag(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match derive_enum_tag_impl(input) {
diff --git a/mingling_macros/src/groupped.rs b/mingling_macros/src/derive/grouped.rs
index 8aee003..307aab6 100644
--- a/mingling_macros/src/groupped.rs
+++ b/mingling_macros/src/derive/grouped.rs
@@ -2,7 +2,7 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, Ident, parse_macro_input};
-pub fn derive_groupped(input: TokenStream) -> TokenStream {
+pub(crate) fn derive_grouped(input: TokenStream) -> TokenStream {
// Parse the input struct/enum
let input = parse_macro_input!(input as DeriveInput);
let struct_name = input.ident;
@@ -12,11 +12,11 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream {
let any_output_convert_impls =
proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident));
- // Generate the Groupped trait implementation
+ // Generate the Grouped trait implementation
let expanded = quote! {
::mingling::macros::register_type!(#struct_name);
- impl ::mingling::Groupped<#group_ident> for #struct_name {
+ impl ::mingling::Grouped<#group_ident> for #struct_name {
fn member_id() -> #group_ident {
#group_ident::#struct_name
}
@@ -29,7 +29,7 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream {
}
#[cfg(feature = "structural_renderer")]
-pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
+pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream {
// Parse the input struct/enum
let input_parsed = parse_macro_input!(input as DeriveInput);
let struct_name = input_parsed.ident.clone();
@@ -39,14 +39,14 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
let any_output_convert_impls =
proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident));
- // Generate both Serialize and Groupped implementations
+ // Generate both Serialize and Grouped implementations
let expanded = quote! {
#[derive(serde::Serialize)]
#input_parsed
::mingling::macros::register_type!(#struct_name);
- impl ::mingling::Groupped<#group_ident> for #struct_name {
+ impl ::mingling::Grouped<#group_ident> for #struct_name {
fn member_id() -> #group_ident {
#group_ident::#struct_name
}
diff --git a/mingling_macros/src/extensions.rs b/mingling_macros/src/extensions.rs
new file mode 100644
index 0000000..aff20e2
--- /dev/null
+++ b/mingling_macros/src/extensions.rs
@@ -0,0 +1,116 @@
+//! Extension point mechanism for Mingling attribute macros.
+//!
+//! This module provides a way for attribute macros like `#[chain]`, `#[renderer]`,
+//! `#[help]`, and `#[completion]` to accept extension identifiers that are
+//! applied as outer attributes before the bare macro.
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse::{Parse, ParseStream};
+use syn::{Ident, Token};
+
+/// Extension: `#[routeify]` — transforms `expr?` into `route!(expr)`.
+#[cfg(feature = "extra_macros")]
+pub(crate) mod routeify;
+
+/// Parsed extensions from an attribute macro like `#[chain(routeify, other_ext)]`.
+pub(crate) struct Extensions {
+ pub(crate) exts: Vec<Ident>,
+}
+
+impl Parse for Extensions {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let mut exts = Vec::new();
+ while !input.is_empty() {
+ let ident: Ident = input.parse()?;
+ exts.push(ident);
+ if input.peek(Token![,]) {
+ let _ = input.parse::<Token![,]>();
+ }
+ }
+ Ok(Extensions { exts })
+ }
+}
+
+/// Parsed extensions for `#[completion(EntryType, routeify, ...)]`.
+#[cfg(feature = "comp")]
+pub(crate) struct CompletionExt {
+ pub(crate) entry_type: proc_macro2::TokenStream,
+ pub(crate) exts: Vec<Ident>,
+}
+
+#[cfg(feature = "comp")]
+impl Parse for CompletionExt {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let entry_type: proc_macro2::TokenStream = input.parse()?;
+ let mut exts = Vec::new();
+ while !input.is_empty() {
+ let _ = input.parse::<Token![,]>();
+ if input.is_empty() {
+ break;
+ }
+ let ident: Ident = input.parse()?;
+ exts.push(ident);
+ }
+ Ok(CompletionExt { entry_type, exts })
+ }
+}
+
+/// Generates a re-dispatch token stream for attribute macros that take **no fixed arguments**
+/// (chain, renderer, help).
+pub(crate) fn try_redispatch_simple(
+ attr: TokenStream,
+ item: &TokenStream,
+ bare_attr_name: &str,
+) -> Option<TokenStream> {
+ if attr.is_empty() {
+ return None;
+ }
+
+ let exts: Extensions = syn::parse(attr).ok()?;
+ if exts.exts.is_empty() {
+ return None;
+ }
+
+ let bare = Ident::new(bare_attr_name, proc_macro2::Span::call_site());
+ let exts = &exts.exts;
+ let item = proc_macro2::TokenStream::from(item.clone());
+
+ Some(
+ quote! {
+ #(#[#exts])*
+ #[#bare]
+ #item
+ }
+ .into(),
+ )
+}
+
+/// Generates a re-dispatch token stream for `#[completion(EntryType, ...)]`.
+#[cfg(feature = "comp")]
+pub(crate) fn try_redispatch_completion(
+ attr: TokenStream,
+ item: &TokenStream,
+) -> Option<TokenStream> {
+ if attr.is_empty() {
+ return None;
+ }
+
+ let parsed: CompletionExt = syn::parse(attr).ok()?;
+ if parsed.exts.is_empty() {
+ return None;
+ }
+
+ let entry_type = &parsed.entry_type;
+ let exts = &parsed.exts;
+ let item = proc_macro2::TokenStream::from(item.clone());
+
+ Some(
+ quote! {
+ #(#[#exts])*
+ #[::mingling::macros::completion(#entry_type)]
+ #item
+ }
+ .into(),
+ )
+}
diff --git a/mingling_macros/src/extensions/routeify.rs b/mingling_macros/src/extensions/routeify.rs
new file mode 100644
index 0000000..f011fb9
--- /dev/null
+++ b/mingling_macros/src/extensions/routeify.rs
@@ -0,0 +1,39 @@
+//! The `#[routeify]` extension — transforms `expr?` into `route!(expr)`.
+//!
+//! Designed as an extension for the Mingling attribute macro system, intended
+//! to be used with `#[chain(routeify)]` or standalone as `#[routeify]`.
+//!
+//! # How it works
+//!
+//! The macro parses the function AST and replaces every `Expr::Try` node with an
+//! equivalent `route!(expr)` invocation.
+
+use proc_macro::TokenStream;
+use quote::ToTokens;
+use syn::visit_mut::VisitMut;
+use syn::{Expr, ItemFn, parse_macro_input};
+
+struct RouteifyTransform;
+
+impl VisitMut for RouteifyTransform {
+ fn visit_expr_mut(&mut self, expr: &mut Expr) {
+ syn::visit_mut::visit_expr_mut(self, expr);
+
+ if let Expr::Try(try_expr) = expr {
+ let inner = &*try_expr.expr;
+ let inner_tokens = inner.to_token_stream();
+
+ if let Ok(macro_expr) = syn::parse2::<Expr>(quote::quote! {
+ ::mingling::macros::route!(#inner_tokens)
+ }) {
+ *expr = macro_expr;
+ }
+ }
+ }
+}
+
+pub(crate) fn routeify_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ let mut input_fn = parse_macro_input!(item as ItemFn);
+ RouteifyTransform.visit_item_fn_mut(&mut input_fn);
+ input_fn.to_token_stream().into()
+}
diff --git a/mingling_macros/src/func.rs b/mingling_macros/src/func.rs
new file mode 100644
index 0000000..720b20a
--- /dev/null
+++ b/mingling_macros/src/func.rs
@@ -0,0 +1,12 @@
+pub(crate) mod dispatcher;
+#[cfg(feature = "extra_macros")]
+pub(crate) mod entry;
+pub(crate) mod gen_program;
+#[cfg(feature = "extra_macros")]
+pub(crate) mod group;
+pub(crate) mod node;
+pub(crate) mod pack;
+#[cfg(feature = "extra_macros")]
+pub(crate) mod pack_err;
+#[cfg(feature = "comp")]
+pub(crate) mod suggest;
diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs
index 2a7c850..a61dd26 100644
--- a/mingling_macros/src/dispatcher.rs
+++ b/mingling_macros/src/func/dispatcher.rs
@@ -75,7 +75,7 @@ impl Parse for DispatcherChainInput {
// are nearly identical and could benefit from refactoring into common helper functions.
#[allow(clippy::too_many_lines)]
-pub fn dispatcher(input: TokenStream) -> TokenStream {
+pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
// Parse the input
let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput);
@@ -134,8 +134,8 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
::mingling::macros::node!(#command_name_str)
}
fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess<#program_type> {
- use ::mingling::Groupped;
- #pack::new(args).to_chain()
+ use ::mingling::Grouped;
+ ::mingling::Routable::to_chain(#pack::new(args))
}
fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> {
Box::new(#command_struct)
@@ -209,7 +209,7 @@ impl Parse for RegisterDispatcherInput {
}
#[cfg(feature = "dispatch_tree")]
-pub fn register_dispatcher(input: TokenStream) -> TokenStream {
+pub(crate) fn register_dispatcher(input: TokenStream) -> TokenStream {
let RegisterDispatcherInput {
node_name,
dispatcher_type,
@@ -243,7 +243,7 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream {
}
#[cfg(not(feature = "dispatch_tree"))]
-pub fn register_dispatcher(_input: TokenStream) -> TokenStream {
+pub(crate) fn register_dispatcher(_input: TokenStream) -> TokenStream {
quote! {}.into()
}
diff --git a/mingling_macros/src/entry.rs b/mingling_macros/src/func/entry.rs
index 2ac5d6b..35209e5 100644
--- a/mingling_macros/src/entry.rs
+++ b/mingling_macros/src/func/entry.rs
@@ -39,7 +39,7 @@ fn parse_strings(input: &syn::parse::ParseBuffer) -> syn::Result<Vec<String>> {
Ok(strings)
}
-pub fn entry(input: TokenStream) -> TokenStream {
+pub(crate) fn entry(input: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(input as EntryInput);
let strings = match &parsed {
diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs
new file mode 100644
index 0000000..7128a9a
--- /dev/null
+++ b/mingling_macros/src/func/gen_program.rs
@@ -0,0 +1,602 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse_macro_input;
+
+use crate::CHAINS;
+use crate::CHAINS_EXIST;
+#[cfg(feature = "dispatch_tree")]
+use crate::COMPILE_TIME_DISPATCHERS;
+#[cfg(feature = "comp")]
+use crate::COMPLETIONS;
+use crate::HELP_REQUESTS;
+use crate::PACKED_TYPES;
+use crate::RENDERERS;
+use crate::RENDERERS_EXIST;
+#[cfg(feature = "structural_renderer")]
+use crate::STRUCTURAL_RENDERERS;
+use crate::attr::{chain, renderer};
+use crate::get_global_set;
+#[cfg(feature = "dispatch_tree")]
+use crate::systems::dispatch_tree_gen;
+
+#[cfg(feature = "async")]
+const ASYNC_ENABLED: bool = true;
+#[cfg(not(feature = "async"))]
+const ASYNC_ENABLED: bool = false;
+
+/// 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();
+ let arrow_idx = s
+ .find("=>")
+ .unwrap_or_else(|| panic!("Entry missing '=>': {s}"));
+ let struct_str = s[..arrow_idx].trim();
+ let variant_str = s[arrow_idx + 2..].trim().trim_end_matches(',').trim();
+ let struct_ident = proc_macro2::Ident::new(struct_str, proc_macro2::Span::call_site());
+ let variant_ident = proc_macro2::Ident::new(variant_str, proc_macro2::Span::call_site());
+ (struct_ident, variant_ident)
+}
+
+/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`.
+/// Always compiled; returns empty map when pathf feature is not enabled.
+fn load_pathf_map() -> std::collections::HashMap<String, String> {
+ if !cfg!(feature = "pathf") {
+ return std::collections::HashMap::new();
+ }
+ let out_dir = std::env::var("OUT_DIR").ok();
+ let crate_name = std::env::var("CARGO_PKG_NAME").ok();
+ match (out_dir, crate_name) {
+ (Some(dir), Some(name)) => {
+ let path = std::path::Path::new(&dir).join(&name).join("type_using.rs");
+ match std::fs::read_to_string(&path) {
+ Ok(content) => content
+ .lines()
+ .filter_map(|line| {
+ let line = line.trim();
+ if let Some(rest) = line.strip_prefix("use ") {
+ let path = rest.strip_suffix(';').unwrap_or(rest);
+ if let Some((_mod, type_name)) = path.rsplit_once("::") {
+ return Some((type_name.to_string(), path.to_string()));
+ }
+ }
+ None
+ })
+ .collect(),
+ Err(_) => std::collections::HashMap::new(),
+ }
+ }
+ _ => std::collections::HashMap::new(),
+ }
+}
+
+/// Resolves a type name to its full path token stream using the pathf mapping.
+pub(crate) fn resolve_type(
+ name: &str,
+ map: &std::collections::HashMap<String, String>,
+) -> proc_macro2::TokenStream {
+ if let Some(full_path) = map.get(name) {
+ syn::parse_str::<proc_macro2::TokenStream>(full_path).unwrap_or_else(|_| {
+ let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
+ quote! { #ident }
+ })
+ } else {
+ let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
+ quote! { #ident }
+ }
+}
+
+pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream {
+ #[cfg(feature = "comp")]
+ let comp_gen = quote! {
+ ::mingling::macros::program_comp_gen!();
+ };
+
+ #[cfg(not(feature = "comp"))]
+ let comp_gen = quote! {};
+
+ TokenStream::from(quote! {
+ /// Alias for the current program type `crate::ThisProgram`
+ pub type Next = ::mingling::ChainProcess<crate::ThisProgram>;
+
+ impl ::mingling::Routable<crate::ThisProgram> for ::mingling::ChainProcess<crate::ThisProgram>
+ {
+ fn to_chain(self) -> ::mingling::ChainProcess<crate::ThisProgram> {
+ match self {
+ ::mingling::ChainProcess::Ok((any, _)) => {
+ ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain))
+ }
+ other => other,
+ }
+ }
+
+ fn to_render(self) -> ::mingling::ChainProcess<crate::ThisProgram> {
+ match self {
+ ::mingling::ChainProcess::Ok((any, _)) => {
+ ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer))
+ }
+ other => other,
+ }
+ }
+ }
+
+ #comp_gen
+ ::mingling::macros::program_fallback_gen!();
+ ::mingling::macros::program_final_gen!();
+ })
+}
+
+#[cfg(feature = "comp")]
+pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
+ #[cfg(feature = "async")]
+ let fn_exec_comp = quote! {
+ #[doc(hidden)]
+ #[::mingling::macros::chain]
+ pub async fn __exec_completion(prev: CompletionContext) -> Next {
+ use ::mingling::Grouped;
+
+ let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
+ match read_ctx {
+ Ok(ctx) => {
+ let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
+ crate::CompletionSuggest::new((ctx, suggest)).to_render()
+ }
+ Err(_) => std::process::exit(1),
+ }
+ }
+ };
+
+ #[cfg(not(feature = "async"))]
+ let fn_exec_comp = quote! {
+ #[doc(hidden)]
+ #[::mingling::macros::chain]
+ pub fn __exec_completion(prev: CompletionContext) -> Next {
+ use ::mingling::Grouped;
+
+ let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
+ match read_ctx {
+ Ok(ctx) => {
+ let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
+ crate::CompletionSuggest::new((ctx, suggest)).to_render()
+ }
+ Err(_) => std::process::exit(1),
+ }
+ }
+ };
+
+ #[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 {
+ use ::mingling::Grouped;
+ ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext);
+ ::mingling::macros::pack!(
+ CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest)
+ );
+ }
+ #internal_dispatcher_comp
+ use __internal_completion_mod::CompletionContext;
+ use __internal_completion_mod::CompletionSuggest;
+ pub use __internal_completion_mod::CMDCompletion;
+
+ #fn_exec_comp
+
+ ::mingling::macros::register_type!(CompletionContext);
+
+ #[allow(unused)]
+ #[doc(hidden)]
+ #[::mingling::macros::renderer]
+ pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult {
+ let result = ::mingling::RenderResult::default();
+ let (ctx, suggest) = prev.inner;
+ ::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(ctx, suggest);
+ result
+ }
+ };
+
+ TokenStream::from(comp_dispatcher)
+}
+
+pub(crate) fn register_type_impl(input: TokenStream) -> TokenStream {
+ let type_ident = parse_macro_input!(input as syn::Ident);
+ let entry_str = type_ident.to_string();
+
+ get_global_set(&PACKED_TYPES)
+ .lock()
+ .unwrap()
+ .insert(entry_str);
+
+ TokenStream::new()
+}
+
+pub(crate) fn register_chain_impl(input: TokenStream) -> TokenStream {
+ chain::register_chain(input)
+}
+
+pub(crate) fn register_renderer_impl(input: TokenStream) -> TokenStream {
+ renderer::register_renderer(input)
+}
+
+pub(crate) fn program_fallback_gen_impl(_input: TokenStream) -> TokenStream {
+ #[cfg(feature = "structural_renderer")]
+ let pack_empty = quote! {
+ #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Grouped, Default)]
+ pub struct ResultEmpty;
+ };
+
+ #[cfg(not(feature = "structural_renderer"))]
+ let pack_empty = quote! {
+ #[derive(::mingling::Grouped, Default)]
+ pub struct ResultEmpty;
+ };
+
+ let expanded = quote! {
+ ::mingling::macros::pack!(ErrorRendererNotFound = String);
+ ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>);
+ #pack_empty
+ };
+ TokenStream::from(expanded)
+}
+
+#[allow(clippy::too_many_lines)]
+pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream {
+ let name = syn::Ident::new("ThisProgram", proc_macro2::Span::call_site());
+
+ let packed_types = get_global_set(&PACKED_TYPES).lock().unwrap().clone();
+
+ let renderers = get_global_set(&RENDERERS).lock().unwrap().clone();
+ let chains = get_global_set(&CHAINS).lock().unwrap().clone();
+ let renderer_exist = get_global_set(&RENDERERS_EXIST).lock().unwrap().clone();
+ let chain_exist = get_global_set(&CHAINS_EXIST).lock().unwrap().clone();
+
+ #[cfg(feature = "structural_renderer")]
+ let structural_renderers = get_global_set(&STRUCTURAL_RENDERERS)
+ .lock()
+ .unwrap()
+ .clone();
+
+ #[cfg(feature = "comp")]
+ let completions = get_global_set(&COMPLETIONS).lock().unwrap().clone();
+
+ let packed_types: Vec<proc_macro2::TokenStream> = packed_types
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let chain_tokens: Vec<proc_macro2::TokenStream> = chains
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let renderer_exist_tokens: Vec<proc_macro2::TokenStream> = renderer_exist
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let chain_exist_tokens: Vec<proc_macro2::TokenStream> = chain_exist
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let pathf_map: std::collections::HashMap<String, String> = if cfg!(feature = "pathf") {
+ load_pathf_map()
+ } else {
+ std::collections::HashMap::new()
+ };
+
+ let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") {
+ pathf_map
+ .values()
+ .map(|path| format!("use {};", path).parse().unwrap_or_default())
+ .collect()
+ } else {
+ Vec::new()
+ };
+
+ #[cfg(feature = "structural_renderer")]
+ let structural_renderer_tokens: Vec<proc_macro2::TokenStream> = structural_renderers
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ #[cfg(feature = "structural_renderer")]
+ let structural_render = quote! {
+ fn structural_render(
+ any: ::mingling::AnyOutput<Self::Enum>,
+ setting: &::mingling::StructuralRendererSetting,
+ ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> {
+ #[allow(unused_imports)]
+ #(#pathf_uses)*
+ match any.member_id {
+ #(#structural_renderer_tokens)*
+ _ => {
+ // Non-structural types: render ResultEmpty (which implements
+ // StructuralData + Serialize) instead of producing nothing.
+ let mut r = ::mingling::RenderResult::default();
+ ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?;
+ Ok(r)
+ }
+ }
+ }
+ };
+
+ #[cfg(not(feature = "structural_renderer"))]
+ let structural_render = quote! {};
+
+ #[cfg(feature = "dispatch_tree")]
+ let compile_time_dispatchers: Vec<String> = get_global_set(&COMPILE_TIME_DISPATCHERS)
+ .lock()
+ .unwrap()
+ .clone()
+ .iter()
+ .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 get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries, &pathf_map);
+ let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries, &pathf_map);
+
+ quote! {
+ #get_nodes_fn
+ #dispatch_trie_fn
+ }
+ };
+
+ #[cfg(not(feature = "dispatch_tree"))]
+ let dispatch_tree_nodes = quote! {};
+
+ #[cfg(feature = "comp")]
+ let completion_tokens: Vec<proc_macro2::TokenStream> = completions
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ #[cfg(feature = "comp")]
+ let comp = quote! {
+ fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest {
+ #[allow(unused_imports)]
+ #(#pathf_uses)*
+ match any.member_id {
+ #(#completion_tokens)*
+ _ => ::mingling::Suggest::FileCompletion,
+ }
+ }
+ };
+
+ #[cfg(not(feature = "comp"))]
+ let comp = quote! {};
+
+ // Build render function arms from stored entries
+ let render_fn =
+ if renderer_tokens.is_empty() {
+ quote! {
+ fn render(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
+ ::mingling::RenderResult::default()
+ }
+ }
+ } else {
+ let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| {
+ let (struct_ident, variant_ident) = parse_entry_pair(entry);
+ let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map);
+ let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map);
+ quote! {
+ Self::#variant_ident => {
+ // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`,
+ // so downcasting to `#variant_ident` is safe.
+ let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() };
+ <#resolved_struct as ::mingling::Renderer>::render(value)
+ }
+ }
+ }).collect();
+ quote! {
+ fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
+ match any.member_id {
+ #(#render_arms)*
+ _ => ::mingling::RenderResult::default(),
+ }
+ }
+ }
+ };
+
+ // Build do_chain function (async and sync versions)
+ let chain_arms_async: Vec<_> = chain_tokens.iter().map(|entry| {
+ let (struct_ident, variant_ident) = parse_entry_pair(entry);
+ let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map);
+ let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map);
+ quote! {
+ Self::#variant_ident => {
+ // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`,
+ // so downcasting to `#variant_ident` is safe.
+ let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() };
+ let fut = async { <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value).await };
+ ::std::boxed::Box::pin(fut)
+ }
+ }
+ }).collect();
+
+ let chain_arms_sync: Vec<_> = chain_tokens
+ .iter()
+ .map(|entry| {
+ let (struct_ident, variant_ident) = parse_entry_pair(entry);
+ let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map);
+ let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map);
+ quote! {
+ Self::#variant_ident => {
+ // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`,
+ // so downcasting to `#variant_ident` is safe.
+ let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() };
+ <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value)
+ }
+ }
+ })
+ .collect();
+
+ let do_chain_fn = if chain_tokens.is_empty() {
+ quote! {
+ fn do_chain(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::ChainProcess<Self::Enum> {
+ ::core::panic!("No chain found for type id")
+ }
+ }
+ } else if ASYNC_ENABLED {
+ quote! {
+ fn do_chain(
+ any: ::mingling::AnyOutput<Self::Enum>,
+ ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::std::marker::Send>> {
+ match any.member_id {
+ #(#chain_arms_async)*
+ _ => ::core::panic!("No chain found for type id: {:?}", any.type_id),
+ }
+ }
+ }
+ } else {
+ quote! {
+ fn do_chain(
+ any: ::mingling::AnyOutput<Self::Enum>,
+ ) -> ::mingling::ChainProcess<Self::Enum> {
+ match any.member_id {
+ #(#chain_arms_sync)*
+ _ => ::core::panic!("No chain found for type id: {:?}", any.type_id),
+ }
+ }
+ }
+ };
+
+ let help_tokens: Vec<proc_macro2::TokenStream> = get_global_set(&HELP_REQUESTS)
+ .lock()
+ .unwrap()
+ .clone()
+ .iter()
+ .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
+ .collect();
+
+ let num_variants = packed_types.len();
+ let repr_type = if u8::try_from(num_variants).is_ok() {
+ quote! { u8 }
+ } else if u16::try_from(num_variants).is_ok() {
+ quote! { u16 }
+ } else if u32::try_from(num_variants).is_ok() {
+ quote! { u32 }
+ } else {
+ quote! { u128 }
+ };
+
+ let expanded = quote! {
+ #[derive(Debug, PartialEq, Eq, Clone)]
+ #[repr(#repr_type)]
+ #[allow(nonstandard_style)]
+ pub enum #name {
+ #(#packed_types),*
+ }
+
+ impl ::std::fmt::Display for #name {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ match self {
+ #(#name::#packed_types => write!(f, stringify!(#packed_types)),)*
+ }
+ }
+ }
+
+ impl ::mingling::ProgramCollect for #name {
+ type Enum = #name;
+ type ErrorDispatcherNotFound = ErrorDispatcherNotFound;
+ type ErrorRendererNotFound = ErrorRendererNotFound;
+ type ResultEmpty = ResultEmpty;
+ fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput<Self::Enum> {
+ ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string()))
+ }
+ fn build_dispatcher_not_found(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> {
+ ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args))
+ }
+ fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> {
+ ::mingling::AnyOutput::new(ResultEmpty)
+ }
+ #render_fn
+ #do_chain_fn
+ fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
+ #[allow(unused_imports)]
+ #(#pathf_uses)*
+ match any.member_id {
+ #(#help_tokens)*
+ _ => ::mingling::RenderResult::default(),
+ }
+ }
+ fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool {
+ match any.member_id {
+ #(#renderer_exist_tokens)*
+ _ => false
+ }
+ }
+ fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool {
+ match any.member_id {
+ #(#chain_exist_tokens)*
+ _ => false
+ }
+ }
+ #dispatch_tree_nodes
+ #structural_render
+ #comp
+ }
+
+ impl #name {
+ /// Creates a new `Program<#name>` instance with default configuration.
+ pub fn new() -> ::mingling::Program<#name> {
+ ::mingling::Program::new()
+ }
+
+ /// Returns a static reference to the global `Program<#name>` singleton.
+ pub fn this() -> &'static ::mingling::Program<#name> {
+ &::mingling::this::<#name>()
+ }
+ }
+ };
+
+ // Clear all global registries to prevent stale state in Rust Analyzer
+ get_global_set(&PACKED_TYPES).lock().unwrap().clear();
+ get_global_set(&CHAINS).lock().unwrap().clear();
+ get_global_set(&CHAINS_EXIST).lock().unwrap().clear();
+ get_global_set(&RENDERERS).lock().unwrap().clear();
+ get_global_set(&RENDERERS_EXIST).lock().unwrap().clear();
+ get_global_set(&HELP_REQUESTS).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()
+ .clear();
+ #[cfg(feature = "structural_renderer")]
+ get_global_set(&STRUCTURAL_RENDERERS)
+ .lock()
+ .unwrap()
+ .clear();
+
+ TokenStream::from(expanded)
+}
diff --git a/mingling_macros/src/group_impl.rs b/mingling_macros/src/func/group.rs
index 59da9dd..b865913 100644
--- a/mingling_macros/src/group_impl.rs
+++ b/mingling_macros/src/func/group.rs
@@ -91,7 +91,7 @@ fn gen_type_use(type_path: &TypePath) -> proc_macro2::TokenStream {
}
}
-pub fn group_macro(input: TokenStream) -> TokenStream {
+pub(crate) fn group_macro(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as GroupInput);
let is_aliased = matches!(input, GroupInput::Aliased { .. });
@@ -124,7 +124,7 @@ pub fn group_macro(input: TokenStream) -> TokenStream {
quote! {}
};
- // Generate the module with the Groupped implementation
+ // Generate the module with the Grouped implementation
let expanded = quote! {
#alias_stmt
#[allow(non_camel_case_types)]
@@ -133,7 +133,7 @@ pub fn group_macro(input: TokenStream) -> TokenStream {
#type_use
#alias_use
- impl ::mingling::Groupped<__MinglingProgram> for #type_name {
+ impl ::mingling::Grouped<__MinglingProgram> for #type_name {
fn member_id() -> __MinglingProgram {
__MinglingProgram::#type_name
}
diff --git a/mingling_macros/src/node.rs b/mingling_macros/src/func/node.rs
index b3a61c6..1b944a1 100644
--- a/mingling_macros/src/node.rs
+++ b/mingling_macros/src/func/node.rs
@@ -18,7 +18,7 @@ impl Parse for NodeInput {
}
}
-pub fn node(input: TokenStream) -> TokenStream {
+pub(crate) fn node(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let input_parsed = syn::parse_macro_input!(input as NodeInput);
let path_str = input_parsed.path.value();
diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/func/pack.rs
index 7f05232..a1a7e6b 100644
--- a/mingling_macros/src/pack.rs
+++ b/mingling_macros/src/func/pack.rs
@@ -25,7 +25,7 @@ impl Parse for PackInput {
}
#[allow(clippy::too_many_lines)]
-pub fn pack(input: TokenStream) -> TokenStream {
+pub(crate) fn pack(input: TokenStream) -> TokenStream {
let pack_input = syn::parse_macro_input!(input as PackInput);
let group_name = crate::default_program_path();
@@ -138,7 +138,7 @@ pub fn pack(input: TokenStream) -> TokenStream {
}
}
- impl ::mingling::Groupped<#group_name> for #type_name {
+ impl ::mingling::Grouped<#group_name> for #type_name {
fn member_id() -> #group_name {
#group_name::#type_name
}
diff --git a/mingling_macros/src/pack_err.rs b/mingling_macros/src/func/pack_err.rs
index ba7cf17..36e550a 100644
--- a/mingling_macros/src/pack_err.rs
+++ b/mingling_macros/src/func/pack_err.rs
@@ -31,7 +31,7 @@ impl syn::parse::Parse for PackErrInput {
}
#[allow(clippy::too_many_lines)]
-pub fn pack_err(input: TokenStream) -> TokenStream {
+pub(crate) fn pack_err(input: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(input as PackErrInput);
match parsed {
@@ -42,7 +42,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream {
// Note: No longer derives Serialize under structural_renderer.
// Use pack_err_structural for structured output support.
let derive = quote! {
- #[derive(::mingling::Groupped)]
+ #[derive(::mingling::Grouped)]
};
let expanded = quote! {
@@ -75,7 +75,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream {
// Note: No longer derives Serialize under structural_renderer.
// Use pack_err_structural for structured output support.
let derive = quote! {
- #[derive(::mingling::Groupped)]
+ #[derive(::mingling::Grouped)]
};
let expanded = quote! {
@@ -123,7 +123,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream {
/// impl ::mingling::__private::StructuralData for ErrorNotFound {}
/// ```
#[cfg(feature = "structural_renderer")]
-pub fn pack_err_structural(input: TokenStream) -> TokenStream {
+pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(input as PackErrInput);
let type_name = match &parsed {
@@ -150,7 +150,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
let snake_name = snake_case!(&name_str);
let expanded = quote! {
- #[derive(::mingling::Groupped, ::serde::Serialize)]
+ #[derive(::mingling::Grouped, ::serde::Serialize)]
pub struct #type_name {
/// The snake_case name of this error, automatically set at compile time.
pub name: String,
@@ -179,7 +179,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
let snake_name = snake_case!(&name_str);
let expanded = quote! {
- #[derive(::mingling::Groupped, ::serde::Serialize)]
+ #[derive(::mingling::Grouped, ::serde::Serialize)]
pub struct #type_name {
/// The snake_case name of this error, automatically set at compile time.
pub name: String,
diff --git a/mingling_macros/src/suggest.rs b/mingling_macros/src/func/suggest.rs
index d3ab446..0f2026f 100644
--- a/mingling_macros/src/suggest.rs
+++ b/mingling_macros/src/func/suggest.rs
@@ -35,7 +35,7 @@ impl Parse for SuggestItem {
}
#[cfg(feature = "comp")]
-pub fn suggest(input: TokenStream) -> TokenStream {
+pub(crate) fn suggest(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as SuggestInput);
let mut items = Vec::new();
@@ -71,7 +71,7 @@ pub fn suggest(input: TokenStream) -> TokenStream {
expanded.into()
}
-pub fn suggest_enum(input: TokenStream) -> TokenStream {
+pub(crate) fn suggest_enum(input: TokenStream) -> TokenStream {
let enum_type = parse_macro_input!(input as syn::Type);
let expanded = quote! {{
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 9419f39..a85648f 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -13,7 +13,7 @@
//! ┌──────────────────────────────────────────────────────────────────┐
//! │ Phase 1: Declaration │
//! │ │
-//! │ dispatcher! pack! node! #[derive(Groupped)] │
+//! │ dispatcher! pack! node! #[derive(Grouped)] │
//! │ │ │ │ │ │
//! │ V V V V │
//! │ Declares Wraps a Builds Makes a type │
@@ -55,7 +55,7 @@
//! | `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(Groupped)]`](derive@Groupped) | Makes a type recognizable by the framework's type registry |
+//! | [`#[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) |
//!
@@ -141,40 +141,48 @@
//! }
//! ```
-use proc_macro::TokenStream;
+#[cfg(feature = "extra_macros")]
use quote::quote;
+
+#[cfg(feature = "extra_macros")]
+use syn::parse_macro_input;
+
+use proc_macro::TokenStream;
use std::collections::BTreeSet;
use std::sync::Mutex;
use std::sync::OnceLock;
-use syn::parse_macro_input;
-mod chain;
+mod attr;
+mod derive;
+mod func;
+mod systems;
+
+mod extensions;
+mod utils;
+
+// Bring all sub-modules into scope at the old paths so that existing
+// references (e.g. `chain::chain_attr`, `renderer::renderer_attr`)
+// continue to work without any `use`-path changes.
#[cfg(feature = "comp")]
-mod completion;
-#[cfg(feature = "dispatch_tree")]
-mod dispatch_tree_gen;
-mod dispatcher;
+use attr::completion;
#[cfg(feature = "clap")]
-mod dispatcher_clap;
+use attr::dispatcher_clap;
#[cfg(feature = "extra_macros")]
-mod entry;
-mod enum_tag;
+use attr::program_setup;
+use attr::{chain, help, renderer};
+use derive::{enum_tag, grouped};
#[cfg(feature = "extra_macros")]
-mod group_impl;
-mod groupped;
-mod help;
-mod node;
-mod pack;
+use func::entry;
#[cfg(feature = "extra_macros")]
-mod pack_err;
+pub(crate) use func::group as group_impl;
#[cfg(feature = "extra_macros")]
-mod program_setup;
-mod renderer;
-mod res_injection;
-#[cfg(feature = "structural_renderer")]
-mod structural_data;
+use func::pack_err;
#[cfg(feature = "comp")]
-mod suggest;
+use func::suggest;
+use func::{dispatcher, node, pack};
+use systems::res_injection;
+#[cfg(feature = "structural_renderer")]
+pub(crate) use systems::structural_data;
pub(crate) fn default_program_path() -> proc_macro2::TokenStream {
quote::quote! { crate::ThisProgram }
@@ -262,44 +270,6 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool {
entry.contains(&format!(":: {variant_name} =>"))
}
-/// Registers an outside-type as a member of a program group without modifying its definition.
-///
-/// This macro allows you to use outside-types from external crates (like `std::io::Error`)
-/// within the Mingling framework by generating a `Groupped` implementation and registering
-/// the type's simple name as an enum variant.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// group!(std::io::Error);
-/// group!(ParseIntError);
-/// ```
-///
-/// The type is registered under the default program (`crate::ThisProgram`).
-///
-/// # How it works
-///
-/// The macro generates a module containing:
-/// - A `use` import for the program path and the outside-type
-/// - An `impl Groupped<Program>` for the outside-type
-/// - A `register_type!` call with the type's simple name
-///
-/// The type's simple name (e.g. `Error`) is used as the enum variant in the generated
-/// program enum, just like `#[derive(Groupped)]` or `pack!`.
-///
-/// # Example
-///
-/// ```rust,ignore
-/// use mingling::macros::group;
-///
-/// // Register std::io::Error as a group member
-/// group!(std::io::Error);
-/// ```
-///
-/// After expansion, the type can be used in chains and renderers like any
-/// `#[derive(Groupped)]` type.
-///
-/// This macro is only available with the `extra_macros` feature.
#[cfg(feature = "extra_macros")]
#[proc_macro]
pub fn group(input: TokenStream) -> TokenStream {
@@ -402,7 +372,7 @@ pub fn node(input: TokenStream) -> TokenStream {
/// - `AsRef<String>`, `AsMut<String>`
/// - `Default` if `String: Default`
/// - `Into<AnyOutput<ThisProgram>>`, `Into<ChainProcess<ThisProgram>>`
-/// - Implements `Groupped<ThisProgram>` with `member_id()` returning the enum variant
+/// - Implements `Grouped<ThisProgram>` with `member_id()` returning the enum variant
///
/// The struct is also registered via `register_type!` so that `gen_program!`
/// can include it in the program enum.
@@ -438,7 +408,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// Creates an error struct with a `name: String` field and optional `info: Type` field.
///
-/// This macro provides a concise way to define error types that implement `Groupped`
+/// This macro provides a concise way to define error types that implement `Grouped`
/// and are registered for inclusion in the program enum.
///
/// The `name` field is automatically set to the snake_case version of the struct name
@@ -461,7 +431,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// For `pack_err!(ErrorNotFound)`:
///
/// ```rust,ignore
-/// #[derive(::mingling::Groupped)]
+/// #[derive(::mingling::Grouped)]
/// pub struct ErrorNotFound {
/// name: String,
/// }
@@ -478,7 +448,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// For `pack_err!(ErrorNotDir = PathBuf)`:
///
/// ```rust,ignore
-/// #[derive(::mingling::Groupped)]
+/// #[derive(::mingling::Grouped)]
/// pub struct ErrorNotDir {
/// name: String,
/// info: PathBuf,
@@ -521,20 +491,25 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
pack_err::pack_err_structural(input)
}
-/// Early-returns an error from a `Result`, converting the `Ok` branch to a
-/// `ChainProcess`.
+/// Early-returns the error from a `Result`, converting the `Ok` branch to the
+/// next chain process value.
///
/// This macro is equivalent to:
/// ```rust,ignore
/// match expr {
/// Ok(r) => r,
-/// Err(e) => return ::mingling::Groupped::to_chain(e),
+/// Err(e) => return ::mingling::Routable::to_chain(e),
/// }
/// ```
///
-/// It is useful inside chain functions where you have a `Result<SomeType, SomeType>`
-/// where both types implement `Groupped` and want to propagate the error case
-/// as an early return via `Groupped::to_chain()`.
+/// It is useful inside chain functions where you have a `Result<SuccessType, ErrorType>`
+/// where both types implement `Routable` and you want to propagate the error case
+/// as an early return via `Routable::to_chain()`.
+///
+/// The key difference from a simple `?` operator is that `route!` converts **both**
+/// the success and error types into the chain process — the `Ok` value is unwrapped
+/// directly, while the `Err` value is converted via `Routable::to_chain()` and
+/// returned early.
///
/// # Example
///
@@ -542,7 +517,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
/// use mingling::macros::{chain, route};
///
/// #[chain]
-/// fn process(prev: SomeEntry) -> ChainProcess<ThisProgram> {
+/// fn process(prev: SomeEntry) -> Next {
/// let value = route!(current_dir().map_err(|e| ErrorEntry::new(e.to_string_lossy().to_string())));
/// // value is the PathBuf from current_dir()
/// value.to_chain()
@@ -555,7 +530,7 @@ pub fn route(input: TokenStream) -> TokenStream {
let expanded = quote! {
match #expr {
Ok(r) => r,
- Err(e) => return ::mingling::Groupped::to_chain(e),
+ Err(e) => return ::mingling::Routable::to_chain(e),
}
};
TokenStream::from(expanded)
@@ -613,7 +588,7 @@ pub fn route(input: TokenStream) -> TokenStream {
#[proc_macro]
pub fn empty_result(_input: TokenStream) -> TokenStream {
let expanded = quote! {
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
+ <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
};
TokenStream::from(expanded)
}
@@ -872,6 +847,11 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
/// - With the `async` feature, async functions are supported; without it, async functions are rejected.
#[proc_macro_attribute]
pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
+ // Extension point: if attr contains extension identifiers like `routeify`,
+ // re-dispatch as #[ext1] #[ext2] #[chain] fn ...
+ if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "chain") {
+ return redispatch;
+ }
chain::chain_attr(attr, item)
}
@@ -939,6 +919,9 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream {
+ if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "renderer") {
+ return redispatch;
+ }
renderer::renderer_attr(attr, item)
}
@@ -993,6 +976,9 @@ pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream {
#[cfg(feature = "comp")]
#[proc_macro_attribute]
pub fn completion(attr: TokenStream, item: TokenStream) -> TokenStream {
+ if let Some(redispatch) = extensions::try_redispatch_completion(attr.clone(), &item) {
+ return redispatch;
+ }
completion::completion_attr(attr, item)
}
@@ -1246,14 +1232,38 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream {
///
/// [`BasicProgramSetup`]: https://docs.rs/mingling/latest/mingling/setup/struct.BasicProgramSetup.html
#[proc_macro_attribute]
-pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
+pub fn help(attr: TokenStream, item: TokenStream) -> TokenStream {
+ if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "help") {
+ return redispatch;
+ }
help::help_attr(item)
}
-/// Derive macro for automatically implementing the `Groupped` trait on a struct.
+/// Extension attribute macro that transforms `expr?` into `route!(expr)`.
+///
+/// Designed for use with `#[chain(routeify, ...)]` to enable concise error
+/// routing in chain functions using the `?` operator syntax.
///
-/// The `#[derive(Groupped)]` macro:
-/// 1. Implements `Groupped<crate::ThisProgram>`.
+/// # Example
+///
+/// ```rust,ignore
+/// #[chain(routeify)]
+/// fn handle_calc(args: EntryCalculate) -> Next {
+/// let a = args.pick(&arg![f32]).to_result()?;
+/// let op = args.pick(&arg![Operator]).to_result()?;
+/// StateCalculate { number_a: a, operator: op, ... }.to_chain()
+/// }
+/// ```
+#[cfg(feature = "extra_macros")]
+#[proc_macro_attribute]
+pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream {
+ extensions::routeify::routeify_impl(attr, item)
+}
+
+/// Derive macro for automatically implementing the `Grouped` trait on a struct.
+///
+/// The `#[derive(Grouped)]` macro:
+/// 1. Implements `Grouped<crate::ThisProgram>`.
/// 2. Registers the type via `register_type!` so it's included in the program enum.
/// 3. Generates `Into<AnyOutput<Group>>` and `Into<ChainProcess<Group>>` conversions.
/// 4. Adds `to_chain()` and `to_render()` methods to the struct.
@@ -1261,7 +1271,7 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
/// # Syntax
///
/// ```rust,ignore
-/// #[derive(Groupped)]
+/// #[derive(Grouped)]
/// struct MyStruct {
/// // ...
/// }
@@ -1270,9 +1280,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::macros::Groupped;
+/// use mingling::macros::Grouped;
///
-/// #[derive(Groupped)]
+/// #[derive(Grouped)]
/// struct Greeting {
/// name: String,
/// }
@@ -1280,9 +1290,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
///
/// This is equivalent to using `pack!` but works with custom structs that
/// have named fields. For simple wrappers, prefer `pack!`.
-#[proc_macro_derive(Groupped, attributes(group))]
-pub fn derive_groupped(input: TokenStream) -> TokenStream {
- groupped::derive_groupped(input)
+#[proc_macro_derive(Grouped, attributes(group))]
+pub fn derive_grouped(input: TokenStream) -> TokenStream {
+ grouped::derive_grouped(input)
}
/// Derive macro for automatically implementing the `EnumTag` trait on an enum
@@ -1351,18 +1361,18 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream {
structural_data::derive_structural_data(input)
}
-/// Derive macro for implementing both `Groupped` and `serde::Serialize` on a struct.
+/// Derive macro for implementing both `Grouped` and `serde::Serialize` on a struct.
///
/// **This macro is only available with the `structural_renderer` feature.**
///
-/// This is identical to `#[derive(Groupped)]` but also adds `#[derive(serde::Serialize)]`
+/// This is identical to `#[derive(Grouped)]` but also adds `#[derive(serde::Serialize)]`
/// to the struct, which is required for the structural renderer to serialize output
/// to formats like JSON, YAML, TOML, or RON.
///
/// # Syntax
///
/// ```rust,ignore
-/// #[derive(GrouppedSerialize)]
+/// #[derive(GroupedSerialize)]
/// struct Info {
/// name: String,
/// age: i32,
@@ -1372,19 +1382,19 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::GrouppedSerialize;
+/// use mingling::GroupedSerialize;
/// use serde::Serialize;
///
-/// #[derive(GrouppedSerialize)]
+/// #[derive(GroupedSerialize)]
/// struct Info {
/// name: String,
/// age: i32,
/// }
/// ```
#[cfg(feature = "structural_renderer")]
-#[proc_macro_derive(GrouppedSerialize, attributes(group))]
-pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
- groupped::derive_groupped_serialize(input)
+#[proc_macro_derive(GroupedSerialize, attributes(group))]
+pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream {
+ grouped::derive_grouped_serialize(input)
}
/// Generates the program enum and all collected types, chains, and renderers.
@@ -1436,119 +1446,20 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
/// gen_program!();
/// ```
#[proc_macro]
-pub fn gen_program(_input: TokenStream) -> TokenStream {
- #[cfg(feature = "comp")]
- let comp_gen = quote! {
- ::mingling::macros::program_comp_gen!();
- };
-
- #[cfg(not(feature = "comp"))]
- let comp_gen = quote! {};
-
- TokenStream::from(quote! {
- pub type Next = ::mingling::ChainProcess<crate::ThisProgram>;
-
- #comp_gen
- ::mingling::macros::program_fallback_gen!();
- ::mingling::macros::program_final_gen!();
- })
+pub fn gen_program(input: TokenStream) -> TokenStream {
+ func::gen_program::gen_program_impl(input)
}
-/// Internal macro used by `gen_program!` to generate completion infrastructure.
-///
-/// **This macro is only available with the `comp` feature.**
-///
-/// This is an internal macro and should not be called directly by user code.
-/// It generates a completion dispatcher, the `CompletionContext` type, and
-/// the execution/render logic for shell completion.
-///
-/// The generated module `__completion_gen` contains:
-/// - A `__comp` dispatcher that routes completion requests
-/// - A `__exec_completion` chain that processes `CompletionContext` into `CompletionSuggest`
-/// - A `__render_completion` renderer that outputs completion suggestions
-#[proc_macro]
#[cfg(feature = "comp")]
-pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
- #[cfg(feature = "async")]
- let fn_exec_comp = quote! {
- #[doc(hidden)]
- #[::mingling::macros::chain]
- pub async fn __exec_completion(prev: CompletionContext) -> Next {
- use ::mingling::Groupped;
-
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
- match read_ctx {
- Ok(ctx) => {
- let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- crate::CompletionSuggest::new((ctx, suggest)).to_render()
- }
- Err(_) => std::process::exit(1),
- }
- }
- };
-
- #[cfg(not(feature = "async"))]
- let fn_exec_comp = quote! {
- #[doc(hidden)]
- #[::mingling::macros::chain]
- pub fn __exec_completion(prev: CompletionContext) -> Next {
- use ::mingling::Groupped;
-
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
- match read_ctx {
- Ok(ctx) => {
- let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- crate::CompletionSuggest::new((ctx, suggest)).to_render()
- }
- Err(_) => std::process::exit(1),
- }
- }
- };
-
- #[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 {
- use ::mingling::Groupped;
- ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext);
- ::mingling::macros::pack!(
- CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest)
- );
- }
- #internal_dispatcher_comp
- use __internal_completion_mod::CompletionContext;
- use __internal_completion_mod::CompletionSuggest;
- pub use __internal_completion_mod::CMDCompletion;
-
- #fn_exec_comp
-
- ::mingling::macros::register_type!(CompletionContext);
-
- #[allow(unused)]
- #[doc(hidden)]
- #[::mingling::macros::renderer]
- pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult {
- let result = ::mingling::RenderResult::default();
- let (ctx, suggest) = prev.inner;
- ::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(ctx, suggest);
- result
- }
- };
-
- TokenStream::from(comp_dispatcher)
+#[proc_macro]
+pub fn program_comp_gen(input: TokenStream) -> TokenStream {
+ func::gen_program::program_comp_gen_impl(input)
}
/// Registers a type into the global packed types registry for inclusion in
/// the program enum generated by `gen_program!`.
///
-/// This macro is called internally by `pack!` and `#[derive(Groupped)]`(`macro.derive_groupped.html`)
+/// This macro is called internally by `pack!` and `#[derive(Grouped)]`(`macro.derive_grouped.html`)
/// and is generally not needed in user code. However, it can be used for manual
/// registration if you are implementing custom type registration outside of
/// the standard macros.
@@ -1567,115 +1478,29 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
/// Panics if the global `PACKED_TYPES` mutex is poisoned.
#[proc_macro]
pub fn register_type(input: TokenStream) -> TokenStream {
- let type_ident = parse_macro_input!(input as syn::Ident);
- let entry_str = type_ident.to_string();
-
- get_global_set(&PACKED_TYPES)
- .lock()
- .unwrap()
- .insert(entry_str);
-
- TokenStream::new()
+ func::gen_program::register_type_impl(input)
}
-/// Registers a chain mapping from a previous type to a chain struct.
-///
-/// This macro is called internally by `#[chain]`(macro.chain.html) and is
-/// generally not needed in user code. It inserts entries into the global
-/// `CHAINS` and `CHAINS_EXIST` registries.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// register_chain!(PreviousType, ChainStruct);
-/// ```
-///
-/// The `PreviousType` is the input type of the chain step, and `ChainStruct`
-/// is the generated struct that implements the `Chain` trait.
#[proc_macro]
pub fn register_chain(input: TokenStream) -> TokenStream {
- chain::register_chain(input)
+ func::gen_program::register_chain_impl(input)
}
-/// Registers a renderer mapping from a type to a renderer struct.
-///
-/// This macro is called internally by `#[renderer]`(macro.renderer.html) and is
-/// generally not needed in user code. It inserts entries into the global
-/// `RENDERERS`, `RENDERERS_EXIST` and (with `structural_renderer` feature)
-/// `STRUCTURAL_RENDERERS` registries.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// register_renderer!(PreviousType, RendererStruct);
-/// ```
-///
-/// The `PreviousType` is the input type of the renderer, and `RendererStruct`
-/// is the generated struct that implements the `Renderer` trait.
#[proc_macro]
pub fn register_renderer(input: TokenStream) -> TokenStream {
- renderer::register_renderer(input)
+ func::gen_program::register_renderer_impl(input)
}
-/// Internal macro used by `gen_program!` to generate fallback types.
-///
-/// This macro generates the fallback wrapper types that are essential
-/// for error handling in the Mingling pipeline:
-///
-/// - **`ErrorRendererNotFound`** — Wraps a `String` (the name of the missing renderer).
-/// Used when no matching renderer is found for a given output type.
-/// - **`ErrorDispatcherNotFound`** — Wraps `Vec<String>` (the unrecognized command args).
-/// Used when no matching dispatcher is found for user input.
-/// - **`ResultEmpty`** — Wraps `()` (the unit type).
-/// Used when the chain returns an empty result.
-///
-/// Users can (and should) write `#[renderer]` functions for these types
-/// to provide meaningful error messages.
-///
-/// This macro is called automatically by `gen_program!` and should not
-/// be called directly by user code.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// // Called internally by gen_program!:
-/// program_fallback_gen!();
-/// ```
-///
-/// # Generated code equivalent
-///
-/// ```rust,ignore
-/// pack!(ErrorRendererNotFound = String);
-/// pack!(ErrorDispatcherNotFound = Vec<String>);
-/// pack!(ResultEmpty = ());
-/// ```
#[proc_macro]
-pub fn program_fallback_gen(_input: TokenStream) -> TokenStream {
- #[cfg(feature = "structural_renderer")]
- let pack_empty = quote! {
- #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Groupped, Default)]
- pub struct ResultEmpty;
- };
-
- #[cfg(not(feature = "structural_renderer"))]
- let pack_empty = quote! {
- #[derive(::mingling::Groupped, Default)]
- pub struct ResultEmpty;
- };
-
- let expanded = quote! {
- ::mingling::macros::pack!(ErrorRendererNotFound = String);
- ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>);
- #pack_empty
- };
- TokenStream::from(expanded)
+pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
+ func::gen_program::program_fallback_gen_impl(input)
}
/// Internal macro used by `gen_program!` to generate the final program enum
/// and its `ProgramCollect` implementation.
///
/// This is the core code generation macro that:
-/// 1. Collects all registered types (from `pack!`, `#[derive(Groupped)]`, etc.) and
+/// 1. Collects all registered types (from `pack!`, `#[derive(Grouped)]`, etc.) and
/// creates an enum with each type as a variant.
/// 2. Generates the `Display` implementation for the enum.
/// 3. Generates the `ProgramCollect` implementation that dispatches to all
@@ -1720,434 +1545,9 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream {
/// pub fn new() -> Program<MyProgram> { Program::new() }
/// }
/// ```
-///
-/// # Panics
-///
-// Feature detection: baked into the proc-macro binary at compile time
-#[cfg(feature = "async")]
-const ASYNC_ENABLED: bool = true;
-#[cfg(not(feature = "async"))]
-const ASYNC_ENABLED: bool = false;
-
-/// 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();
- let arrow_idx = s
- .find("=>")
- .unwrap_or_else(|| panic!("Entry missing '=>': {s}"));
- let struct_str = s[..arrow_idx].trim();
- let variant_str = s[arrow_idx + 2..].trim().trim_end_matches(',').trim();
- let struct_ident = proc_macro2::Ident::new(struct_str, proc_macro2::Span::call_site());
- let variant_ident = proc_macro2::Ident::new(variant_str, proc_macro2::Span::call_site());
- (struct_ident, variant_ident)
-}
-
-/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`.
-/// Always compiled; returns empty map when pathf feature is not enabled.
-fn load_pathf_map() -> std::collections::HashMap<String, String> {
- if !cfg!(feature = "pathf") {
- return std::collections::HashMap::new();
- }
- let out_dir = std::env::var("OUT_DIR").ok();
- let crate_name = std::env::var("CARGO_PKG_NAME").ok();
- match (out_dir, crate_name) {
- (Some(dir), Some(name)) => {
- let path = std::path::Path::new(&dir).join(&name).join("type_using.rs");
- match std::fs::read_to_string(&path) {
- Ok(content) => content
- .lines()
- .filter_map(|line| {
- let line = line.trim();
- if let Some(rest) = line.strip_prefix("use ") {
- let path = rest.strip_suffix(';').unwrap_or(rest);
- if let Some((_mod, type_name)) = path.rsplit_once("::") {
- return Some((type_name.to_string(), path.to_string()));
- }
- }
- None
- })
- .collect(),
- Err(_) => std::collections::HashMap::new(),
- }
- }
- _ => std::collections::HashMap::new(),
- }
-}
-
-/// Resolves a type name to its full path token stream using the pathf mapping.
-pub(crate) fn resolve_type(
- name: &str,
- map: &std::collections::HashMap<String, String>,
-) -> proc_macro2::TokenStream {
- if let Some(full_path) = map.get(name) {
- syn::parse_str::<proc_macro2::TokenStream>(full_path).unwrap_or_else(|_| {
- let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
- quote! { #ident }
- })
- } else {
- let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site());
- quote! { #ident }
- }
-}
-
-/// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.)
-/// are poisoned.
#[proc_macro]
-#[allow(clippy::too_many_lines)]
-pub fn program_final_gen(_input: TokenStream) -> TokenStream {
- let name = syn::Ident::new("ThisProgram", proc_macro2::Span::call_site());
-
- let packed_types = get_global_set(&PACKED_TYPES).lock().unwrap().clone();
-
- let renderers = get_global_set(&RENDERERS).lock().unwrap().clone();
- let chains = get_global_set(&CHAINS).lock().unwrap().clone();
- let renderer_exist = get_global_set(&RENDERERS_EXIST).lock().unwrap().clone();
- let chain_exist = get_global_set(&CHAINS_EXIST).lock().unwrap().clone();
-
- #[cfg(feature = "structural_renderer")]
- let structural_renderers = get_global_set(&STRUCTURAL_RENDERERS)
- .lock()
- .unwrap()
- .clone();
-
- #[cfg(feature = "comp")]
- let completions = get_global_set(&COMPLETIONS).lock().unwrap().clone();
-
- let packed_types: Vec<proc_macro2::TokenStream> = packed_types
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let chain_tokens: Vec<proc_macro2::TokenStream> = chains
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let renderer_exist_tokens: Vec<proc_macro2::TokenStream> = renderer_exist
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let chain_exist_tokens: Vec<proc_macro2::TokenStream> = chain_exist
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let pathf_map: std::collections::HashMap<String, String> = if cfg!(feature = "pathf") {
- load_pathf_map()
- } else {
- std::collections::HashMap::new()
- };
-
- let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") {
- pathf_map
- .values()
- .map(|path| format!("use {};", path).parse().unwrap_or_default())
- .collect()
- } else {
- Vec::new()
- };
-
- #[cfg(feature = "structural_renderer")]
- let structural_renderer_tokens: Vec<proc_macro2::TokenStream> = structural_renderers
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- #[cfg(feature = "structural_renderer")]
- let structural_render = quote! {
- fn structural_render(
- any: ::mingling::AnyOutput<Self::Enum>,
- setting: &::mingling::StructuralRendererSetting,
- ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> {
- #[allow(unused_imports)]
- #(#pathf_uses)*
- match any.member_id {
- #(#structural_renderer_tokens)*
- _ => {
- // Non-structural types: render ResultEmpty (which implements
- // StructuralData + Serialize) instead of producing nothing.
- let mut r = ::mingling::RenderResult::default();
- ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?;
- Ok(r)
- }
- }
- }
- };
-
- #[cfg(not(feature = "structural_renderer"))]
- let structural_render = quote! {};
-
- #[cfg(feature = "dispatch_tree")]
- let compile_time_dispatchers: Vec<String> = get_global_set(&COMPILE_TIME_DISPATCHERS)
- .lock()
- .unwrap()
- .clone()
- .iter()
- .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 get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries, &pathf_map);
- let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries, &pathf_map);
-
- quote! {
- #get_nodes_fn
- #dispatch_trie_fn
- }
- };
-
- #[cfg(not(feature = "dispatch_tree"))]
- let dispatch_tree_nodes = quote! {};
-
- #[cfg(feature = "comp")]
- let completion_tokens: Vec<proc_macro2::TokenStream> = completions
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- #[cfg(feature = "comp")]
- let comp = quote! {
- fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest {
- #[allow(unused_imports)]
- #(#pathf_uses)*
- match any.member_id {
- #(#completion_tokens)*
- _ => ::mingling::Suggest::FileCompletion,
- }
- }
- };
-
- #[cfg(not(feature = "comp"))]
- let comp = quote! {};
-
- // Build render function arms from stored entries
- let render_fn =
- if renderer_tokens.is_empty() {
- quote! {
- fn render(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
- ::mingling::RenderResult::default()
- }
- }
- } else {
- let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| {
- let (struct_ident, variant_ident) = parse_entry_pair(entry);
- let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map);
- let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map);
- quote! {
- Self::#variant_ident => {
- // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`,
- // so downcasting to `#variant_ident` is safe.
- let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() };
- <#resolved_struct as ::mingling::Renderer>::render(value)
- }
- }
- }).collect();
- quote! {
- fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
- match any.member_id {
- #(#render_arms)*
- _ => ::mingling::RenderResult::default(),
- }
- }
- }
- };
-
- // Build do_chain function (async and sync versions)
- let chain_arms_async: Vec<_> = chain_tokens.iter().map(|entry| {
- let (struct_ident, variant_ident) = parse_entry_pair(entry);
- let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map);
- let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map);
- quote! {
- Self::#variant_ident => {
- // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`,
- // so downcasting to `#variant_ident` is safe.
- let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() };
- let fut = async { <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value).await };
- ::std::boxed::Box::pin(fut)
- }
- }
- }).collect();
-
- let chain_arms_sync: Vec<_> = chain_tokens
- .iter()
- .map(|entry| {
- let (struct_ident, variant_ident) = parse_entry_pair(entry);
- let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map);
- let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map);
- quote! {
- Self::#variant_ident => {
- // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`,
- // so downcasting to `#variant_ident` is safe.
- let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() };
- <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value)
- }
- }
- })
- .collect();
-
- let do_chain_fn = if chain_tokens.is_empty() {
- quote! {
- fn do_chain(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::ChainProcess<Self::Enum> {
- ::core::panic!("No chain found for type id")
- }
- }
- } else if ASYNC_ENABLED {
- quote! {
- fn do_chain(
- any: ::mingling::AnyOutput<Self::Enum>,
- ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::std::marker::Send>> {
- match any.member_id {
- #(#chain_arms_async)*
- _ => ::core::panic!("No chain found for type id: {:?}", any.type_id),
- }
- }
- }
- } else {
- quote! {
- fn do_chain(
- any: ::mingling::AnyOutput<Self::Enum>,
- ) -> ::mingling::ChainProcess<Self::Enum> {
- match any.member_id {
- #(#chain_arms_sync)*
- _ => ::core::panic!("No chain found for type id: {:?}", any.type_id),
- }
- }
- }
- };
-
- let help_tokens: Vec<proc_macro2::TokenStream> = get_global_set(&HELP_REQUESTS)
- .lock()
- .unwrap()
- .clone()
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let num_variants = packed_types.len();
- let repr_type = if u8::try_from(num_variants).is_ok() {
- quote! { u8 }
- } else if u16::try_from(num_variants).is_ok() {
- quote! { u16 }
- } else if u32::try_from(num_variants).is_ok() {
- quote! { u32 }
- } else {
- quote! { u128 }
- };
-
- let expanded = quote! {
- #[derive(Debug, PartialEq, Eq, Clone)]
- #[repr(#repr_type)]
- #[allow(nonstandard_style)]
- pub enum #name {
- #(#packed_types),*
- }
-
- impl ::std::fmt::Display for #name {
- fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- match self {
- #(#name::#packed_types => write!(f, stringify!(#packed_types)),)*
- }
- }
- }
-
- impl ::mingling::ProgramCollect for #name {
- type Enum = #name;
- type ErrorDispatcherNotFound = ErrorDispatcherNotFound;
- type ErrorRendererNotFound = ErrorRendererNotFound;
- type ResultEmpty = ResultEmpty;
- fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput<Self::Enum> {
- ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string()))
- }
- fn build_dispatcher_not_found(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> {
- ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args))
- }
- fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> {
- ::mingling::AnyOutput::new(ResultEmpty)
- }
- #render_fn
- #do_chain_fn
- fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
- #[allow(unused_imports)]
- #(#pathf_uses)*
- match any.member_id {
- #(#help_tokens)*
- _ => ::mingling::RenderResult::default(),
- }
- }
- fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool {
- match any.member_id {
- #(#renderer_exist_tokens)*
- _ => false
- }
- }
- fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool {
- match any.member_id {
- #(#chain_exist_tokens)*
- _ => false
- }
- }
- #dispatch_tree_nodes
- #structural_render
- #comp
- }
-
- impl #name {
- /// Creates a new `Program<#name>` instance with default configuration.
- pub fn new() -> ::mingling::Program<#name> {
- ::mingling::Program::new()
- }
-
- /// Returns a static reference to the global `Program<#name>` singleton.
- pub fn this() -> &'static ::mingling::Program<#name> {
- &::mingling::this::<#name>()
- }
- }
- };
-
- // Clear all global registries to prevent stale state in Rust Analyzer
- get_global_set(&PACKED_TYPES).lock().unwrap().clear();
- get_global_set(&CHAINS).lock().unwrap().clear();
- get_global_set(&CHAINS_EXIST).lock().unwrap().clear();
- get_global_set(&RENDERERS).lock().unwrap().clear();
- get_global_set(&RENDERERS_EXIST).lock().unwrap().clear();
- get_global_set(&HELP_REQUESTS).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()
- .clear();
- #[cfg(feature = "structural_renderer")]
- get_global_set(&STRUCTURAL_RENDERERS)
- .lock()
- .unwrap()
- .clear();
-
- TokenStream::from(expanded)
+pub fn program_final_gen(input: TokenStream) -> TokenStream {
+ func::gen_program::program_final_gen_impl(input)
}
/// Builds a `Suggest` instance with inline suggestion items.
diff --git a/mingling_macros/src/systems.rs b/mingling_macros/src/systems.rs
new file mode 100644
index 0000000..53e58c5
--- /dev/null
+++ b/mingling_macros/src/systems.rs
@@ -0,0 +1,5 @@
+#[cfg(feature = "dispatch_tree")]
+pub(crate) mod dispatch_tree_gen;
+pub(crate) mod res_injection;
+#[cfg(feature = "structural_renderer")]
+pub(crate) mod structural_data;
diff --git a/mingling_macros/src/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs
index b66e2f4..7383421 100644
--- a/mingling_macros/src/dispatch_tree_gen.rs
+++ b/mingling_macros/src/systems/dispatch_tree_gen.rs
@@ -4,11 +4,11 @@ use just_fmt::snake_case;
use proc_macro2::TokenStream;
use quote::quote;
-use crate::resolve_type;
+use crate::func::gen_program::resolve_type;
/// Generate the `get_nodes()` function body for a ProgramCollect impl.
/// If `pathf_map` is non-empty, resolves internal dispatcher statics using full paths.
-pub fn gen_get_nodes(
+pub(crate) fn gen_get_nodes(
entries: &[(String, String, String)],
pathf_map: &HashMap<String, String>,
) -> TokenStream {
@@ -40,7 +40,7 @@ pub fn gen_get_nodes(
/// Single-node groups use `starts_with`; multi-node groups recurse with `nth()` match.
///
/// If `pathf_map` is non-empty, resolves dispatcher types using full paths.
-pub fn gen_dispatch_args_trie(
+pub(crate) fn gen_dispatch_args_trie(
entries: &[(String, String, String)],
pathf_map: &HashMap<String, String>,
) -> TokenStream {
diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/systems/res_injection.rs
index 606b9a6..606b9a6 100644
--- a/mingling_macros/src/res_injection.rs
+++ b/mingling_macros/src/systems/res_injection.rs
diff --git a/mingling_macros/src/structural_data.rs b/mingling_macros/src/systems/structural_data.rs
index 5350d7e..74bcf09 100644
--- a/mingling_macros/src/structural_data.rs
+++ b/mingling_macros/src/systems/structural_data.rs
@@ -176,7 +176,7 @@ pub(crate) fn pack_structural(input: TokenStream) -> TokenStream {
}
}
- impl ::mingling::Groupped<#program_path> for #type_name {
+ impl ::mingling::Grouped<#program_path> for #type_name {
fn member_id() -> #program_path {
#program_path::#type_name
}
@@ -297,7 +297,7 @@ pub(crate) fn group_structural(input: TokenStream) -> TokenStream {
#type_use
#alias_use
- impl ::mingling::Groupped<__MinglingProgram> for #type_name {
+ impl ::mingling::Grouped<__MinglingProgram> for #type_name {
fn member_id() -> __MinglingProgram {
__MinglingProgram::#type_name
}
diff --git a/mingling_macros/src/utils.rs b/mingling_macros/src/utils.rs
new file mode 100644
index 0000000..49d0e7a
--- /dev/null
+++ b/mingling_macros/src/utils.rs
@@ -0,0 +1 @@
+// Shared utilities for the macro crate.
diff --git a/mingling_pathf/README.md b/mingling_pathf/README.md
index 89d1811..9706f49 100644
--- a/mingling_pathf/README.md
+++ b/mingling_pathf/README.md
@@ -7,7 +7,7 @@
## Overview
-`mingling_pathf` provides the `pathf` feature for Mingling. It automatically analyzes the full module paths of all Mingling types in a crate at build-time (types defined via `pack!`, `#[derive(Groupped)]`, `#[chain]`, `#[renderer]`, etc.), and generates a mapping from type names to module paths for consumption by `gen_program!()` at compile-time.
+`mingling_pathf` provides the `pathf` feature for Mingling. It automatically analyzes the full module paths of all Mingling types in a crate at build-time (types defined via `pack!`, `#[derive(Grouped)]`, `#[chain]`, `#[renderer]`, etc.), and generates a mapping from type names to module paths for consumption by `gen_program!()` at compile-time.
When enabled, `gen_program!()` uses full module paths for type references in the generated dispatch code (e.g., `downcast::<myapp::sub::ResultMyName>()`), eliminating the need to `use` all types in the module where `gen_program!()` is called. This allows for a more flexible module organization without the constraint of centralized `use` statements.
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
index 3765971..5bbc3b4 100644
--- a/mingling_pathf/src/pattern_analyzer.rs
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -31,7 +31,7 @@ pub fn init_with_config(config: PathfinderConfig) -> PatternAnalyzer {
analyzer.add_pattern(BasicStructPattern);
analyzer.add_pattern(PackPattern);
analyzer.add_pattern(GroupPattern);
- analyzer.add_pattern(GrouppedDerivePattern);
+ analyzer.add_pattern(GroupedDerivePattern);
analyzer.add_pattern(ChainPattern);
analyzer.add_pattern(RendererPattern);
analyzer.add_pattern(HelpPattern);
diff --git a/mingling_pathf/src/patterns.rs b/mingling_pathf/src/patterns.rs
index 9801e9b..9845b73 100644
--- a/mingling_pathf/src/patterns.rs
+++ b/mingling_pathf/src/patterns.rs
@@ -6,7 +6,7 @@ pub use completion::*;
pub use dispatcher::*;
pub use dispatcher_clap::*;
pub use group::*;
-pub use groupped_derive::*;
+pub use grouped_derive::*;
pub use help::*;
pub use pack::*;
pub use renderer::*;
@@ -17,7 +17,7 @@ mod completion;
mod dispatcher;
mod dispatcher_clap;
mod group;
-mod groupped_derive;
+mod grouped_derive;
mod help;
mod pack;
mod renderer;
diff --git a/mingling_pathf/src/patterns/groupped_derive.rs b/mingling_pathf/src/patterns/grouped_derive.rs
index 91daaef..9522c1f 100644
--- a/mingling_pathf/src/patterns/groupped_derive.rs
+++ b/mingling_pathf/src/patterns/grouped_derive.rs
@@ -1,5 +1,5 @@
-//! The `GrouppedDerivePattern` matches structs, enums, and unions annotated with
-//! `#[derive(Groupped)]` or `#[derive(GrouppedSerialize)]` (or any combination
+//! The `GroupedDerivePattern` matches structs, enums, and unions annotated with
+//! `#[derive(Grouped)]` or `#[derive(GroupedSerialize)]` (or any combination
//! with other derives). It also recurses into `mod` items to find nested types.
//! This is used to track grouped items for code generation or analysis.
@@ -7,17 +7,17 @@ use syn::Item;
use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
-/// Matches `#[derive(Groupped)]` and `#[derive(GrouppedSerialize)]`.
+/// Matches `#[derive(Grouped)]` and `#[derive(GroupedSerialize)]`.
///
/// Covers the forms:
-/// - `#[derive(Groupped)] struct T { ... }`
-/// - `#[derive(Groupped, Serialize, ...)] struct T { ... }`
-/// - `#[derive(GrouppedSerialize)] struct T { ... }`
-pub struct GrouppedDerivePattern;
+/// - `#[derive(Grouped)] struct T { ... }`
+/// - `#[derive(Grouped, Serialize, ...)] struct T { ... }`
+/// - `#[derive(GroupedSerialize)] struct T { ... }`
+pub struct GroupedDerivePattern;
-impl AnalyzePattern for GrouppedDerivePattern {
+impl AnalyzePattern for GroupedDerivePattern {
fn contains(&self, content: &str) -> bool {
- content.contains("Groupped")
+ content.contains("Grouped")
}
fn analyze(&self, content: &str) -> Vec<AnalyzeItem> {
@@ -29,19 +29,19 @@ impl AnalyzePattern for GrouppedDerivePattern {
for item in &syntax.items {
match item {
- Item::Struct(s) if has_groupped_derive(&s.attrs) => {
+ Item::Struct(s) if has_grouped_derive(&s.attrs) => {
items.push(AnalyzeItem {
module: String::new(),
item_name: s.ident.to_string(),
});
}
- Item::Enum(e) if has_groupped_derive(&e.attrs) => {
+ Item::Enum(e) if has_grouped_derive(&e.attrs) => {
items.push(AnalyzeItem {
module: String::new(),
item_name: e.ident.to_string(),
});
}
- Item::Union(u) if has_groupped_derive(&u.attrs) => {
+ Item::Union(u) if has_grouped_derive(&u.attrs) => {
items.push(AnalyzeItem {
module: String::new(),
item_name: u.ident.to_string(),
@@ -51,13 +51,13 @@ impl AnalyzePattern for GrouppedDerivePattern {
if let Some((_, nested)) = &item_mod.content {
for n in nested {
match n {
- Item::Struct(s) if has_groupped_derive(&s.attrs) => {
+ Item::Struct(s) if has_grouped_derive(&s.attrs) => {
items.push(AnalyzeItem {
module: item_mod.ident.to_string(),
item_name: s.ident.to_string(),
});
}
- Item::Enum(e) if has_groupped_derive(&e.attrs) => {
+ Item::Enum(e) if has_grouped_derive(&e.attrs) => {
items.push(AnalyzeItem {
module: item_mod.ident.to_string(),
item_name: e.ident.to_string(),
@@ -76,10 +76,10 @@ impl AnalyzePattern for GrouppedDerivePattern {
}
}
-fn has_groupped_derive(attrs: &[syn::Attribute]) -> bool {
+fn has_grouped_derive(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if attr.path().is_ident("derive") {
- // Correctly parse comma-separated paths in #[derive(Groupped, Debug, ...)]
+ // Correctly parse comma-separated paths in #[derive(Grouped, Debug, ...)]
attr.parse_args_with(|input: syn::parse::ParseStream| {
let paths =
syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated(
@@ -87,7 +87,7 @@ fn has_groupped_derive(attrs: &[syn::Attribute]) -> bool {
)?;
Ok(paths.iter().any(|p| {
let name = p.segments.last().unwrap().ident.to_string();
- name == "Groupped" || name == "GrouppedSerialize"
+ name == "Grouped" || name == "GroupedSerialize"
}))
})
.unwrap_or(false)
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index 824cbbf..7e7cbd5 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -222,11 +222,11 @@ fn test_group_analyze() {
}
#[test]
-fn test_groupped_derive_analyze() {
+fn test_grouped_derive_analyze() {
let analyzer = mingling_pathf::pattern_analyzer::init();
let file = current_dir()
.unwrap()
- .join("src/test_files/test_groupped_derive.rs");
+ .join("src/test_files/test_grouped_derive.rs");
let r = analyzer.analyze_file(file).unwrap();
let required: Vec<&str> = vec![
diff --git a/mingling_pathf/test/src/test_files/test_groupped_derive.rs b/mingling_pathf/test/src/test_files/test_grouped_derive.rs
index 913587c..20055e9 100644
--- a/mingling_pathf/test/src/test_files/test_groupped_derive.rs
+++ b/mingling_pathf/test/src/test_files/test_grouped_derive.rs
@@ -1,42 +1,42 @@
-#[derive(Groupped)]
+#[derive(Grouped)]
struct Derived1 {
value: String,
}
-#[derive(Groupped, Debug, Clone)]
+#[derive(Grouped, Debug, Clone)]
struct Derived2 {
value: i32,
}
-#[derive(GrouppedSerialize)]
+#[derive(GroupedSerialize)]
struct Derived3 {
value: bool,
}
-#[derive(Groupped)]
+#[derive(Grouped)]
enum EnumDerived1 {
A,
B,
}
-#[derive(GrouppedSerialize)]
+#[derive(GroupedSerialize)]
enum EnumDerived2 {
X(String),
Y(i32),
}
pub mod sub {
- #[derive(Groupped)]
+ #[derive(Grouped)]
struct Derived1 {
value: String,
}
- #[derive(GrouppedSerialize)]
+ #[derive(GroupedSerialize)]
struct Derived3 {
value: bool,
}
- #[derive(Groupped)]
+ #[derive(Grouped)]
enum EnumDerived1 {
A,
}
diff --git a/mling/src/cli.rs b/mling/src/cli.rs
index 45a49d0..69e4fe9 100644
--- a/mling/src/cli.rs
+++ b/mling/src/cli.rs
@@ -11,7 +11,7 @@ use crate::{
};
use colored::Colorize;
use mingling::{
- Groupped, Program, RenderResult,
+ Grouped, Program, RenderResult,
hook::ProgramHook,
macros::{chain, help, pack, program_setup, renderer},
res::ResExitCode,
diff --git a/mling/src/proj_mgr/generator.rs b/mling/src/proj_mgr/generator.rs
index 4f965d9..f0dbdd7 100644
--- a/mling/src/proj_mgr/generator.rs
+++ b/mling/src/proj_mgr/generator.rs
@@ -1,7 +1,7 @@
use std::path::{self, PathBuf};
use mingling::{
- Groupped, RenderResult,
+ Grouped, RenderResult,
macros::{chain, pack, renderer, route},
};
use std::io::Write as _;
diff --git a/mling/src/proj_mgr/show_binaries.rs b/mling/src/proj_mgr/show_binaries.rs
index 9d5caf0..4fb5c28 100644
--- a/mling/src/proj_mgr/show_binaries.rs
+++ b/mling/src/proj_mgr/show_binaries.rs
@@ -2,7 +2,7 @@ use std::path::PathBuf;
use colored::Colorize;
use mingling::{
- Groupped, RenderResult,
+ Grouped, RenderResult,
macros::{chain, pack, renderer},
};
use serde::Serialize;
@@ -17,7 +17,7 @@ use crate::{
res::ResManifestPath,
};
-#[derive(Serialize, Groupped)]
+#[derive(Serialize, Grouped)]
pub struct ResultBinaries {
pub binaries: Vec<DataBinary>,
}
diff --git a/mling/src/proj_mgr/show_directories.rs b/mling/src/proj_mgr/show_directories.rs
index 7d7c074..5c5f448 100644
--- a/mling/src/proj_mgr/show_directories.rs
+++ b/mling/src/proj_mgr/show_directories.rs
@@ -1,6 +1,6 @@
use colored::Colorize;
use mingling::{
- Groupped, RenderResult,
+ Grouped, RenderResult,
macros::{chain, pack, renderer},
};
use serde::Serialize;
@@ -12,12 +12,12 @@ use crate::{
res::ResManifestPath,
};
-#[derive(Serialize, Groupped)]
+#[derive(Serialize, Grouped)]
pub struct ResultWorkspaceDirectory {
pub path: String,
}
-#[derive(Serialize, Groupped)]
+#[derive(Serialize, Grouped)]
pub struct ResultTargetDirectory {
pub path: String,
}