aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-17 05:49:19 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-17 05:49:19 +0800
commit57c53affe3542cb6bd4e79ee4c18f20a1bd76b2d (patch)
tree1cd4aef44cb7a45a8cd9d520b598f5f181e24c76
parentef23cd944402939605c78a4a853ef6e33af02c21 (diff)
refactor!: replace pack! macros with derive-based pipeline types
Remove the `pack!`, `pack_err!`, `pack_structural!`, and `pack_err_structural!` macros, replacing all pipeline type definitions with `#[derive(Grouped)]` and `#[derive(Grouped, Wrap)]` attributes. This changes the generated struct shape from named-field structs with an `inner` field to tuple structs accessed via `.0`, and removes the auto-generated `name` and `info` fields from error types.
-rw-r--r--CHANGELOG.md48
-rw-r--r--GETTING-STARTED.md60
-rw-r--r--README.md17
-rw-r--r--docs/_zh_CN/pages/11-resource-system.md11
-rw-r--r--docs/_zh_CN/pages/12-exit-code.md7
-rw-r--r--docs/_zh_CN/pages/13-hook.md5
-rw-r--r--docs/_zh_CN/pages/14-testing.md39
-rw-r--r--docs/_zh_CN/pages/2-define-a-dispatcher.md10
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md54
-rw-r--r--docs/_zh_CN/pages/4-render-result.md35
-rw-r--r--docs/_zh_CN/pages/5-multiple-commands.md20
-rw-r--r--docs/_zh_CN/pages/6-argument-parse-picker.md64
-rw-r--r--docs/_zh_CN/pages/9-error-handling.md40
-rw-r--r--docs/_zh_CN/pages/advanced/2-structural-renderer.md20
-rw-r--r--docs/_zh_CN/pages/concepts/2-resource.md3
-rw-r--r--docs/_zh_CN/pages/concepts/3-any-output.md6
-rw-r--r--docs/_zh_CN/pages/concepts/4-program-collect.md2
-rw-r--r--docs/_zh_CN/pages/other/features.md59
-rw-r--r--docs/_zh_CN/pages/other/naming_rule.md25
-rw-r--r--docs/example-pages/examples.json17
-rw-r--r--docs/pages/11-resource-system.md11
-rw-r--r--docs/pages/12-exit-code.md3
-rw-r--r--docs/pages/13-hook.md5
-rw-r--r--docs/pages/14-testing.md39
-rw-r--r--docs/pages/2-define-a-dispatcher.md10
-rw-r--r--docs/pages/3-define-a-chain.md54
-rw-r--r--docs/pages/4-render-result.md35
-rw-r--r--docs/pages/5-multiple-commands.md20
-rw-r--r--docs/pages/6-argument-parse-picker.md64
-rw-r--r--docs/pages/9-error-handling.md40
-rw-r--r--docs/pages/advanced/2-structural-renderer.md20
-rw-r--r--docs/pages/concepts/2-resource.md3
-rw-r--r--docs/pages/concepts/3-any-output.md6
-rw-r--r--docs/pages/concepts/4-program-collect.md2
-rw-r--r--docs/pages/other/features.md59
-rw-r--r--docs/pages/other/naming_rule.md24
-rw-r--r--docs/res/changlog_examples/feat_program_res.rs5
-rw-r--r--examples/example-argument-picker/src/main.rs69
-rw-r--r--examples/example-async-support/src/main.rs5
-rw-r--r--examples/example-basic/src/main.rs25
-rw-r--r--examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs5
-rw-r--r--examples/example-combine-pathf-metadata/src/sub/mod.rs12
-rw-r--r--examples/example-command-macro/src/main.rs13
-rw-r--r--examples/example-completion/src/main.rs5
-rw-r--r--examples/example-error-handling/src/main.rs31
-rw-r--r--examples/example-exitcode/src/main.rs13
-rw-r--r--examples/example-hook/src/main.rs5
-rw-r--r--examples/example-lazy-resources/src/main.rs3
-rw-r--r--examples/example-metadata/src/main.rs13
-rw-r--r--examples/example-outside-type/src/main.rs7
-rw-r--r--examples/example-pack-err/Cargo.lock235
-rw-r--r--examples/example-pack-err/Cargo.toml16
-rw-r--r--examples/example-pack-err/page.toml10
-rw-r--r--examples/example-pack-err/src/main.rs151
-rw-r--r--examples/example-pack-err/test.toml35
-rw-r--r--examples/example-panic-unwind/src/main.rs6
-rw-r--r--examples/example-pathfinder/src/sub/mod.rs6
-rw-r--r--examples/example-repl-basic/src/main.rs26
-rw-r--r--examples/example-setup/src/main.rs5
-rw-r--r--examples/example-structural-renderer/src/main.rs5
-rw-r--r--examples/example-unit-test/src/main.rs41
-rw-r--r--examples/full-todolist/src/main.rs36
-rw-r--r--mingling/src/docs/lib.md5
-rw-r--r--mingling/src/example_docs.rs447
-rw-r--r--mingling/src/gen_program.rs12
-rw-r--r--mingling/src/lib.rs21
-rw-r--r--mingling_cli/src/config/cmd_cfg.rs35
-rw-r--r--mingling_cli/src/lib.rs2
-rw-r--r--mingling_cli/src/linter/cmd_explain.rs30
-rw-r--r--mingling_cli/src/linter/cmd_lint.rs12
-rw-r--r--mingling_cli/src/linter/mlint_report.rs21
-rw-r--r--mingling_cli/src/metadata/cmd_metadata.rs7
-rw-r--r--mingling_cli/src/pkg_mgr.rs21
-rw-r--r--mingling_cli/src/pkg_mgr/cmd_install.rs43
-rw-r--r--mingling_cli/src/pkg_mgr/cmd_internal_loadpkgs.rs22
-rw-r--r--mingling_cli/src/pkg_mgr/cmd_pkg_disable.rs26
-rw-r--r--mingling_cli/src/pkg_mgr/cmd_pkg_enable.rs27
-rw-r--r--mingling_cli/src/pkg_mgr/cmd_pkg_show.rs11
-rw-r--r--mingling_cli/src/pkg_mgr/cmd_uninstall.rs38
-rw-r--r--mingling_cli/src/proj_mgr/cmd_class_add.rs61
-rw-r--r--mingling_cli/src/proj_mgr/cmd_proj_init.rs82
-rw-r--r--mingling_macros/src/attr/dispatcher_clap.rs7
-rw-r--r--mingling_macros/src/func.rs7
-rw-r--r--mingling_macros/src/func/dispatcher.rs10
-rw-r--r--mingling_macros/src/func/entry.rs2
-rw-r--r--mingling_macros/src/func/gen_program.rs3
-rw-r--r--mingling_macros/src/func/pack.rs154
-rw-r--r--mingling_macros/src/func/pack_err.rs108
-rw-r--r--mingling_macros/src/func/pack_err_structural.rs121
-rw-r--r--mingling_macros/src/func/pack_structural.rs170
-rw-r--r--mingling_macros/src/func/program_comp_gen.rs15
-rw-r--r--mingling_macros/src/func/program_fallback_gen.rs8
-rw-r--r--mingling_macros/src/func/program_final_gen.rs4
-rw-r--r--mingling_macros/src/lib.rs238
-rw-r--r--mingling_macros/src/systems.rs5
-rw-r--r--mingling_macros/src/systems/structural_data.rs7
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs5
-rw-r--r--mingling_pathf/src/patterns.rs2
-rw-r--r--mingling_pathf/src/patterns/pack.rs104
-rw-r--r--mingling_pathf/test/src/lib.rs27
-rw-r--r--mingling_pathf/test/src/test_files/test_pack.rs17
101 files changed, 1229 insertions, 2428 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea71a92..62b1f7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -301,6 +301,54 @@ None
_Behavioral note:_ the `picked` values, flag parsing semantics, and the "longest registered prefix wins" matching rule are all preserved by the `picker` feature's `arg-picker` API. The removal is purely a dead-code cleanup: the legacy built-in parser has been fully superseded by `picker`, and downstream code must migrate to the new API names described above.
+5. **[`macros`]** **[BREAKING REMOVAL]** Removed the `pack!`, `pack_err!`, `pack_structural!`, and `pack_err_structural!` macros and replaced all pipeline type definitions with `#[derive(Grouped)]` (and, where applicable, `#[derive(Grouped, Wrap)]`) on user-defined structs.
+
+ ### What changed
+
+ The `pack!` family of macros — which generated a wrapper struct with a fixed `inner` field and a suite of trait impls (`From`/`Into`, `AsRef`/`AsMut`, `Deref`/`DerefMut`, conditional `Default`, `Grouped`, and `Into<AnyOutput>`/`Into<ChainProcess>`) — has been removed. Pipeline types are now defined directly as ordinary Rust structs annotated with the `Grouped` derive macro, optionally combined with `Wrap` for the ergonomic trait impls previously provided by `pack!`.
+
+ **Removed API:**
+
+ - **`mingling::macros::pack!`** — Removed entirely (both the `pack`/`pack` and `pack`/`pack_structural` re-exports and the underlying macro implementations).
+ - **`mingling::macros::pack_err!`** — Removed (extras feature).
+ - **`mingling::macros::pack_structural!`** — Removed (structural_renderer feature).
+ - **`mingling::macros::pack_err_structural!`** — Removed (structural_renderer + extras features).
+ - _*All `pack`* re-exports_* from `mingling::prelude` and `mingling::macros` — removed.
+ - **`mingling_macros` implementation modules** — Deleted `func/pack.rs`, `func/pack_err.rs`, `func/pack_structural.rs`, `func/pack_err_structural.rs`, and `systems/structural_data.rs`.
+ - **`mingling_pathf::patterns::PackPattern`** — Removed from `pattern_analyzer::init()` and the `patterns` module (both the `patterns/pack.rs` file and its `pub use pack::*;` re-export).
+ - **Pathf test asset** — Deleted `mingling_pathf/test/src/test_files/test_pack.rs` and the `test_pack_analyze` test in `mingling_pathf/test/src/lib.rs`.
+ - **`mingling/src/docs/lib.md` / `mingling/src/example_docs.rs`** — The `example_pack_err` doc module (and the corresponding `examples/example-pack-err/` example) were removed (Cargo.toml, Cargo.lock, src/main.rs, page.toml, test.toml, and the `examples.json` entry).
+ - **`docs/pages/other/features.md`** — The `pack_err!` row and detailed section were removed in favor of a "Declaring Error Types" section documenting `#[derive(Grouped, Default)]` (unit form) / `#[derive(Grouped, Wrap)]` (typed form).
+ - **`docs/_zh_CN/pages/other/features.md`** — Same changes as the English `features.md`.
+ - **`mingling/src/gen_program.rs`** — Doc comments updated from "created by the `pack!` macro" to "generated by the `gen_program!` macro" for `Entry`, `ErrorRendererNotFound`, `EntryFallback`, and `CompletionContext`. The "You can register it using `with_dispatcher`" doc on `CMDCompletion` was removed (since `with_dispatcher` no longer exists). The "and many others" phrase in the `macros` module doc was trimmed to remove `pack!` and friends.
+
+ **Migration guide:**
+
+ | Old `pack!`-family macro usage | New equivalent |
+ | ------------------------------------------ | ----------------------------------------------------------------------------------------- |
+ | `pack!(TypeName = Inner);` | `#[derive(Grouped, Wrap)] pub struct TypeName(Inner);` |
+ | `pack!(TypeName = (A, B));` | `#[derive(Grouped, Wrap)] pub struct TypeName((A, B));` |
+ | `pack!(TypeName = ());` (unit) | `#[derive(Grouped, Wrap, Default)] pub struct TypeName(());` |
+ | `pack_err!(ErrorName);` (simple) | `#[derive(Grouped, Default)] pub struct ErrorName;` |
+ | `pack_err!(ErrorName = Inner);` (typed) | `#[derive(Grouped, Wrap)] pub struct ErrorName(Inner);` |
+ | `pack_structural!(TypeName = Inner);` | `#[derive(serde::Serialize, StructuralData, Grouped, Wrap)] pub struct TypeName(Inner);` |
+ | `pack_err_structural!(ErrorName);` | `#[derive(serde::Serialize, StructuralData, Grouped, Default)] pub struct ErrorName;` |
+ | `pack_err_structural!(ErrorName = Inner);` | `#[derive(serde::Serialize, StructuralData, Grouped, Wrap)] pub struct ErrorName(Inner);` |
+
+ Behavioral differences to note when migrating:
+
+ - **Field access** — The old `pack!` types exposed a `pub inner` field. The new `#[derive(Grouped, Wrap)]` types are tuple structs whose single field is accessed as `.0` (or `.0.0` / `.0.1` when the inner type is itself a tuple, e.g. `(String, String)`). All internal call sites and examples were updated accordingly (e.g. `StateConfigEdit((String, String))` now accesses `kv.0.0` / `kv.0.1`, `StatePkgEnable((String, String))` accesses `p.0.0` / `p.0.1`).
+ - **Constructor** — `TypeName::new(inner)` becomes `TypeName(inner)`. `Dispatcher::begin` now constructs the entry as `#pack(args)` instead of `#pack::new(args)`.
+ - **`name` field / `info` field** — `pack_err!`'s auto-generated `name: String` field (snake_cased at compile time) and `info: Type` field are gone. Unit errors use `#[derive(Grouped, Default)]` and are constructed as plain unit values (`ErrorFoo`), while typed errors wrap their payload as `.0` (accessed as `err.0` instead of `err.info`).
+ - **`Default`** — `pack!`'s `Default` was conditional on the inner type. The new `#[derive(Grouped, Wrap, Default)]` requires the inner type to also impl `Default`; for unit-like errors use `#[derive(Grouped, Default)]` (a plain unit struct).
+ - **`AsRef` / `AsMut`** — The `Wrap` derive generates `Deref`/`DerefMut`, `From`, and `Into` but not `AsRef`/`AsMut`. Code relying on `AsRef`/`AsMut` from `pack!` should switch to `Deref` (`*value`) or field access.
+ - **Structured output** — `pack_structural!` / `pack_err_structural!` auto-derived `serde::Serialize` and `StructuralData`. The new equivalent requires adding `#[derive(serde::Serialize, StructuralData, ...)]` explicitly (and `use mingling::StructuralData;` when needed).
+ - **`register_type!`** — Both `Grouped` and `Wrap` derives invoke `register_type!` internally, so no separate registration call is needed.
+
+ **Examples of internal updates in this release** (all files updated from `pack!`/`pack_err!`/etc. to the derive-based form): `examples/example-argument-picker`, `examples/example-async-support`, `examples/example-basic`, `examples/example-combine-pathf-dispatch-tree`, `examples/example-combine-pathf-metadata`, `examples/example-command-macro`, `examples/example-completion`, `examples/example-error-handling`, `examples/example-exitcode`, `examples/example-hook`, `examples/example-lazy-resources`, `examples/example-metadata`, `examples/example-outside-type`, `examples/example-panic-unwind`, `examples/example-pathfinder`, `examples/example-repl-basic`, `examples/example-setup`, `examples/example-structural-renderer`, `examples/example-unit-test`, `examples/full-todolist`, `mingling_cli/src/config/cmd_cfg.rs`, `mingling_cli/src/lib.rs`, `mingling_cli/src/linter/cmd_explain.rs`, `mingling_cli/src/linter/cmd_lint.rs`, `mingling_cli/src/linter/mlint_report.rs`, `mingling_cli/src/metadata/cmd_metadata.rs`, `mingling_cli/src/pkg_mgr.rs`, `mingling_cli/src/pkg_mgr/cmd_install.rs`, `mingling_cli/src/pkg_mgr/cmd_internal_loadpkgs.rs`, `mingling_cli/src/pkg_mgr/cmd_pkg_disable.rs`, `mingling_cli/src/pkg_mgr/cmd_pkg_enable.rs`, `mingling_cli/src/pkg_mgr/cmd_pkg_show.rs`, `mingling_cli/src/pkg_mgr/cmd_uninstall.rs`, `mingling_cli/src/proj_mgr/cmd_class_add.rs`, `mingling_cli/src/proj_mgr/cmd_proj_init.rs`, the `dispatcher!` / `entry!` / `gen_program!` / `program_comp_gen!` / `program_fallback_gen!` / `program_final_gen!` macro implementations, `mingling_macros/src/func/dispatcher_clap.rs`, `mingling/src/example_docs.rs`, `mingling/src/docs/lib.md`, `README.md`, `GETTING-STARTED.md`, and all docs pages / tests listed above.
+
+ _Behavioral note:_ the runtime semantics of pipeline types are unchanged — `#[derive(Grouped, Wrap)]` produces types with the same `Grouped` identity, `Into<AnyOutput>`/`Into<ChainProcess>` routing, `Deref`/`DerefMut`, and `From`/`Into` conversions that `pack!` provided. The removal is purely an API move from magic macros to standard Rust derives, reducing macro surface area and making pipeline types inspectable and composable like any other struct.
+
---
## Contents
diff --git a/GETTING-STARTED.md b/GETTING-STARTED.md
index c1b8e25..0730136 100644
--- a/GETTING-STARTED.md
+++ b/GETTING-STARTED.md
@@ -66,23 +66,24 @@ The `#[chain]` attribute turns a plain function into an execution step. Think of
```rust
dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let greeting = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting).into()
+ ResultGreeting(greeting).into()
}
```
Key points:
- The return type is `Next` — a type alias for `ChainProcess<ThisProgram>`.
-- You chain results by calling `.to_chain()` on any `pack!`-ed type.
+- You chain results by calling `.to_chain()` on any type defined with `#[derive(Grouped)]`.
- You can have **multiple chain functions** for the same command, each transforming the data further.
- With the `async` feature, chain functions can be `async fn`.
@@ -93,11 +94,11 @@ Key points:
The `#[renderer]` attribute turns a function into an output handler. It receives the final result of a chain and returns a `RenderResult`.
```rust
-use mingling::macros::pack;
use mingling::prelude::*;
use std::io::Write;
-pack!(ResultGreeting = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
#[renderer]
fn render_greeting(greeting: ResultGreeting) -> RenderResult {
@@ -132,7 +133,8 @@ Mingling provides a **Picker** for argument extraction. You can use `pick()` or
```rust
// Features: ["picker"]
dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
@@ -140,7 +142,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.pick(&arg![String]) // positional argument: first string
.pick_or(&arg![repeat: u8, 'r'], || 1) // optional flag with default value
.unwrap();
- ResultGreeting::new(format!("{} x{}", name, count)).into()
+ ResultGreeting(format!("{} x{}", name, count)).into()
}
```
@@ -195,7 +197,8 @@ With the `comp` feature, Mingling provides a fully dynamic completion system. Yo
use mingling::{macros::suggest, ShellContext, Suggest};
dispatcher!("greet", EntryGreet);
-pack!(ResultName = (u8, String));
+#[derive(Grouped, Wrap)]
+pub struct ResultName((u8, String));
#[completion(EntryGreet)]
fn complete_greet(ctx: &ShellContext) -> Suggest {
@@ -268,29 +271,34 @@ fn complete_lang(_: &ShellContext) -> Suggest {
## 7. Error Handling
-Mingling doesn't use `?` operator propagation. Instead, errors are just **alternative results** that flow through the same chain/render pipeline. Create error types with `pack!` and route to them with `.to_render()`:
+Mingling doesn't use `?` operator propagation. Instead, errors are just **alternative results** that flow through the same chain/render pipeline. Create error types with `#[derive(Grouped)]` / `#[derive(Grouped, Wrap)]` and route to them with `.to_render()`:
```rust
-use mingling::macros::pack;
use mingling::prelude::*;
use std::io::Write;
dispatcher!("hello", EntryHello);
-pack!(ResultName = String);
-pack!(ErrorNoNameProvided = ());
-pack!(ErrorNameTooLong = u16);
+
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
+
+#[derive(Grouped)]
+pub struct ErrorNoNameProvided;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameTooLong(u16);
#[chain]
fn handle(args: EntryHello) -> Next {
- let Some(name) = args.inner.first().cloned() else {
- return ErrorNoNameProvided::default().to_render(); // ← early return to error renderer
+ let Some(name) = args.0.first().cloned() else {
+ return ErrorNoNameProvided.to_render(); // ← early return to error renderer
};
if name.len() > 10 {
- return ErrorNameTooLong::new(name.len() as u16).to_render();
+ return ErrorNameTooLong(name.len() as u16).to_render();
}
- ResultName::new(name).to_render() // ← success path
+ ResultName(name).to_render() // ← success path
}
#[renderer]
@@ -633,7 +641,9 @@ use std::io::Write;
use std::time::Duration;
dispatcher!("download", EntryDownload);
-pack!(ResultDownloaded = String);
+
+#[derive(Grouped, Wrap)]
+pub struct ResultDownloaded(String);
#[chain]
pub async fn handle_download(args: EntryDownload) -> Next {
@@ -643,7 +653,7 @@ pub async fn handle_download(args: EntryDownload) -> Next {
async fn download_file(name: String) -> ResultDownloaded {
tokio::time::sleep(Duration::from_secs(1)).await;
- ResultDownloaded::new(name)
+ ResultDownloaded(name)
}
#[renderer]
@@ -670,7 +680,7 @@ use mingling::macros::gen_program;
gen_program!();
```
-It must be placed **after** all your `dispatcher!`, `pack!`, `#[chain]`, `#[renderer]`, and `#[help]` declarations.
+It must be placed **after** all your `dispatcher!` calls, `#[derive(Grouped, Wrap)]` type definitions, and `#[chain]`, `#[renderer]`, and `#[help]` declarations.
---
@@ -679,7 +689,6 @@ It must be placed **after** all your `dispatcher!`, `pack!`, `#[chain]`, `#[rend
Here's a complete, runnable program:
```rust
-use mingling::macros::pack;
use mingling::prelude::*;
use std::io::Write;
@@ -690,16 +699,17 @@ fn main() {
program.exec_and_exit();
}
-pack!(ResultGreeting = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let greeting = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting).into()
+ ResultGreeting(greeting).into()
}
#[renderer]
diff --git a/README.md b/README.md
index 91aa166..20947f2 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,8 @@ Mingling abstracts the behavior of a program's lifecycle into three phases: **Di
```rust
dispatcher!("current", EntryCurrent);
-pack!(StateNext = ());
+#[derive(Grouped, Wrap, Default)]
+pub struct StateNext(());
#[chain]
fn handle_current(_: EntryCurrent) -> StateNext {
@@ -66,22 +67,26 @@ use mingling::macros::buffer;
use mingling::prelude::*;
dispatcher!("calc", EntryCalculate);
-pack!(StateSumNumbers = Vec<i32>);
-pack!(ResultNumber = i32);
+
+#[derive(Grouped, Wrap)]
+pub struct StateSumNumbers(Vec<i32>);
+
+#[derive(Grouped, Wrap)]
+pub struct ResultNumber(i32);
// Entry: parse arguments and pass state to the calculation step
#[chain]
fn handle_calc(args: EntryCalculate) -> StateSumNumbers {
let numbers = args.pick(&arg![Vec<i32>]).unwrap();
- StateSumNumbers::new(numbers)
+ StateSumNumbers(numbers)
}
// Calculate: pass the result to the rendering step
#[chain]
fn handle_state_sum_numbers(sum: StateSumNumbers) -> ResultNumber {
- let numbers = sum.inner;
+ let numbers = sum.0;
let total: i32 = numbers.iter().sum();
- ResultNumber::new(total)
+ ResultNumber(total)
}
// Renderer: return the render result and let the framework handle output
diff --git a/docs/_zh_CN/pages/11-resource-system.md b/docs/_zh_CN/pages/11-resource-system.md
index 40d8cd9..b0fbe31 100644
--- a/docs/_zh_CN/pages/11-resource-system.md
+++ b/docs/_zh_CN/pages/11-resource-system.md
@@ -31,11 +31,12 @@ fn main() {
@@@#[derive(Default, Clone)]
@@@struct ResCurrentDir(String);
@@@dispatcher!("pwd", EntryPrintWorkingDir);
-@@@pack!(ResultPath = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultPath(String);
// 通过 &T 注入只读资源
#[chain]
fn handle_pwd(_args: EntryPrintWorkingDir, cwd: &ResCurrentDir) -> Next {
- ResultPath::new(cwd.0.clone()).to_render()
+ ResultPath(cwd.0.clone()).to_render()
}
#[renderer(buffer)]
@@ -53,7 +54,8 @@ fn render_path(result: ResultPath) {
@@@#[derive(Default, Clone)]
@@@struct ResVisitCount(u32);
@@@dispatcher!("visit", EntryVisit);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next {
counter.0 += 1;
@@ -74,7 +76,8 @@ Chain 可以同时注入任意多个资源,框架按类型自动匹配:
@@@#[derive(Default, Clone)] struct ResConfig(String);
@@@#[derive(Default, Clone)] struct ResCounter(u32);
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
// 同时注入只读 + 可修改
#[chain]
fn handle_test(_args: EntryTest, config: &ResConfig, counter: &mut ResCounter) -> Next {
diff --git a/docs/_zh_CN/pages/12-exit-code.md b/docs/_zh_CN/pages/12-exit-code.md
index 7c55b60..9270b84 100644
--- a/docs/_zh_CN/pages/12-exit-code.md
+++ b/docs/_zh_CN/pages/12-exit-code.md
@@ -3,9 +3,7 @@
如何使用资源系统管理程序退出码
</p>
-程序退出时给 shell 一个正确的退出码是 CLI 的基本素养
-
-。Mingling 提供了开箱即用的 `ExitCodeSetup`,配合 `ResExitCode` 资源,让退出码控制变得极其简单。
+程序退出时给 shell 一个正确的退出码是 CLI 的基本素养。Mingling 提供了开箱即用的 `ExitCodeSetup`,配合 `ResExitCode` 资源,让退出码控制变得极其简单。
## 启用 ExitCodeSetup
@@ -31,7 +29,8 @@ fn main() {
```rust
@@@use mingling::res::ResExitCode;
@@@use mingling::setup::ExitCodeSetup;
-@@@pack!(EntryCheck = Vec<String>);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct EntryCheck(Vec<String>);
#[chain]
fn handle_check(_args: EntryCheck, ec: &mut ResExitCode) {
// 检查失败的时候修改退出码资源
diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md
index 811957c..860bd2b 100644
--- a/docs/_zh_CN/pages/13-hook.md
+++ b/docs/_zh_CN/pages/13-hook.md
@@ -55,10 +55,11 @@ Hook 覆盖了管线的完整生命周期:
@@@use mingling::hook::ProgramHook;
@@@
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next {
-@@@ ResultName::new(args.inner.first().cloned().unwrap_or_default()).to_render()
+@@@ ResultName(args.0.first().cloned().unwrap_or_default()).to_render()
@@@}
@@@#[renderer] fn render_name(r: ResultName) -> RenderResult { RenderResult::new() }
fn main() {
diff --git a/docs/_zh_CN/pages/14-testing.md b/docs/_zh_CN/pages/14-testing.md
index 30f3fc4..13585ee 100644
--- a/docs/_zh_CN/pages/14-testing.md
+++ b/docs/_zh_CN/pages/14-testing.md
@@ -12,7 +12,8 @@ Chain 只是一个接收输入、返回输出的函数,Renderer 也只是接
Renderer 是最容易测试的——调用函数,断言返回结果:
```rust
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer]
fn render_greet(result: ResultName) -> RenderResult {
let mut r = RenderResult::new();
@@ -22,7 +23,7 @@ fn render_greet(result: ResultName) -> RenderResult {
#[test]
fn test_render_name() {
- let result = render_name(ResultName::new("Alice".to_string()));
+ let result = render_name(ResultName("Alice".to_string()));
assert_eq!(result.to_string().as_str(), "Hello, Alice!\n");
}
```
@@ -36,27 +37,29 @@ fn test_render_name() {
```rust
@@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process};
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
+@@@ let name = args.0.first().cloned().unwrap_or_default();
@@@ if name.is_empty() {
@@@ ErrorNoName::default().to_render()
@@@ } else {
-@@@ ResultName::new(name).to_render()
+@@@ ResultName(name).to_render()
@@@ }
@@@}
#[test]
fn test_handle_hello_with_name() {
- let chain_process = handle_hello(EntryGreet::new(vec!["Alice".to_string()])).into();
+ let chain_process = handle_hello(EntryHello(vec!["Alice".to_string()])).into();
// 断言这是一个渲染结果(不是继续 chain)
assert_render_result!(chain_process);
// 断言 member_id 是 ResultName
assert_member_id!(chain_process, ResultName);
// 解包出内部值
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -78,11 +81,12 @@ fn test_handle_hello_with_name() {
@@@use mingling::{assert_member_id, unpack_chain_process};
@@@use mingling::macros::entry;
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
-@@@ ResultName::new(name).to_render()
+@@@ let name = args.0.first().cloned().unwrap_or_default();
+@@@ ResultName(name).to_render()
@@@}
#[test]
fn test_with_entry_macro() {
@@ -90,7 +94,7 @@ fn test_with_entry_macro() {
let entry = entry!("--name", "Alice");
let chain_process = handle_hello(entry).into();
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -103,23 +107,24 @@ fn test_with_entry_macro() {
@@@#[derive(Default, Clone)]
@@@struct ResPrefix(String);
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultGreeting = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
@@@
#[chain]
fn handle_hello(args: EntryHello, prefix: &ResPrefix) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- ResultGreeting::new(format!("{}, {}", prefix.0, name)).to_render()
+ let name = args.0.first().cloned().unwrap_or_default();
+ ResultGreeting(format!("{}, {}", prefix.0, name)).to_render()
}
#[test]
fn test_handle_with_resource() {
// 资源需要在测试中手动传入
let result = handle_hello(
- EntryHello::new(vec!["World".to_string()]),
+ EntryHello(vec!["World".to_string()]),
&ResPrefix("Hello".to_string()),
);
let greeting = unpack_chain_process!(result, ResultGreeting, ThisProgram);
- assert_eq!(greeting.inner, "Hello, World");
+ assert_eq!(greeting.0, "Hello, World");
}
```
diff --git a/docs/_zh_CN/pages/2-define-a-dispatcher.md b/docs/_zh_CN/pages/2-define-a-dispatcher.md
index 7ae26f1..77a1513 100644
--- a/docs/_zh_CN/pages/2-define-a-dispatcher.md
+++ b/docs/_zh_CN/pages/2-define-a-dispatcher.md
@@ -48,17 +48,15 @@ dispatcher!("remote.rm", EntryRemoteRm);
```rust
// 示意,dispatcher! 宏实际生成的代码
-pub struct EntryGreet {
- pub inner: Vec<String>,
-}
+pub struct EntryGreet(pub Vec<String>);
```
-用户在命令行输入 `greet Alice Bob`,`EntryGreet.inner` 就是 `vec!["Alice", "Bob"]`。
+用户在命令行输入 `greet Alice Bob`,`EntryGreet` 包裹的 `Vec<String>`(即字段 `0`)就是 `vec!["Alice", "Bob"]`。
> [!IMPORTANT]
-> Entry 的 `inner` 只包含 **匹配后剩余的参数**。
+> Entry 包裹的参数只包含 **匹配后剩余的参数**。
>
-> 以 `remote add origin` 为例,`remote` 和 `add` 用于匹配命令路径,只有 `origin` 会进入 `EntryRemoteAdd.inner`。
+> 以 `remote add origin` 为例,`remote` 和 `add` 用于匹配命令路径,只有 `origin` 会进入 `EntryRemoteAdd`(即字段 `0`)。
## 进阶:隐式声明
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md
index c8d8a24..3356b74 100644
--- a/docs/_zh_CN/pages/3-define-a-chain.md
+++ b/docs/_zh_CN/pages/3-define-a-chain.md
@@ -17,14 +17,15 @@
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// args 就是用户输入经过匹配后剩下的参数
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
// 把结果包装成 Next,告诉调度器下一步去哪
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -32,7 +33,7 @@ fn handle_greet(args: EntryGreet) -> Next {
Chain 函数签名里写着它需要什么——`args: EntryGreet`
-然后用 `ResultName::new(name)` 返回一个新类型。
+然后用 `ResultName(name)` 返回一个新类型。
这个返回的 `Next` 会展开成 `impl Into<ChainProcess<ThisProgram>>`。
@@ -41,17 +42,30 @@ Chain 函数签名里写着它需要什么——`args: EntryGreet`
>
> 可以去 [任意输出机制](pages/concepts/3-any-output) 章节了解 `ChainProcess`。
-## `pack!` 宏
+## 用 `#[derive]` 定义管线类型
-你大概猜到了,`pack!(ResultName = String)` 定义了一个管线中传递的类型:
+你大概猜到了,`#[derive(Grouped, Wrap)] pub struct ResultName(String);` 定义了一个管线中传递的类型:
```rust
-// pack!(ResultName = String) 大概生成了这样的代码
+// 实际写代码时只需一行 #[derive(Grouped, Wrap)],它大概展开成下面这些实现:
-#[derive(Grouped)]
-pub struct ResultName {
- pub inner: String,
+pub struct ResultName(String);
+
+impl From<String> for ResultName {
+ fn from(inner: String) -> Self {
+ ResultName(inner)
+ }
+}
+
+impl std::ops::Deref for ResultName {
+ type Target = String;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
}
+
+// Grouped 生成 member_id() → ThisProgram::ResultName,
+// 赋予类型路由身份和 Into<ChainProcess> 转换。
```
你可以把它理解为一个 打了标签的 `String`。
@@ -59,7 +73,7 @@ pub struct ResultName {
调度器通过这个标签来精确路由,确保数据不会混淆 —— 比如发给 `RenderGreet` 的数据不会被误传给 `RenderError`。
> [!NOTE]
-> 与简单的类型别名 (`type`) 不同,`pack!` 会生成一个全新的类型,拥有独立的 `TypeId`。
+> 与简单的类型别名 (`type`) 不同,`#[derive(Grouped, Wrap)]` 会定义一个全新的类型,拥有独立的 `TypeId`。
命名上推荐这样的习惯:
@@ -70,25 +84,26 @@ pub struct ResultName {
| 最终结果 | `Result` + 描述 | `ResultGreetSomeone` |
| 错误 | `Error` + 描述 | `ErrorUserNotFound` |
-详见 [命名规范](pages/other/naming_rule),不过现在你只需要记住:**用 `pack!` 给你的数据取一个有意义的名字**。
+详见 [命名规范](pages/other/naming_rule),不过现在你只需要记住:**用 `#[derive(Grouped)]`(可搭配 `Wrap`)给你的数据取一个有意义的名字**。
## 从 Entry 中提取参数
-`EntryGreet` 的 `inner` 是一个 `Vec<String>`,你可以在 Chain 里自由地处理它:
+`EntryGreet` 包裹的 `Vec<String>`(即字段 `0`)你可以在 Chain 里自由地处理它:
```rust
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// 取第一个参数,没有就用默认值
let name = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -103,16 +118,17 @@ fn handle_greet(args: EntryGreet) -> Next {
dispatcher!("greet", EntryGreet);
// 2. 声明管线中的数据类型
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. 处理逻辑
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
fn main() {
diff --git a/docs/_zh_CN/pages/4-render-result.md b/docs/_zh_CN/pages/4-render-result.md
index e3e49f5..ad915ba 100644
--- a/docs/_zh_CN/pages/4-render-result.md
+++ b/docs/_zh_CN/pages/4-render-result.md
@@ -3,7 +3,7 @@
使用 renderer 宏声明渲染器,将结果输出
</p>
-现在,我们创建了 Dispatcher 和 Chain,也通过 `pack!` 产出了一个 Result 类型。最后一步:**把结果展示给用户**。
+现在,我们创建了 Dispatcher 和 Chain,也通过 `#[derive(Grouped, Wrap)]` 产出了一个 Result 类型。最后一步:**把结果展示给用户**。
## `#[renderer]` 宏
@@ -11,7 +11,8 @@
```rust
@@@use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -27,7 +28,8 @@ Renderer 接收 Chain 产出的结果,然后返回一个 `RenderResult`。在
```rust
use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -54,17 +56,18 @@ use mingling::macros::buffer;
// 1. 用 Dispatcher 声明命令
dispatcher!("greet", EntryGreet);
-// 2. 用 pack! 声明结果数据
-pack!(ResultName = String);
+// 2. 用 #[derive(Grouped, Wrap)] 声明结果数据
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. 用 Chain 处理逻辑
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
// 4. 用 Renderer 输出结果
@@ -122,10 +125,10 @@ use mingling::macros::buffer;
#[renderer(buffer)]
fn render_entry_fallback(err: EntryFallback) {
- if err.inner.is_empty() {
+ if err.0.is_empty() {
r_println!("Unknown command");
} else {
- r_println!("Command not found: \"{}\"", err.inner.join(" "));
+ r_println!("Command not found: \"{}\"", err.0.join(" "));
}
}
```
@@ -144,13 +147,13 @@ Command not found: "great"
你完成了第一个完整的 Mingling 程序!来回顾一下学到的东西:
-| 概念 | 对应宏/函数 | 一句话 |
-| -------- | ---------------- | -------------------------- |
-| 声明命令 | `dispatcher!` | 告诉程序用户能输入什么 |
-| 处理逻辑 | `#[chain]` | 收到参数后做什么 |
-| 输出结果 | `#[renderer]` | 怎么把结果告诉用户 |
-| 类型包装 | `pack!` | 给你的数据取个有意义的名字 |
-| 程序入口 | `gen_program!()` | 自动生成管线的接线图 |
+| 概念 | 对应宏/函数 | 一句话 |
+| -------- | -------------------------- | -------------------------- |
+| 声明命令 | `dispatcher!` | 告诉程序用户能输入什么 |
+| 处理逻辑 | `#[chain]` | 收到参数后做什么 |
+| 输出结果 | `#[renderer]` | 怎么把结果告诉用户 |
+| 类型包装 | `#[derive(Grouped, Wrap)]` | 给你的数据取个有意义的名字 |
+| 程序入口 | `gen_program!()` | 自动生成管线的接线图 |
在真实项目中你还会用到资源注入、hook、补全、REPL 等高级功能,不过核心骨架永远不变:**Dispatcher → Chain → Renderer**。
diff --git a/docs/_zh_CN/pages/5-multiple-commands.md b/docs/_zh_CN/pages/5-multiple-commands.md
index e0a58e7..9d9d37e 100644
--- a/docs/_zh_CN/pages/5-multiple-commands.md
+++ b/docs/_zh_CN/pages/5-multiple-commands.md
@@ -15,19 +15,21 @@
dispatcher!("greet", EntryGreet);
dispatcher!("add", EntryAdd);
-pack!(ResultGreeting = String);
-pack!(ResultSum = i32);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ResultSum(i32);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(name).into()
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
+ ResultGreeting(name).into()
}
#[chain]
fn handle_add(args: EntryAdd) -> Next {
- let sum: i32 = args.inner.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
- ResultSum::new(sum).into()
+ let sum: i32 = args.0.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
+ ResultSum(sum).into()
}
#[renderer(buffer)]
@@ -70,10 +72,10 @@ dispatcher!("remote.rm", EntryRemoteRm);
## 数据类型的独立性
-注意我们用了两个不同的 `pack!`:
+注意我们用了两个不同的类型:
-- `pack!(ResultGreeting = String)`
-- `pack!(ResultSum = i32)`
+- `#[derive(Grouped, Wrap)] pub struct ResultGreeting(String);`
+- `#[derive(Grouped, Wrap)] pub struct ResultSum(i32);`
它们都是独立的类型,`gen_program!()` 会给它们分配不同的枚举变体。
diff --git a/docs/_zh_CN/pages/6-argument-parse-picker.md b/docs/_zh_CN/pages/6-argument-parse-picker.md
index 7944d0a..6c88423 100644
--- a/docs/_zh_CN/pages/6-argument-parse-picker.md
+++ b/docs/_zh_CN/pages/6-argument-parse-picker.md
@@ -3,7 +3,7 @@
用 Picker 完成基本的参数解析
</p>
-前面教程中我们都是手动从 `EntryGreet.inner`(`Vec<String>`)中提取参数。
+前面教程中我们都是手动从 `EntryGreet.0`(`Vec<String>`)中提取参数。
```rust
@@@ fn main() {
@@ -27,14 +27,15 @@ features = ["picker"]
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -45,13 +46,14 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
-@@@ResultName::new(name).into()
+@@@ResultName(name).into()
@@@}
```
@@ -60,7 +62,8 @@ let name = prev
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -81,14 +84,15 @@ let name = prev
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![name: String, 'n'], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -99,7 +103,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -122,7 +127,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
```rust
// Features: ["picker"]
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultInfo = (String, u8, u32));
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultInfo((String, u8, u32));
#[chain]
fn handle_test_entry(prev: EntryTest) -> Next {
@@ -132,7 +138,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
.pick_or_default(&arg![id: u32, 'I'])
.unwrap();
- ResultInfo::new((name, age, id)).into()
+ ResultInfo((name, age, id)).into()
}
```
@@ -150,8 +156,10 @@ fn handle_test_entry(prev: EntryTest) -> Next {
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -162,7 +170,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.to_result()
);
- ResultName::new(name).into()
+ ResultName(name).into()
}
#[renderer(buffer)]
@@ -177,12 +185,13 @@ fn render_greet(result: ResultName) {
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::macros::route;
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result());
+let name = route!(args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result());
@@@ mingling::macros::empty_result!()
@@@ }
```
@@ -191,11 +200,12 @@ let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_cha
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() {
+let name = match args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result() {
Ok(r) => r,
Err(e) => return e,
};
@@ -210,7 +220,8 @@ let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chai
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -225,7 +236,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -237,7 +248,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
// Features: ["picker"]
@@@use mingling::picker::value::Flag;
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_entry(prev: EntryTest) -> Next {
@@ -280,12 +292,13 @@ impl SinglePickable for Address {
}
}
@@@dispatcher!("connect", EntryConnect);
-@@@pack!(ResultConnected = Address);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultConnected(Address);
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
let address: Address = prev.pick_or_default(&arg![Address]).unwrap();
- ResultConnected::new(address).into()
+ ResultConnected(address).into()
}
#[renderer(buffer)]
@@ -333,12 +346,13 @@ impl SinglePickable for Fruits {
}
}
@@@dispatcher!("eat", EntryEat);
-@@@pack!(ResultFruit = Fruits);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultFruit(Fruits);
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap();
- ResultFruit::new(fruit).into()
+ ResultFruit(fruit).into()
}
#[renderer(buffer)]
diff --git a/docs/_zh_CN/pages/9-error-handling.md b/docs/_zh_CN/pages/9-error-handling.md
index dce48d1..463b829 100644
--- a/docs/_zh_CN/pages/9-error-handling.md
+++ b/docs/_zh_CN/pages/9-error-handling.md
@@ -20,17 +20,19 @@
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
```
@@ -40,9 +42,11 @@ fn handle_greet(args: EntryGreet) -> Next {
```rust
@@@use mingling::macros::buffer;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultGreeting = String);
-@@@pack!(ErrorNameEmpty = String);
-@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting::new(args.inner.first().cloned().unwrap_or_default()).to_render() }
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ErrorNameEmpty(String);
+@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting(args.0.first().cloned().unwrap_or_default()).to_render() }
#[renderer(buffer)]
fn render_greet(result: ResultGreeting) {
@@ -63,16 +67,18 @@ fn render_error_name_empty(err: ErrorNameEmpty) {
@@@use mingling::macros::buffer;
dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
@@ -104,14 +110,14 @@ Hello, Alice!
Error: name is required
```
-## 关于 `pack_err!`
+## 关于错误类型
-如果你启用了 `extras`,还可以用 `pack_err!` 快速声明带有自动 `name` 字段的错误类型:
+不需要额外上下文、只起“标记”作用的错误类型,可以直接用 `#[derive(Grouped, Default)]` 声明:
```rust
// Features: ["extras"]
-pack_err!(ErrorNotFound);
-// 生成: struct ErrorNotFound { pub name: String }
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
```
详见 [特性列表](pages/other/features)。
diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
index f2e1717..f678e2f 100644
--- a/docs/_zh_CN/pages/advanced/2-structural-renderer.md
+++ b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
@@ -21,7 +21,7 @@ features = ["structural_renderer"]
## 基本用法
-启用 `StructuralRendererSetup` 后,用 `pack_structural!` 替代 `pack!` 来声明支持结构化输出的类型:
+启用 `StructuralRendererSetup` 后,用 `#[derive(StructuralData)]`(搭配 `serde::Serialize`、`Grouped` 和 `Wrap`)来声明支持结构化输出的类型:
```rust
// Features: ["structural_renderer"]
@@ -29,16 +29,18 @@ features = ["structural_renderer"]
// serde = "1"
@@@use mingling::macros::buffer;
@@@use mingling::setup::StructuralRendererSetup;
+@@@use mingling::StructuralData;
@@@dispatcher!("render", EntryRender);
-// pack_structural! 等价于 pack! + StructuralData
-pack_structural!(ResultInfo = (String, i32));
+// #[derive(Grouped, Wrap)] + StructuralData + serde::Serialize 等价于旧版的 pack_structural!
+#[derive(serde::Serialize, StructuralData, Grouped, Wrap)]
+pub struct ResultInfo((String, i32));
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
- ResultInfo::new((name, age)).into()
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ ResultInfo((name, age)).into()
}
#[renderer(buffer)]
@@ -61,7 +63,7 @@ fn render_info(r: ResultInfo) {
## 自定义输出结构
-`pack_structural!` 的默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Grouped)]` 手动定义类型:
+用 `#[derive(Grouped, Wrap)]` 包装的元组结构体默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Grouped)]` 手动定义类型:
```rust
// Features: ["structural_renderer"]
@@ -82,8 +84,8 @@ struct Info {
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
Info { name, age }.to_render()
}
diff --git a/docs/_zh_CN/pages/concepts/2-resource.md b/docs/_zh_CN/pages/concepts/2-resource.md
index 1052254..154fa7d 100644
--- a/docs/_zh_CN/pages/concepts/2-resource.md
+++ b/docs/_zh_CN/pages/concepts/2-resource.md
@@ -34,7 +34,8 @@
```rust
@@@ use mingling::res::ResExitCode;
-@@@ pack!(ErrorFileNotFound = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFileNotFound(());
#[chain]
fn handle_error_file_not_found(
error: ErrorFileNotFound,
diff --git a/docs/_zh_CN/pages/concepts/3-any-output.md b/docs/_zh_CN/pages/concepts/3-any-output.md
index 118e856..1a2164c 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(Grouped)]` 标记的类型都被分配到这个枚举的一个变体。
+每个被 `#[derive(Grouped)]`(可搭配 `Wrap`)标记的类型都被分配到这个枚举的一个变体。
## ChainProcess:数据 + 路由
@@ -48,7 +48,7 @@ trait Grouped<G> {
}
```
-当你用 `pack!(ResultName = String)` 时,宏自动为 `ResultName` 实现 `Grouped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
+当你用 `#[derive(Grouped, Wrap)] pub struct ResultName(String);` 时,派生宏自动为 `ResultName` 实现 `Grouped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
`to_chain()` 和 `to_render()` 本质上是 `AnyOutput` 的快捷方法,分别构造 `ChainProcess::Ok(any, Chain)` 和 `ChainProcess::Ok(any, Renderer)`。
@@ -66,7 +66,7 @@ trait Grouped<G> {
> [!TIP]
> 日常开发中你不需要手动操作 `AnyOutput` 或 `ChainProcess`。
>
-> `pack!`、`#[chain]`、`#[renderer]` 这些宏帮你处理了所有的包装和解包。
+> `#[derive(Grouped)]`、`#[chain]`、`#[renderer]` 这些宏帮你处理了所有的包装和解包。
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/_zh_CN/pages/concepts/4-program-collect.md b/docs/_zh_CN/pages/concepts/4-program-collect.md
index a0236f6..616f645 100644
--- a/docs/_zh_CN/pages/concepts/4-program-collect.md
+++ b/docs/_zh_CN/pages/concepts/4-program-collect.md
@@ -9,7 +9,7 @@
### 1. 生成枚举
-扫描当前模块中所有 `pack!`、`#[chain]`、`#[renderer]` 等宏标记的类型,为每个类型生成一个枚举变体。
+扫描当前模块中所有 `#[derive(Grouped)]`、`#[chain]`、`#[renderer]` 等宏标记的类型,为每个类型生成一个枚举变体。
这个枚举就是 `AnyOutput<G>` 中 `G` 的类型 —— 调度器靠枚举变体来区分管线中传递的不同数据。
diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md
index 30231ce..5fa7c86 100644
--- a/docs/_zh_CN/pages/other/features.md
+++ b/docs/_zh_CN/pages/other/features.md
@@ -80,11 +80,12 @@ features = ["build_full"]
```rust
// Features: ["async"]
-pack!(StateFoo = ());
+#[derive(Grouped, Wrap)]
+pub struct StateFoo(());
#[chain]
async fn handle_state_foo(foo: StateFoo) -> Next {
- StateFoo::new(()).into()
+ StateFoo(()).into()
}
```
@@ -151,14 +152,13 @@ build_comp_scripts("myprogram").unwrap();
例如,允许 `dispatcher!("greet")` 的缩写形式,自动生成 `CMDGreet` / `EntryGreet`。
-| 宏 | 说明 |
-| ------------------------------------------------------- | -------------------------------------- |
-| `empty_result!()` | 链中提前返回空结果的简写 |
-| `entry!(Type, ["a", "b"])` | 构造入口类型的测试数据 |
-| `group!(Type)` | 将外部类型注册为组成员,无需修改其定义 |
-| `pack_err!(ErrorType)` / `pack_err!(ErrorType = Inner)` | 创建带自动 `name` 字段的错误类型 |
-| `#[program_setup]` | 声明程序初始化函数 |
-| `dispatcher!("cmd.path")` **缩写形式** | 省略 `EntryStruct`,入口类型名自动推导 |
+| 宏 | 说明 |
+| -------------------------------------- | -------------------------------------- |
+| `empty_result!()` | 链中提前返回空结果的简写 |
+| `entry!(Type, ["a", "b"])` | 构造入口类型的测试数据 |
+| `group!(Type)` | 将外部类型注册为组成员,无需修改其定义 |
+| `#[program_setup]` | 声明程序初始化函数 |
+| `dispatcher!("cmd.path")` **缩写形式** | 省略 `EntryStruct`,入口类型名自动推导 |
<details>
<summary> Details </summary>
@@ -168,10 +168,13 @@ build_comp_scripts("myprogram").unwrap();
```rust
// Features: ["extras"]
-pack!(StatePrev1 = ());
-pack!(StatePrev2 = ());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev1(());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev2(());
-pack!(StateNext = ());
+#[derive(Grouped, Wrap)]
+pub struct StateNext(());
#[chain]
fn handle_state_prev2(_p: StatePrev2) {
@@ -186,7 +189,7 @@ fn handle_state_prev1(_p: StatePrev1) -> Next {
// 当需要 Next 且不需要返回值,便可以使用它
empty_result!()
} else {
- StateNext::new(()).into()
+ StateNext(()).into()
}
}
```
@@ -217,7 +220,8 @@ fn no_error_setup(program: &mut Program<ThisProgram>) {
// Features: ["extras"]
use mingling::macros::entry;
-pack!(EntryHello = Vec<String>);
+#[derive(Grouped, Wrap)]
+pub struct EntryHello(Vec<String>);
fn main() {
let result: Next = handle_hello(entry!("--name", "Bob")).into();
@@ -231,7 +235,7 @@ fn handle_hello(args: EntryHello) {}
### `group!`
将外部类型注册为程序组成员,无需修改原始类型的定义。
-类型名会直接作为枚举变体,与 `pack!` 或 `#[derive(Grouped)]` 一致。
+类型名会直接作为枚举变体,与 `#[derive(Grouped)]` 一致。
```rust
// Features: ["extras"]
@@ -243,26 +247,23 @@ use std::num::ParseIntError;
group!(std::num::ParseIntError);
```
-### `pack_err!`
+### 定义错误类型
-创建带自动 `name: String` 字段的错误结构体,字段值自动设为结构体名的蛇形命名。
-可选择包裹一个内部类型以携带额外上下文。
+0.5.0 起 `pack_err!` 已移除,错误类型直接用 derive 声明:
+不携带额外上下文时用 `#[derive(Grouped, Default)]`(仅作标记),或
+用 `#[derive(Grouped, Wrap)]` 包裹一个内部类型以携带上下文。
```rust
// Features: ["extras"]
use std::path::PathBuf;
-// 简单形式——仅包含 name 字段:
-pack_err!(ErrorNotFound);
-// 生成:
-// struct ErrorNotFound { pub name: String }
-// impl Default for ErrorNotFound { ... }
+// 简单形式——只作为标记使用:
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
-// 带类型的形式——包含额外的 info 字段:
-pack_err!(ErrorNotDir = PathBuf);
-// 生成:
-// struct ErrorNotDir { pub name: String, pub info: PathBuf }
-// impl ErrorNotDir { pub fn new(info: PathBuf) -> Self { ... } }
+// 带类型的形式——包裹一个内部类型以携带上下文:
+#[derive(Grouped, Wrap)]
+pub struct ErrorNotDir(PathBuf);
```
</details>
diff --git a/docs/_zh_CN/pages/other/naming_rule.md b/docs/_zh_CN/pages/other/naming_rule.md
index 1bca8f6..7694f56 100644
--- a/docs/_zh_CN/pages/other/naming_rule.md
+++ b/docs/_zh_CN/pages/other/naming_rule.md
@@ -94,7 +94,7 @@ Result + 内容
| `ResultGreetSomeone` | 问候结果 |
| `ResultFruitList` | 水果列表结果 |
-结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 代替 `pack!()` 包装,以获得更灵活的字段控制。
+结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 标注结构体,以获得更灵活的字段控制。
### 错误
@@ -146,7 +146,8 @@ Error + 描述
| 资源(可变) | `counter`、`cache`、`session` 等 |
```rust
-@@@ pack!(EntryRemoteAdd = Vec<String>);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct EntryRemoteAdd(Vec<String>);
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ #[derive(Default, Clone)]
@@ -168,9 +169,12 @@ fn handle_remote_add(args: EntryRemoteAdd, cwd: &ResCurrentDir, db: &mut ResData
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ impl ResDatabase { fn has_remote(&self, remote: &String) -> bool { true } }
-@@@ pack!(StateOperationRemotes = String);
-@@@ pack!(ResultRemoteAdded = String);
-@@@ pack!(ErrorRepositoryNotFound = String);
+@@@ #[derive(Grouped, Wrap, Default)]
+@@@ pub struct StateOperationRemotes(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ResultRemoteAdded(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorRepositoryNotFound(String);
// 分发器
dispatcher!("remote.add", EntryRemoteAdd);
@@ -183,23 +187,24 @@ fn handle_remote_add(args: EntryRemoteAdd) -> Next {
// 状态 → 错误或结果
#[chain]
fn handle_state_operation_remotes(state: StateOperationRemotes, db: &ResDatabase) -> Next {
- if db.has_remote(&state.inner) {
- ErrorRepositoryNotFound::new(state.inner).to_render()
+ if db.has_remote(&state.0) {
+ ErrorRepositoryNotFound(state.0).to_render()
} else {
- ResultRemoteAdded::new(state.inner).to_render()
+ ResultRemoteAdded(state.0).to_render()
}
}
// 结果渲染
+
#[renderer(buffer)]
fn render_remote_added(result: ResultRemoteAdded) {
- r_println!("Remote added: {}", result.inner);
+ r_println!("Remote added: {}", result.0);
}
// 错误渲染
#[renderer(buffer)]
fn render_error_repository_not_found(err: ErrorRepositoryNotFound) {
- r_println!("Error: remote '{}' not found", err.inner);
+ r_println!("Error: remote '{}' not found", err.0);
}
```
diff --git a/docs/example-pages/examples.json b/docs/example-pages/examples.json
index 8d6eaa6..38f73cd 100644
--- a/docs/example-pages/examples.json
+++ b/docs/example-pages/examples.json
@@ -273,23 +273,6 @@
]
},
{
- "id": "example-pack-err",
- "name": "Pack an Error",
- "icon": "🛑",
- "category": "macros",
- "desc": "Demonstrates how to use the `pack_err!` macro to define error types with automatic `name` field (snake_case at compile time) and optional `info` field. Also shows `--json` serialization when `structural_renderer` is enabled.\n",
- "tags": [
- "pack_err!",
- "extras",
- "structural_renderer",
- "--json"
- ],
- "files": [
- "src/main.rs",
- "Cargo.toml"
- ]
- },
- {
"id": "example-panic-unwind",
"name": "Panic Unwind",
"icon": "💥",
diff --git a/docs/pages/11-resource-system.md b/docs/pages/11-resource-system.md
index 3a4dc36..ef0d7b8 100644
--- a/docs/pages/11-resource-system.md
+++ b/docs/pages/11-resource-system.md
@@ -31,11 +31,12 @@ In a Chain or Renderer, simply declare the resource in the parameter list:
@@@#[derive(Default, Clone)]
@@@struct ResCurrentDir(String);
@@@dispatcher!("pwd", EntryPrintWorkingDir);
-@@@pack!(ResultPath = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultPath(String);
// Inject read-only resource via &T
#[chain]
fn handle_pwd(_args: EntryPrintWorkingDir, cwd: &ResCurrentDir) -> Next {
- ResultPath::new(cwd.0.clone()).to_render()
+ ResultPath(cwd.0.clone()).to_render()
}
#[renderer(buffer)]
@@ -53,7 +54,8 @@ Use `&mut T` to inject a mutable resource:
@@@#[derive(Default, Clone)]
@@@struct ResVisitCount(u32);
@@@dispatcher!("visit", EntryVisit);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next {
counter.0 += 1;
@@ -74,7 +76,8 @@ A Chain can inject any number of resources at once — the framework matches the
@@@#[derive(Default, Clone)] struct ResConfig(String);
@@@#[derive(Default, Clone)] struct ResCounter(u32);
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
// Inject both read-only and mutable resources
#[chain]
fn handle_test(_args: EntryTest, config: &ResConfig, counter: &mut ResCounter) -> Next {
diff --git a/docs/pages/12-exit-code.md b/docs/pages/12-exit-code.md
index 6828cde..8fa320c 100644
--- a/docs/pages/12-exit-code.md
+++ b/docs/pages/12-exit-code.md
@@ -29,7 +29,8 @@ In a Chain or Renderer, inject `ResExitCode` to modify the exit code:
```rust
@@@use mingling::res::ResExitCode;
@@@use mingling::setup::ExitCodeSetup;
-@@@pack!(EntryCheck = Vec<String>);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct EntryCheck(Vec<String>);
#[chain]
fn handle_check(_args: EntryCheck, ec: &mut ResExitCode) {
// Modify exit code when check fails
diff --git a/docs/pages/13-hook.md b/docs/pages/13-hook.md
index 90df379..6eefd12 100644
--- a/docs/pages/13-hook.md
+++ b/docs/pages/13-hook.md
@@ -55,10 +55,11 @@ Each hook callback receives a corresponding `Hook*Info` struct containing contex
@@@use mingling::hook::ProgramHook;
@@@
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@
@@@#[chain] fn handle_greet(args: EntryGreet) -> Next {
-@@@ ResultName::new(args.inner.first().cloned().unwrap_or_default()).to_render()
+@@@ ResultName(args.0.first().cloned().unwrap_or_default()).to_render()
@@@}
@@@#[renderer] fn render_name(r: ResultName) -> RenderResult { RenderResult::new() }
fn main() {
diff --git a/docs/pages/14-testing.md b/docs/pages/14-testing.md
index 9f6b6ed..f0413a7 100644
--- a/docs/pages/14-testing.md
+++ b/docs/pages/14-testing.md
@@ -12,7 +12,8 @@ A Chain is just a function that takes input and returns output; a Renderer is ju
Renderer is the easiest to test — call the function, assert the result:
```rust
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer]
fn render_greet(result: ResultName) -> RenderResult {
let mut r = RenderResult::new();
@@ -22,7 +23,7 @@ fn render_greet(result: ResultName) -> RenderResult {
#[test]
fn test_render_name() {
- let result = render_name(ResultName::new("Alice".to_string()));
+ let result = render_name(ResultName("Alice".to_string()));
assert_eq!(result.to_string().as_str(), "Hello, Alice!\n");
}
```
@@ -36,27 +37,29 @@ Testing a Chain is slightly more complex because its return value is `Next` (act
```rust
@@@use mingling::{assert_member_id, assert_render_result, unpack_chain_process};
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
+@@@ let name = args.0.first().cloned().unwrap_or_default();
@@@ if name.is_empty() {
@@@ ErrorNoName::default().to_render()
@@@ } else {
-@@@ ResultName::new(name).to_render()
+@@@ ResultName(name).to_render()
@@@ }
@@@}
#[test]
fn test_handle_hello_with_name() {
- let chain_process = handle_hello(EntryGreet::new(vec!["Alice".to_string()])).into();
+ let chain_process = handle_hello(EntryGreet(vec!["Alice".to_string()])).into();
// Asserts this is a render result (not continuing the chain)
assert_render_result!(chain_process);
// Asserts member_id is ResultName
assert_member_id!(chain_process, ResultName);
// Unpacks the inner value
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -78,11 +81,12 @@ If `extras` is enabled, you can use `entry!` to quickly construct an Entry:
@@@use mingling::{assert_member_id, unpack_chain_process};
@@@use mingling::macros::entry;
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_hello(args: EntryHello) -> Next {
-@@@ let name = args.inner.first().cloned().unwrap_or_default();
-@@@ ResultName::new(name).to_render()
+@@@ let name = args.0.first().cloned().unwrap_or_default();
+@@@ ResultName(name).to_render()
@@@}
#[test]
fn test_with_entry_macro() {
@@ -90,7 +94,7 @@ fn test_with_entry_macro() {
let entry = entry!("--name", "Alice");
let chain_process = handle_hello(entry).into();
let result_name = unpack_chain_process!(chain_process, ResultName);
- assert_eq!(result_name.inner, "Alice");
+ assert_eq!(result_name.0, "Alice");
}
```
@@ -103,23 +107,24 @@ If a Chain uses resources, you need to provide resource instances in the test:
@@@#[derive(Default, Clone)]
@@@struct ResPrefix(String);
@@@dispatcher!("hello", EntryHello);
-@@@pack!(ResultGreeting = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
@@@
#[chain]
fn handle_hello(args: EntryHello, prefix: &ResPrefix) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- ResultGreeting::new(format!("{}, {}", prefix.0, name)).to_render()
+ let name = args.0.first().cloned().unwrap_or_default();
+ ResultGreeting(format!("{}, {}", prefix.0, name)).to_render()
}
#[test]
fn test_handle_with_resource() {
// Resources need to be passed manually in tests
let result = handle_hello(
- EntryHello::new(vec!["World".to_string()]),
+ EntryHello(vec!["World".to_string()]),
&ResPrefix("Hello".to_string()),
);
let greeting = unpack_chain_process!(result, ResultGreeting, ThisProgram);
- assert_eq!(greeting.inner, "Hello, World");
+ assert_eq!(greeting.0, "Hello, World");
}
```
diff --git a/docs/pages/2-define-a-dispatcher.md b/docs/pages/2-define-a-dispatcher.md
index efd744d..5aafb19 100644
--- a/docs/pages/2-define-a-dispatcher.md
+++ b/docs/pages/2-define-a-dispatcher.md
@@ -48,17 +48,15 @@ You might be curious about what's inside `EntryGreet`. It's essentially a struct
```rust
// Illustration of code generated by the dispatcher! macro
-pub struct EntryGreet {
- pub inner: Vec<String>,
-}
+pub struct EntryGreet(pub Vec<String>);
```
-When the user types `greet Alice Bob` on the command line, `EntryGreet.inner` becomes `vec!["Alice", "Bob"]`.
+When the user types `greet Alice Bob` on the command line, `EntryGreet`'s `.0` becomes `vec!["Alice", "Bob"]`.
> [!IMPORTANT]
-> Entry's `inner` only contains **the remaining args after matching**.
+> Entry's `.0` only contains **the remaining args after matching**.
>
-> Take `remote add origin` as an example: `remote` and `add` are used for matching the command path, only `origin` goes into `EntryRemoteAdd.inner`.
+> Take `remote add origin` as an example: `remote` and `add` are used for matching the command path, only `origin` goes into `EntryRemoteAdd.0`.
## Advanced: Implicit Declaration
diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md
index 72ade94..ea39f6b 100644
--- a/docs/pages/3-define-a-chain.md
+++ b/docs/pages/3-define-a-chain.md
@@ -17,14 +17,15 @@ We need a Chain to process it.
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// args contains the remaining params after matching user input
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
// Wrap the result into Next, telling the dispatcher where to go next
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -32,7 +33,7 @@ Notice anything?
The Chain function signature declares what it needs — `args: EntryGreet`.
-Then it returns a newtype via `ResultName::new(name)`.
+Then it returns a newtype via `ResultName(name)`.
This returned `Next` expands into `impl Into<ChainProcess<ThisProgram>>`.
@@ -41,17 +42,30 @@ This returned `Next` expands into `impl Into<ChainProcess<ThisProgram>>`.
>
> Check out the [Any Output Mechanism](pages/concepts/3-any-output) chapter to learn about `ChainProcess`.
-## The `pack!` Macro
+## Declaring Types with `#[derive(Grouped, Wrap)]`
-You've probably guessed it — `pack!(ResultName = String)` defines a type that flows through the pipeline:
+You've probably guessed it — `#[derive(Grouped, Wrap)] pub struct ResultName(String);` defines a type that flows through the pipeline:
```rust
-// pack!(ResultName = String) generates code roughly like this
+// #[derive(Grouped, Wrap)] generates code roughly like this
-#[derive(Grouped)]
-pub struct ResultName {
- pub inner: String,
+pub struct ResultName(String);
+
+impl From<String> for ResultName {
+ fn from(inner: String) -> Self {
+ ResultName(inner)
+ }
+}
+
+impl std::ops::Deref for ResultName {
+ type Target = String;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
}
+
+// Grouped generates member_id() → ThisProgram::ResultName,
+// giving the type its routing identity and Into<ChainProcess> conversion.
```
Think of it as a **tagged** `String`.
@@ -59,7 +73,7 @@ Think of it as a **tagged** `String`.
The dispatcher uses this tag for precise routing, ensuring data doesn't get mixed up — e.g., data sent to `RenderGreet` won't be misdelivered to `RenderError`.
> [!NOTE]
-> Unlike a simple type alias (`type`), `pack!` generates a completely new type with its own `TypeId`.
+> Unlike a simple type alias (`type`), `#[derive(Grouped, Wrap)]` declares a completely new type with its own `TypeId`.
Here's a recommended naming convention:
@@ -70,25 +84,26 @@ Here's a recommended naming convention:
| Result | `Result` + description | `ResultGreetSomeone` |
| Error | `Error` + description | `ErrorUserNotFound` |
-See [Naming Convention](pages/other/naming_rule) for details, but for now just remember: **use `pack!` to give your data a meaningful name**.
+See [Naming Convention](pages/other/naming_rule) for details, but for now just remember: **use `#[derive(Grouped)]` (optionally with `Wrap`) to give your data a meaningful name**.
## Extracting Params from Entry
-`EntryGreet`'s `inner` is a `Vec<String>`, which you can freely process inside a Chain:
+`EntryGreet`'s `.0` is a `Vec<String>`, which you can freely process inside a Chain:
```rust
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
// Take the first param, or use a default
let name = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -103,16 +118,17 @@ Now let's connect the Dispatcher and Chain:
dispatcher!("greet", EntryGreet);
// 2. Declare the pipeline data type
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. Processing logic
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
fn main() {
diff --git a/docs/pages/4-render-result.md b/docs/pages/4-render-result.md
index fdf8b12..70ffc96 100644
--- a/docs/pages/4-render-result.md
+++ b/docs/pages/4-render-result.md
@@ -3,7 +3,7 @@
Declare a renderer using the <code>#[renderer]</code> macro to output results.
</p>
-Now we've created a Dispatcher and a Chain, and produced a Result type via `pack!`. The final step: **present the result to the user**.
+Now we've created a Dispatcher and a Chain, and produced a Result type via `#[derive(Grouped, Wrap)]`. The final step: **present the result to the user**.
## The `#[renderer]` Macro
@@ -11,7 +11,8 @@ Similar to `#[chain]`, `#[renderer]` marks a function that produces output:
```rust
@@@use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -27,7 +28,8 @@ If you find explicitly creating and returning a `RenderResult` too verbose, you
```rust
use mingling::macros::buffer;
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[renderer(buffer)]
fn render_name(name: ResultName) {
r_println!("Hello, {}!", *name);
@@ -54,17 +56,18 @@ use mingling::macros::buffer;
// 1. Declare commands with a Dispatcher
dispatcher!("greet", EntryGreet);
-// 2. Declare result data with pack!
-pack!(ResultName = String);
+// 2. Declare result data with #[derive(Grouped, Wrap)]
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// 3. Handle logic with a Chain
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner
+ let name = args.0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name).into()
+ ResultName(name).into()
}
// 4. Output results with a Renderer
@@ -122,10 +125,10 @@ use mingling::macros::buffer;
#[renderer(buffer)]
fn render_entry_fallback(err: EntryFallback) {
- if err.inner.is_empty() {
+ if err.0.is_empty() {
r_println!("Unknown command");
} else {
- r_println!("Command not found: \"{}\"", err.inner.join(" "));
+ r_println!("Command not found: \"{}\"", err.0.join(" "));
}
}
```
@@ -144,13 +147,13 @@ Command not found: "great"
You've completed your first full Mingling program! Let's recap what you've learned:
-| Concept | Macro / Function | One-liner |
-| -------------- | ---------------- | --------------------------------------- |
-| Declare cmds | `dispatcher!` | Tell the program what the user can type |
-| Handle logic | `#[chain]` | What to do when args are received |
-| Output results | `#[renderer]` | How to present results to the user |
-| Type wrapping | `pack!` | Give your data a meaningful name |
-| Program entry | `gen_program!()` | Auto-generate the pipeline wiring |
+| Concept | Macro / Function | One-liner |
+| -------------- | -------------------------- | --------------------------------------- |
+| Declare cmds | `dispatcher!` | Tell the program what the user can type |
+| Handle logic | `#[chain]` | What to do when args are received |
+| Output results | `#[renderer]` | How to present results to the user |
+| Type wrapping | `#[derive(Grouped, Wrap)]` | Give your data a meaningful name |
+| Program entry | `gen_program!()` | Auto-generate the pipeline wiring |
In real projects you'll also use advanced features like resource injection, hooks, completions, REPL, etc., but the core skeleton stays the same: **Dispatcher → Chain → Renderer**.
diff --git a/docs/pages/5-multiple-commands.md b/docs/pages/5-multiple-commands.md
index a5c09b0..9979cd1 100644
--- a/docs/pages/5-multiple-commands.md
+++ b/docs/pages/5-multiple-commands.md
@@ -15,19 +15,21 @@ Work in the same project:
dispatcher!("greet", EntryGreet);
dispatcher!("add", EntryAdd);
-pack!(ResultGreeting = String);
-pack!(ResultSum = i32);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ResultSum(i32);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(name).into()
+ let name = args.0.first().cloned().unwrap_or_else(|| "World".to_string());
+ ResultGreeting(name).into()
}
#[chain]
fn handle_add(args: EntryAdd) -> Next {
- let sum: i32 = args.inner.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
- ResultSum::new(sum).into()
+ let sum: i32 = args.0.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
+ ResultSum(sum).into()
}
#[renderer(buffer)]
@@ -70,10 +72,10 @@ Each subcommand's Entry, Chain, and Renderer are completely independent and don'
## Type Independence
-Notice we used two different `pack!` macros:
+Notice we used two different `#[derive(Grouped, Wrap)]` structs:
-- `pack!(ResultGreeting = String)`
-- `pack!(ResultSum = i32)`
+- `#[derive(Grouped, Wrap)] pub struct ResultGreeting(String);`
+- `#[derive(Grouped, Wrap)] pub struct ResultSum(i32);`
They are independent types, and `gen_program!()` assigns them different enum variants.
diff --git a/docs/pages/6-argument-parse-picker.md b/docs/pages/6-argument-parse-picker.md
index da0fff1..9fe74eb 100644
--- a/docs/pages/6-argument-parse-picker.md
+++ b/docs/pages/6-argument-parse-picker.md
@@ -3,7 +3,7 @@
Use Picker to perform basic argument parsing
</p>
-In previous tutorials, we manually extracted parameters from `EntryGreet.inner` (`Vec<String>`).
+In previous tutorials, we manually extracted parameters from `EntryGreet.0` (`Vec<String>`).
```rust
@@@ fn main() {
@@ -27,14 +27,15 @@ Now let's see how `Picker` is written:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -45,13 +46,14 @@ For the code above:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![String], || "World".to_string())
.unwrap();
-@@@ResultName::new(name).into()
+@@@ResultName(name).into()
@@@}
```
@@ -60,7 +62,8 @@ Its semantics are:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -81,14 +84,15 @@ If your program needs to parse flag arguments (e.g. `greet --name Alice`), decla
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev
.pick_or(&arg![name: String, 'n'], || "World".to_string())
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -99,7 +103,8 @@ Its semantics:
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) {
@@@let name: String =
@@ -122,7 +127,8 @@ For a single pick, `.unwrap()` returns the value directly; for multiple picks, i
```rust
// Features: ["picker"]
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultInfo = (String, u8, u32));
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultInfo((String, u8, u32));
#[chain]
fn handle_test_entry(prev: EntryTest) -> Next {
@@ -132,7 +138,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
.pick_or_default(&arg![id: u32, 'I'])
.unwrap();
- ResultInfo::new((name, age, id)).into()
+ ResultInfo((name, age, id)).into()
}
```
@@ -150,8 +156,10 @@ Here's a simple example:
@@@use mingling::macros::buffer;
@@@use mingling::macros::route;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
-@@@pack!(ErrorNoName = ());
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ErrorNoName(());
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -162,7 +170,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.to_result()
);
- ResultName::new(name).into()
+ ResultName(name).into()
}
#[renderer(buffer)]
@@ -177,12 +185,13 @@ However, **Mingling**'s `extras` feature provides the `route!` macro for simplif
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::macros::route;
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = route!(args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result());
+let name = route!(args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result());
@@@ mingling::macros::empty_result!()
@@@ }
```
@@ -191,11 +200,12 @@ It expands to:
```rust
// Features: ["picker", "extras"]
-@@@ pack!(ErrorFail = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFail(());
@@@ use mingling::picker::IntoPicker;
@@@ fn func() -> mingling::ChainProcess<ThisProgram> {
@@@ let args: Vec<String> = vec![];
-let name = match args.pick_or_route(&arg![String], || ErrorFail::new(()).to_chain()).to_result() {
+let name = match args.pick_or_route(&arg![String], || ErrorFail(()).to_chain()).to_result() {
Ok(r) => r,
Err(e) => return e,
};
@@ -210,7 +220,8 @@ After picking user input with `pick`, you can use `post` to process it immediate
```rust
// Features: ["picker"]
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultName = String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultName(String);
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
@@ -225,7 +236,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.unwrap();
- ResultName::new(name).into()
+ ResultName(name).into()
}
```
@@ -237,7 +248,8 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
// Features: ["picker"]
@@@use mingling::picker::value::Flag;
@@@dispatcher!("test", EntryTest);
-@@@pack!(ResultDone = ());
+@@@#[derive(Grouped, Wrap, Default)]
+@@@pub struct ResultDone(());
#[chain]
fn handle_entry(prev: EntryTest) -> Next {
@@ -280,12 +292,13 @@ impl SinglePickable for Address {
}
}
@@@dispatcher!("connect", EntryConnect);
-@@@pack!(ResultConnected = Address);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultConnected(Address);
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
let address: Address = prev.pick_or_default(&arg![Address]).unwrap();
- ResultConnected::new(address).into()
+ ResultConnected(address).into()
}
#[renderer(buffer)]
@@ -333,12 +346,13 @@ impl SinglePickable for Fruits {
}
}
@@@dispatcher!("eat", EntryEat);
-@@@pack!(ResultFruit = Fruits);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultFruit(Fruits);
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
let fruit: Fruits = prev.pick_or_default(&arg![Fruits]).unwrap();
- ResultFruit::new(fruit).into()
+ ResultFruit(fruit).into()
}
#[renderer(buffer)]
diff --git a/docs/pages/9-error-handling.md b/docs/pages/9-error-handling.md
index eefc0f0..0cacf01 100644
--- a/docs/pages/9-error-handling.md
+++ b/docs/pages/9-error-handling.md
@@ -20,17 +20,19 @@ Error values can also take either path—you can render the error msg directly,
```rust
@@@dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
```
@@ -40,9 +42,11 @@ Then write separate Renderers:
```rust
@@@use mingling::macros::buffer;
@@@dispatcher!("greet", EntryGreet);
-@@@pack!(ResultGreeting = String);
-@@@pack!(ErrorNameEmpty = String);
-@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting::new(args.inner.first().cloned().unwrap_or_default()).to_render() }
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ResultGreeting(String);
+@@@#[derive(Grouped, Wrap)]
+@@@pub struct ErrorNameEmpty(String);
+@@@#[chain] fn handle_greet(args: EntryGreet) -> Next { ResultGreeting(args.0.first().cloned().unwrap_or_default()).to_render() }
#[renderer(buffer)]
fn render_greet(result: ResultGreeting) {
@@ -63,16 +67,18 @@ Each Renderer does its own job; what the user sees depends on what the Chain ret
@@@use mingling::macros::buffer;
dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
-pack!(ErrorNameEmpty = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameEmpty(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
+ let name = args.0.first().cloned().unwrap_or_default();
if name.is_empty() {
- ErrorNameEmpty::new("name is required".to_string()).to_render()
+ ErrorNameEmpty("name is required".to_string()).to_render()
} else {
- ResultGreeting::new(name).to_render()
+ ResultGreeting(name).to_render()
}
}
@@ -104,14 +110,14 @@ Hello, Alice!
Error: name is required
```
-## About `pack_err!`
+## Declaring Error Types
-If you've enabled `extras`, you can use `pack_err!` to quickly declare an error type with an auto-generated `name` field:
+You can use `#[derive(Grouped, Default)]` to quickly declare an error type with no payload:
```rust
// Features: ["extras"]
-pack_err!(ErrorNotFound);
-// Generates: struct ErrorNotFound { pub name: String }
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
```
See [Feature List](pages/other/features) for details.
diff --git a/docs/pages/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md
index e444ee6..23c7ec3 100644
--- a/docs/pages/advanced/2-structural-renderer.md
+++ b/docs/pages/advanced/2-structural-renderer.md
@@ -21,7 +21,7 @@ For more formats, enable `structural_renderer_full` (includes JSON, YAML, TOML,
## Basic Usage
-After enabling `StructuralRendererSetup`, use `pack_structural!` instead of `pack!` to declare types that support structured output:
+After enabling `StructuralRendererSetup`, use `#[derive(StructuralData, Grouped, Wrap)]` to declare types that support structured output:
```rust
// Features: ["structural_renderer"]
@@ -29,16 +29,18 @@ After enabling `StructuralRendererSetup`, use `pack_structural!` instead of `pac
// serde = "1"
@@@use mingling::macros::buffer;
@@@use mingling::setup::StructuralRendererSetup;
+@@@use mingling::StructuralData;
@@@dispatcher!("render", EntryRender);
-// pack_structural! is equivalent to pack! + StructuralData
-pack_structural!(ResultInfo = (String, i32));
+// StructuralData + Grouped + Wrap gives the type structured output support
+#[derive(serde::Serialize, StructuralData, Grouped, Wrap)]
+pub struct ResultInfo((String, i32));
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
- ResultInfo::new((name, age)).into()
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ ResultInfo((name, age)).into()
}
#[renderer(buffer)]
@@ -61,7 +63,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, Grouped)]`:
+The default output from a tuple newtype (e.g. `#[derive(StructuralData, Grouped, Wrap)]`) wraps the value under an `inner` key. For full control over the output structure, define the type manually with `#[derive(StructuralData, Serialize, Grouped)]`:
```rust
// Features: ["structural_renderer"]
@@ -82,8 +84,8 @@ struct Info {
#[chain]
fn handle_render(args: EntryRender) -> Next {
- let name = args.inner.first().cloned().unwrap_or_default();
- let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
+ let name = args.0.first().cloned().unwrap_or_default();
+ let age = args.0.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
Info { name, age }.to_render()
}
diff --git a/docs/pages/concepts/2-resource.md b/docs/pages/concepts/2-resource.md
index ad7ee16..6f0ef99 100644
--- a/docs/pages/concepts/2-resource.md
+++ b/docs/pages/concepts/2-resource.md
@@ -34,7 +34,8 @@ For example:
```rust
@@@ use mingling::res::ResExitCode;
-@@@ pack!(ErrorFileNotFound = ());
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorFileNotFound(());
#[chain]
fn handle_error_file_not_found(
error: ErrorFileNotFound,
diff --git a/docs/pages/concepts/3-any-output.md b/docs/pages/concepts/3-any-output.md
index f02805f..2b07906 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(Grouped)]` is assigned to one variant of this enum.
+Each type annotated with `#[derive(Grouped)]` (or `#[derive(Grouped, Wrap)]`) is assigned to one variant of this enum.
## ChainProcess: Data + Routing
@@ -48,7 +48,7 @@ trait Grouped<G> {
}
```
-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.
+When you write `#[derive(Grouped)]` on `ResultName`, the derive 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.
@@ -66,7 +66,7 @@ This mechanism ensures **type safety**: the dispatch code generated by `gen_prog
> [!TIP]
> In day-to-day dev, you don't need to manually touch `AnyOutput` or `ChainProcess`.
>
-> Macros like `pack!`, `#[chain]`, and `#[renderer]` handle all the wrapping and unwrapping for you.
+> Macros and derives like `#[derive(Grouped, Wrap)]`, `#[chain]`, and `#[renderer]` handle all the wrapping and unwrapping for you.
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
diff --git a/docs/pages/concepts/4-program-collect.md b/docs/pages/concepts/4-program-collect.md
index c5203c3..bc1fbc9 100644
--- a/docs/pages/concepts/4-program-collect.md
+++ b/docs/pages/concepts/4-program-collect.md
@@ -9,7 +9,7 @@ Every Mingling program ends with a `gen_program!()` call. Behind the scenes, it
### 1. Generate an enum
-Scans the current module for all types marked with `pack!`, `#[chain]`, `#[renderer]` and similar macros, then generates an enum variant for each type.
+Scans the current module for all types marked with `#[derive(Grouped)]`, `#[chain]`, `#[renderer]` and similar macros, then generates an enum variant for each type.
This enum is the type of `G` in `AnyOutput<G>` — the scheduler uses enum variants to distinguish different data flowing through the pipeline.
diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md
index 3779390..b2c0ea5 100644
--- a/docs/pages/other/features.md
+++ b/docs/pages/other/features.md
@@ -80,11 +80,12 @@ Enables async runtime support, allowing `#[chain]` to bind `async` functions, e.
```rust
// Features: ["async"]
-pack!(StateFoo = ());
+#[derive(Grouped, Wrap)]
+pub struct StateFoo(());
#[chain]
async fn handle_state_foo(foo: StateFoo) -> Next {
- StateFoo::new(()).into()
+ StateFoo(()).into()
}
```
@@ -151,14 +152,13 @@ Enables an additional set of macros, providing more convenient syntactic sugar a
For example, allows the shorthand form `dispatcher!("greet")`, which auto-generates `CMDGreet` / `EntryGreet`.
-| Macro | Description |
-| ------------------------------------------------------- | --------------------------------------------------------------- |
-| `empty_result!()` | Shorthand for returning an empty result early in a chain |
-| `entry!(Type, ["a", "b"])` | Construct test data for an entry type |
-| `group!(Type)` | Register external types as group members without modifying them |
-| `pack_err!(ErrorType)` / `pack_err!(ErrorType = Inner)` | Create error types with an automatic `name` field |
-| `#[program_setup]` | Declare a program initialization function |
-| `dispatcher!("cmd.path")` **shorthand** | Omit `EntryStruct`, the entry name is auto-derived |
+| Macro | Description |
+| --------------------------------------- | --------------------------------------------------------------- |
+| `empty_result!()` | Shorthand for returning an empty result early in a chain |
+| `entry!(Type, ["a", "b"])` | Construct test data for an entry type |
+| `group!(Type)` | Register external types as group members without modifying them |
+| `#[program_setup]` | Declare a program initialization function |
+| `dispatcher!("cmd.path")` **shorthand** | Omit `EntryStruct`, the entry name is auto-derived |
<details>
<summary> Details </summary>
@@ -168,10 +168,13 @@ For example, allows the shorthand form `dispatcher!("greet")`, which auto-genera
```rust
// Features: ["extras"]
-pack!(StatePrev1 = ());
-pack!(StatePrev2 = ());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev1(());
+#[derive(Grouped, Wrap)]
+pub struct StatePrev2(());
-pack!(StateNext = ());
+#[derive(Grouped, Wrap)]
+pub struct StateNext(());
#[chain]
fn handle_state_prev2(_p: StatePrev2) {
@@ -186,7 +189,7 @@ fn handle_state_prev1(_p: StatePrev1) -> Next {
// When Next is needed but no return value is required, use this
empty_result!()
} else {
- StateNext::new(()).into()
+ StateNext(()).into()
}
}
```
@@ -217,7 +220,8 @@ fn no_error_setup(program: &mut Program<ThisProgram>) {
// Features: ["extras"]
use mingling::macros::entry;
-pack!(EntryHello = Vec<String>);
+#[derive(Grouped, Wrap)]
+pub struct EntryHello(Vec<String>);
fn main() {
let result: Next = handle_hello(entry!("--name", "Bob")).into();
@@ -231,7 +235,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(Grouped)]`.
+The type's simple name is used as the enum variant, just like `#[derive(Grouped)]`.
```rust
// Features: ["extras"]
@@ -243,26 +247,23 @@ use std::num::ParseIntError;
group!(std::num::ParseIntError);
```
-### `pack_err!`
+### Declaring Error Types
-Creates an error struct with an automatic `name: String` field set to the snake_case
-of the struct name. Optionally wraps an inner type for additional context.
+Error types are declared with derives — the old `pack_err!` macro was removed in 0.5.0.
+Use `#[derive(Grouped, Default)]` for a unit error (no payload), or
+`#[derive(Grouped, Wrap)]` to wrap an inner type for additional context.
```rust
// Features: ["extras"]
use std::path::PathBuf;
-// Simple form — only a name field:
-pack_err!(ErrorNotFound);
-// Generates:
-// struct ErrorNotFound { pub name: String }
-// impl Default for ErrorNotFound { ... }
+// Unit form — no payload:
+#[derive(Grouped, Default)]
+pub struct ErrorNotFound;
-// Typed form — with additional info field:
-pack_err!(ErrorNotDir = PathBuf);
-// Generates:
-// struct ErrorNotDir { pub name: String, pub info: PathBuf }
-// impl ErrorNotDir { pub fn new(info: PathBuf) -> Self { ... } }
+// Typed form — wraps an inner type:
+#[derive(Grouped, Wrap)]
+pub struct ErrorNotDir(PathBuf);
```
</details>
diff --git a/docs/pages/other/naming_rule.md b/docs/pages/other/naming_rule.md
index 770fd10..fb678aa 100644
--- a/docs/pages/other/naming_rule.md
+++ b/docs/pages/other/naming_rule.md
@@ -94,7 +94,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(Grouped)]` 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 prefer a named-field struct with `#[derive(Grouped)]` over a single-field tuple wrapper (`#[derive(Grouped, Wrap)]`) for more flexible field control.
### Error
@@ -146,7 +146,8 @@ Error + Description
| Resource (mutable) | `counter`, `cache`, `session`, etc. |
```rust
-@@@ pack!(EntryRemoteAdd = Vec<String>);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct EntryRemoteAdd(Vec<String>);
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ #[derive(Default, Clone)]
@@ -168,9 +169,12 @@ fn handle_remote_add(args: EntryRemoteAdd, cwd: &ResCurrentDir, db: &mut ResData
@@@ #[derive(Default, Clone)]
@@@ struct ResDatabase { }
@@@ impl ResDatabase { fn has_remote(&self, remote: &String) -> bool { true } }
-@@@ pack!(StateOperationRemotes = String);
-@@@ pack!(ResultRemoteAdded = String);
-@@@ pack!(ErrorRepositoryNotFound = String);
+@@@ #[derive(Grouped, Wrap, Default)]
+@@@ pub struct StateOperationRemotes(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ResultRemoteAdded(String);
+@@@ #[derive(Grouped, Wrap)]
+@@@ pub struct ErrorRepositoryNotFound(String);
// Dispatcher
dispatcher!("remote.add", EntryRemoteAdd);
@@ -183,10 +187,10 @@ fn handle_remote_add(args: EntryRemoteAdd) -> Next {
// State → Error or Result
#[chain]
fn handle_state_operation_remotes(state: StateOperationRemotes, db: &ResDatabase) -> Next {
- if db.has_remote(&state.inner) {
- ErrorRepositoryNotFound::new(state.inner).to_render()
+ if db.has_remote(&state.0) {
+ ErrorRepositoryNotFound(state.0).to_render()
} else {
- ResultRemoteAdded::new(state.inner).to_render()
+ ResultRemoteAdded(state.0).to_render()
}
}
@@ -194,13 +198,13 @@ fn handle_state_operation_remotes(state: StateOperationRemotes, db: &ResDatabase
#[renderer(buffer)]
fn render_remote_added(result: ResultRemoteAdded) {
- r_println!("Remote added: {}", result.inner);
+ r_println!("Remote added: {}", result.0);
}
// Error rendering
#[renderer(buffer)]
fn render_error_repository_not_found(err: ErrorRepositoryNotFound) {
- r_println!("Error: remote '{}' not found", err.inner);
+ r_println!("Error: remote '{}' not found", err.0);
}
```
diff --git a/docs/res/changlog_examples/feat_program_res.rs b/docs/res/changlog_examples/feat_program_res.rs
index 5133000..e9cc4e2 100644
--- a/docs/res/changlog_examples/feat_program_res.rs
+++ b/docs/res/changlog_examples/feat_program_res.rs
@@ -22,11 +22,12 @@ fn main() {
dispatcher!("modify", EntryModify);
-pack!(DisplayGlobal = ());
+#[derive(Grouped, Wrap)]
+pub struct DisplayGlobal(());
#[chain]
fn modify(prev: EntryModify) {
- let (name, age) = Picker::<()>::new(prev.inner)
+ let (name, age) = Picker::<()>::new(prev.0)
.pick::<String>("--name")
.pick::<i32>("--age")
.unpack_directly();
diff --git a/examples/example-argument-picker/src/main.rs b/examples/example-argument-picker/src/main.rs
index 99eee30..aefb86e 100644
--- a/examples/example-argument-picker/src/main.rs
+++ b/examples/example-argument-picker/src/main.rs
@@ -44,17 +44,32 @@ use mingling::setup::picker::BasicProgramSetup;
dispatcher!("calc", EntryCalculate);
-pack_err!(ErrorNumberANotProvided);
-pack_err!(ErrorNumberBNotProvided);
-pack_err!(ErrorNumberOperatorNotProvided);
-pack_err!(ErrorDivisionByZero);
+#[derive(Grouped, Default)]
+pub struct ErrorNumberANotProvided;
-pack!(StateAdd = (f32, f32));
-pack!(StateSubtract = (f32, f32));
-pack!(StateMultiply = (f32, f32));
-pack!(StateDivide = (f32, f32));
+#[derive(Grouped, Default)]
+pub struct ErrorNumberBNotProvided;
-pack!(ResultNumber = f32);
+#[derive(Grouped, Default)]
+pub struct ErrorNumberOperatorNotProvided;
+
+#[derive(Grouped, Default)]
+pub struct ErrorDivisionByZero;
+
+#[derive(Grouped, Wrap)]
+pub struct StateAdd((f32, f32));
+
+#[derive(Grouped, Wrap)]
+pub struct StateSubtract((f32, f32));
+
+#[derive(Grouped, Wrap)]
+pub struct StateMultiply((f32, f32));
+
+#[derive(Grouped, Wrap)]
+pub struct StateDivide((f32, f32));
+
+#[derive(Grouped, Wrap)]
+pub struct ResultNumber(f32);
#[derive(Grouped)]
struct StateCalculate {
@@ -135,13 +150,13 @@ fn handle_calc(args: EntryCalculate) -> Next {
// Use the arg! macro to define a positional argument of type f32
// |
// vvvvvvvvvv
- args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain())
+ args.pick_or_route(&arg![f32], || ErrorNumberANotProvided.to_chain())
.pick_or_route(&arg![Operator], || {
- ErrorNumberOperatorNotProvided::default().to_chain()
+ ErrorNumberOperatorNotProvided.to_chain()
}) // Returns a routable type when not found or fails to parse
// |
- // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
- .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain())
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+ .pick_or_route(&arg![f32], || ErrorNumberBNotProvided.to_chain())
// Use `to_result` to parse arguments
// and convert to Result<(Tuple, ...), Route> type
.to_result()
@@ -149,7 +164,7 @@ fn handle_calc(args: EntryCalculate) -> Next {
// --------- IMPORTANT ---------
if operator == Operator::Slash && number_b == 0. {
- return ErrorDivisionByZero::default().to_chain();
+ return ErrorDivisionByZero.to_chain();
}
StateCalculate {
@@ -163,41 +178,41 @@ fn handle_calc(args: EntryCalculate) -> Next {
#[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(),
+ (Operator::Plus, a, b) => StateAdd((a, b)).to_chain(),
+ (Operator::Dash, a, b) => StateSubtract((a, b)).to_chain(),
+ (Operator::Slash, a, b) => StateDivide((a, b)).to_chain(),
+ (Operator::Star, a, b) => StateMultiply((a, b)).to_chain(),
}
}
#[chain]
fn handle_state_add(state_add: StateAdd) -> ResultNumber {
- let (a, b) = state_add.inner;
- ResultNumber::new(a + b)
+ let (a, b) = state_add.0;
+ ResultNumber(a + b)
}
#[chain]
fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber {
- let (a, b) = state_subtract.inner;
- ResultNumber::new(a - b)
+ let (a, b) = state_subtract.0;
+ ResultNumber(a - b)
}
#[chain]
fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber {
- let (a, b) = state_multiply.inner;
- ResultNumber::new(a * b)
+ let (a, b) = state_multiply.0;
+ ResultNumber(a * b)
}
#[chain]
fn handle_state_divide(state_divide: StateDivide) -> ResultNumber {
- let (a, b) = state_divide.inner;
- ResultNumber::new(a / b)
+ let (a, b) = state_divide.0;
+ ResultNumber(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 };
+ let result = if round { result.round() } else { result.0 };
format!("Result: {}", result)
}
diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs
index 090602a..7e212d9 100644
--- a/examples/example-async-support/src/main.rs
+++ b/examples/example-async-support/src/main.rs
@@ -41,7 +41,8 @@ async fn main() {
dispatcher!("download", EntryDownload);
-pack!(ResultDownloaded = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultDownloaded(String);
// --------- IMPORTANT ---------
#[chain]
@@ -65,5 +66,5 @@ gen_program!();
async fn fake_download(file_name: String) -> ResultDownloaded {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
- ResultDownloaded::new(file_name)
+ ResultDownloaded(file_name)
}
diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs
index 44736ad..ce41b2d 100644
--- a/examples/example-basic/src/main.rs
+++ b/examples/example-basic/src/main.rs
@@ -17,11 +17,11 @@ use mingling::prelude::*;
use std::io::Write;
// Define the `greet` subcommand
-// _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm")
-// / _____________________ dispatcher name
-// | / _________ entry, records raw arguments
-// | | / ^^^^^^^^^^^^^
-// vvvvv vvvvvvvv vvvvvvvvvv \_ equivalent to pack!(EntryGreet = Vec<String>)
+// _________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm")
+// /
+// | _________ entry, records raw arguments
+// | / ^^^^^^^^^^^^^
+// vvvvv vvvvvvvvvv \_ a newtype wrapper around Vec<String>
dispatcher!("greet", EntryGreet);
fn main() {
@@ -33,21 +33,22 @@ fn main() {
}
// Quickly wrap a type into a type recognizable by the current program
-// ____________________ Wrapped type name
-// / _______ Wrapped type inner value
-// | /
-// vvvvvvvvvv vvvvvv
-pack!(ResultName = String);
+// ___________________ Registers this type into ThisProgram
+// / _______ Adds DerefMut, Deref, Into, From wrappers
+// | /
+// vvvvvvvvvv vvvvv
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// Define the `handle_greet` chain for parsing input text
// ____________________ Previous type:
// / Mingling deduces types at runtime and routes them to this function
// | _____ will be expanded to:
-// | / impl Into<mingling::ChainProcess<ThisProgram>>
+// | / ChainProcess<ThisProgram>
#[chain] // vvvvvvvvvv vvvv
fn handle_greet(args: EntryGreet) -> Next {
let name: ResultName = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string())
diff --git a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs
index 2b7aba9..b04c683 100644
--- a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs
+++ b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs
@@ -4,12 +4,13 @@ use std::io::Write;
dispatcher!("hello");
-pack!(ResultMessage = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultMessage(String);
#[chain]
pub fn handle_my(args: EntryHello) -> Next {
let name: ResultMessage = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string())
diff --git a/examples/example-combine-pathf-metadata/src/sub/mod.rs b/examples/example-combine-pathf-metadata/src/sub/mod.rs
index ba56285..38efa2f 100644
--- a/examples/example-combine-pathf-metadata/src/sub/mod.rs
+++ b/examples/example-combine-pathf-metadata/src/sub/mod.rs
@@ -7,6 +7,7 @@ use std::io::Write;
// Implicit dispatcher form — creates `CMDHello` / `EntryHello` in this module
dispatcher!("hello");
+
// Creates `CMDDescription` / `EntryDescription` in this module
dispatcher!("desc", EntryDescription);
@@ -27,14 +28,17 @@ pub fn hello_desc() -> Description {
}
}
-pack!(ResultName = String);
-pack!(DescResult = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
+
+#[derive(Grouped, Wrap)]
+pub struct DescResult(String);
/// Chain for `hello` — reads the name and produces a `ResultName`.
#[chain]
pub fn handle_hello(args: EntryHello) -> Next {
let name: ResultName = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string())
@@ -51,7 +55,7 @@ pub fn handle_desc(_args: EntryDescription) -> Next {
None => "EntryHello has no description".to_string(),
};
// --------- IMPORTANT ---------
- DescResult::new(msg).to_render()
+ DescResult(msg).to_render()
}
/// Renders the greeting message with the provided name.
diff --git a/examples/example-command-macro/src/main.rs b/examples/example-command-macro/src/main.rs
index e525098..6740518 100644
--- a/examples/example-command-macro/src/main.rs
+++ b/examples/example-command-macro/src/main.rs
@@ -22,27 +22,30 @@ fn main() {
ThisProgram::new().exec_and_exit();
}
-pack!(ResultGreeting = String);
-pack!(ResultGoodbye = ());
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
+
+#[derive(Grouped)]
+pub struct ResultGoodbye;
// --------- IMPORTANT ---------
// Auto-generates dispatcher!("hello.world", EntryHelloWorld);
#[command]
fn hello_world() -> ResultGreeting {
- ResultGreeting::new("World".to_string())
+ ResultGreeting("World".to_string())
}
// Auto-generates dispatcher!("hello-world", EntryGreetSomeone);
#[command(node = "greet-someone")]
fn greet_someone(args: Vec<String>) -> ResultGreeting {
let name = args.pick_or(&arg![String], || "World".to_string()).unwrap();
- ResultGreeting::new(name)
+ ResultGreeting(name)
}
// Auto-generates dispatcher!("goodbye", EntryGoodBye);
#[command(entry = EntryGoodBye)]
fn goodbye() -> ResultGoodbye {
- ResultGoodbye::default()
+ ResultGoodbye
}
// --------- IMPORTANT ---------
diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs
index d363697..e14326f 100644
--- a/examples/example-completion/src/main.rs
+++ b/examples/example-completion/src/main.rs
@@ -101,7 +101,8 @@ fn complete_greet_entry(ctx: &ShellContext) -> Suggest {
// --------- IMPORTANT ---------
dispatcher!("greet", EntryGreet);
-pack!(ResultName = (u8, String));
+#[derive(Grouped, Wrap)]
+pub struct ResultName((u8, String));
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
@@ -116,7 +117,7 @@ fn handle_greet(args: EntryGreet) -> Next {
/// Renders the greeting with the result name and repeat count.
#[renderer]
fn render_name(result: ResultName) -> RenderResult {
- let (repeat, name) = result.inner;
+ let (repeat, name) = result.0;
let mut render_result = RenderResult::new();
let mut parts = Vec::with_capacity(repeat as usize);
for _ in 0..repeat {
diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs
index 05b451b..ec7e6c9 100644
--- a/examples/example-error-handling/src/main.rs
+++ b/examples/example-error-handling/src/main.rs
@@ -29,35 +29,41 @@ use std::io::Write;
dispatcher!("hello", EntryHello);
// Define error types
-pack!(ErrorNoNameProvided = ());
-pack!(ErrorNameTooLong = u16);
-pack!(ErrorNameNotAvailable = ());
+#[derive(Grouped)]
+pub struct ErrorNoNameProvided;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameTooLong(u16);
+
+#[derive(Grouped)]
+pub struct ErrorNameNotAvailable;
// Define success type
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
// Pre-registered names
static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
#[chain]
fn handle_hello(args: EntryHello) -> Next {
- let Some(name) = args.inner.first().cloned() else {
+ let Some(name) = args.0.first().cloned() else {
// If no name is provided, pass ErrorNoNameProvided
- return ErrorNoNameProvided::default().to_render();
+ return ErrorNoNameProvided.to_render();
};
if name.len() > 10 {
// If the name is too long, pass ErrorNameTooLong
- return ErrorNameTooLong::new(name.len() as u16).to_render();
+ return ErrorNameTooLong(name.len() as u16).to_render();
}
if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
// If the name already exists, pass ErrorNameNotAvailable
- return ErrorNameNotAvailable::default().to_render();
+ return ErrorNameNotAvailable.to_render();
}
// If the name is valid, pass ResultName
- ResultName::new(name).to_render()
+ ResultName(name).to_render()
}
/// Renders a successful greeting with the given name.
@@ -96,12 +102,7 @@ fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult {
#[renderer]
fn render_entry_fallback(err: EntryFallback) -> RenderResult {
let mut render_result = RenderResult::new();
- writeln!(
- render_result,
- "Command not found: \"{}\"",
- err.inner.join(" ")
- )
- .ok();
+ writeln!(render_result, "Command not found: \"{}\"", err.0.join(" ")).ok();
render_result
}
diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs
index d3e035e..c7f731d 100644
--- a/examples/example-exitcode/src/main.rs
+++ b/examples/example-exitcode/src/main.rs
@@ -36,18 +36,21 @@ fn main() {
dispatcher!("hello", EntryHello);
-pack!(ErrorNoNameProvided = ());
-pack!(ResultName = String);
+#[derive(Grouped)]
+pub struct ErrorNoNameProvided;
+
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
fn handle_hello(args: EntryHello) -> Next {
- let Some(name) = args.inner.first().cloned() else {
+ let Some(name) = args.0.first().cloned() else {
// If no name is provided, pass ErrorNoNameProvided
- return ErrorNoNameProvided::default().to_render();
+ return ErrorNoNameProvided.to_render();
};
// If the name is valid, pass ResultName
- ResultName::new(name).to_render()
+ ResultName(name).to_render()
}
/// Renders a successful greeting with the given name.
diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs
index 1807e5e..721cea0 100644
--- a/examples/example-hook/src/main.rs
+++ b/examples/example-hook/src/main.rs
@@ -53,12 +53,13 @@ fn main() {
program.exec_and_exit();
}
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let name: ResultName = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string())
diff --git a/examples/example-lazy-resources/src/main.rs b/examples/example-lazy-resources/src/main.rs
index cc1604a..2243f14 100644
--- a/examples/example-lazy-resources/src/main.rs
+++ b/examples/example-lazy-resources/src/main.rs
@@ -52,7 +52,8 @@ fn init_res_large_data() -> ResLargeData {
dispatcher!("show", EntryShow);
dispatcher!("none", EntryNone);
-pack!(ResultShow = BTreeMap<Key, Value>);
+#[derive(Grouped, Wrap)]
+pub struct ResultShow(BTreeMap<Key, Value>);
fn main() {
let mut program = ThisProgram::new();
diff --git a/examples/example-metadata/src/main.rs b/examples/example-metadata/src/main.rs
index 94facb4..99a8aae 100644
--- a/examples/example-metadata/src/main.rs
+++ b/examples/example-metadata/src/main.rs
@@ -56,14 +56,17 @@ pub fn greet_desc() -> Description {
}
// --------- IMPORTANT ---------
-pack!(ResultName = String);
-pack!(DescResult = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
+
+#[derive(Grouped, Wrap)]
+pub struct DescResult(String);
/// Chain for `greet` — reads the name and produces a `ResultName`.
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let name: ResultName = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string())
@@ -81,7 +84,7 @@ fn handle_desc(_args: EntryDescription) -> Next {
None => "EntryGreet has no description".to_string(),
};
// --------- IMPORTANT ---------
- DescResult::new(msg).to_render()
+ DescResult(msg).to_render()
}
/// Chain for `nodoc` — asks for metadata on an entry that has none.
@@ -94,7 +97,7 @@ fn handle_nodoc(_args: EntryNoDescription) -> Next {
None => "EntryDescription has no description".to_string(),
};
// --------- IMPORTANT ---------
- DescResult::new(msg).to_render()
+ DescResult(msg).to_render()
}
/// Renders the greeting message with the provided name.
diff --git a/examples/example-outside-type/src/main.rs b/examples/example-outside-type/src/main.rs
index a04727f..8721d9a 100644
--- a/examples/example-outside-type/src/main.rs
+++ b/examples/example-outside-type/src/main.rs
@@ -43,7 +43,8 @@ group!(ErrorIo = std::io::Error);
// you can use this syntax to create an alias simultaneously
// --------- IMPORTANT ---------
-pack!(ParsedNumber = i32);
+#[derive(Grouped, Wrap)]
+pub struct ParsedNumber(i32);
/// Parse the first argument as an `i32`
///
@@ -51,9 +52,9 @@ pack!(ParsedNumber = i32);
/// On failure, routes to `render_parse_error` via the registered outside type.
#[chain]
fn parse_number(args: EntryParse) -> Next {
- let input = args.inner.first().cloned().unwrap_or_default();
+ let input = args.0.first().cloned().unwrap_or_default();
match input.parse::<i32>() {
- Ok(num) => ParsedNumber::new(num).to_chain(),
+ Ok(num) => ParsedNumber(num).to_chain(),
Err(e) => e.to_chain(),
}
}
diff --git a/examples/example-pack-err/Cargo.lock b/examples/example-pack-err/Cargo.lock
deleted file mode 100644
index bcfbb41..0000000
--- a/examples/example-pack-err/Cargo.lock
+++ /dev/null
@@ -1,235 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "example-pack-err"
-version = "0.1.0"
-dependencies = [
- "mingling",
- "serde",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.17.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
-
-[[package]]
-name = "indexmap"
-version = "2.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
-name = "itoa"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
-
-[[package]]
-name = "just_fmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
-
-[[package]]
-name = "memchr"
-version = "2.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
-
-[[package]]
-name = "might_be_async"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eca871cea620b07bd2e6da0c883891a25bead698c43a9ab64b0fd663a7a78d5f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "toml",
-]
-
-[[package]]
-name = "mingling"
-version = "0.5.0"
-dependencies = [
- "mingling_core",
- "mingling_macros",
- "serde",
-]
-
-[[package]]
-name = "mingling_core"
-version = "0.5.0"
-dependencies = [
- "just_fmt",
- "might_be_async",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "mingling_macros"
-version = "0.5.0"
-dependencies = [
- "just_fmt",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "serde"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
-dependencies = [
- "serde_core",
- "serde_derive",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.150"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
-dependencies = [
- "itoa",
- "memchr",
- "serde",
- "serde_core",
- "zmij",
-]
-
-[[package]]
-name = "serde_spanned"
-version = "0.6.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.118"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "toml"
-version = "0.8.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
-dependencies = [
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_edit",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.22.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
-dependencies = [
- "indexmap",
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_write",
- "winnow",
-]
-
-[[package]]
-name = "toml_write"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-
-[[package]]
-name = "winnow"
-version = "0.7.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "zmij"
-version = "1.0.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/examples/example-pack-err/Cargo.toml b/examples/example-pack-err/Cargo.toml
deleted file mode 100644
index ddab6fd..0000000
--- a/examples/example-pack-err/Cargo.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-[package]
-name = "example-pack-err"
-version = "0.1.0"
-edition = "2024"
-
-[dependencies]
-serde = { version = "1.0.228", features = ["derive"] }
-
-[dependencies.mingling]
-path = "../../mingling"
-features = [
- "structural_renderer",
- "extras",
-]
-
-[workspace]
diff --git a/examples/example-pack-err/page.toml b/examples/example-pack-err/page.toml
deleted file mode 100644
index 7423087..0000000
--- a/examples/example-pack-err/page.toml
+++ /dev/null
@@ -1,10 +0,0 @@
-[example]
-id = "example-pack-err"
-name = "Pack an Error"
-icon = "🛑"
-category = "macros"
-desc = """
-Demonstrates how to use the `pack_err!` macro to define error types with automatic `name` field (snake_case at compile time) and optional `info` field. Also shows `--json` serialization when `structural_renderer` is enabled.
-"""
-tags = ["pack_err!", "extras", "structural_renderer", "--json"]
-files = ["src/main.rs", "Cargo.toml"]
diff --git a/examples/example-pack-err/src/main.rs b/examples/example-pack-err/src/main.rs
deleted file mode 100644
index e30e4cb..0000000
--- a/examples/example-pack-err/src/main.rs
+++ /dev/null
@@ -1,151 +0,0 @@
-//! Example `pack_err!`
-//!
-//! > This example demonstrates how to use the `pack_err!` macro to define error types
-//! > with automatic `name` field (set to snake_case at compile time) and optional `info` field.
-//! > Also demonstrates `--json` serialization when `structural_renderer` is enabled.
-//!
-//! Run:
-//! ```bash
-//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find
-//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find Cargo.toml
-//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find src
-//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural --json
-//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural Cargo.toml --json
-//! cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural src --json
-//! ```
-//!
-//! Output:
-//! ```plaintext
-//! Search path not provided
-//! Not a directory: Cargo.toml
-//! Found directory: src
-//! {"name":"error_not_found"}
-//! {"name":"error_not_dir","info":"Cargo.toml"}
-//! {"inner":"src"}
-//! {"name":"error_not_found_structural"}
-//! {"name":"error_not_dir_structural","info":"Cargo.toml"}
-//! ```
-
-use mingling::prelude::*;
-use mingling::setup::StructuralRendererSetup;
-use std::io::Write;
-use std::path::PathBuf;
-
-dispatcher!("find", EntryFind);
-dispatcher!("find-structural", EntryFindStructural);
-
-// --------- IMPORTANT ---------
-// `pack_err!` is a convenient macro for defining error types.
-//
-// Simple form: pack_err!(ErrorNotFound);
-// Typed form: pack_err!(ErrorNotDir = PathBuf);
-//
-// The simple form generates a struct with `name: String` and `impl Default`.
-// name = "error_not_found" (automatically snake_cased at compile time)
-//
-// The typed form additionally generates `pub fn new(info)`.
-// name = "error_not_dir"
-//
-// When `structural_renderer` is enabled, the struct also gets
-// `#[derive(serde::Serialize)]` for --json / --yaml output.
-// --------- IMPORTANT ---------
-
-// Simple form — name = "error_not_found"
-pack_err!(ErrorNotFound);
-
-// Typed form — name = "error_not_dir"
-pack_err!(ErrorNotDir = PathBuf);
-
-// Simple form — with StructuralData support for --json / --yaml
-pack_err_structural!(ErrorNotFoundStructural);
-
-// Typed form — with StructuralData support for --json / --yaml
-pack_err_structural!(ErrorNotDirStructural = PathBuf);
-
-// Success type with StructuralData support
-pack_structural!(ResultPath = PathBuf);
-
-#[chain]
-fn handle_find(args: EntryFind) -> Next {
- let Some(path_str) = args.inner.first().cloned() else {
- // No path provided → use the simple error form (Default)
- return ErrorNotFound::default().to_render();
- };
-
- let path = PathBuf::from(&path_str);
- if path.is_dir() {
- // Is a directory → success
- ResultPath::new(path).to_render()
- } else {
- // Not a directory (or doesn't exist) → use the typed error form
- ErrorNotDir::new(path).to_render()
- }
-}
-
-#[chain]
-fn handle_find_structural(args: EntryFindStructural) -> Next {
- let Some(path_str) = args.inner.first().cloned() else {
- // No path provided → use the simple error form (Default)
- return ErrorNotFoundStructural::default().to_render();
- };
-
- let path = PathBuf::from(&path_str);
- if path.is_dir() {
- // Is a directory → success
- ResultPath::new(path).to_render()
- } else {
- // Not a directory (or doesn't exist) → use the typed error form
- ErrorNotDirStructural::new(path).to_render()
- }
-}
-
-/// Renders the successful result with the found directory path.
-#[renderer]
-fn render_result_path(path: ResultPath) -> RenderResult {
- let mut render_result = RenderResult::new();
- writeln!(render_result, "Found directory: {}", path.display()).ok();
- render_result
-}
-
-/// Renders the error when no search path is provided.
-#[renderer]
-fn render_error_not_found(_: ErrorNotFound) -> RenderResult {
- let mut render_result = RenderResult::new();
- writeln!(render_result, "Search path not provided").ok();
- render_result
-}
-
-/// Renders the error when the given path is not a directory.
-#[renderer]
-fn render_error_not_dir(err: ErrorNotDir) -> RenderResult {
- let mut render_result = RenderResult::new();
- writeln!(render_result, "Not a directory: {}", err.info.display()).ok();
- render_result
-}
-
-/// Renders the structural error when no search path is provided.
-#[renderer]
-fn render_error_not_found_structural(_: ErrorNotFoundStructural) -> RenderResult {
- let mut render_result = RenderResult::new();
- writeln!(render_result, "Search path not provided").ok();
- render_result
-}
-
-/// Renders the structural error when the given path is not a directory.
-#[renderer]
-fn render_error_not_dir_structural(err: ErrorNotDirStructural) -> RenderResult {
- let mut render_result = RenderResult::new();
- writeln!(render_result, "Not a directory: {}", err.info.display()).ok();
- render_result
-}
-
-gen_program!();
-
-fn main() {
- let mut program = ThisProgram::new();
-
- // Add StructuralRendererSetup to support --json / --yaml flags
- program.with_setup(StructuralRendererSetup);
-
- let _ = program.exec();
-}
diff --git a/examples/example-pack-err/test.toml b/examples/example-pack-err/test.toml
deleted file mode 100644
index c4509cb..0000000
--- a/examples/example-pack-err/test.toml
+++ /dev/null
@@ -1,35 +0,0 @@
-[[runs]]
-input = [ "find" ]
-
-expect.exit-code = 0
-expect.result = "Search path not provided"
-
-[[runs]]
-input = [ "find", "Cargo.toml" ]
-
-expect.exit-code = 0
-expect.result = "Not a directory: Cargo.toml"
-
-[[runs]]
-input = [ "find", "examples" ]
-
-expect.exit-code = 0
-expect.result = "Found directory: examples"
-
-[[runs]]
-input = [ "find-structural", "--json" ]
-
-expect.exit-code = 0
-expect.result = "{\"name\":\"error_not_found_structural\"}"
-
-[[runs]]
-input = [ "find-structural", "Cargo.toml", "--json" ]
-
-expect.exit-code = 0
-expect.result = "{\"name\":\"error_not_dir_structural\",\"info\":\"Cargo.toml\"}"
-
-[[runs]]
-input = [ "find-structural", "examples", "--json" ]
-
-expect.exit-code = 0
-expect.result = "{\"inner\":\"examples\"}"
diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs
index 59adf07..f91b0f0 100644
--- a/examples/example-panic-unwind/src/main.rs
+++ b/examples/example-panic-unwind/src/main.rs
@@ -20,7 +20,9 @@ use mingling::{hook::ProgramHook, prelude::*};
use std::io::Write;
dispatcher!("panic", EntryPanic);
-pack!(NotPanic = ());
+
+#[derive(Grouped)]
+pub struct NotPanic;
fn main() {
let mut program = ThisProgram::new();
@@ -47,7 +49,7 @@ fn handle_panic(prev: EntryPanic) -> Next {
// Panic happens here, will be caught
panic!("{}", s)
}
- None => NotPanic::default().into(),
+ None => NotPanic.into(),
}
}
diff --git a/examples/example-pathfinder/src/sub/mod.rs b/examples/example-pathfinder/src/sub/mod.rs
index cf0a97a..b90c618 100644
--- a/examples/example-pathfinder/src/sub/mod.rs
+++ b/examples/example-pathfinder/src/sub/mod.rs
@@ -3,12 +3,14 @@ use mingling::prelude::*;
use std::io::Write;
dispatcher!("greet", EntryGreet);
-pack!(ResultName = String);
+
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
pub fn handle_greet(args: EntryGreet) -> Next {
let name: ResultName = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string())
diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs
index d0fad6d..9b81ced 100644
--- a/examples/example-repl-basic/src/main.rs
+++ b/examples/example-repl-basic/src/main.rs
@@ -70,7 +70,8 @@ fn main() {
}
// Create error route
-pack!(ErrorDirectoryNotExist = PathBuf);
+#[derive(Grouped, Wrap)]
+pub struct ErrorDirectoryNotExist(PathBuf);
// Create commands: cd ls exit
dispatcher!("cd", EntryCd);
@@ -79,16 +80,18 @@ dispatcher!("exit", EntryExit);
dispatcher!("clear", EntryClear);
// Define data needed for the cd command's execution phase
-pack!(StateChangeDirectory = String);
+#[derive(Grouped, Wrap)]
+pub struct StateChangeDirectory(String);
// Define data needed for the ls command's rendering phase
-pack!(ResultList = Vec<String>);
+#[derive(Grouped, Wrap)]
+pub struct ResultList(Vec<String>);
// Parse cd command arguments
#[chain]
fn parse_cd_args(prev: EntryCd) -> Next {
let join = prev.pick_or_default(&arg![String]).unwrap();
- StateChangeDirectory::new(join).into()
+ StateChangeDirectory(join).into()
}
// Execute directory change
@@ -96,12 +99,12 @@ fn parse_cd_args(prev: EntryCd) -> Next {
fn handle_cd(prev: StateChangeDirectory, current_dir: &mut ResCurrentDir) -> Next {
use just_fmt::fmt_path::fmt_path;
- let join = prev.inner;
+ let join = prev.0;
let new_dir = fmt_path(current_dir.dir.join(join)).unwrap_or_default();
// If the path is not found, route to error handling
if !new_dir.exists() {
- return ErrorDirectoryNotExist::new(new_dir).to_render();
+ return ErrorDirectoryNotExist(new_dir).to_render();
}
current_dir.dir = new_dir;
@@ -126,14 +129,14 @@ fn handle_ls(_prev: EntryLs, current_dir: &ResCurrentDir) -> Next {
.collect();
// Render ResultList
- ResultList::new(entries).to_render()
+ ResultList(entries).to_render()
}
/// Render ResultList data
#[renderer]
fn render_list(list: ResultList) -> RenderResult {
let mut render_result = RenderResult::new();
- for item in list.inner {
+ for item in list.0 {
writeln!(render_result, "{}", item).ok();
}
render_result
@@ -160,12 +163,7 @@ fn handle_clear(_prev: EntryClear) {
#[renderer]
fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) -> RenderResult {
let mut render_result = RenderResult::new();
- writeln!(
- render_result,
- "Directory not found: {}",
- err.inner.display()
- )
- .ok();
+ writeln!(render_result, "Directory not found: {}", err.0.display()).ok();
render_result
}
diff --git a/examples/example-setup/src/main.rs b/examples/example-setup/src/main.rs
index 523a567..59c503f 100644
--- a/examples/example-setup/src/main.rs
+++ b/examples/example-setup/src/main.rs
@@ -55,13 +55,14 @@ fn custom_setup(program: &mut Program<ThisProgram>) {
dispatcher!("greet", EntryGreet);
-pack!(ResultGreeting = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultGreeting(String);
/// Chain: reads the `ResAppName` and `ResAppVersion` resources.
#[chain]
fn handle_greet(args: EntryGreet, app: &ResAppName, version: &ResAppVersion) -> Next {
let who = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs
index a1bddbd..c1cfd69 100644
--- a/examples/example-structural-renderer/src/main.rs
+++ b/examples/example-structural-renderer/src/main.rs
@@ -32,7 +32,8 @@ fn main() {
}
// --------- IMPORTANT ---------
-// For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
+// For beautiful output structure, do not wrap the types that need to be output
+// in a newtype; instead, use a named struct.
// Instead, manually implement
// ____________________________________ Mark as structured data so it can be rendered
// / ____________________ Implement serde::Serialize
@@ -48,7 +49,7 @@ struct Info {
}
// This will output: {"member_name":"name","member_age":32} structure
-// If using pack!(Info = (String, i32));
+// If wrapping with a tuple newtype (e.g. `#[derive(Grouped, Wrap)] pub struct Info((String, i32));`)
// Output: {"inner":["name", 32]}
// --------- IMPORTANT ---------
diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs
index 29ff9da..e4c6504 100644
--- a/examples/example-unit-test/src/main.rs
+++ b/examples/example-unit-test/src/main.rs
@@ -36,30 +36,30 @@ mod tests {
let hello_with_valid_name = handle_hello(entry!("Peter")).into();
assert_render_result!(hello_with_valid_name);
let result_name = unpack_chain_process!(hello_with_valid_name, ResultName);
- assert_eq!(result_name.inner, "Peter");
+ assert_eq!(result_name.0, "Peter");
}
#[test]
fn test_render_result_name() {
- let r = render_result_name(ResultName::new("Peter".into()));
+ let r = render_result_name(ResultName("Peter".into()));
assert_eq!(r.to_string().as_str(), "Hello, Peter!")
}
#[test]
fn test_render_error_no_name_provided() {
- let r = render_error_no_name_provided(ErrorNoNameProvided::default());
+ let r = render_error_no_name_provided(ErrorNoNameProvided);
assert_eq!(r.to_string().as_str(), "No name provided")
}
#[test]
fn test_render_error_name_not_available() {
- let r = render_error_name_not_available(ErrorNameNotAvailable::default());
+ let r = render_error_name_not_available(ErrorNameNotAvailable);
assert_eq!(r.to_string().as_str(), "Name not available")
}
#[test]
fn test_render_error_name_too_long() {
- let r = render_error_name_too_long(ErrorNameTooLong::new(17));
+ let r = render_error_name_too_long(ErrorNameTooLong(17));
assert_eq!(r.to_string().as_str(), "Name too long: 17 > 10")
}
// --------- IMPORTANT ---------
@@ -67,29 +67,35 @@ mod tests {
dispatcher!("hello", EntryHello);
-pack!(ErrorNoNameProvided = ());
-pack!(ErrorNameTooLong = u16);
-pack!(ErrorNameNotAvailable = ());
+#[derive(Grouped)]
+pub struct ErrorNoNameProvided;
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorNameTooLong(u16);
+
+#[derive(Grouped)]
+pub struct ErrorNameNotAvailable;
+
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
#[chain]
fn handle_hello(args: EntryHello) -> Next {
- let Some(name) = args.inner.first().cloned() else {
- return ErrorNoNameProvided::default().to_render();
+ let Some(name) = args.0.first().cloned() else {
+ return ErrorNoNameProvided.to_render();
};
if name.len() > 10 {
- return ErrorNameTooLong::new(name.len() as u16).to_render();
+ return ErrorNameTooLong(name.len() as u16).to_render();
}
if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
- return ErrorNameNotAvailable::default().to_render();
+ return ErrorNameNotAvailable.to_render();
}
- ResultName::new(name).to_render()
+ ResultName(name).to_render()
}
/// Renders a successful greeting with the given name.
@@ -128,12 +134,7 @@ fn render_error_name_too_long(len: ErrorNameTooLong) -> RenderResult {
#[renderer]
fn render_entry_fallback(err: EntryFallback) -> RenderResult {
let mut render_result = RenderResult::new();
- writeln!(
- render_result,
- "Command not found: \"{}\"",
- err.inner.join(" ")
- )
- .ok();
+ writeln!(render_result, "Command not found: \"{}\"", err.0.join(" ")).ok();
render_result
}
diff --git a/examples/full-todolist/src/main.rs b/examples/full-todolist/src/main.rs
index eea417a..9c7bfe8 100644
--- a/examples/full-todolist/src/main.rs
+++ b/examples/full-todolist/src/main.rs
@@ -36,15 +36,25 @@ dispatcher!("clean");
// Define states
-pack!(StateAddTodo = String);
-pack!(StateCompleteTodo = i32);
-pack!(StateListTodo = bool);
+#[derive(Grouped, Wrap)]
+pub struct StateAddTodo(String);
+
+#[derive(Grouped, Wrap)]
+pub struct StateCompleteTodo(i32);
+
+#[derive(Grouped, Wrap)]
+pub struct StateListTodo(bool);
// Define errors
-pack!(ErrorNoTaskDescriptionProvided = ());
-pack!(ErrorNoIndexProvided = ());
-pack!(ErrorIndexOutOfBounds = ());
+#[derive(Grouped)]
+pub struct ErrorNoTaskDescriptionProvided;
+
+#[derive(Grouped)]
+pub struct ErrorNoIndexProvided;
+
+#[derive(Grouped)]
+pub struct ErrorIndexOutOfBounds;
fn main() {
let mut program = ThisProgram::new();
@@ -76,11 +86,11 @@ fn main() {
fn handle_add(args: EntryAdd) -> Next {
let task: String = route! {
args.pick_or_route(&arg![String], || {
- ErrorNoTaskDescriptionProvided::new(()).to_chain()
+ ErrorNoTaskDescriptionProvided.to_chain()
})
.to_result()
};
- StateAddTodo::new(task).to_chain()
+ StateAddTodo(task).to_chain()
}
#[chain]
@@ -92,7 +102,7 @@ fn handle_state_add_todo(
let todolist = todolist.get_mut();
// Unpack state and read description
- let description = state.inner;
+ let description = state.0;
todolist.items.push(Todo {
item: description,
@@ -117,10 +127,10 @@ fn handle_list(_args: EntryList, todolist: &mut LazyRes<ResTodoList>) -> Next {
#[chain]
fn handle_complete(args: EntryComplete) -> Next {
let index: i32 = route! {
- args.pick_or_route(&arg![i32], || ErrorNoIndexProvided::new(()).to_chain())
+ args.pick_or_route(&arg![i32], || ErrorNoIndexProvided.to_chain())
.to_result()
};
- StateCompleteTodo::new(index).to_chain()
+ StateCompleteTodo(index).to_chain()
}
#[chain]
@@ -129,12 +139,12 @@ fn handle_state_complete_todo(
todolist: &mut LazyRes<ResTodoList>,
) -> Next {
let todolist = todolist.get_mut();
- let index = state.inner as usize;
+ let index = state.0 as usize;
if index < todolist.items.len() {
todolist.items[index].completed = true;
todolist.clone().to_render()
} else {
- ErrorIndexOutOfBounds::new(()).to_render()
+ ErrorIndexOutOfBounds.to_render()
}
}
diff --git a/mingling/src/docs/lib.md b/mingling/src/docs/lib.md
index 91858d0..3b1f328 100644
--- a/mingling/src/docs/lib.md
+++ b/mingling/src/docs/lib.md
@@ -29,12 +29,13 @@ fn main() {
program.exec_and_exit();
}
-pack!(ResultName = String);
+#[derive(Grouped, Wrap)]
+pub struct ResultName(String);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let name: ResultName = args
- .inner
+ .0
.first()
.cloned()
.unwrap_or_else(|| "World".to_string())
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index 9406ddc..d37e762 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -64,17 +64,32 @@
///
/// dispatcher!("calc", EntryCalculate);
///
-/// pack_err!(ErrorNumberANotProvided);
-/// pack_err!(ErrorNumberBNotProvided);
-/// pack_err!(ErrorNumberOperatorNotProvided);
-/// pack_err!(ErrorDivisionByZero);
+/// #[derive(Grouped, Default)]
+/// pub struct ErrorNumberANotProvided;
///
-/// pack!(StateAdd = (f32, f32));
-/// pack!(StateSubtract = (f32, f32));
-/// pack!(StateMultiply = (f32, f32));
-/// pack!(StateDivide = (f32, f32));
+/// #[derive(Grouped, Default)]
+/// pub struct ErrorNumberBNotProvided;
///
-/// pack!(ResultNumber = f32);
+/// #[derive(Grouped, Default)]
+/// pub struct ErrorNumberOperatorNotProvided;
+///
+/// #[derive(Grouped, Default)]
+/// pub struct ErrorDivisionByZero;
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct StateAdd((f32, f32));
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct StateSubtract((f32, f32));
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct StateMultiply((f32, f32));
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct StateDivide((f32, f32));
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultNumber(f32);
///
/// #[derive(Grouped)]
/// struct StateCalculate {
@@ -155,13 +170,13 @@
/// // Use the arg! macro to define a positional argument of type f32
/// // |
/// // vvvvvvvvvv
-/// args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain())
+/// args.pick_or_route(&arg![f32], || ErrorNumberANotProvided.to_chain())
/// .pick_or_route(&arg![Operator], || {
-/// ErrorNumberOperatorNotProvided::default().to_chain()
+/// ErrorNumberOperatorNotProvided.to_chain()
/// }) // Returns a routable type when not found or fails to parse
/// // |
-/// // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
-/// .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain())
+/// // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+/// .pick_or_route(&arg![f32], || ErrorNumberBNotProvided.to_chain())
/// // Use `to_result` to parse arguments
/// // and convert to Result<(Tuple, ...), Route> type
/// .to_result()
@@ -169,7 +184,7 @@
/// // --------- IMPORTANT ---------
///
/// if operator == Operator::Slash && number_b == 0. {
-/// return ErrorDivisionByZero::default().to_chain();
+/// return ErrorDivisionByZero.to_chain();
/// }
///
/// StateCalculate {
@@ -183,41 +198,41 @@
/// #[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(),
+/// (Operator::Plus, a, b) => StateAdd((a, b)).to_chain(),
+/// (Operator::Dash, a, b) => StateSubtract((a, b)).to_chain(),
+/// (Operator::Slash, a, b) => StateDivide((a, b)).to_chain(),
+/// (Operator::Star, a, b) => StateMultiply((a, b)).to_chain(),
/// }
/// }
///
/// #[chain]
/// fn handle_state_add(state_add: StateAdd) -> ResultNumber {
-/// let (a, b) = state_add.inner;
-/// ResultNumber::new(a + b)
+/// let (a, b) = state_add.0;
+/// ResultNumber(a + b)
/// }
///
/// #[chain]
/// fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber {
-/// let (a, b) = state_subtract.inner;
-/// ResultNumber::new(a - b)
+/// let (a, b) = state_subtract.0;
+/// ResultNumber(a - b)
/// }
///
/// #[chain]
/// fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber {
-/// let (a, b) = state_multiply.inner;
-/// ResultNumber::new(a * b)
+/// let (a, b) = state_multiply.0;
+/// ResultNumber(a * b)
/// }
///
/// #[chain]
/// fn handle_state_divide(state_divide: StateDivide) -> ResultNumber {
-/// let (a, b) = state_divide.inner;
-/// ResultNumber::new(a / b)
+/// let (a, b) = state_divide.0;
+/// ResultNumber(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 };
+/// let result = if round { result.round() } else { result.0 };
/// format!("Result: {}", result)
/// }
///
@@ -310,7 +325,8 @@ pub mod example_argument_picker {}
///
/// dispatcher!("download", EntryDownload);
///
-/// pack!(ResultDownloaded = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultDownloaded(String);
///
/// // --------- IMPORTANT ---------
/// #[chain]
@@ -334,7 +350,7 @@ pub mod example_argument_picker {}
///
/// async fn fake_download(file_name: String) -> ResultDownloaded {
/// tokio::time::sleep(std::time::Duration::from_secs(1)).await;
-/// ResultDownloaded::new(file_name)
+/// ResultDownloaded(file_name)
/// }
/// ```
pub mod example_async_support {}
@@ -372,11 +388,11 @@ pub mod example_async_support {}
/// use std::io::Write;
///
/// // Define the `greet` subcommand
-/// // _____________________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm")
-/// // / _____________________ dispatcher name
-/// // | / _________ entry, records raw arguments
-/// // | | / ^^^^^^^^^^^^^
-/// // vvvvv vvvvvvvv vvvvvvvvvv \_ equivalent to pack!(EntryGreet = Vec<String>)
+/// // _________________ subcmd name, can be nested (e.g. "remote.add" "remote.rm")
+/// // /
+/// // | _________ entry, records raw arguments
+/// // | / ^^^^^^^^^^^^^
+/// // vvvvv vvvvvvvvvv \_ a newtype wrapper around Vec<String>
/// dispatcher!("greet", EntryGreet);
///
/// fn main() {
@@ -388,21 +404,22 @@ pub mod example_async_support {}
/// }
///
/// // Quickly wrap a type into a type recognizable by the current program
-/// // ____________________ Wrapped type name
-/// // / _______ Wrapped type inner value
-/// // | /
-/// // vvvvvvvvvv vvvvvv
-/// pack!(ResultName = String);
+/// // ___________________ Registers this type into ThisProgram
+/// // / _______ Adds DerefMut, Deref, Into, From wrappers
+/// // | /
+/// // vvvvvvvvvv vvvvv
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultName(String);
///
/// // Define the `handle_greet` chain for parsing input text
/// // ____________________ Previous type:
/// // / Mingling deduces types at runtime and routes them to this function
/// // | _____ will be expanded to:
-/// // | / impl Into<mingling::ChainProcess<ThisProgram>>
+/// // | / ChainProcess<ThisProgram>
/// #[chain] // vvvvvvvvvv vvvv
/// fn handle_greet(args: EntryGreet) -> Next {
/// let name: ResultName = args
-/// .inner
+/// .0
/// .first()
/// .cloned()
/// .unwrap_or_else(|| "World".to_string())
@@ -745,27 +762,30 @@ pub mod example_combine_pathf_metadata {}
/// ThisProgram::new().exec_and_exit();
/// }
///
-/// pack!(ResultGreeting = String);
-/// pack!(ResultGoodbye = ());
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultGreeting(String);
+///
+/// #[derive(Grouped)]
+/// pub struct ResultGoodbye;
///
/// // --------- IMPORTANT ---------
/// // Auto-generates dispatcher!("hello.world", EntryHelloWorld);
/// #[command]
/// fn hello_world() -> ResultGreeting {
-/// ResultGreeting::new("World".to_string())
+/// ResultGreeting("World".to_string())
/// }
///
/// // Auto-generates dispatcher!("hello-world", EntryGreetSomeone);
/// #[command(node = "greet-someone")]
/// fn greet_someone(args: Vec<String>) -> ResultGreeting {
/// let name = args.pick_or(&arg![String], || "World".to_string()).unwrap();
-/// ResultGreeting::new(name)
+/// ResultGreeting(name)
/// }
///
/// // Auto-generates dispatcher!("goodbye", EntryGoodBye);
/// #[command(entry = EntryGoodBye)]
/// fn goodbye() -> ResultGoodbye {
-/// ResultGoodbye::default()
+/// ResultGoodbye
/// }
/// // --------- IMPORTANT ---------
///
@@ -918,7 +938,8 @@ pub mod example_command_macro {}
/// // --------- IMPORTANT ---------
///
/// dispatcher!("greet", EntryGreet);
-/// pack!(ResultName = (u8, String));
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultName((u8, String));
///
/// #[chain]
/// fn handle_greet(args: EntryGreet) -> Next {
@@ -933,7 +954,7 @@ pub mod example_command_macro {}
/// /// Renders the greeting with the result name and repeat count.
/// #[renderer]
/// fn render_name(result: ResultName) -> RenderResult {
-/// let (repeat, name) = result.inner;
+/// let (repeat, name) = result.0;
/// let mut render_result = RenderResult::new();
/// let mut parts = Vec::with_capacity(repeat as usize);
/// for _ in 0..repeat {
@@ -1215,35 +1236,41 @@ pub mod example_enum_tag {}
/// dispatcher!("hello", EntryHello);
///
/// // Define error types
-/// pack!(ErrorNoNameProvided = ());
-/// pack!(ErrorNameTooLong = u16);
-/// pack!(ErrorNameNotAvailable = ());
+/// #[derive(Grouped)]
+/// pub struct ErrorNoNameProvided;
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct ErrorNameTooLong(u16);
+///
+/// #[derive(Grouped)]
+/// pub struct ErrorNameNotAvailable;
///
/// // Define success type
-/// pack!(ResultName = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultName(String);
///
/// // Pre-registered names
/// static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
///
/// #[chain]
/// fn handle_hello(args: EntryHello) -> Next {
-/// let Some(name) = args.inner.first().cloned() else {
+/// let Some(name) = args.0.first().cloned() else {
/// // If no name is provided, pass ErrorNoNameProvided
-/// return ErrorNoNameProvided::default().to_render();
+/// return ErrorNoNameProvided.to_render();
/// };
///
/// if name.len() > 10 {
/// // If the name is too long, pass ErrorNameTooLong
-/// return ErrorNameTooLong::new(name.len() as u16).to_render();
+/// return ErrorNameTooLong(name.len() as u16).to_render();
/// }
///
/// if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
/// // If the name already exists, pass ErrorNameNotAvailable
-/// return ErrorNameNotAvailable::default().to_render();
+/// return ErrorNameNotAvailable.to_render();
/// }
///
/// // If the name is valid, pass ResultName
-/// ResultName::new(name).to_render()
+/// ResultName(name).to_render()
/// }
///
/// /// Renders a successful greeting with the given name.
@@ -1282,12 +1309,7 @@ pub mod example_enum_tag {}
/// #[renderer]
/// fn render_entry_fallback(err: EntryFallback) -> RenderResult {
/// let mut render_result = RenderResult::new();
-/// writeln!(
-/// render_result,
-/// "Command not found: \"{}\"",
-/// err.inner.join(" ")
-/// )
-/// .ok();
+/// writeln!(render_result, "Command not found: \"{}\"", err.0.join(" ")).ok();
/// render_result
/// }
///
@@ -1351,18 +1373,21 @@ pub mod example_error_handling {}
///
/// dispatcher!("hello", EntryHello);
///
-/// pack!(ErrorNoNameProvided = ());
-/// pack!(ResultName = String);
+/// #[derive(Grouped)]
+/// pub struct ErrorNoNameProvided;
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultName(String);
///
/// #[chain]
/// fn handle_hello(args: EntryHello) -> Next {
-/// let Some(name) = args.inner.first().cloned() else {
+/// let Some(name) = args.0.first().cloned() else {
/// // If no name is provided, pass ErrorNoNameProvided
-/// return ErrorNoNameProvided::default().to_render();
+/// return ErrorNoNameProvided.to_render();
/// };
///
/// // If the name is valid, pass ResultName
-/// ResultName::new(name).to_render()
+/// ResultName(name).to_render()
/// }
///
/// /// Renders a successful greeting with the given name.
@@ -1527,12 +1552,13 @@ pub mod example_help {}
/// program.exec_and_exit();
/// }
///
-/// pack!(ResultName = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultName(String);
///
/// #[chain]
/// fn handle_greet(args: EntryGreet) -> Next {
/// let name: ResultName = args
-/// .inner
+/// .0
/// .first()
/// .cloned()
/// .unwrap_or_else(|| "World".to_string())
@@ -1656,7 +1682,8 @@ pub mod example_implicit_dispatcher {}
/// dispatcher!("show", EntryShow);
/// dispatcher!("none", EntryNone);
///
-/// pack!(ResultShow = BTreeMap<Key, Value>);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultShow(BTreeMap<Key, Value>);
///
/// fn main() {
/// let mut program = ThisProgram::new();
@@ -1774,14 +1801,17 @@ pub mod example_lazy_resources {}
/// }
/// // --------- IMPORTANT ---------
///
-/// pack!(ResultName = String);
-/// pack!(DescResult = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultName(String);
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct DescResult(String);
///
/// /// Chain for `greet` — reads the name and produces a `ResultName`.
/// #[chain]
/// fn handle_greet(args: EntryGreet) -> Next {
/// let name: ResultName = args
-/// .inner
+/// .0
/// .first()
/// .cloned()
/// .unwrap_or_else(|| "World".to_string())
@@ -1799,7 +1829,7 @@ pub mod example_lazy_resources {}
/// None => "EntryGreet has no description".to_string(),
/// };
/// // --------- IMPORTANT ---------
-/// DescResult::new(msg).to_render()
+/// DescResult(msg).to_render()
/// }
///
/// /// Chain for `nodoc` — asks for metadata on an entry that has none.
@@ -1812,7 +1842,7 @@ pub mod example_lazy_resources {}
/// None => "EntryDescription has no description".to_string(),
/// };
/// // --------- IMPORTANT ---------
-/// DescResult::new(msg).to_render()
+/// DescResult(msg).to_render()
/// }
///
/// /// Renders the greeting message with the provided name.
@@ -1897,7 +1927,8 @@ pub mod example_metadata {}
/// // you can use this syntax to create an alias simultaneously
/// // --------- IMPORTANT ---------
///
-/// pack!(ParsedNumber = i32);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ParsedNumber(i32);
///
/// /// Parse the first argument as an `i32`
/// ///
@@ -1905,9 +1936,9 @@ pub mod example_metadata {}
/// /// On failure, routes to `render_parse_error` via the registered outside type.
/// #[chain]
/// fn parse_number(args: EntryParse) -> Next {
-/// let input = args.inner.first().cloned().unwrap_or_default();
+/// let input = args.0.first().cloned().unwrap_or_default();
/// match input.parse::<i32>() {
-/// Ok(num) => ParsedNumber::new(num).to_chain(),
+/// Ok(num) => ParsedNumber(num).to_chain(),
/// Err(e) => e.to_chain(),
/// }
/// }
@@ -1950,181 +1981,6 @@ pub mod example_metadata {}
/// gen_program!();
/// ```
pub mod example_outside_type {}
-/// Example `pack_err!`
-///
-/// > This example demonstrates how to use the `pack_err!` macro to define error types
-/// > with automatic `name` field (set to snake_case at compile time) and optional `info` field.
-/// > Also demonstrates `--json` serialization when `structural_renderer` is enabled.
-///
-/// Run:
-/// ```bash
-/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find
-/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find Cargo.toml
-/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find src
-/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural --json
-/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural Cargo.toml --json
-/// cargo run --manifest-path examples/example-pack-err/Cargo.toml --quiet -- find-structural src --json
-/// ```
-///
-/// Output:
-/// ```plaintext
-/// Search path not provided
-/// Not a directory: Cargo.toml
-/// Found directory: src
-/// {"name":"error_not_found"}
-/// {"name":"error_not_dir","info":"Cargo.toml"}
-/// {"inner":"src"}
-/// {"name":"error_not_found_structural"}
-/// {"name":"error_not_dir_structural","info":"Cargo.toml"}
-/// ```
-///
-/// Source code (./Cargo.toml)
-/// ```toml
-/// [package]
-/// name = "example-pack-err"
-/// version = "0.1.0"
-/// edition = "2024"
-///
-/// [dependencies]
-/// serde = { version = "1.0.228", features = ["derive"] }
-///
-/// [dependencies.mingling]
-/// path = "../../mingling"
-/// features = [
-/// "structural_renderer",
-/// "extras",
-/// ]
-///
-/// [workspace]
-/// ```
-///
-/// Source code (./src/main.rs)
-/// ```ignore
-/// use mingling::prelude::*;
-/// use mingling::setup::StructuralRendererSetup;
-/// use std::io::Write;
-/// use std::path::PathBuf;
-///
-/// dispatcher!("find", EntryFind);
-/// dispatcher!("find-structural", EntryFindStructural);
-///
-/// // --------- IMPORTANT ---------
-/// // `pack_err!` is a convenient macro for defining error types.
-/// //
-/// // Simple form: pack_err!(ErrorNotFound);
-/// // Typed form: pack_err!(ErrorNotDir = PathBuf);
-/// //
-/// // The simple form generates a struct with `name: String` and `impl Default`.
-/// // name = "error_not_found" (automatically snake_cased at compile time)
-/// //
-/// // The typed form additionally generates `pub fn new(info)`.
-/// // name = "error_not_dir"
-/// //
-/// // When `structural_renderer` is enabled, the struct also gets
-/// // `#[derive(serde::Serialize)]` for --json / --yaml output.
-/// // --------- IMPORTANT ---------
-///
-/// // Simple form — name = "error_not_found"
-/// pack_err!(ErrorNotFound);
-///
-/// // Typed form — name = "error_not_dir"
-/// pack_err!(ErrorNotDir = PathBuf);
-///
-/// // Simple form — with StructuralData support for --json / --yaml
-/// pack_err_structural!(ErrorNotFoundStructural);
-///
-/// // Typed form — with StructuralData support for --json / --yaml
-/// pack_err_structural!(ErrorNotDirStructural = PathBuf);
-///
-/// // Success type with StructuralData support
-/// pack_structural!(ResultPath = PathBuf);
-///
-/// #[chain]
-/// fn handle_find(args: EntryFind) -> Next {
-/// let Some(path_str) = args.inner.first().cloned() else {
-/// // No path provided → use the simple error form (Default)
-/// return ErrorNotFound::default().to_render();
-/// };
-///
-/// let path = PathBuf::from(&path_str);
-/// if path.is_dir() {
-/// // Is a directory → success
-/// ResultPath::new(path).to_render()
-/// } else {
-/// // Not a directory (or doesn't exist) → use the typed error form
-/// ErrorNotDir::new(path).to_render()
-/// }
-/// }
-///
-/// #[chain]
-/// fn handle_find_structural(args: EntryFindStructural) -> Next {
-/// let Some(path_str) = args.inner.first().cloned() else {
-/// // No path provided → use the simple error form (Default)
-/// return ErrorNotFoundStructural::default().to_render();
-/// };
-///
-/// let path = PathBuf::from(&path_str);
-/// if path.is_dir() {
-/// // Is a directory → success
-/// ResultPath::new(path).to_render()
-/// } else {
-/// // Not a directory (or doesn't exist) → use the typed error form
-/// ErrorNotDirStructural::new(path).to_render()
-/// }
-/// }
-///
-/// /// Renders the successful result with the found directory path.
-/// #[renderer]
-/// fn render_result_path(path: ResultPath) -> RenderResult {
-/// let mut render_result = RenderResult::new();
-/// writeln!(render_result, "Found directory: {}", path.display()).ok();
-/// render_result
-/// }
-///
-/// /// Renders the error when no search path is provided.
-/// #[renderer]
-/// fn render_error_not_found(_: ErrorNotFound) -> RenderResult {
-/// let mut render_result = RenderResult::new();
-/// writeln!(render_result, "Search path not provided").ok();
-/// render_result
-/// }
-///
-/// /// Renders the error when the given path is not a directory.
-/// #[renderer]
-/// fn render_error_not_dir(err: ErrorNotDir) -> RenderResult {
-/// let mut render_result = RenderResult::new();
-/// writeln!(render_result, "Not a directory: {}", err.info.display()).ok();
-/// render_result
-/// }
-///
-/// /// Renders the structural error when no search path is provided.
-/// #[renderer]
-/// fn render_error_not_found_structural(_: ErrorNotFoundStructural) -> RenderResult {
-/// let mut render_result = RenderResult::new();
-/// writeln!(render_result, "Search path not provided").ok();
-/// render_result
-/// }
-///
-/// /// Renders the structural error when the given path is not a directory.
-/// #[renderer]
-/// fn render_error_not_dir_structural(err: ErrorNotDirStructural) -> RenderResult {
-/// let mut render_result = RenderResult::new();
-/// writeln!(render_result, "Not a directory: {}", err.info.display()).ok();
-/// render_result
-/// }
-///
-/// gen_program!();
-///
-/// fn main() {
-/// let mut program = ThisProgram::new();
-///
-/// // Add StructuralRendererSetup to support --json / --yaml flags
-/// program.with_setup(StructuralRendererSetup);
-///
-/// let _ = program.exec();
-/// }
-/// ```
-pub mod example_pack_err {}
/// Example Panic Unwind
///
/// > This example introduces how to catch Panic in the Mingling program loop
@@ -2171,7 +2027,9 @@ pub mod example_pack_err {}
/// use std::io::Write;
///
/// dispatcher!("panic", EntryPanic);
-/// pack!(NotPanic = ());
+///
+/// #[derive(Grouped)]
+/// pub struct NotPanic;
///
/// fn main() {
/// let mut program = ThisProgram::new();
@@ -2198,7 +2056,7 @@ pub mod example_pack_err {}
/// // Panic happens here, will be caught
/// panic!("{}", s)
/// }
-/// None => NotPanic::default().into(),
+/// None => NotPanic.into(),
/// }
/// }
///
@@ -2364,7 +2222,8 @@ pub mod example_pathfinder {}
/// }
///
/// // Create error route
-/// pack!(ErrorDirectoryNotExist = PathBuf);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ErrorDirectoryNotExist(PathBuf);
///
/// // Create commands: cd ls exit
/// dispatcher!("cd", EntryCd);
@@ -2373,16 +2232,18 @@ pub mod example_pathfinder {}
/// dispatcher!("clear", EntryClear);
///
/// // Define data needed for the cd command's execution phase
-/// pack!(StateChangeDirectory = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct StateChangeDirectory(String);
///
/// // Define data needed for the ls command's rendering phase
-/// pack!(ResultList = Vec<String>);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultList(Vec<String>);
///
/// // Parse cd command arguments
/// #[chain]
/// fn parse_cd_args(prev: EntryCd) -> Next {
/// let join = prev.pick_or_default(&arg![String]).unwrap();
-/// StateChangeDirectory::new(join).into()
+/// StateChangeDirectory(join).into()
/// }
///
/// // Execute directory change
@@ -2390,12 +2251,12 @@ pub mod example_pathfinder {}
/// fn handle_cd(prev: StateChangeDirectory, current_dir: &mut ResCurrentDir) -> Next {
/// use just_fmt::fmt_path::fmt_path;
///
-/// let join = prev.inner;
+/// let join = prev.0;
/// let new_dir = fmt_path(current_dir.dir.join(join)).unwrap_or_default();
///
/// // If the path is not found, route to error handling
/// if !new_dir.exists() {
-/// return ErrorDirectoryNotExist::new(new_dir).to_render();
+/// return ErrorDirectoryNotExist(new_dir).to_render();
/// }
///
/// current_dir.dir = new_dir;
@@ -2420,14 +2281,14 @@ pub mod example_pathfinder {}
/// .collect();
///
/// // Render ResultList
-/// ResultList::new(entries).to_render()
+/// ResultList(entries).to_render()
/// }
///
/// /// Render ResultList data
/// #[renderer]
/// fn render_list(list: ResultList) -> RenderResult {
/// let mut render_result = RenderResult::new();
-/// for item in list.inner {
+/// for item in list.0 {
/// writeln!(render_result, "{}", item).ok();
/// }
/// render_result
@@ -2454,12 +2315,7 @@ pub mod example_pathfinder {}
/// #[renderer]
/// fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) -> RenderResult {
/// let mut render_result = RenderResult::new();
-/// writeln!(
-/// render_result,
-/// "Directory not found: {}",
-/// err.inner.display()
-/// )
-/// .ok();
+/// writeln!(render_result, "Directory not found: {}", err.0.display()).ok();
/// render_result
/// }
///
@@ -2636,13 +2492,14 @@ pub mod example_resources {}
///
/// dispatcher!("greet", EntryGreet);
///
-/// pack!(ResultGreeting = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultGreeting(String);
///
/// /// Chain: reads the `ResAppName` and `ResAppVersion` resources.
/// #[chain]
/// fn handle_greet(args: EntryGreet, app: &ResAppName, version: &ResAppVersion) -> Next {
/// let who = args
-/// .inner
+/// .0
/// .first()
/// .cloned()
/// .unwrap_or_else(|| "World".to_string());
@@ -2718,7 +2575,8 @@ pub mod example_setup {}
/// }
///
/// // --------- IMPORTANT ---------
-/// // For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
+/// // For beautiful output structure, do not wrap the types that need to be output
+/// // in a newtype; instead, use a named struct.
/// // Instead, manually implement
/// // ____________________________________ Mark as structured data so it can be rendered
/// // / ____________________ Implement serde::Serialize
@@ -2734,7 +2592,7 @@ pub mod example_setup {}
/// }
/// // This will output: {"member_name":"name","member_age":32} structure
///
-/// // If using pack!(Info = (String, i32));
+/// // If wrapping with a tuple newtype (e.g. `#[derive(Grouped, Wrap)] pub struct Info((String, i32));`)
/// // Output: {"inner":["name", 32]}
///
/// // --------- IMPORTANT ---------
@@ -2812,30 +2670,30 @@ pub mod example_structural_renderer {}
/// let hello_with_valid_name = handle_hello(entry!("Peter")).into();
/// assert_render_result!(hello_with_valid_name);
/// let result_name = unpack_chain_process!(hello_with_valid_name, ResultName);
-/// assert_eq!(result_name.inner, "Peter");
+/// assert_eq!(result_name.0, "Peter");
/// }
///
/// #[test]
/// fn test_render_result_name() {
-/// let r = render_result_name(ResultName::new("Peter".into()));
+/// let r = render_result_name(ResultName("Peter".into()));
/// assert_eq!(r.to_string().as_str(), "Hello, Peter!")
/// }
///
/// #[test]
/// fn test_render_error_no_name_provided() {
-/// let r = render_error_no_name_provided(ErrorNoNameProvided::default());
+/// let r = render_error_no_name_provided(ErrorNoNameProvided);
/// assert_eq!(r.to_string().as_str(), "No name provided")
/// }
///
/// #[test]
/// fn test_render_error_name_not_available() {
-/// let r = render_error_name_not_available(ErrorNameNotAvailable::default());
+/// let r = render_error_name_not_available(ErrorNameNotAvailable);
/// assert_eq!(r.to_string().as_str(), "Name not available")
/// }
///
/// #[test]
/// fn test_render_error_name_too_long() {
-/// let r = render_error_name_too_long(ErrorNameTooLong::new(17));
+/// let r = render_error_name_too_long(ErrorNameTooLong(17));
/// assert_eq!(r.to_string().as_str(), "Name too long: 17 > 10")
/// }
/// // --------- IMPORTANT ---------
@@ -2843,29 +2701,35 @@ pub mod example_structural_renderer {}
///
/// dispatcher!("hello", EntryHello);
///
-/// pack!(ErrorNoNameProvided = ());
-/// pack!(ErrorNameTooLong = u16);
-/// pack!(ErrorNameNotAvailable = ());
+/// #[derive(Grouped)]
+/// pub struct ErrorNoNameProvided;
+///
+/// #[derive(Grouped, Wrap)]
+/// pub struct ErrorNameTooLong(u16);
+///
+/// #[derive(Grouped)]
+/// pub struct ErrorNameNotAvailable;
///
-/// pack!(ResultName = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct ResultName(String);
///
/// static VEC_REGISTERED_NAMES: &[&str] = &["Alice", "Bob", "Charlie", "David", "Eve"];
///
/// #[chain]
/// fn handle_hello(args: EntryHello) -> Next {
-/// let Some(name) = args.inner.first().cloned() else {
-/// return ErrorNoNameProvided::default().to_render();
+/// let Some(name) = args.0.first().cloned() else {
+/// return ErrorNoNameProvided.to_render();
/// };
///
/// if name.len() > 10 {
-/// return ErrorNameTooLong::new(name.len() as u16).to_render();
+/// return ErrorNameTooLong(name.len() as u16).to_render();
/// }
///
/// if VEC_REGISTERED_NAMES.contains(&name.as_str()) {
-/// return ErrorNameNotAvailable::default().to_render();
+/// return ErrorNameNotAvailable.to_render();
/// }
///
-/// ResultName::new(name).to_render()
+/// ResultName(name).to_render()
/// }
///
/// /// Renders a successful greeting with the given name.
@@ -2904,12 +2768,7 @@ pub mod example_structural_renderer {}
/// #[renderer]
/// fn render_entry_fallback(err: EntryFallback) -> RenderResult {
/// let mut render_result = RenderResult::new();
-/// writeln!(
-/// render_result,
-/// "Command not found: \"{}\"",
-/// err.inner.join(" ")
-/// )
-/// .ok();
+/// writeln!(render_result, "Command not found: \"{}\"", err.0.join(" ")).ok();
/// render_result
/// }
///
diff --git a/mingling/src/gen_program.rs b/mingling/src/gen_program.rs
index 2c476a2..ed60668 100644
--- a/mingling/src/gen_program.rs
+++ b/mingling/src/gen_program.rs
@@ -12,7 +12,7 @@ pub type Next = ChainProcess<ThisProgram>;
/// The generic program entry point.
///
-/// This type is created by the `pack!` macro as a variant of the
+/// This type is generated by the `gen_program!` macro as a variant of the
/// program's output type set (`ThisProgram`).
pub struct Entry {
/// The arguments provided by the user
@@ -41,7 +41,7 @@ pub enum ThisProgram {
/// A struct representing a "renderer not found" error.
///
-/// This type is created by the `pack!` macro as a variant of the
+/// This type is generated by the `gen_program!` macro as a variant of the
/// program's output type set (`ThisProgram`).
pub struct ErrorRendererNotFound {
/// The name of the renderer that was not found
@@ -50,7 +50,7 @@ pub struct ErrorRendererNotFound {
/// A struct representing a "dispatcher not found" error.
///
-/// This type is created by the `pack!` macro as a variant of the
+/// This type is generated by the `gen_program!` macro as a variant of the
/// program's output type set (`ThisProgram`).
pub struct EntryFallback {
/// The arguments provided by the user
@@ -61,8 +61,6 @@ pub struct EntryFallback {
pub struct ResultEmpty;
/// A dispatcher representing the subcommand `__comp` itself.
-///
-/// **You can register it using `with_dispatcher`.**
#[cfg(feature = "comp")]
pub struct CMDCompletion;
@@ -71,7 +69,7 @@ pub struct CMDCompletion;
/// This struct holds the raw command-line arguments that were passed to the `__comp`
/// subcommand, which will be used to compute completion suggestions.
///
-/// This type is created by the `pack!` macro as a variant of the
+/// This type is generated by the `gen_program!` macro as a variant of the
/// program's output type set (`ThisProgram`).
#[cfg(feature = "comp")]
pub struct CompletionContext {
@@ -85,7 +83,7 @@ pub struct CompletionContext {
/// which together provide the information needed to render completion candidates
/// to the user's shell.
///
-/// This type is created by the `pack!` macro as a variant of the
+/// This type is generated by the `gen_program!` macro as a variant of the
/// program's output type set (`ThisProgram`).
#[cfg(feature = "comp")]
pub struct CompletionSuggest {
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 6a7074b..55240e5 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -51,7 +51,7 @@ pub mod consts {
///
/// This module re-exports all macros provided by the `mingling_macros` crate,
/// including `dispatcher!`, `chain!`, `renderer!`,
-/// `gen_program!`, `pack!`, and many others. These macros form the core
+/// `gen_program!`, and many others. These macros form the core
/// building blocks of the Mingling framework.
///
/// For detailed documentation, usage examples, and the full list of available
@@ -84,13 +84,6 @@ pub mod macros {
pub use mingling_macros::help;
pub use mingling_macros::metadata;
pub use mingling_macros::mlint;
- pub use mingling_macros::pack;
- #[cfg(feature = "extras")]
- pub use mingling_macros::pack_err;
- #[cfg(all(feature = "structural_renderer", feature = "extras"))]
- pub use mingling_macros::pack_err_structural;
- #[cfg(feature = "structural_renderer")]
- pub use mingling_macros::pack_structural;
#[cfg(feature = "comp")]
#[doc(hidden)]
pub use mingling_macros::program_comp_gen;
@@ -212,19 +205,7 @@ pub mod prelude {
#[cfg(feature = "macros")]
pub use crate::macros::gen_program;
#[cfg(feature = "macros")]
- pub use crate::macros::pack;
- #[cfg(all(feature = "extras", feature = "macros"))]
- pub use crate::macros::pack_err;
- #[cfg(feature = "macros")]
pub use crate::macros::renderer;
- #[cfg(all(
- feature = "macros",
- feature = "structural_renderer",
- feature = "extras"
- ))]
- pub use mingling_macros::pack_err_structural;
- #[cfg(all(feature = "macros", feature = "structural_renderer"))]
- pub use mingling_macros::pack_structural;
pub use mingling_macros::r_append;
pub use mingling_macros::r_eprint;
pub use mingling_macros::r_eprintln;
diff --git a/mingling_cli/src/config/cmd_cfg.rs b/mingling_cli/src/config/cmd_cfg.rs
index c10195e..8baa4a1 100644
--- a/mingling_cli/src/config/cmd_cfg.rs
+++ b/mingling_cli/src/config/cmd_cfg.rs
@@ -1,8 +1,6 @@
use mingling::{
- LazyRes, Routable, ShellContext, Suggest,
- macros::{
- arg, buffer, chain, command, completion, metadata, pack, r_println, renderer, suggest,
- },
+ Grouped, LazyRes, Routable, ShellContext, Suggest, Wrap,
+ macros::{arg, buffer, chain, command, completion, metadata, r_println, renderer, suggest},
metadata::Description,
picker::{EntryPicker, PickerArg, value::Flag},
};
@@ -11,10 +9,17 @@ use crate::{Entry, Next, config::ResMlingConfig};
const FLAG_PAIR: PickerArg<Flag> = arg![pair: Flag];
-pack!(StateConfigEdit = (String, String));
-pack!(ResultConfigKeyValuePair = String);
-pack!(ResultConfigValue = String);
-pack!(ResultConfig = ());
+#[derive(Grouped, Wrap)]
+pub struct StateConfigEdit((String, String));
+
+#[derive(Grouped, Wrap)]
+pub struct ResultConfigKeyValuePair(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ResultConfigValue(String);
+
+#[derive(Grouped)]
+pub struct ResultConfig;
#[command]
pub fn cfg(args: Entry) -> Next {
@@ -27,19 +32,19 @@ pub fn cfg(args: Entry) -> Next {
match (key, value) {
(Some(k), Some(v)) => {
// Edit
- StateConfigEdit::new((k, v)).into()
+ StateConfigEdit((k, v)).into()
}
(Some(k), None) => {
// Display
if *show_pair {
- ResultConfigKeyValuePair::new(k).to_render()
+ ResultConfigKeyValuePair(k).to_render()
} else {
- ResultConfigValue::new(k).to_render()
+ ResultConfigValue(k).to_render()
}
}
(None, None) => {
// List
- ResultConfig::new(()).to_render()
+ ResultConfig.to_render()
}
_ => {
unreachable!("This path is unreachable given the positional parsing done by arg-picker")
@@ -50,13 +55,13 @@ pub fn cfg(args: Entry) -> Next {
#[chain]
pub fn handle_state_config_edit(kv: StateConfigEdit, config: &mut LazyRes<ResMlingConfig>) {
let config = config.get_mut();
- config.edit(&kv.0, &kv.1);
+ config.edit(&kv.0.0, &kv.0.1);
}
#[renderer(buffer)]
pub fn render_config_kvp(r: ResultConfigKeyValuePair, config: &mut LazyRes<ResMlingConfig>) {
let config = config.get_ref();
- let key = r.inner;
+ let key = r.0;
let value = config.get(&key);
r_println!(
"\"{}\" = \"{}\"",
@@ -68,7 +73,7 @@ pub fn render_config_kvp(r: ResultConfigKeyValuePair, config: &mut LazyRes<ResMl
#[renderer(buffer)]
pub fn render_config_value(r: ResultConfigValue, config: &mut LazyRes<ResMlingConfig>) {
let config = config.get_ref();
- let key = r.inner;
+ let key = r.0;
let value = config.get(&key);
r_println!("{}", value)
}
diff --git a/mingling_cli/src/lib.rs b/mingling_cli/src/lib.rs
index 902ab32..e82300e 100644
--- a/mingling_cli/src/lib.rs
+++ b/mingling_cli/src/lib.rs
@@ -45,7 +45,7 @@ pub fn complete_global(_ctx: &ShellContext) -> Suggest {
#[renderer]
pub fn handle_fallback(args: EntryFallback) -> RenderResult {
let mut r = RenderResult::new();
- let args = args.inner;
+ let args = args.0;
if !args.is_empty() {
eprintln_cargo!(
r,
diff --git a/mingling_cli/src/linter/cmd_explain.rs b/mingling_cli/src/linter/cmd_explain.rs
index 5346255..923e890 100644
--- a/mingling_cli/src/linter/cmd_explain.rs
+++ b/mingling_cli/src/linter/cmd_explain.rs
@@ -1,10 +1,7 @@
use crate::{Next, eprintln_cargo, linter::registry::ResLintRegistry};
use mingling::{
- Grouped, LazyRes, RenderResult, Routable, ShellContext, Suggest, SuggestItem,
- macros::{
- arg, buffer, chain, completion, dispatcher, metadata, pack, pack_err, r_println, renderer,
- routeify,
- },
+ Grouped, LazyRes, RenderResult, Routable, ShellContext, Suggest, SuggestItem, Wrap,
+ macros::{arg, buffer, chain, completion, dispatcher, metadata, r_println, renderer, routeify},
metadata::Description,
picker::EntryPicker,
};
@@ -16,9 +13,14 @@ pub fn desc_explain() -> Description {
"Explain the meaning of the specified Lint".into()
}
-pack!(StateExplainLint = String);
-pack_err!(ErrorNoExplainLintProvided);
-pack_err!(ErrorNoSuchLint = String);
+#[derive(Grouped, Wrap)]
+pub struct StateExplainLint(String);
+
+#[derive(Grouped, Default)]
+pub struct ErrorNoExplainLintProvided;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorNoSuchLint(String);
#[derive(Debug, Default, Grouped)]
pub struct ResultExplainLint {
@@ -33,11 +35,9 @@ pub struct ResultExplainLint {
#[chain(routeify)]
pub fn handle_explain(args: EntryExplain) -> Next {
let lint_name = args
- .pick_or_route(&arg![String], || {
- ErrorNoExplainLintProvided::default().to_chain()
- })
+ .pick_or_route(&arg![String], || ErrorNoExplainLintProvided.to_chain())
.to_result()?;
- StateExplainLint::new(lint_name).into()
+ StateExplainLint(lint_name).into()
}
#[chain]
@@ -46,9 +46,9 @@ pub fn handle_state_explain_lint(
registry: &mut LazyRes<ResLintRegistry>,
) -> Next {
let registry = registry.get_ref();
- let lint_name = p.inner;
+ let lint_name = p.0;
let Some(entry) = registry.lints.iter().find(|l| l.name == lint_name) else {
- return ErrorNoSuchLint::new(lint_name).to_chain();
+ return ErrorNoSuchLint(lint_name).to_chain();
};
ResultExplainLint {
lint_name: entry.name.clone(),
@@ -87,7 +87,7 @@ pub fn render_error_no_such_lint(
) -> RenderResult {
let mut r = RenderResult::new();
let registry = registry.get_ref();
- eprintln_cargo!(r, "No such lint: \"{}\"", err.info);
+ eprintln_cargo!(r, "No such lint: \"{}\"", err.0);
r_println!(r, "");
r_println!(r, "Available lints:");
for entry in registry.lints.iter() {
diff --git a/mingling_cli/src/linter/cmd_lint.rs b/mingling_cli/src/linter/cmd_lint.rs
index 07c18c5..7cab63a 100644
--- a/mingling_cli/src/linter/cmd_lint.rs
+++ b/mingling_cli/src/linter/cmd_lint.rs
@@ -1,10 +1,11 @@
use crate::linter::mlint_report::{MlintReport, StateLintReports};
use cargo_metadata::Metadata;
use mingling::consts::REMAINS;
-use mingling::macros::{arg, chain, completion, dispatcher, metadata, pack, suggest};
+use mingling::macros::{arg, chain, completion, dispatcher, metadata, suggest};
use mingling::metadata::Description;
use mingling::picker::parselib::ParserStyle;
use mingling::picker::{EntryPicker, PickerArg};
+use mingling::{Grouped, Wrap};
use mingling::{LazyRes, ShellContext, Suggest};
use tokio::task::JoinSet;
@@ -77,7 +78,8 @@ async fn linter_main(metadata: &Metadata) -> Vec<MlintReport> {
all_reports
}
-pack!(StateBeginLinter = ());
+#[derive(Grouped, Wrap)]
+pub struct StateBeginLinter(());
#[chain]
pub fn handle_lint(args: EntryLint) -> StateBeginLinter {
@@ -88,7 +90,7 @@ pub fn handle_lint(args: EntryLint) -> StateBeginLinter {
// If with_checker is not set, proceed directly to the mingling lint phase
let Some(with_checker) = with_checker else {
- return StateBeginLinter::new(());
+ return StateBeginLinter(());
};
let with_checker: Vec<&str> = with_checker.split(',').collect();
@@ -97,7 +99,7 @@ pub fn handle_lint(args: EntryLint) -> StateBeginLinter {
// Run the outer checker (e.g. cargo check) with output passed through directly
execute_checker(&with_checker, checker_args.as_slice());
- StateBeginLinter::new(())
+ StateBeginLinter(())
}
/// Run the outer checker (e.g. cargo check) with output passed through directly.
@@ -135,7 +137,7 @@ pub async fn handle_state_begin_linter(
) -> StateLintReports {
let metadata = metadata.get_ref().data();
let reports = linter_main(metadata).await;
- StateLintReports::new(reports)
+ StateLintReports(reports)
}
#[completion(EntryLint)]
diff --git a/mingling_cli/src/linter/mlint_report.rs b/mingling_cli/src/linter/mlint_report.rs
index b594b9d..d7bf652 100644
--- a/mingling_cli/src/linter/mlint_report.rs
+++ b/mingling_cli/src/linter/mlint_report.rs
@@ -9,8 +9,8 @@ use cargo_metadata::{Message, PackageId};
use annotate_snippets::level::{ERROR, HELP, NOTE, WARNING};
use annotate_snippets::{AnnotationKind, Group, Patch, Renderer, Snippet};
-use mingling::macros::{buffer, chain, pack, r_append, r_eprintln, renderer};
-use mingling::{RendererInvoker, Routable};
+use mingling::macros::{buffer, chain, r_append, r_eprintln, renderer};
+use mingling::{Grouped, RendererInvoker, Routable, Wrap};
use crate::Next;
use crate::metadata::setup::ResUsingJson;
@@ -416,22 +416,25 @@ impl MlintReport {
}
}
-pack!(StateLintReports = Vec<MlintReport>);
-pack!(ResultLintReportsAnnotateSnippet = Vec<MlintReport>);
-pack!(ResultLintReportsJson = Vec<MlintReport>);
+#[derive(Grouped, Wrap)]
+pub struct StateLintReports(pub Vec<MlintReport>);
+#[derive(Grouped, Wrap)]
+pub struct ResultLintReportsAnnotateSnippet(Vec<MlintReport>);
+#[derive(Grouped, Wrap)]
+pub struct ResultLintReportsJson(Vec<MlintReport>);
#[chain]
pub fn handle_state_lint_reports(reports: StateLintReports, using_json: &ResUsingJson) -> Next {
if using_json.using {
- ResultLintReportsJson::new(reports.inner).to_render()
+ ResultLintReportsJson(reports.0).to_render()
} else {
- ResultLintReportsAnnotateSnippet::new(reports.inner).to_render()
+ ResultLintReportsAnnotateSnippet(reports.0).to_render()
}
}
#[renderer(buffer)]
pub fn render_lint_reports(reports: ResultLintReportsAnnotateSnippet) {
- for report in reports.inner {
+ for report in reports.0 {
r_eprintln!("{}", report.to_annotate_snippet_render());
}
}
@@ -441,7 +444,7 @@ pub fn render_lint_reports_json(
reports: ResultLintReportsJson,
message_renderer: &RendererInvoker<Message>,
) {
- for report in reports.inner {
+ for report in reports.0 {
let message = report.to_compiler_message();
let result = message_renderer.invoke(message);
r_append!(result);
diff --git a/mingling_cli/src/metadata/cmd_metadata.rs b/mingling_cli/src/metadata/cmd_metadata.rs
index 3ee9f09..7f8d4ad 100644
--- a/mingling_cli/src/metadata/cmd_metadata.rs
+++ b/mingling_cli/src/metadata/cmd_metadata.rs
@@ -1,7 +1,7 @@
use cargo_metadata::Metadata;
use mingling::{
- LazyRes,
- macros::{chain, dispatcher, metadata, pack},
+ Grouped, LazyRes, Wrap,
+ macros::{chain, dispatcher, metadata},
metadata::Description,
};
@@ -16,7 +16,8 @@ pub fn desc_metadata() -> Description {
.into()
}
-pack!(ResultMetadata = ResMetadata);
+#[derive(Grouped, Wrap)]
+pub struct ResultMetadata(ResMetadata);
#[chain]
pub fn handle_metadata(_: EntryMetadata, metadata: &mut LazyRes<ResMetadata>) -> Metadata {
diff --git a/mingling_cli/src/pkg_mgr.rs b/mingling_cli/src/pkg_mgr.rs
index e140991..8493804 100644
--- a/mingling_cli/src/pkg_mgr.rs
+++ b/mingling_cli/src/pkg_mgr.rs
@@ -8,16 +8,23 @@ pub mod cmd_uninstall;
use std::path::PathBuf;
use mingling::{
- Program, RenderResult,
- macros::{pack_err, program_setup, r_println, renderer},
+ Grouped, Program, RenderResult, Wrap,
+ macros::{program_setup, r_println, renderer},
};
use crate::{ThisProgram, eprintln_cargo, hprintln_cargo};
-pack_err!(ErrorRootPackageNotFound);
-pack_err!(ErrorNoDataDirectory);
-pack_err!(ErrorPackageSpecInvalid = String);
-pack_err!(ErrorPackageNameRequired);
+#[derive(Grouped, Default)]
+pub struct ErrorRootPackageNotFound;
+
+#[derive(Grouped, Default)]
+pub struct ErrorNoDataDirectory;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorPackageSpecInvalid(String);
+
+#[derive(Grouped, Default)]
+pub struct ErrorPackageNameRequired;
/// The `mingling/packages` packages directory under the user's data directory.
#[derive(Debug, Default, Clone)]
@@ -55,7 +62,7 @@ pub fn render_error_no_data_directory(_: ErrorNoDataDirectory) -> RenderResult {
#[renderer]
pub fn render_error_package_spec_invalid(err: ErrorPackageSpecInvalid) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "invalid package spec: {}", err.info);
+ eprintln_cargo!(r, "invalid package spec: {}", err.0);
r
}
diff --git a/mingling_cli/src/pkg_mgr/cmd_install.rs b/mingling_cli/src/pkg_mgr/cmd_install.rs
index 731ba98..8b2f3b7 100644
--- a/mingling_cli/src/pkg_mgr/cmd_install.rs
+++ b/mingling_cli/src/pkg_mgr/cmd_install.rs
@@ -2,8 +2,8 @@ use std::{env, fs, io, path::PathBuf, process::Command};
use cargo_metadata::TargetKind;
use mingling::{
- Grouped, LazyRes, RenderResult, Routable, ShellContext, Suggest,
- macros::{arg, chain, command, completion, metadata, pack_err, renderer, routeify, suggest},
+ Grouped, LazyRes, RenderResult, Routable, ShellContext, Suggest, Wrap,
+ macros::{arg, chain, command, completion, metadata, renderer, routeify, suggest},
metadata::Description,
picker::{EntryPicker, PickerArg, value::Flag},
};
@@ -15,9 +15,14 @@ use crate::{
println_cargo,
};
-pack_err!(ErrorBuildFailed = String);
-pack_err!(ErrorBinaryNotFound = String);
-pack_err!(ErrorPkgEnableFailed = String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorBuildFailed(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorBinaryNotFound(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorPkgEnableFailed(String);
/// Flag: `--enable` — run `mling pkg-enable` after a successful install
/// to enable the package being installed.
@@ -75,13 +80,13 @@ pub fn install(
let metadata = metadata.get_ref().data();
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let root_package = metadata
.root_package()
.or_else(|| metadata.workspace_packages().first().copied())
- .ok_or(ErrorRootPackageNotFound::default())?;
+ .ok_or(ErrorRootPackageNotFound)?;
StateInstallBuild {
workspace_root: metadata.workspace_root.clone().into_std_path_buf(),
@@ -103,11 +108,9 @@ pub fn handle_state_install_build(state: StateInstallBuild) -> Next {
.args(["build", "--release"])
.current_dir(&state.workspace_root)
.status()
- .map_err(|e| {
- ErrorBuildFailed::new(format!("failed to run `cargo build --release`: {e}"))
- })?;
+ .map_err(|e| ErrorBuildFailed(format!("failed to run `cargo build --release`: {e}")))?;
if !status.success() {
- return ErrorBuildFailed::new(format!("`cargo build --release` failed with {status}"))
+ return ErrorBuildFailed(format!("`cargo build --release` failed with {status}"))
.to_chain();
}
@@ -146,7 +149,7 @@ pub fn handle_state_install_copy(
let bin_file = format!("{}{}", target.name, state.exe_suffix);
let src = state.release_dir.join(&bin_file);
if !src.is_file() {
- return ErrorBinaryNotFound::new(bin_file).to_chain();
+ return ErrorBinaryNotFound(bin_file).to_chain();
}
let dst = state.install_dir.join(&bin_file);
fs::copy(&src, &dst).map_err(|e| {
@@ -181,7 +184,7 @@ pub fn handle_state_install_copy(
let root_package = metadata
.root_package()
.or_else(|| metadata.workspace_packages().first().copied())
- .ok_or(ErrorRootPackageNotFound::default())?;
+ .ok_or(ErrorRootPackageNotFound)?;
return StateInstallEnable {
install_dir: state.install_dir,
installed: state.installed,
@@ -207,13 +210,11 @@ pub fn handle_state_install_enable(state: StateInstallEnable) -> Next {
.args(["pkg-enable", &spec])
.status()
.map_err(|e| {
- ErrorPkgEnableFailed::new(format!("failed to run `mling pkg-enable {spec}`: {e}"))
+ ErrorPkgEnableFailed(format!("failed to run `mling pkg-enable {spec}`: {e}"))
})?;
if !status.success() {
- return ErrorPkgEnableFailed::new(format!(
- "`mling pkg-enable {spec}` failed with {status}"
- ))
- .to_chain();
+ return ErrorPkgEnableFailed(format!("`mling pkg-enable {spec}` failed with {status}"))
+ .to_chain();
}
ResultInstall {
@@ -236,21 +237,21 @@ pub fn render_result_install(result: ResultInstall) -> RenderResult {
#[renderer]
pub fn render_error_build_failed(err: ErrorBuildFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_binary_not_found(err: ErrorBinaryNotFound) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "binary not found: {}", err.info);
+ eprintln_cargo!(r, "binary not found: {}", err.0);
r
}
#[renderer]
pub fn render_error_pkg_enable_failed(err: ErrorPkgEnableFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
diff --git a/mingling_cli/src/pkg_mgr/cmd_internal_loadpkgs.rs b/mingling_cli/src/pkg_mgr/cmd_internal_loadpkgs.rs
index b4928ce..f0ac88a 100644
--- a/mingling_cli/src/pkg_mgr/cmd_internal_loadpkgs.rs
+++ b/mingling_cli/src/pkg_mgr/cmd_internal_loadpkgs.rs
@@ -4,8 +4,8 @@ use std::{
};
use mingling::{
- Routable,
- macros::{buffer, command, pack, r_println, renderer, routeify},
+ Grouped, Routable, Wrap,
+ macros::{buffer, command, r_println, renderer, routeify},
};
use crate::{
@@ -14,16 +14,18 @@ use crate::{
};
// Version directory paths of every enabled package.
-pack!(ResultLoadPkgsPaths = Vec<PathBuf>);
+#[derive(Grouped, Wrap)]
+pub struct ResultLoadPkgsPaths(Vec<PathBuf>);
// Completion script paths of every enabled package.
-pack!(ResultLoadPkgsComps = Vec<PathBuf>);
+#[derive(Grouped, Wrap)]
+pub struct ResultLoadPkgsComps(Vec<PathBuf>);
#[command(node = "__loadpkgs_path", routeify)]
pub fn load_packages_paths(packages_dir: &ResPackagesDir) -> Next {
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let paths = enabled_version_dirs(packages_dir).map_err(|e| {
io::Error::new(
@@ -31,14 +33,14 @@ pub fn load_packages_paths(packages_dir: &ResPackagesDir) -> Next {
format!("failed to read {}: {e}", packages_dir.display()),
)
})?;
- ResultLoadPkgsPaths::new(paths).to_chain()
+ ResultLoadPkgsPaths(paths).to_chain()
}
#[command(node = "__loadpkgs_comp_scripts", routeify)]
pub fn load_packages_comp_scripts(packages_dir: &ResPackagesDir) -> Next {
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let scripts = comp_scripts(packages_dir).map_err(|e| {
io::Error::new(
@@ -46,19 +48,19 @@ pub fn load_packages_comp_scripts(packages_dir: &ResPackagesDir) -> Next {
format!("failed to read {}: {e}", packages_dir.display()),
)
})?;
- ResultLoadPkgsComps::new(scripts).to_chain()
+ ResultLoadPkgsComps(scripts).to_chain()
}
#[renderer(buffer)]
pub fn render_result_load_pkgs_paths(r: ResultLoadPkgsPaths) {
- for path in r.inner {
+ for path in r.0 {
r_println!("{}", path.display());
}
}
#[renderer(buffer)]
pub fn render_result_load_pkgs_comps(r: ResultLoadPkgsComps) {
- for path in r.inner {
+ for path in r.0 {
r_println!("{}", path.display());
}
}
diff --git a/mingling_cli/src/pkg_mgr/cmd_pkg_disable.rs b/mingling_cli/src/pkg_mgr/cmd_pkg_disable.rs
index 041bb09..b4989eb 100644
--- a/mingling_cli/src/pkg_mgr/cmd_pkg_disable.rs
+++ b/mingling_cli/src/pkg_mgr/cmd_pkg_disable.rs
@@ -1,8 +1,8 @@
use std::{fs, io};
use mingling::{
- Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem,
- macros::{arg, chain, command, completion, metadata, pack, pack_err, renderer, routeify},
+ Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem, Wrap,
+ macros::{arg, chain, command, completion, metadata, renderer, routeify},
metadata::Description,
picker::{EntryPicker, PickerArg},
};
@@ -18,10 +18,12 @@ use crate::{
/// Positional argument: package name
pub static ARG_NAME: PickerArg<String> = arg![String];
-pack_err!(ErrorPackageNotEnabled = String);
+#[derive(Grouped, Wrap)]
+pub struct ErrorPackageNotEnabled(String);
// The name of the package to disable
-pack!(StatePkgDisable = String);
+#[derive(Grouped, Wrap)]
+pub struct StatePkgDisable(String);
#[derive(Debug, Default, Grouped)]
pub struct ResultPkgDisable {
@@ -37,30 +39,30 @@ pub fn desc_pkg_disable() -> Description {
#[command(node = "pkg-disable", routeify)]
pub fn package_disable(args: EntryPkgDisable, packages_dir: &ResPackagesDir) -> Next {
let name = args
- .pick_or_route(&ARG_NAME, || ErrorPackageNameRequired::default().to_chain())
+ .pick_or_route(&ARG_NAME, || ErrorPackageNameRequired.to_chain())
.to_result()?;
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
if name.contains('/') || name.contains('\\') || name.contains("..") || name.contains('@') {
- return ErrorPackageSpecInvalid::new(name).to_chain();
+ return ErrorPackageSpecInvalid(name).to_chain();
}
- StatePkgDisable::new(name).to_chain()
+ StatePkgDisable(name).to_chain()
}
#[chain(routeify)]
pub fn handle_state_pkg_disable(p: StatePkgDisable, packages_dir: &ResPackagesDir) -> Next {
- let name = p.inner;
+ let name = p.0;
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let file = packages_dir.join(&name);
if !file.is_file() {
- return ErrorPackageNotEnabled::new(name).to_chain();
+ return ErrorPackageNotEnabled(name).to_chain();
}
fs::remove_file(&file).map_err(|e| {
io::Error::new(
@@ -86,7 +88,7 @@ pub fn render_result_pkg_disable(result: ResultPkgDisable) -> RenderResult {
#[renderer]
pub fn render_error_package_not_enabled(err: ErrorPackageNotEnabled) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "package is not enabled: {}", err.info);
+ eprintln_cargo!(r, "package is not enabled: {}", err.0);
r
}
diff --git a/mingling_cli/src/pkg_mgr/cmd_pkg_enable.rs b/mingling_cli/src/pkg_mgr/cmd_pkg_enable.rs
index 8f6a234..1235cd3 100644
--- a/mingling_cli/src/pkg_mgr/cmd_pkg_enable.rs
+++ b/mingling_cli/src/pkg_mgr/cmd_pkg_enable.rs
@@ -1,8 +1,8 @@
use std::{fs, io};
use mingling::{
- Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem,
- macros::{arg, chain, command, completion, metadata, pack, pack_err, renderer, routeify},
+ Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem, Wrap,
+ macros::{arg, chain, command, completion, metadata, renderer, routeify},
metadata::Description,
picker::{EntryPicker, PickerArg},
};
@@ -18,8 +18,11 @@ use crate::{
/// Positional argument: package spec (`foo`, `foo@0`, `foo@0.1`, `foo@0.1.2`)
pub static ARG_SPEC: PickerArg<String> = arg![String];
-pack_err!(ErrorNoMatchingVersion = String);
-pack!(StatePkgEnable = (String, String));
+#[derive(Grouped, Wrap)]
+pub struct ErrorNoMatchingVersion(String);
+
+#[derive(Grouped, Wrap)]
+pub struct StatePkgEnable((String, String));
#[derive(Debug, Default, Grouped)]
pub struct ResultPkgEnable {
@@ -35,14 +38,14 @@ pub fn desc_pkg_enable() -> Description {
#[command(node = "pkg-enable", routeify)]
pub fn package_enable(args: EntryPkgEnable, packages_dir: &ResPackagesDir) -> Next {
let spec = args
- .pick_or_route(&ARG_SPEC, || ErrorPackageNameRequired::default().to_chain())
+ .pick_or_route(&ARG_SPEC, || ErrorPackageNameRequired.to_chain())
.to_result()?;
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
if spec.contains('/') || spec.contains('\\') || spec.contains("..") {
- return ErrorPackageSpecInvalid::new(spec).to_chain();
+ return ErrorPackageSpecInvalid(spec).to_chain();
}
let (name, version_part) = match spec.split_once('@') {
@@ -78,18 +81,18 @@ pub fn package_enable(args: EntryPkgEnable, packages_dir: &ResPackagesDir) -> Ne
}
let Some((_, version)) = candidates.into_iter().max_by(|a, b| a.1.cmp(&b.1)) else {
- return ErrorNoMatchingVersion::new(spec).to_chain();
+ return ErrorNoMatchingVersion(spec).to_chain();
};
- StatePkgEnable::new((name, version.to_string())).to_chain()
+ StatePkgEnable((name, version.to_string())).to_chain()
}
#[chain(routeify)]
pub fn handle_state_pkg_enable(p: StatePkgEnable, packages_dir: &ResPackagesDir) -> Next {
- let (name, version) = p.inner;
+ let (name, version) = p.0;
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let file = packages_dir.join(&name);
@@ -110,7 +113,7 @@ pub fn render_result_pkg_enable(result: ResultPkgEnable) -> RenderResult {
#[renderer]
pub fn render_error_no_matching_version(err: ErrorNoMatchingVersion) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "no matching version for: {}", err.info);
+ eprintln_cargo!(r, "no matching version for: {}", err.0);
r
}
diff --git a/mingling_cli/src/pkg_mgr/cmd_pkg_show.rs b/mingling_cli/src/pkg_mgr/cmd_pkg_show.rs
index 88c79ef..ea26021 100644
--- a/mingling_cli/src/pkg_mgr/cmd_pkg_show.rs
+++ b/mingling_cli/src/pkg_mgr/cmd_pkg_show.rs
@@ -3,7 +3,7 @@ use std::{collections::BTreeMap, fs, io};
use colored::Colorize;
use mingling::{
Grouped, RenderResult, Routable,
- macros::{buffer, command, metadata, pack_err, r_println, renderer, routeify},
+ macros::{buffer, command, metadata, r_println, renderer, routeify},
metadata::Description,
};
@@ -15,8 +15,10 @@ use crate::{
#[derive(Debug, Default, Clone)]
pub struct PkgShowEntry {
pub name: String,
+
/// Enabled version, from the content of the enable file.
pub enabled: Option<String>,
+
/// Installed versions, newest first.
pub versions: Vec<String>,
}
@@ -31,13 +33,14 @@ pub fn desc_pkg_show() -> Description {
"Show locally installed packages".into()
}
-pack_err!(ErrorNoPackagesInstalled);
+#[derive(Grouped, Default)]
+pub struct ErrorNoPackagesInstalled;
#[command(node = "pkg-show", routeify)]
pub fn package_show(packages_dir: &ResPackagesDir) -> Next {
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let mut entries: BTreeMap<String, PkgShowEntry> = BTreeMap::new();
@@ -84,7 +87,7 @@ pub fn package_show(packages_dir: &ResPackagesDir) -> Next {
let packages: Vec<PkgShowEntry> = entries.into_values().collect();
if packages.is_empty() {
- return ErrorNoPackagesInstalled::default().into();
+ return ErrorNoPackagesInstalled.into();
}
ResultPkgShow { packages }.to_chain()
diff --git a/mingling_cli/src/pkg_mgr/cmd_uninstall.rs b/mingling_cli/src/pkg_mgr/cmd_uninstall.rs
index 2b288d6..0c3886c 100644
--- a/mingling_cli/src/pkg_mgr/cmd_uninstall.rs
+++ b/mingling_cli/src/pkg_mgr/cmd_uninstall.rs
@@ -1,8 +1,8 @@
use std::{fs, io, path::PathBuf};
use mingling::{
- LazyRes, RenderResult, Routable, ShellContext, Suggest, SuggestItem,
- macros::{arg, chain, command, completion, metadata, pack, pack_err, renderer, routeify},
+ Grouped, LazyRes, RenderResult, Routable, ShellContext, Suggest, SuggestItem, Wrap,
+ macros::{arg, chain, command, completion, metadata, renderer, routeify},
metadata::Description,
picker::{EntryPicker, PickerArg},
};
@@ -20,16 +20,20 @@ use crate::{
pub static ARG_PACKAGE: PickerArg<Option<String>> = arg![Option<String>];
// Directory names to remove, e.g. `["omg@0.1.0", "omg@0.1.1"]`
-pack!(StateUninstallPackages = Vec<String>);
+#[derive(Grouped, Wrap)]
+pub struct StateUninstallPackages(Vec<String>);
// Directories that were successfully removed.
-pack!(ResultPackageUninstalled = Vec<PathBuf>);
+#[derive(Grouped, Wrap)]
+pub struct ResultPackageUninstalled(Vec<PathBuf>);
// Directories that were not installed.
-pack_err!(ErrorPackageNotInstall = Vec<PathBuf>);
+#[derive(Grouped, Wrap)]
+pub struct ErrorPackageNotInstall(Vec<PathBuf>);
// No installed package matched the given spec.
-pack_err!(ErrorNoMatchingPackages);
+#[derive(Grouped, Default)]
+pub struct ErrorNoMatchingPackages;
/// `{data_dir}/mingling/packages`
#[metadata(EntryUninstall)]
@@ -46,7 +50,7 @@ pub fn uninstall(
let spec = args.pick(&ARG_PACKAGE).to_result()?;
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let targets = match spec {
@@ -56,13 +60,13 @@ pub fn uninstall(
let root_package = metadata
.root_package()
.or_else(|| metadata.workspace_packages().first().copied())
- .ok_or(ErrorRootPackageNotFound::default())?;
+ .ok_or(ErrorRootPackageNotFound)?;
vec![format!("{}@{}", root_package.name, root_package.version)]
}
// `name` matches every installed version, `name@version` matches exactly
Some(spec) => {
if spec.contains('/') || spec.contains('\\') || spec.contains("..") {
- return ErrorPackageSpecInvalid::new(spec).to_chain();
+ return ErrorPackageSpecInvalid(spec).to_chain();
}
if spec.contains('@') {
vec![spec]
@@ -87,7 +91,7 @@ pub fn uninstall(
}
};
- StateUninstallPackages::new(targets).to_chain()
+ StateUninstallPackages(targets).to_chain()
}
#[chain(routeify)]
@@ -97,13 +101,13 @@ pub fn handle_state_uninstall_packages(
) -> Next {
let packages_dir = &packages_dir.path;
if packages_dir.as_os_str().is_empty() {
- return ErrorNoDataDirectory::default().to_chain();
+ return ErrorNoDataDirectory.to_chain();
}
let mut removed = Vec::new();
let mut not_installed = Vec::new();
- for name in p.inner {
+ for name in p.0 {
let dir = packages_dir.join(&name);
if !dir.exists() {
not_installed.push(dir);
@@ -116,18 +120,18 @@ pub fn handle_state_uninstall_packages(
}
if removed.is_empty() && not_installed.is_empty() {
- return ErrorNoMatchingPackages::default().to_chain();
+ return ErrorNoMatchingPackages.to_chain();
}
if !removed.is_empty() {
- return ResultPackageUninstalled::new(removed).to_chain();
+ return ResultPackageUninstalled(removed).to_chain();
}
- ErrorPackageNotInstall::new(not_installed).to_chain()
+ ErrorPackageNotInstall(not_installed).to_chain()
}
#[renderer]
pub fn render_result_package_uninstalled(r: ResultPackageUninstalled) -> RenderResult {
let mut result = RenderResult::new();
- for dir in r.inner {
+ for dir in r.0 {
println_cargo!(result, "Uninstalled: {}", dir.display());
}
result
@@ -136,7 +140,7 @@ pub fn render_result_package_uninstalled(r: ResultPackageUninstalled) -> RenderR
#[renderer]
pub fn render_error_package_not_install(err: ErrorPackageNotInstall) -> RenderResult {
let mut result = RenderResult::new();
- for dir in err.info {
+ for dir in err.0 {
eprintln_cargo!(result, "not installed: {}", dir.display());
}
result
diff --git a/mingling_cli/src/proj_mgr/cmd_class_add.rs b/mingling_cli/src/proj_mgr/cmd_class_add.rs
index 9c729a9..f9272a5 100644
--- a/mingling_cli/src/proj_mgr/cmd_class_add.rs
+++ b/mingling_cli/src/proj_mgr/cmd_class_add.rs
@@ -6,8 +6,8 @@ use std::{
use just_fmt::{camel_case, kebab_case, pascal_case, snake_case};
use just_template::Template;
use mingling::{
- Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem,
- macros::{arg, chain, command, completion, metadata, pack, pack_err, renderer, routeify},
+ Grouped, RenderResult, Routable, ShellContext, Suggest, SuggestItem, Wrap,
+ macros::{arg, chain, command, completion, metadata, renderer, routeify},
metadata::Description,
picker::EntryPicker,
res::ResCurrentDir,
@@ -29,7 +29,8 @@ pub struct ClassEntry {
pub description: String,
}
-pack!(StateClassAdd = (String, String));
+#[derive(Grouped, Wrap)]
+pub struct StateClassAdd((String, String));
/// Result of adding a class: the generated file path.
#[derive(Debug, Default, Grouped)]
@@ -37,19 +38,28 @@ pub struct ResultClassAdd {
pub output: PathBuf,
}
-pack_err!(ErrorClassNameRequired = ());
-pack_err!(ErrorClassConfigMissing = String);
-pack_err!(ErrorClassNotFound = String);
-pack_err!(ErrorClassTemplateMissing = String);
-pack_err!(ErrorClassWriteFailed = String);
+#[derive(Grouped)]
+pub struct ErrorClassNameRequired;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassConfigMissing(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassNotFound(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassTemplateMissing(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorClassWriteFailed(String);
#[command(node = "class-add", routeify)]
pub fn class_add(args: EntryClassAdd) -> Next {
let (class_name, name) = args
- .pick_or_route(&arg![String], || ErrorClassNameRequired::new(()).to_chain())
- .pick_or_route(&arg![String], || ErrorClassNameRequired::new(()).to_chain())
+ .pick_or_route(&arg![String], || ErrorClassNameRequired.to_chain())
+ .pick_or_route(&arg![String], || ErrorClassNameRequired.to_chain())
.to_result()?;
- StateClassAdd::new((class_name, name)).to_chain()
+ StateClassAdd((class_name, name)).to_chain()
}
/// Walk upward from `start` to find the first directory containing `.mling`.
@@ -91,11 +101,11 @@ fn deverbatim(path: &Path) -> PathBuf {
/// name-derived parameters into `<output-dir>/<snake_case>.rs`.
#[chain(routeify)]
pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next {
- let (class_name, name) = state.inner;
+ let (class_name, name) = state.0;
// Resolve the project root: the nearest ancestor directory with `.mling`.
let Some(project_root) = find_project_root(cwd) else {
- return ErrorClassConfigMissing::new(format!(
+ return ErrorClassConfigMissing(format!(
"no `.mling` directory found from {} upward; run this inside a mingling project",
cwd.display()
))
@@ -105,14 +115,14 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
// Read `.mling/classes.toml` (the class registry).
let classes_path = project_root.join(".mling").join("classes.toml");
let content = fs::read_to_string(&classes_path).map_err(|e| {
- ErrorClassConfigMissing::new(format!("failed to read {}: {e}", classes_path.display()))
+ ErrorClassConfigMissing(format!("failed to read {}: {e}", classes_path.display()))
})?;
let classes = parse_classes(&content)
- .map_err(|e| ErrorClassConfigMissing::new(format!("invalid classes.toml: {e}")))?;
+ .map_err(|e| ErrorClassConfigMissing(format!("invalid classes.toml: {e}")))?;
// Find the requested class type.
let Some(entry) = classes.iter().find(|c| c.name == class_name) else {
- return ErrorClassNotFound::new(format!(
+ return ErrorClassNotFound(format!(
"class `{class_name}` not found in {}",
classes_path.display()
))
@@ -122,7 +132,7 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
// Read the class template (relative to `.mling/`).
let template_path = project_root.join(".mling").join(&entry.template);
let template_content = fs::read_to_string(&template_path).map_err(|e| {
- ErrorClassTemplateMissing::new(format!("failed to read {}: {e}", template_path.display()))
+ ErrorClassTemplateMissing(format!("failed to read {}: {e}", template_path.display()))
})?;
// Derive the name variants used by the template placeholders.
@@ -139,7 +149,7 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
tmpl.insert_param("upper_snake_case".to_string(), upper_snake);
tmpl.insert_param("camel_case".to_string(), camel);
let expanded = tmpl.expand().ok_or_else(|| {
- ErrorClassWriteFailed::new(format!(
+ ErrorClassWriteFailed(format!(
"failed to expand class template: {}",
template_path.display()
))
@@ -149,11 +159,10 @@ pub fn handle_state_class_add(state: StateClassAdd, cwd: &ResCurrentDir) -> Next
let output_dir = project_root.join(&entry.output_dir);
let output = output_dir.join(format!("{snake}.rs"));
fs::create_dir_all(&output_dir).map_err(|e| {
- ErrorClassWriteFailed::new(format!("failed to create {}: {e}", output_dir.display()))
- })?;
- fs::write(&output, expanded).map_err(|e| {
- ErrorClassWriteFailed::new(format!("failed to write {}: {e}", output.display()))
+ ErrorClassWriteFailed(format!("failed to create {}: {e}", output_dir.display()))
})?;
+ fs::write(&output, expanded)
+ .map_err(|e| ErrorClassWriteFailed(format!("failed to write {}: {e}", output.display())))?;
ResultClassAdd { output }.to_chain()
}
@@ -215,28 +224,28 @@ pub fn render_error_class_name_required(_err: ErrorClassNameRequired) -> RenderR
#[renderer]
pub fn render_error_class_config_missing(err: ErrorClassConfigMissing) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_class_not_found(err: ErrorClassNotFound) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_class_template_missing(err: ErrorClassTemplateMissing) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_class_write_failed(err: ErrorClassWriteFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
diff --git a/mingling_cli/src/proj_mgr/cmd_proj_init.rs b/mingling_cli/src/proj_mgr/cmd_proj_init.rs
index 1ef8d1e..edad83c 100644
--- a/mingling_cli/src/proj_mgr/cmd_proj_init.rs
+++ b/mingling_cli/src/proj_mgr/cmd_proj_init.rs
@@ -7,8 +7,8 @@ use std::{
use just_fmt::snake_case;
use just_template::Template;
use mingling::{
- Grouped, LazyRes, RenderResult, Routable,
- macros::{arg, chain, command, metadata, pack, pack_err, r_println, renderer, routeify},
+ Grouped, LazyRes, RenderResult, Routable, Wrap,
+ macros::{arg, chain, command, metadata, r_println, renderer, routeify},
metadata::Description,
picker::EntryPicker,
res::ResCurrentDir,
@@ -30,8 +30,11 @@ const RULE_FILENAME: &str = "rule.toml";
/// The directory under the project root where the template cache lives.
const CACHE_DIR_NAME: &str = "tmpl-cache";
-pack!(StateProjectGenerate = ());
-pack!(StateProjectChecklistReady = Vec<String>);
+#[derive(Grouped)]
+pub struct StateProjectGenerate;
+
+#[derive(Grouped, Wrap)]
+pub struct StateProjectChecklistReady(Vec<String>);
/// Result of the checklist phase: the extracted checklist handed to the user.
#[derive(Debug, Default, Grouped)]
@@ -46,20 +49,31 @@ pub struct ResultProjectGenerate {
pub hidden: Vec<PathBuf>,
}
-pack_err!(ErrorTemplateNotProvided = ());
-pack_err!(ErrorTemplateCopyFailed = String);
-pack_err!(ErrorTemplateFetchFailed = String);
-pack_err!(ErrorChecklistMissing = String);
-pack_err!(ErrorRuleParseFailed = String);
-pack_err!(ErrorTemplateExpandFailed = String);
+#[derive(Grouped)]
+pub struct ErrorTemplateNotProvided;
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorTemplateCopyFailed(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorTemplateFetchFailed(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorChecklistMissing(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorRuleParseFailed(String);
+
+#[derive(Grouped, Wrap)]
+pub struct ErrorTemplateExpandFailed(String);
#[command(node = "proj-init", routeify)]
pub fn proj_init(args: Entry, cwd: &ResCurrentDir) -> Next {
// Check if the checklist.toml file exists in the current directory
if cwd.join(CHECKLIST_FILENAME).exists() {
- StateProjectGenerate::new(()).into()
+ StateProjectGenerate.into()
} else {
- StateProjectChecklistReady::new(args.inner).into()
+ StateProjectChecklistReady(args.0).into()
}
}
@@ -73,7 +87,7 @@ pub fn handle_state_proj_checklist_ready(
) -> Next {
let source: TemplateSource = args
.pick_or_route(&arg![TemplateSource], || {
- ErrorTemplateNotProvided::new(()).to_chain()
+ ErrorTemplateNotProvided.to_chain()
})
.to_result()?;
@@ -88,7 +102,7 @@ pub fn handle_state_proj_checklist_ready(
configured
});
resolve_git(&source_url, &reference, &variant, &cache_dir())
- .map_err(ErrorTemplateFetchFailed::new)?
+ .map_err(ErrorTemplateFetchFailed)?
}
};
@@ -96,15 +110,15 @@ pub fn handle_state_proj_checklist_ready(
// directory; create it if it doesn't exist
let tmpl_cache = cwd.join(".mling").join(CACHE_DIR_NAME);
fs::create_dir_all(&tmpl_cache).map_err(|e| {
- ErrorTemplateCopyFailed::new(format!("failed to create {}: {e}", tmpl_cache.display()))
+ ErrorTemplateCopyFailed(format!("failed to create {}: {e}", tmpl_cache.display()))
})?;
copy_dir_contents(&template_root, &tmpl_cache)
- .map_err(|e| ErrorTemplateCopyFailed::new(e.to_string()))?;
+ .map_err(|e| ErrorTemplateCopyFailed(e.to_string()))?;
// Move the internal checklist.toml to ./ for the user to fill in
let checklist_src = tmpl_cache.join(CHECKLIST_FILENAME);
if !checklist_src.is_file() {
- return ErrorChecklistMissing::new(format!(
+ return ErrorChecklistMissing(format!(
"no checklist.toml found inside {}",
template_root.display()
))
@@ -112,7 +126,7 @@ pub fn handle_state_proj_checklist_ready(
}
let checklist_dst = cwd.join(CHECKLIST_FILENAME);
fs::rename(&checklist_src, &checklist_dst).map_err(|e| {
- ErrorTemplateCopyFailed::new(format!(
+ ErrorTemplateCopyFailed(format!(
"failed to move checklist.toml to {}: {e}",
checklist_dst.display()
))
@@ -130,7 +144,7 @@ pub fn handle_state_proj_checklist_ready(
pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDir) -> Next {
let tmpl_cache = cwd.join(".mling").join(CACHE_DIR_NAME);
if !tmpl_cache.is_dir() {
- return ErrorChecklistMissing::new(format!(
+ return ErrorChecklistMissing(format!(
"template cache not found at {}; run `mling proj-init` with a template directory first",
tmpl_cache.display()
))
@@ -140,22 +154,22 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
// Read the user-filled checklist.toml
let checklist_path = cwd.join(CHECKLIST_FILENAME);
let checklist_content = fs::read_to_string(&checklist_path).map_err(|e| {
- ErrorChecklistMissing::new(format!("failed to read {}: {e}", checklist_path.display()))
+ ErrorChecklistMissing(format!("failed to read {}: {e}", checklist_path.display()))
})?;
let answers = parse_checklist(&checklist_content)
- .map_err(|e| ErrorRuleParseFailed::new(format!("invalid checklist.toml: {e}")))?;
+ .map_err(|e| ErrorRuleParseFailed(format!("invalid checklist.toml: {e}")))?;
// Read rule.toml (template rules)
let rule_content = fs::read_to_string(tmpl_cache.join(RULE_FILENAME))
- .map_err(|e| ErrorRuleParseFailed::new(format!("failed to read rule.toml: {e}")))?;
+ .map_err(|e| ErrorRuleParseFailed(format!("failed to read rule.toml: {e}")))?;
let rules = parse_rules(&rule_content)
- .map_err(|e| ErrorRuleParseFailed::new(format!("invalid rule.toml: {e}")))?;
+ .map_err(|e| ErrorRuleParseFailed(format!("invalid rule.toml: {e}")))?;
// Compute final answers from checklist values + defaults declared in rule.toml
let answers = resolve_answers(&answers, &rules);
// Mutually exclusive toggle groups must not both be enabled.
- validate_mutexes(&answers, &rules).map_err(ErrorRuleParseFailed::new)?;
+ validate_mutexes(&answers, &rules).map_err(ErrorRuleParseFailed)?;
// Derive the crate name from the program name (e.g. `my-cli` -> `my_cli`).
let mut params: HashMap<String, String> = answers.clone();
@@ -171,7 +185,7 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
// Expand all template entries to the project root
let mut generated = Vec::new();
expand_tree(&tmpl_cache, cwd, &params, &mut generated, true)
- .map_err(ErrorTemplateExpandFailed::new)?;
+ .map_err(ErrorTemplateExpandFailed)?;
// hide-file: delete the corresponding generated file when the rule is true
@@ -182,7 +196,7 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
}
let target = cwd.join(hide.file.trim_start_matches("./"));
remove_path(&target).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!("failed to hide {}: {e}", target.display()))
+ ErrorTemplateExpandFailed(format!("failed to hide {}: {e}", target.display()))
})?;
hidden.push(target);
}
@@ -194,19 +208,19 @@ pub fn handle_state_project_generate(_: StateProjectGenerate, cwd: &ResCurrentDi
}
let target = cwd.join(hide.dir.trim_start_matches("./"));
remove_path(&target).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!("failed to hide {}: {e}", target.display()))
+ ErrorTemplateExpandFailed(format!("failed to hide {}: {e}", target.display()))
})?;
hidden.push(target);
}
// Clean up the cache
fs::remove_dir_all(&tmpl_cache).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!("failed to remove {}: {e}", tmpl_cache.display()))
+ ErrorTemplateExpandFailed(format!("failed to remove {}: {e}", tmpl_cache.display()))
})?;
// Project generated; remove the temporary checklist file
remove_path(&checklist_path).map_err(|e| {
- ErrorTemplateExpandFailed::new(format!(
+ ErrorTemplateExpandFailed(format!(
"failed to remove {}: {e}",
checklist_path.display()
))
@@ -327,35 +341,35 @@ pub fn render_error_template_not_provided(_err: ErrorTemplateNotProvided) -> Ren
#[renderer]
pub fn render_error_template_copy_failed(err: ErrorTemplateCopyFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "failed to copy template: {}", err.info);
+ eprintln_cargo!(r, "failed to copy template: {}", err.0);
r
}
#[renderer]
pub fn render_error_template_fetch_failed(err: ErrorTemplateFetchFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "failed to fetch template: {}", err.info);
+ eprintln_cargo!(r, "failed to fetch template: {}", err.0);
r
}
#[renderer]
pub fn render_error_checklist_missing(err: ErrorChecklistMissing) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_rule_parse_failed(err: ErrorRuleParseFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
#[renderer]
pub fn render_error_template_expand_failed(err: ErrorTemplateExpandFailed) -> RenderResult {
let mut r = RenderResult::new();
- eprintln_cargo!(r, "{}", err.info);
+ eprintln_cargo!(r, "{}", err.0);
r
}
diff --git a/mingling_macros/src/attr/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs
index 46238d6..c2dd952 100644
--- a/mingling_macros/src/attr/dispatcher_clap.rs
+++ b/mingling_macros/src/attr/dispatcher_clap.rs
@@ -133,7 +133,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
match <#struct_name as ::clap::Parser>::try_parse_from(clap_args) {
Ok(parsed) => ::mingling::Routable::<#program_path>::to_chain(parsed),
Err(e) => {
- return ::mingling::Routable::<#program_path>::to_render(#error_struct::new(format!("{}", e.render().ansi())))
+ return ::mingling::Routable::<#program_path>::to_render(#error_struct(format!("{}", e.render().ansi())))
},
}
}
@@ -143,7 +143,8 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
// Generate the error pack type
let error_pack = options.error_struct.as_ref().map(|error_struct| {
quote! {
- ::mingling::macros::pack!(#error_struct = String);
+ #[derive(::mingling::Grouped, ::mingling::Wrap, Default)]
+ pub struct #error_struct(pub ::std::string::String);
}
});
@@ -188,7 +189,7 @@ pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> Toke
// Keep the original struct definition
#input_struct
- // Generate the error wrapper type via pack!
+ // Generate the error wrapper type
#error_pack
// Generate the help block if enabled
diff --git a/mingling_macros/src/func.rs b/mingling_macros/src/func.rs
index 57d0ec5..baa23db 100644
--- a/mingling_macros/src/func.rs
+++ b/mingling_macros/src/func.rs
@@ -9,13 +9,6 @@ pub(crate) mod gen_program;
pub(crate) mod group;
#[cfg(all(feature = "structural_renderer", feature = "extras"))]
pub(crate) mod group_structural;
-pub(crate) mod pack;
-#[cfg(feature = "extras")]
-pub(crate) mod pack_err;
-#[cfg(all(feature = "structural_renderer", feature = "extras"))]
-pub(crate) mod pack_err_structural;
-#[cfg(feature = "structural_renderer")]
-pub(crate) mod pack_structural;
#[cfg(feature = "comp")]
pub(crate) mod program_comp_gen;
pub(crate) mod program_fallback_gen;
diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs
index c4d67a9..41dd2d0 100644
--- a/mingling_macros/src/func/dispatcher.rs
+++ b/mingling_macros/src/func/dispatcher.rs
@@ -115,7 +115,9 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
let program_type = crate::default_program_path();
let expanded = quote! {
- ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec<String>);
+ #[derive(::mingling::Grouped, ::mingling::Wrap, Default)]
+ #(#entry_attrs)*
+ pub struct #pack(pub ::std::vec::Vec<::std::string::String>);
#(#cmd_attrs)*
#[doc(hidden)]
@@ -127,7 +129,7 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
impl From<#pack> for crate::Entry {
fn from(value: #pack) -> Self {
- crate::Entry::new(value.inner)
+ crate::Entry(value.0)
}
}
@@ -136,7 +138,7 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream {
impl ::mingling::Dispatcher<#program_type> for #hidden_dispatcher {
fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess<#program_type> {
use ::mingling::Grouped;
- ::mingling::Routable::to_chain(#pack::new(args))
+ ::mingling::Routable::to_chain(#pack(args))
}
}
};
@@ -149,7 +151,7 @@ fn get_comp_entry(entry_name: &Ident) -> TokenStream2 {
let comp_entry = quote! {
impl ::mingling::CompletionEntry for #entry_name {
fn get_input(self) -> Vec<String> {
- self.inner.clone()
+ self.0.clone()
}
}
};
diff --git a/mingling_macros/src/func/entry.rs b/mingling_macros/src/func/entry.rs
index 82dfd3e..18e485f 100644
--- a/mingling_macros/src/func/entry.rs
+++ b/mingling_macros/src/func/entry.rs
@@ -57,7 +57,7 @@ pub(crate) fn entry(input: TokenStream) -> TokenStream {
let expanded = match parsed {
EntryInput::Typed { ident, .. } => {
quote! {
- #ident::new(vec![#(#string_exprs),*])
+ #ident(vec![#(#string_exprs),*])
}
}
EntryInput::Untyped { .. } => {
diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs
index a53f86f..c0a7ea8 100644
--- a/mingling_macros/src/func/gen_program.rs
+++ b/mingling_macros/src/func/gen_program.rs
@@ -57,7 +57,8 @@ pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream {
/// Alias for the current program type `ThisProgram`
pub type Next = ::mingling::ChainProcess<ThisProgram>;
- ::mingling::macros::pack!(Entry = Vec<String>);
+ #[derive(::mingling::Grouped, ::mingling::Wrap, Default)]
+ pub struct Entry(pub ::std::vec::Vec<::std::string::String>);
impl ::mingling::Routable<ThisProgram> for ::mingling::ChainProcess<ThisProgram>
{
diff --git a/mingling_macros/src/func/pack.rs b/mingling_macros/src/func/pack.rs
deleted file mode 100644
index d4fcb60..0000000
--- a/mingling_macros/src/func/pack.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-// Doc Not Optimize
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::parse::{Parse, ParseStream};
-use syn::{Attribute, Ident, Result as SynResult, Token, Type};
-
-struct PackInput {
- attrs: Vec<Attribute>,
- type_name: Ident,
- inner_type: Type,
-}
-
-impl Parse for PackInput {
- fn parse(input: ParseStream) -> SynResult<Self> {
- let attrs = input.call(Attribute::parse_outer)?;
- let type_name: Ident = input.parse()?;
- input.parse::<Token![=]>()?;
- let inner_type: Type = input.parse()?;
-
- Ok(Self {
- attrs,
- type_name,
- inner_type,
- })
- }
-}
-
-#[allow(clippy::too_many_lines)]
-pub(crate) fn pack(input: TokenStream) -> TokenStream {
- let pack_input = syn::parse_macro_input!(input as PackInput);
-
- let group_name = crate::default_program_path();
- let type_name = pack_input.type_name;
- let inner_type = pack_input.inner_type;
- let attrs = pack_input.attrs;
-
- // Generate the struct definition
- // Note: No longer derives Serialize under structural_renderer.
- // Use pack_structual! for structured output support.
- let struct_def = quote! {
- #(#attrs)*
- pub struct #type_name {
- pub(crate) inner: #inner_type,
- }
- };
-
- // Generate the new() method
- let new_impl = quote! {
- impl #type_name {
- /// Creates a new instance of the wrapper type
- pub fn new(inner: #inner_type) -> Self {
- Self { inner }
- }
- }
- };
-
- // Generate From and Into implementations
- let from_into_impl = quote! {
- impl From<#inner_type> for #type_name {
- fn from(inner: #inner_type) -> Self {
- Self::new(inner)
- }
- }
-
- impl From<#type_name> for #inner_type {
- fn from(wrapper: #type_name) -> #inner_type {
- wrapper.inner
- }
- }
- };
-
- // Generate AsRef and AsMut implementations
- let as_ref_impl = quote! {
- impl ::std::convert::AsRef<#inner_type> for #type_name {
- fn as_ref(&self) -> &#inner_type {
- &self.inner
- }
- }
-
- impl ::std::convert::AsMut<#inner_type> for #type_name {
- fn as_mut(&mut self) -> &mut #inner_type {
- &mut self.inner
- }
- }
- };
-
- // Generate Deref and DerefMut implementations
- let deref_impl = quote! {
- impl ::std::ops::Deref for #type_name {
- type Target = #inner_type;
-
- fn deref(&self) -> &Self::Target {
- &self.inner
- }
- }
-
- impl ::std::ops::DerefMut for #type_name {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.inner
- }
- }
- };
-
- // Check if the inner type implements Default by generating conditional code
- let default_impl = quote! {
- impl ::std::default::Default for #type_name
- where
- #inner_type: ::std::default::Default,
- {
- fn default() -> Self {
- Self::new(::std::default::Default::default())
- }
- }
- };
-
- let register_impl = quote! {
- ::mingling::macros::register_type!(#type_name);
- };
-
- let expanded = quote! {
- #struct_def
-
- #new_impl
- #from_into_impl
- #as_ref_impl
- #deref_impl
- #default_impl
- #register_impl
-
- impl Into<mingling::AnyOutput<#group_name>> for #type_name {
- fn into(self) -> mingling::AnyOutput<#group_name> {
- mingling::AnyOutput::new(self)
- }
- }
-
- impl Into<mingling::ChainProcess<#group_name>> for #type_name {
- fn into(self) -> mingling::ChainProcess<#group_name> {
- mingling::AnyOutput::new(self).route_chain()
- }
- }
-
- /// SAFETY: This is an internal implementation of the `pack!` macro,
- /// guaranteeing that the enum value registered by the `register_type!` macro
- /// is exactly the same as the actual return value,
- /// which can be confirmed via the `Ident` in the `quote!` block.
- unsafe impl ::mingling::Grouped<#group_name> for #type_name {
- fn member_id() -> #group_name {
- #group_name::#type_name
- }
- }
- };
-
- expanded.into()
-}
diff --git a/mingling_macros/src/func/pack_err.rs b/mingling_macros/src/func/pack_err.rs
deleted file mode 100644
index e925b82..0000000
--- a/mingling_macros/src/func/pack_err.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-// Doc Not Optimize
-use just_fmt::snake_case;
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::{Ident, Token, Type, parse_macro_input};
-
-enum PackErrInput {
- /// `pack_err!(ErrorNotFound)`
- Simple { type_name: Ident },
- /// `pack_err!(ErrorNotDir = PathBuf)`
- Typed {
- type_name: Ident,
- inner_type: Box<Type>,
- },
-}
-
-impl syn::parse::Parse for PackErrInput {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- let type_name: Ident = input.parse()?;
-
- if input.peek(Token![=]) {
- input.parse::<Token![=]>()?;
- let inner_type: Type = input.parse()?;
- Ok(Self::Typed {
- type_name,
- inner_type: Box::new(inner_type),
- })
- } else {
- Ok(Self::Simple { type_name })
- }
- }
-}
-
-#[allow(clippy::too_many_lines)]
-pub(crate) fn pack_err(input: TokenStream) -> TokenStream {
- let parsed = parse_macro_input!(input as PackErrInput);
-
- match parsed {
- PackErrInput::Simple { type_name } => {
- let name_str = type_name.to_string();
- let snake_name = snake_case!(&name_str);
-
- // Note: No longer derives Serialize under structural_renderer.
- // Use pack_err_structural for structured output support.
- let derive = quote! {
- #[derive(::mingling::Grouped)]
- };
-
- let expanded = quote! {
- #derive
- pub struct #type_name {
- /// The snake_case name of this error, automatically set at compile time.
- pub name: String,
- }
-
- impl ::std::default::Default for #type_name {
- fn default() -> Self {
- Self {
- name: #snake_name.into(),
- }
- }
- }
-
- ::mingling::macros::register_type!(#type_name);
- };
-
- expanded.into()
- }
- PackErrInput::Typed {
- type_name,
- inner_type,
- } => {
- let name_str = type_name.to_string();
- let snake_name = snake_case!(&name_str);
-
- // Note: No longer derives Serialize under structural_renderer.
- // Use pack_err_structural for structured output support.
- let derive = quote! {
- #[derive(::mingling::Grouped)]
- };
-
- let expanded = quote! {
- #derive
- pub struct #type_name {
- /// The snake_case name of this error, automatically set at compile time.
- pub name: String,
- /// Additional context info for this error.
- pub info: #inner_type,
- }
-
- impl #type_name {
- /// Creates a new error with the given info.
- /// The `name` field is automatically set to the snake_case of the struct name.
- pub fn new(info: #inner_type) -> Self {
- Self {
- name: #snake_name.into(),
- info,
- }
- }
- }
-
- ::mingling::macros::register_type!(#type_name);
- };
-
- expanded.into()
- }
- }
-}
diff --git a/mingling_macros/src/func/pack_err_structural.rs b/mingling_macros/src/func/pack_err_structural.rs
deleted file mode 100644
index 950b8dc..0000000
--- a/mingling_macros/src/func/pack_err_structural.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-// Doc Not Optimize
-use just_fmt::snake_case;
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::{Ident, Token, Type, parse_macro_input};
-
-/// `pack_err_structural!` — like `pack_err!` but also marks the type as
-/// supporting structured output via `StructuralData`.
-pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream {
- let parsed = parse_macro_input!(input as PackErrInput);
-
- let type_name = match &parsed {
- PackErrInput::Simple { type_name } | PackErrInput::Typed { type_name, .. } => {
- type_name.clone()
- }
- };
-
- // Register in STRUCTURED_TYPES
- let type_name_str = type_name.to_string();
- crate::get_global_set(&crate::STRUCTURED_TYPES)
- .lock()
- .unwrap()
- .insert(type_name_str);
-
- let structural_data = quote! {
- impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
- impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
- };
-
- // Generate the struct + impls (same as pack_err! but with Serialize derive + sealed)
- match parsed {
- PackErrInput::Simple { type_name } => {
- let name_str = type_name.to_string();
- let snake_name = snake_case!(&name_str);
-
- let expanded = quote! {
- #[derive(::mingling::Grouped, ::serde::Serialize)]
- pub struct #type_name {
- /// The snake_case name of this error, automatically set at compile time.
- pub name: String,
- }
-
- impl ::std::default::Default for #type_name {
- fn default() -> Self {
- Self {
- name: #snake_name.into(),
- }
- }
- }
-
- ::mingling::macros::register_type!(#type_name);
-
- #structural_data
- };
-
- expanded.into()
- }
- PackErrInput::Typed {
- type_name,
- inner_type,
- } => {
- let name_str = type_name.to_string();
- let snake_name = snake_case!(&name_str);
-
- let expanded = quote! {
- #[derive(::mingling::Grouped, ::serde::Serialize)]
- pub struct #type_name {
- /// The snake_case name of this error, automatically set at compile time.
- pub name: String,
- /// Additional context info for this error.
- pub info: #inner_type,
- }
-
- impl #type_name {
- /// Creates a new error with the given info.
- /// The `name` field is automatically set to the snake_case of the struct name.
- pub fn new(info: #inner_type) -> Self {
- Self {
- name: #snake_name.into(),
- info,
- }
- }
- }
-
- ::mingling::macros::register_type!(#type_name);
-
- #structural_data
- };
-
- expanded.into()
- }
- }
-}
-
-// Re-use pack_err's input parser
-enum PackErrInput {
- Simple {
- type_name: Ident,
- },
- Typed {
- type_name: Ident,
- inner_type: Box<Type>,
- },
-}
-
-impl syn::parse::Parse for PackErrInput {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- let type_name: Ident = input.parse()?;
-
- if input.peek(Token![=]) {
- input.parse::<Token![=]>()?;
- let inner_type: Type = input.parse()?;
- Ok(Self::Typed {
- type_name,
- inner_type: Box::new(inner_type),
- })
- } else {
- Ok(Self::Simple { type_name })
- }
- }
-}
diff --git a/mingling_macros/src/func/pack_structural.rs b/mingling_macros/src/func/pack_structural.rs
deleted file mode 100644
index fb13fbf..0000000
--- a/mingling_macros/src/func/pack_structural.rs
+++ /dev/null
@@ -1,170 +0,0 @@
-// Doc Not Optimize
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::Ident;
-
-use crate::get_global_set;
-
-/// `pack_structural!` — like `pack!` but also marks the type as supporting
-/// structured output via `StructuralData`.
-#[allow(clippy::too_many_lines)]
-pub(crate) fn pack_structural(input: TokenStream) -> TokenStream {
- // Parse same input format as `pack!`
- let input_parsed = syn::parse_macro_input!(input as PackStructuralInput);
- let type_name = input_parsed.type_name;
- let inner_type = input_parsed.inner_type;
- let attrs = input_parsed.attrs;
- let program_path = crate::default_program_path();
-
- // Register in STRUCTURED_TYPES
- let type_name_str = type_name.to_string();
- get_global_set(&crate::STRUCTURED_TYPES)
- .lock()
- .unwrap()
- .insert(type_name_str);
-
- // Struct definition (with Serialize derive, same as pack! under structural_renderer)
- #[cfg(not(feature = "structural_renderer"))]
- let struct_def = quote! {
- #(#attrs)*
- pub struct #type_name {
- pub inner: #inner_type,
- }
- };
-
- #[cfg(feature = "structural_renderer")]
- let struct_def = quote! {
- #(#attrs)*
- #[derive(serde::Serialize)]
- pub struct #type_name {
- pub inner: #inner_type,
- }
- };
-
- // Helper impls (same as pack!)
- let new_impl = quote! {
- impl #type_name {
- pub fn new(inner: #inner_type) -> Self {
- Self { inner }
- }
- }
- };
-
- let from_into_impl = quote! {
- impl From<#inner_type> for #type_name {
- fn from(inner: #inner_type) -> Self {
- Self::new(inner)
- }
- }
- impl From<#type_name> for #inner_type {
- fn from(wrapper: #type_name) -> #inner_type {
- wrapper.inner
- }
- }
- };
-
- let as_ref_impl = quote! {
- impl ::std::convert::AsRef<#inner_type> for #type_name {
- fn as_ref(&self) -> &#inner_type {
- &self.inner
- }
- }
- impl ::std::convert::AsMut<#inner_type> for #type_name {
- fn as_mut(&mut self) -> &mut #inner_type {
- &mut self.inner
- }
- }
- };
-
- let deref_impl = quote! {
- impl ::std::ops::Deref for #type_name {
- type Target = #inner_type;
- fn deref(&self) -> &Self::Target {
- &self.inner
- }
- }
- impl ::std::ops::DerefMut for #type_name {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.inner
- }
- }
- };
-
- let default_impl = quote! {
- impl ::std::default::Default for #type_name
- where
- #inner_type: ::std::default::Default,
- {
- fn default() -> Self {
- Self::new(::std::default::Default::default())
- }
- }
- };
-
- let register_impl = quote! {
- ::mingling::macros::register_type!(#type_name);
- };
-
- // StructuralData impl + sealed + registration
- let structural_impl = quote! {
- impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
- impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
- };
-
- let expanded = quote! {
- #struct_def
-
- #new_impl
- #from_into_impl
- #as_ref_impl
- #deref_impl
- #default_impl
- #register_impl
- #structural_impl
-
- impl Into<::mingling::AnyOutput<#program_path>> for #type_name {
- fn into(self) -> ::mingling::AnyOutput<#program_path> {
- ::mingling::AnyOutput::new(self)
- }
- }
-
- impl Into<::mingling::ChainProcess<#program_path>> for #type_name {
- fn into(self) -> ::mingling::ChainProcess<#program_path> {
- ::mingling::AnyOutput::new(self).route_chain()
- }
- }
-
- /// SAFETY: This is an internal implementation of the `pack_structural!` macro,
- /// guaranteeing that the enum value registered by the `register_type!` macro
- /// is exactly the same as the actual return value,
- /// which can be confirmed via the `Ident` in the `quote!` block.
- unsafe impl ::mingling::Grouped<#program_path> for #type_name {
- fn member_id() -> #program_path {
- #program_path::#type_name
- }
- }
- };
-
- expanded.into()
-}
-
-/// Input for `pack_structural!` — same format as `pack!`.
-struct PackStructuralInput {
- attrs: Vec<syn::Attribute>,
- type_name: Ident,
- inner_type: syn::Type,
-}
-
-impl syn::parse::Parse for PackStructuralInput {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- let attrs = input.call(syn::Attribute::parse_outer)?;
- let type_name: Ident = input.parse()?;
- input.parse::<syn::Token![=]>()?;
- let inner_type: syn::Type = input.parse()?;
- Ok(Self {
- attrs,
- type_name,
- inner_type,
- })
- }
-}
diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs
index ed04379..cdd2597 100644
--- a/mingling_macros/src/func/program_comp_gen.rs
+++ b/mingling_macros/src/func/program_comp_gen.rs
@@ -11,11 +11,11 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
pub async fn __exec_completion(prev: CompletionContext) -> Next {
use ::mingling::Grouped;
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
+ let read_ctx = ::mingling::ShellContext::try_from(prev.0);
match read_ctx {
Ok(ctx) => {
let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest::new((ctx, suggest)))
+ ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest((ctx, suggest)))
}
Err(_) => std::process::exit(1),
}
@@ -29,11 +29,11 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
pub fn __exec_completion(prev: CompletionContext) -> Next {
use ::mingling::Grouped;
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
+ let read_ctx = ::mingling::ShellContext::try_from(prev.0);
match read_ctx {
Ok(ctx) => {
let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest::new((ctx, suggest)))
+ ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest((ctx, suggest)))
}
Err(_) => std::process::exit(1),
}
@@ -50,9 +50,8 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
mod __internal_completion_mod {
use ::mingling::Grouped;
::mingling::macros::dispatcher!("__comp", CompletionContext);
- ::mingling::macros::pack!(
- CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest)
- );
+ #[derive(::mingling::Grouped, ::mingling::Wrap)]
+ pub struct CompletionSuggest(pub (::mingling::ShellContext, ::mingling::Suggest));
}
#internal_dispatcher_comp
use __internal_completion_mod::CompletionContext;
@@ -67,7 +66,7 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream {
#[::mingling::macros::renderer]
pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult {
let result = ::mingling::RenderResult::default();
- let (ctx, suggest) = prev.inner;
+ let (ctx, suggest) = prev.0;
::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(&ctx, suggest);
result
}
diff --git a/mingling_macros/src/func/program_fallback_gen.rs b/mingling_macros/src/func/program_fallback_gen.rs
index df53c60..1f3fdce 100644
--- a/mingling_macros/src/func/program_fallback_gen.rs
+++ b/mingling_macros/src/func/program_fallback_gen.rs
@@ -16,8 +16,12 @@ pub(crate) fn program_fallback_gen_impl(_input: TokenStream) -> TokenStream {
};
let expanded = quote! {
- ::mingling::macros::pack!(ErrorRendererNotFound = String);
- ::mingling::macros::pack!(EntryFallback = Vec<String>);
+ #[derive(::mingling::Grouped, ::mingling::Wrap, Default)]
+ pub struct ErrorRendererNotFound(pub ::std::string::String);
+
+ #[derive(::mingling::Grouped, ::mingling::Wrap, Default)]
+ pub struct EntryFallback(pub ::std::vec::Vec<::std::string::String>);
+
#pack_empty
};
TokenStream::from(expanded)
diff --git a/mingling_macros/src/func/program_final_gen.rs b/mingling_macros/src/func/program_final_gen.rs
index d549a2b..8123eaf 100644
--- a/mingling_macros/src/func/program_final_gen.rs
+++ b/mingling_macros/src/func/program_final_gen.rs
@@ -376,10 +376,10 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream {
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()))
+ ::mingling::AnyOutput::new(ErrorRendererNotFound(member_id.to_string()))
}
fn build_entry_fallback(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> {
- ::mingling::AnyOutput::new(EntryFallback::new(args))
+ ::mingling::AnyOutput::new(EntryFallback(args))
}
fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> {
::mingling::AnyOutput::new(ResultEmpty)
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 7da7db3..d0556dc 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -34,15 +34,13 @@ use attr::dispatcher_clap;
use attr::program_setup;
use attr::{chain, help, metadata, renderer};
use derive::{enum_tag, grouped, wrap};
+use func::dispatcher;
#[cfg(feature = "extras")]
use func::entry;
#[cfg(feature = "extras")]
pub(crate) use func::group as group_impl;
-#[cfg(feature = "extras")]
-use func::pack_err;
#[cfg(feature = "comp")]
use func::suggest;
-use func::{dispatcher, pack};
use systems::res_injection;
pub(crate) fn default_program_path() -> proc_macro2::TokenStream {
quote::quote! { crate::ThisProgram }
@@ -60,7 +58,7 @@ pub(crate) type Registry = OnceLock<Mutex<BTreeSet<String>>>;
pub(crate) static STRUCTURAL_RENDERERS: Registry = OnceLock::new();
/// Types explicitly marked with `#[derive(StructuralData)]` or created via
-/// `pack_structural!` / `group_structural!`.
+/// `group_structural!`.
#[cfg(feature = "structural_renderer")]
pub(crate) static STRUCTURED_TYPES: Registry = OnceLock::new();
@@ -183,163 +181,6 @@ pub fn group_structural(input: TokenStream) -> TokenStream {
func::group_structural::group_structural(input)
}
-/// Creates a type-safe wrapper struct around an inner type, with automatic
-/// trait implementations for use in the Mingling chain/render pipeline.
-///
-/// The generated struct implements: `From`/`Into`, `AsRef`/`AsMut`, `Deref`/`DerefMut`,
-/// `Default` (conditional on inner type), and conversion into `AnyOutput` /
-/// `ChainProcess` for routing.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// // Default program name (uses `ThisProgram`):
-/// pack!(TypeName = InnerType);
-///
-/// // Explicit program name:
-/// pack!(MyProgram, TypeName = InnerType);
-/// ```
-///
-/// # Example
-///
-/// ```rust,ignore
-/// use mingling::macros::pack;
-///
-/// // Creates `Hello` wrapping `String`, registered under `ThisProgram`:
-/// pack!(Hello = String);
-///
-/// // Creates `Greeting` wrapping `String`, registered under `MyApp`:
-/// pack!(MyApp, Greeting = String);
-/// ```
-///
-/// After expansion, `Hello` has:
-/// - `Hello::new(String)` — constructor
-/// - `Hello::to_chain()` — routes to the next chain processor
-/// - `Hello::to_render()` — routes to a renderer
-/// - `From<String> for Hello`, `From<Hello> for String`
-/// - `Deref<Target = String>`, `DerefMut`
-/// - `AsRef<String>`, `AsMut<String>`
-/// - `Default` if `String: Default`
-/// - `Into<AnyOutput<ThisProgram>>`, `Into<ChainProcess<ThisProgram>>`
-/// - 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.
-///
-/// When the `structural_renderer` feature is enabled, the struct also gets
-/// `#[derive(serde::Serialize)]`.
-#[proc_macro]
-pub fn pack(input: TokenStream) -> TokenStream {
- pack::pack(input)
-}
-
-/// Like `pack!` but also marks the type as supporting structured output
-/// (JSON / YAML / TOML / RON) via `StructuralData`.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// pack_structural!(Info = (String, i32));
-/// ```
-///
-/// This is equivalent to:
-/// ```rust,ignore
-/// pack!(Info = (String, i32));
-/// impl ::mingling::StructuralData for Info {}
-/// ```
-///
-/// Requires the `structural_renderer` feature.
-#[cfg(feature = "structural_renderer")]
-#[proc_macro]
-pub fn pack_structural(input: TokenStream) -> TokenStream {
- func::pack_structural::pack_structural(input)
-}
-
-/// 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 `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
-/// at compile time.
-///
-/// # Syntax
-///
-/// Two forms are supported:
-///
-/// ```rust,ignore
-/// // Simple form — generates a struct with only `name: String` and a `Default` impl:
-/// pack_err!(ErrorNotFound);
-///
-/// // Typed form — generates a struct with `name: String` + `info: Type` and a `new(info)` constructor:
-/// pack_err!(ErrorNotDir = PathBuf);
-/// ```
-///
-/// # Generated code
-///
-/// For `pack_err!(ErrorNotFound)`:
-///
-/// ```rust,ignore
-/// #[derive(::mingling::Grouped)]
-/// pub struct ErrorNotFound {
-/// name: String,
-/// }
-///
-/// impl Default for ErrorNotFound {
-/// fn default() -> Self {
-/// Self {
-/// name: "error_not_found".into(),
-/// }
-/// }
-/// }
-/// ```
-///
-/// For `pack_err!(ErrorNotDir = PathBuf)`:
-///
-/// ```rust,ignore
-/// #[derive(::mingling::Grouped)]
-/// pub struct ErrorNotDir {
-/// name: String,
-/// info: PathBuf,
-/// }
-///
-/// impl ErrorNotDir {
-/// pub fn new(info: PathBuf) -> Self {
-/// Self {
-/// name: "error_not_dir".into(),
-/// info,
-/// }
-/// }
-/// }
-/// ```
-///
-/// When the `structural_renderer` feature is enabled, the struct also gets
-/// `#[derive(serde::Serialize)]`.
-///
-/// This macro is only available with the `extras` feature.
-#[cfg(feature = "extras")]
-#[proc_macro]
-pub fn pack_err(input: TokenStream) -> TokenStream {
- pack_err::pack_err(input)
-}
-
-/// Like `pack_err!` but also marks the type for structured output
-/// (JSON / YAML / TOML / RON) via `StructuralData`.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// pack_err_structural!(ErrorNotFound);
-/// pack_err_structural!(ErrorNotDir = PathBuf);
-/// ```
-///
-/// Requires the `structural_renderer` and `extras` features.
-#[cfg(all(feature = "structural_renderer", feature = "extras"))]
-#[proc_macro]
-pub fn pack_err_structural(input: TokenStream) -> TokenStream {
- func::pack_err_structural::pack_err_structural(input)
-}
-
/// Early-returns the error from a `Result`, converting the `Ok` branch to the
/// next chain process value.
///
@@ -555,7 +396,7 @@ pub fn empty_result(input: TokenStream) -> TokenStream {
///
/// The macro generates:
///
-/// 1. **Entry struct** — A `pack!`-style wrapper around `Vec<String>` (the raw args).
+/// 1. **Entry struct** — A newtype wrapper around `Vec<String>` (the raw args).
/// Registered in the program enum via `register_type!`.
/// 2. **Dispatcher struct** — A hidden zero-sized struct implementing [`Dispatcher<Program>`]:
/// - `begin(args)` wraps `args` into the entry type and routes to chain.
@@ -664,78 +505,89 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
/// # Sync Example
///
/// ```rust,ignore
-/// use mingling::macros::{chain, pack, gen_program};
+/// use mingling::macros::{chain, gen_program};
+/// use mingling::{Grouped, Wrap};
///
-/// pack!(MyOutput = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct MyOutput(String);
///
/// #[chain]
/// fn greet(prev: HelloEntry) -> Next {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
-/// MyOutput::new(name)
+/// MyOutput(name)
/// }
/// ```
///
/// # Sync Example with Resource Injection
///
/// ```rust,ignore
-/// use mingling::macros::{chain, pack, gen_program};
+/// use mingling::macros::{chain, gen_program};
+/// use mingling::{Grouped, Wrap};
///
/// #[derive(Default, Clone)]
/// struct UserName(String);
///
-/// pack!(Greeting = String);
-/// pack!(DisplayCount = ());
+/// #[derive(Grouped, Wrap)]
+/// pub struct Greeting(String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct DisplayCount(());
///
/// #[chain]
/// fn greet(prev: HelloEntry, user_name: &UserName, count: &mut u64) -> Next {
/// *count += 1;
-/// Greeting::new(format!("Hello, {}!", user_name.0))
+/// Greeting(format!("Hello, {}!", user_name.0))
/// }
/// ```
///
/// # Async Example (with `async` feature)
///
/// ```rust,ignore
-/// use mingling::macros::{chain, pack, gen_program};
+/// use mingling::macros::{chain, gen_program};
+/// use mingling::{Grouped, Wrap};
///
-/// pack!(MyOutput = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct MyOutput(String);
///
/// #[chain]
/// async fn greet(prev: HelloEntry) -> Next {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
/// some_async_fn(&name).await;
-/// MyOutput::new(name)
+/// MyOutput(name)
/// }
/// ```
///
/// # Async Example with Immutable Resource Injection
///
/// ```rust,ignore
-/// use mingling::macros::{chain, pack, gen_program};
+/// use mingling::macros::{chain, gen_program};
+/// use mingling::{Grouped, Wrap};
///
-/// pack!(MyOutput = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct MyOutput(String);
///
/// #[chain]
/// async fn greet(prev: HelloEntry, prefix: &Prefix) -> Next {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
/// some_async_fn(&name).await;
-/// MyOutput::new(format!("{}{}", prefix.0, name))
+/// MyOutput(format!("{}{}", prefix.0, name))
/// }
/// ```
///
/// # Async Example with Mutable Resource Injection
///
/// ```rust,ignore
-/// use mingling::macros::{chain, pack, gen_program};
+/// use mingling::macros::{chain, gen_program};
+/// use mingling::{Grouped, Wrap};
///
-/// pack!(MyOutput = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct MyOutput(String);
///
/// #[chain]
/// async fn greet(prev: HelloEntry, ec: &mut ResExitCode) -> Next {
/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
/// ec.exit_code = 42;
/// some_async_fn(&name).await;
-/// MyOutput::new(name)
+/// MyOutput(name)
/// }
/// ```
///
@@ -777,10 +629,12 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::macros::{renderer, pack, gen_program};
+/// use mingling::macros::{renderer, gen_program};
+/// use mingling::{Grouped, Wrap};
/// use std::io::Write;
///
-/// pack!(Greeting = String);
+/// #[derive(Grouped, Wrap)]
+/// pub struct Greeting(String);
///
/// #[renderer]
/// fn render_greeting(prev: Greeting) -> RenderResult {
@@ -1063,7 +917,7 @@ pub fn dispatcher_clap(attr: TokenStream, item: TokenStream) -> TokenStream {
/// Creates a packed entry value from a list of string literals.
///
/// This is a convenience macro for constructing entry wrapper types (created
-/// via `pack!` or `dispatcher!`) with test data, typically used in unit tests
+/// via `dispatcher!`) with test data, typically used in unit tests
/// or quick prototypes.
///
/// # Syntax
@@ -1071,9 +925,9 @@ pub fn dispatcher_clap(attr: TokenStream, item: TokenStream) -> TokenStream {
/// Two forms:
///
/// ```rust,ignore
-/// // Named form — wraps into a specific pack type:
+/// // Named form — wraps into a specific entry type:
/// entry!(MyEntry, ["a", "b", "c"])
-/// // Expands to: MyEntry::new(vec!["a".to_string(), "b".to_string(), "c".to_string()])
+/// // Expands to: MyEntry(vec!["a".to_string(), "b".to_string(), "c".to_string()])
///
/// // Bracket form — returns Vec<String>.into() for type inference:
/// entry!["a", "b", "c"]
@@ -1085,7 +939,7 @@ pub fn dispatcher_clap(attr: TokenStream, item: TokenStream) -> TokenStream {
/// ```rust,ignore
/// use mingling::macros::entry;
///
-/// // Named form (with a specific pack type):
+/// // Named form (with a specific entry type):
/// let args = entry!(MyEntry, ["--name", "Alice", "--count", "5"]);
///
/// // Bracket form (type inference):
@@ -1094,8 +948,7 @@ pub fn dispatcher_clap(attr: TokenStream, item: TokenStream) -> TokenStream {
///
/// # See also
///
-/// - `pack!` — For creating the wrapper types used with `entry!`.
-/// - `dispatcher!` — Which implicitly creates entry types via `pack!`.
+/// - `dispatcher!` — Which implicitly creates entry types.
#[cfg(feature = "extras")]
#[proc_macro]
pub fn entry(input: TokenStream) -> TokenStream {
@@ -1204,11 +1057,12 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::macros::{help, pack, gen_program};
+/// use mingling::macros::{help, gen_program};
/// use mingling::{prelude::*, setup::BasicProgramSetup, RenderResult};
/// use std::io::Write;
///
-/// pack!(MyEntry = Vec<String>);
+/// #[derive(Grouped, Wrap)]
+/// pub struct MyEntry(Vec<String>);
///
/// #[help]
/// fn help_my_entry(prev: MyEntry) -> RenderResult {
@@ -1620,8 +1474,8 @@ pub fn derive_wrap(input: TokenStream) -> TokenStream {
/// }
/// ```
///
-/// This is equivalent to using `pack!` but works with custom structs that
-/// have named fields. For simple wrappers, prefer `pack!`.
+/// This is equivalent to using `#[derive(Grouped)]` but works with custom structs that
+/// have named fields.
#[proc_macro_derive(Grouped, attributes(group))]
pub fn derive_grouped(input: TokenStream) -> TokenStream {
grouped::derive_grouped(input)
@@ -1813,7 +1667,7 @@ pub fn program_comp_gen(input: TokenStream) -> TokenStream {
/// 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(Grouped)]`(`macro.derive_grouped.html`)
+/// This macro is called internally by `#[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.
@@ -1885,7 +1739,7 @@ pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
/// and its `ProgramCollect` implementation.
///
/// This is the core code generation macro that:
-/// 1. Collects all registered types (from `pack!`, `#[derive(Grouped)]`, etc.) and
+/// 1. Collects all registered types (from `#[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
diff --git a/mingling_macros/src/systems.rs b/mingling_macros/src/systems.rs
index ef3624e..3279b51 100644
--- a/mingling_macros/src/systems.rs
+++ b/mingling_macros/src/systems.rs
@@ -1,8 +1,7 @@
-// Doc Not Optimize
#[cfg(not(feature = "dispatch_tree"))]
pub(crate) mod dispatch_list_gen;
+
#[cfg(feature = "dispatch_tree")]
pub(crate) mod dispatch_tree_gen;
+
pub(crate) mod res_injection;
-#[cfg(feature = "structural_renderer")]
-pub(crate) mod structural_data;
diff --git a/mingling_macros/src/systems/structural_data.rs b/mingling_macros/src/systems/structural_data.rs
deleted file mode 100644
index ac9b1ca..0000000
--- a/mingling_macros/src/systems/structural_data.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-// Doc Not Optimize
-//! Legacy structural data module.
-//!
-//! Functions have been moved to:
-//! - `derive::structural_data` — `derive_structural_data`
-//! - `func::pack_structural` — `pack_structural`
-//! - `func::group_structural` — `group_structural`
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
index b7f923c..5b8b096 100644
--- a/mingling_pathf/src/pattern_analyzer.rs
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -4,7 +4,7 @@
//!
//! It provides a pluggable architecture via the `AnalyzePattern` trait, allowing different
//! syntactic patterns to be registered and applied. Built-in patterns cover common structures
-//! such as basic structs, packs, groups, derives, chains, renderers, help, completion, and
+//! such as basic structs, groups, derives, chains, renderers, help, completion, and
//! dispatch patterns (both standard and clap-based).
//!
//! The entry points are:
@@ -18,14 +18,13 @@ use std::path::Path;
use crate::error::MinglingPathfinderError;
use crate::patterns::{
ChainPattern, CommandPattern, CompletionPattern, DispatcherClapPattern, DispatcherPattern,
- GroupPattern, GroupedDerivePattern, HelpPattern, MetadataPattern, PackPattern, RendererPattern,
+ GroupPattern, GroupedDerivePattern, HelpPattern, MetadataPattern, RendererPattern,
};
/// Creates a default `PatternAnalyzer` with all built-in patterns pre-registered.
#[must_use]
pub fn init() -> PatternAnalyzer {
let mut analyzer = PatternAnalyzer::new();
- analyzer.add_pattern(PackPattern);
analyzer.add_pattern(GroupPattern);
analyzer.add_pattern(GroupedDerivePattern);
analyzer.add_pattern(ChainPattern);
diff --git a/mingling_pathf/src/patterns.rs b/mingling_pathf/src/patterns.rs
index fa0fc8b..43c60ac 100644
--- a/mingling_pathf/src/patterns.rs
+++ b/mingling_pathf/src/patterns.rs
@@ -10,7 +10,6 @@ pub use group::*;
pub use grouped_derive::*;
pub use help::*;
pub use metadata::*;
-pub use pack::*;
pub use renderer::*;
mod chain;
@@ -22,5 +21,4 @@ mod group;
mod grouped_derive;
mod help;
mod metadata;
-mod pack;
mod renderer;
diff --git a/mingling_pathf/src/patterns/pack.rs b/mingling_pathf/src/patterns/pack.rs
deleted file mode 100644
index 10327a7..0000000
--- a/mingling_pathf/src/patterns/pack.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-// Doc Not Optimize
-//! The `PackPattern` matches types defined by `pack!`, `pack_err!`, `pack_structural!`, and `pack_err_structural!` macros.
-//! It extracts the registered type name (e.g., `TypeName` from `pack!(TypeName = InnerType)`).
-//! This is used to track packed type definitions for code generation or analysis.
-
-use syn::Item;
-
-use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
-
-/// Matches types defined by `pack!`, `pack_err!`, `pack_structural!`, `pack_err_structural!` macros.
-///
-/// Covered forms:
-/// - `pack!(TypeName = InnerType)`
-/// - `pack! { TypeName = InnerType }`
-/// - `pack_err!(TypeName)`
-/// - `pack_err!(TypeName = InnerType)`
-/// - `pack_structural!` series same as above
-pub struct PackPattern;
-
-impl AnalyzePattern for PackPattern {
- fn contains(&self, content: &str) -> bool {
- content.contains("pack!")
- || content.contains("pack_err!")
- || content.contains("pack_structural!")
- || content.contains("pack_err_structural!")
- }
-
- fn analyze(&self, content: &str) -> Vec<AnalyzeItem> {
- let Ok(syntax) = syn::parse_file(content) else {
- return Vec::new();
- };
-
- let mut items = Vec::new();
-
- for item in &syntax.items {
- match item {
- // Top-level macro calls
- Item::Macro(m) => {
- if let Some(name) = try_extract_pack_name(m) {
- items.push(AnalyzeItem::local(String::new(), name));
- }
- }
- // Macro calls inside inline modules
- Item::Mod(item_mod) => {
- if let Some((_, nested)) = &item_mod.content {
- for n in nested {
- if let Item::Macro(m) = n
- && let Some(name) = try_extract_pack_name(m)
- {
- items.push(AnalyzeItem::local(item_mod.ident.to_string(), name));
- }
- }
- }
- }
- _ => {}
- }
- }
-
- items
- }
-}
-
-/// If the macro call is `pack!` / `pack_err!` / etc., extract the registered type name.
-fn try_extract_pack_name(m: &syn::ItemMacro) -> Option<String> {
- let macro_name = m.mac.path.segments.last()?.ident.to_string();
-
- match macro_name.as_str() {
- "pack" | "pack_err" | "pack_structural" | "pack_err_structural" => {}
- _ => return None,
- }
-
- let tokens = &m.mac.tokens;
-
- // `pack!(T)` or `pack!(T = U)` — the first ident is the type name
- // Parse simply with syn
- if let Ok(ident) = syn::parse2::<syn::Ident>(tokens.clone()) {
- // pack!(TypeName) — just a single ident
- return Some(ident.to_string());
- }
-
- // Try to parse `Ident = Type`
- // Clone tokens first to avoid partial consumption
- let stream = tokens.clone();
- let mut iter = stream.into_iter();
-
- // Skip leading attributes/doc comments
- loop {
- if let proc_macro2::TokenTree::Ident(ident) = iter.next()? {
- // Found the first ident, this is the type name
- let type_name = ident.to_string();
-
- // Check if `=` follows
- if let Some(proc_macro2::TokenTree::Punct(p)) = iter.next()
- && p.as_char() == '='
- {
- // pack!(TypeName = InnerType)
- return Some(type_name);
- }
-
- // pack_err!(TypeName) — only a single ident
- return Some(type_name);
- }
- }
-}
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index fff2597..22e1cc2 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -174,33 +174,6 @@ fn test_completion_analyze() {
}
#[test]
-fn test_pack_analyze() {
- let analyzer = mingling_pathf::pattern_analyzer::init();
- let file = current_dir().unwrap().join("src/test_files/test_pack.rs");
-
- let r = analyzer.analyze_file(file).unwrap();
- let required: Vec<&str> = vec![
- "::ResultPack1",
- "::ErrorPack1",
- "::ErrorPack2",
- "::ResultPack2",
- "::ErrorPack3",
- "::ErrorPack4",
- "::sub::ResultPack1",
- "::sub::ErrorPack1",
- "::sub::ErrorPack2",
- "::sub::ResultPack2",
- "::sub::ErrorPack3",
- "::sub::ErrorPack4",
- ];
-
- assert_eq!(r.len(), required.len());
- for entry in &required {
- assert!(r.contains(*entry), "Result should contain: {}", entry);
- }
-}
-
-#[test]
fn test_group_analyze() {
let analyzer = mingling_pathf::pattern_analyzer::init();
let file = current_dir().unwrap().join("src/test_files/test_group.rs");
diff --git a/mingling_pathf/test/src/test_files/test_pack.rs b/mingling_pathf/test/src/test_files/test_pack.rs
deleted file mode 100644
index 759e35f..0000000
--- a/mingling_pathf/test/src/test_files/test_pack.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-mingling::macros::pack!(ResultPack1 = String);
-mingling::macros::pack_err!(ErrorPack1);
-mingling::macros::pack_err!(ErrorPack2 = PathBuf);
-
-pack!(ResultPack2 = (u8, String));
-pack_err!(ErrorPack3);
-pack_err!(ErrorPack4 = PathBuf);
-
-pub mod sub {
- mingling::macros::pack!(ResultPack1 = String);
- mingling::macros::pack_err!(ErrorPack1);
- mingling::macros::pack_err!(ErrorPack2 = PathBuf);
-
- pack!(ResultPack2 = (u8, String));
- pack_err!(ErrorPack3);
- pack_err!(ErrorPack4 = PathBuf);
-}