aboutsummaryrefslogtreecommitdiff
path: root/CHANGELOG.md
diff options
context:
space:
mode:
Diffstat (limited to 'CHANGELOG.md')
-rw-r--r--CHANGELOG.md406
1 files changed, 404 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1712b4b..62b1f7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,8 @@ Any contributor making changes to the project must record their changes in this
**- Milestone.1 "MVP" -**
- [Unreleased](#unreleased)
-- [Release 0.4.0 (Unreleased)](#release-040-unreleased)
+- [Release 0.5.0 (Unreleased)](#release-050-unreleased)
+- [Release 0.4.0 (2026-08-16)](#release-040-2026-08-16)
- [Release 0.3.0 (2026-07-27)](#release-030-2026-07-27)
- [Release 0.2.2 (2026-07-10)](#release-022-2026-07-10)
- [Release 0.2.1 (2026-07-01)](#release-021-2026-07-01)
@@ -53,7 +54,306 @@ None
## Contents
-### 0.4.0 (Unreleased)
+### 0.5.0 (Unreleased)
+
+#### Fixes:
+
+None
+
+#### Optimizations:
+
+None
+
+#### Features:
+
+1. **[`macros:wrap`]** Added the `#[derive(Wrap)]` derive macro that treats a struct as its inner (wrapped) type. The macro generates:
+
+ - `From<Inner> for Self` — construct the wrapper from the inner value
+ - `From<Self> for Inner` — unwrap back to the inner value (i.e. `Into<Inner>`)
+ - `Deref` / `DerefMut` — delegate all methods to the inner value
+
+ **Inner field selection:**
+
+ - Tuple struct with one field → that field is the inner type
+ - Named struct with one field → that field is the inner type
+ - Named struct with multiple fields → mark exactly one field with `#[wrap]`; the remaining fields are initialized with `Default::default()` when constructing via `From<Inner>`
+
+ **Example:**
+
+ ```rust,ignore
+ use mingling::macros::Wrap;
+
+ #[derive(Wrap)]
+ struct Name(String);
+
+ #[derive(Wrap)]
+ struct Greeting {
+ name: String,
+ }
+
+ #[derive(Wrap)]
+ struct Task {
+ #[wrap]
+ content: String,
+ done: bool,
+ }
+
+ let name = Name::from("Mingling".to_string());
+ // `Deref` forwards methods to the inner `String`
+ assert_eq!(name.len(), 8);
+ let inner: String = name.into();
+ ```
+
+ The macro is re-exported from `mingling::Wrap` and `mingling::prelude::Wrap` (feature-gated behind `macros`).
+
+#### **BREAKING CHANGES** (API CHANGES):
+
+1. **[`core:comp`]** **[`macros:dispatch_tree`]** **[BREAKING RENAME]** Renamed the prefix-tree dispatch method `dispatch_args_trie` to `dispatch_args` across the codebase.
+
+ **Renamed methods:**
+
+ - `ProgramCollect::dispatch_args_trie` → `ProgramCollect::dispatch_args` (trait definition in `mingling_core/src/program/collection.rs`)
+ - `Program::dispatch_args_trie` → `Program::dispatch_args` (in `mingling_core/src/program.rs`)
+ - `MockProgramCollect::dispatch_args_trie` → `MockProgramCollect::dispatch_args` (in `mingling_core/src/program/collection/mock.rs`)
+ - The `dispatch_args_trie` function body generated by `dispatch_tree_gen.rs` now emits a `dispatch_args` method instead.
+ - All call sites updated: `mingling_core/src/program/exec.rs`, `mingling_macros/src/systems/dispatch_tree_gen.rs`, and two call sites in `mingling_core/src/comp.rs` (both in the `CompletionHelper::complete` method and in the `entry_description` helper's dispatch-tree branch).
+
+ **Migration guide:**
+
+ - Any code calling `C::dispatch_args_trie(...)` or `program.dispatch_args_trie(...)` must now call `C::dispatch_args(...)` or `program.dispatch_args(...)`.
+ - Any manual `ProgramCollect` implementation (e.g., in tests or mocks) must rename the method from `dispatch_args_trie` to `dispatch_args`.
+
+ _No behavioral changes — this is a pure rename of the prefix-tree dispatch method. The method's semantics, signature, and dispatch-tree behavior are unchanged; only its name dropped the redundant `_trie` suffix.
+
+2. **[`macros:dispatcher`]** **[BREAKING]** Dispatchers are now always registered at compile time — the `with_dispatcher` / `with_dispatchers` dynamic registration API on `Program` has been removed entirely.
+
+ ### What changed
+
+ Previously, the `dispatch_tree` feature controlled _whether_ dispatchers were collected at compile time. With the feature enabled, `dispatcher!` (and the related `dispatcher_clap!`, `#[command]`, and completion macros) emitted a `__internal_dispatcher_*` static and registered the node in the global `COMPILE_TIME_DISPATCHERS` registry via `register_dispatcher!`; `gen_program!` then built a `dispatch_args` trie from that registry. Without the feature, users had to manually register dispatchers at runtime via `program.with_dispatcher(CMDGreet)`.
+
+ Now, **all dispatchers are always collected at compile time** regardless of the `dispatch_tree` feature:
+
+ - `dispatcher!`, `dispatcher_clap!`, `#[command]`, and the `comp`-generated completion dispatcher **always** emit the `__internal_dispatcher_*` static and call `register_dispatcher!`.
+ - `gen_program!` always reads `COMPILE_TIME_DISPATCHERS` and generates both `ProgramCollect::dispatch_args` and `ProgramCollect::get_nodes` from it.
+ - The `dispatch_tree` feature now only selects the _internal matching strategy_: a char-level trie when enabled, a linear longest-prefix list otherwise. Both strategies are generated from the same compile-time-collected entries.
+ - The `Program.dispatcher` field and the `with_dispatcher` / `with_dispatchers` methods (and the deprecated `Dispatchers` multi-registration helper) have been **removed**.
+
+ ### Removed API
+ - `Program::with_dispatcher<Disp>(&mut self, dispatcher: Disp) -> &mut Self` — **removed**
+ - `Program::with_dispatchers<D>(&mut self, dispatchers: D) -> &mut Self` — **removed** (already deprecated)
+ - `Dispatchers<G>` struct and all its `From` tuple impls (up to 7 elements), `Deref`, and `Into<Vec<_>>` conversions — **removed** (already deprecated)
+ - `Program::dispatch_args_dynamic(...)` — **removed** (renamed to `dispatch_args` in **BREAKING CHANGE #1**)
+ - `ProgramCollect::dispatch_args` no longer has a fallback default body and is now a required (non-optional) method — any manual `ProgramCollect` impl (tests, mocks, etc.) must implement both `dispatch_args` and `get_nodes`.
+
+ ### New internal module
+
+ A new `dispatch_list_gen` module was added to `mingling_macros` (`systems/dispatch_list_gen.rs`) providing `gen_dispatch_args`, which generates a linear `dispatch_args` body used when the `dispatch_tree` feature is **disabled**. It sorts nodes by display-name length (longest first) so the first matching node is the most specific one, mirroring the old dynamic dispatcher's "longest registered prefix wins" rule:
+
+ ```rust,ignore
+ fn dispatch_args(
+ raw: &[String],
+ ) -> Result<AnyOutput<Self::Enum>, ProgramInternalExecuteError> {
+ let raw_string = format!("{} ", raw.join(" "));
+ // ... linear if-chain over each node, longest prefix first ...
+ Ok(Self::build_entry_fallback(raw.to_vec()))
+ }
+ ```
+
+ `dispatch_tree_gen::gen_dispatch_args_trie` continues to provide the trie strategy (and now also exposes the shared `gen_get_nodes` helper, moved from `dispatch_tree_gen` into `program_final_gen`).
+
+ ### pathf changes
+ - **`mingling_pathf::config::PathfinderConfig`** — **removed** (deleted `config.rs`). The `use_dispatch_tree` flag no longer exists.
+ - **`pattern_analyzer::init_with_config(config)`** — **removed**; `init()` now always registers `DispatcherPattern` / `DispatcherClapPattern` with compile-time collection enabled.
+ - **`DispatcherPattern` / `DispatcherClapPattern`** — no longer carry a `use_dispatch_tree` field (`new()` takes no arguments). Both patterns now always extract the `__internal_dispatcher_*` static for every matched command.
+ - **`mingling_pathf::analyze_and_build_type_mapping_for` / `analyze_and_build_type_mapping`** — signatures no longer take a `&PathfinderConfig` argument.
+ - **`mingling_core::build::pathf`** — the wrappers no longer pass a config (the `config::*` re-export was removed).
+
+ ### Migration guide
+ - **Remove all `program.with_dispatcher(...)` calls.** Dispatchers are now automatically collected by `gen_program!` — no explicit registration is needed. Examples of affected call sites (all updated in this release): `example-basic`, `example-argument-parse`, `example-argument-picker`, `example-async-support`, `example-clap-binding`, `example-command-macro`, `example-completion`, `example-custom-pickable`, `example-dispatch-tree`, `example-enum-tag`, `example-error-handling`, `example-exitcode`, `example-help`, `example-hook`, `example-implicit-dispatcher`, `example-lazy-resources`, `example-metadata`, `example-outside-type`, `example-pack-err`, `example-panic-unwind`, `example-pathfinder`, `example-repl-basic`, `example-resources`, `example-setup`, `example-structural-renderer`, `example-unit-test`, and `full-todolist`.
+ - **Any manual `ProgramCollect` impl** must now implement both required methods `dispatch_args` and `get_nodes`.
+ - **The `dispatch_tree` feature is now purely an internal optimization** (trie vs. linear-list command matching). It no longer changes whether dispatchers are collected at compile time — that behavior is unconditional. Update any documentation/comments that claimed otherwise.
+ - **The `__internal_dispatcher_*` static and the compile-time registration** are now always emitted, so `pathf`-based `use` imports for these types are unconditional.
+
+ _Behavioral note:_ the runtime behavior of programs is unchanged — all dispatchers registered via `with_dispatcher` are now simply gathered automatically, and the "longest registered prefix wins" matching rule is preserved by both the trie and the linear-list strategies.
+
+3. **[`core`]** **[`macros`]** **[BREAKING REMOVAL]** Removed the `Node` type, the `node!` macro, and the `Dispatcher::node()` / `Dispatcher::clone_dispatcher()` methods. Command path matching is now handled entirely by the compile-time-collected string command names, and dispatchers are identified by a hidden internally-generated `__Dispatcher*` struct.
+
+ ### What changed
+
+ The `Node` struct (in `mingling_core::asset::node`) was a path hierarchy of kebab-cased string segments used by the old dynamic dispatcher to match user input. With dispatchers now always collected at compile time (see **BREAKING CHANGE #2** above), the `Node` type and its supporting machinery became dead code. Command names are now stored and matched as plain string literals during compile-time registration.
+
+ **Removed API:**
+
+ - **`mingling_core::Node`** — Removed the entire `node` module and its public re-export. This includes the struct itself, its `From<&str>` / `From<String>` impls, `join()`, `PartialEq` / `Eq`, `PartialOrd` / `Ord`, `Display`, and `Default`.
+ - **`mingling::macros::node!`** — Removed the `node!` procedural macro. It was only used internally by `dispatcher!` / `dispatcher_clap!` / `#[command]` to construct a `Node` from a dot-separated string; now that `Node` is gone, the macro is obsolete.
+ - **`Dispatcher::node(&self) -> Node`** — Removed from the `Dispatcher` trait. Dispatchers no longer expose a `Node` hierarchy; the command path is embedded in the compile-time registration via `register_dispatcher!("name", ...)`.
+ - **`Dispatcher::clone_dispatcher(&self) -> Box<dyn Dispatcher<C>>`** — Removed from the `Dispatcher` trait, along with the blanket `Clone for Box<dyn Dispatcher<G>>` impl (which relied on `clone_dispatcher`). Dynamic dispatch / boxing of dispatchers is no longer supported.
+ - **`mingling::macros::node` re-export** — Removed from `mingling_macros/src/lib.rs` and `mingling/src/lib.rs`.
+
+ **Dispatcher trait now requires only `begin`:**
+
+ ```rust
+ pub trait Dispatcher<C> {
+ fn begin(&self, args: Vec<String>) -> ChainProcess<C>;
+ }
+ ```
+
+ **Generated dispatcher struct renamed:**
+
+ The `dispatcher!` / `dispatcher_clap!` / `#[command]` macros now generate a **hidden** dispatcher struct named `__Dispatcher{Pascal}` (e.g., `__DispatcherGreet`) instead of the user-facing `CMD*` struct. The struct is marked `#[doc(hidden)]` and `#[allow(nonstandard_style)]`. Users never reference it directly — it is registered at compile time via `register_dispatcher!` and matched purely by its string command name.
+
+ **`Dispatcher` trait example** (from `mingling_core/src/asset/dispatcher.rs` docs):
+
+ ```rust,ignore
+ impl Dispatcher<ThisProgram> for CMDGreet {
+ fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+ Routable::to_chain(Foo { args })
+ }
+ }
+ ```
+
+ **`dispatcher!` syntax change** (reflected in **BREAKING CHANGE #1**'s rename context):
+
+ - Old: `dispatcher!("greet", CMDGreet => EntryGreet)`
+ - New: `dispatcher!("greet", EntryGreet)`
+
+ The `CMD*` is no longer part of the user-facing syntax — the dispatcher struct is generated internally as `__Dispatcher*`. The old form produces a compile error with a migration hint.
+
+ **`dispatcher_clap!` syntax change:**
+
+ - Old: `#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreet)]`
+ - New: `#[dispatcher_clap("greet", help = true, error = ErrorGreet)]`
+
+ `#[dispatcher_clap("greet")]` (bare, no options) is still valid.
+
+ **`#[command]` syntax change:**
+
+ - The `name = CMDName` attribute argument was removed. `#[command(entry = EntryGreet)]` still works; the dispatcher struct is generated internally as `__DispatcherGreet`.
+
+ ### Migration guide
+ - **Replace all `dispatcher!("name", CMDType => EntryType)` calls** with `dispatcher!("name", EntryType)`. The `CMD*` identifier is no longer generated or referenced.
+ - **Replace all `#[dispatcher_clap("name", CMDType, ...)]` attributes** with `#[dispatcher_clap("name", ...)]` (drop the `CMDType` argument).
+ - **Remove `name = CMDName` from `#[command(...)]`** attributes. If you still reference the old generated `CMDName` type (e.g., in `program.with_dispatcher(...)`), remove that call entirely — dispatchers are auto-collected (see **BREAKING CHANGE #2**).
+ - **Remove any `use mingling::Node;` imports** and any code constructing / manipulating `Node` values. If you had custom `Dispatcher` implementations, they must (a) drop the `node()` and `clone_dispatcher()` methods and (b) rely on the compile-time registration (via `dispatcher!` / `dispatcher_clap!` / `#[command]`) rather than manual `with_dispatcher` dynamic dispatch.
+ - **Remove any `node!("...")` macro invocations.** The `node!` macro no longer exists.
+ - **Any manual `Dispatcher` impls** now only need `begin()`. If you previously implemented `node()` for a custom dispatcher used with `program.with_dispatcher(...)`, that whole registration model is removed — see **BREAKING CHANGE #2** for the compile-time-only registration approach.
+
+ _No behavioral changes to command matching — the semantics of dot-separated command paths, kebab-case normalization, and "longest registered prefix wins" are all preserved by the compiled-in string-based dispatch trie / linear list. The removal is purely an API simplification: the `Node` intermediate abstraction and the dispatcher-boxing machinery are gone._
+
+4. **[`feat:parser`]** **[BREAKING REMOVAL]** Removed the legacy `parser` feature and its entire module tree — `mingling::parser` (with `Argument`, `Picker`, `Pickable`, `PickableEnum`, `AsPicker`, `Yes`, `True`, `PathCheckRule`, `PathsChecker`, `PathChecker`, and the built-in `size`-based `usize` size parsing). The `Parser` has been fully superseded by the `picker` feature, which uses the standalone `arg-picker` crate. The `size` crate dependency (only used by the parser's `usize` size-string parsing) has also been removed from `mingling/Cargo.toml`.
+
+ ### What changed
+
+ The `parser` feature provided an internal argument-parsing module (`mingling::parser`) with a fluent `Picker` API for extracting typed values from `Vec<String>` command-line arguments. This functionality has been entirely replaced by the `picker` feature (`arg-picker` crate), so the legacy built-in parser is now dead code and has been removed.
+
+ **Removed public modules and types:**
+
+ - **`mingling::parser` module** — Entire module removed (`mingling/src/parser.rs` and the whole `mingling/src/parser/` directory), including:
+ - **`Argument`** — The struct wrapping `Vec<String>` with `pick_argument`, `pick_arguments`, `pick_flag`, `dump_remains`, and `strip_all_flags` methods.
+ - **`Picker`** — The fluent builder struct with `pick`, `pick_or`, `pick_or_route`, `require`, and `operate_args` methods.
+ - **`Pickable`** — The trait defining `pick(&mut Argument, Flag) -> Option<Self::Output>`.
+ - **`PickableEnum`** — The marker trait for `EnumTag`-implementing enums providing blanket `Pickable` impls.
+ - **`AsPicker`** — The blanket trait implementing `pick`/`pick_or`/`pick_or_route` for all `Into<Vec<String>>` types.
+ - **`Pick1`–`Pick12` / `PickWithRoute1`–`PickWithRoute12`** — Builder structs for chained picks, with `after`, `after_or_route`, `unpack`, `unpack_directly`, and `operate_args`.
+ - **`Yes`** / **`True`** — Explicit boolean-like enums with `is_yes`/`is_no` and `is_true`/`is_false` helpers.
+ - **`PathCheckRule`** / **`PathsChecker`** / **`PathChecker`** — Path validation helpers (`must_file`, `must_dir`, `must_exist`, etc.).
+ - **Built-in `Pickable` impls** — For `String`, `Vec<String>`, all integer/float types, `bool`, `usize` (special size-string parsing like `"25MiB"`), `Vec<usize>`, `Vec<PathBuf>`, `PathBuf`, `Argument`, and `Option<T>`.
+ - The `usize` size-string parsing (e.g. `"25mib"` → `25 * 1024 * 1024`) used the external `size` crate, which has been removed from the dependency tree.
+
+ **Other changes:**
+
+ - **`mingling::features::MINGLING_PARSER`** constant — Removed from `mingling/src/features.rs`.
+ - **`mingling::prelude::AsPicker`** re-export — Removed from the prelude.
+ - **Deleted examples** — Removed `example-argument-parse` and `example-custom-pickable` (and their entries in `docs/example-pages/examples.json`), as they only demonstrated the legacy `parser` API.
+ - **Docs updated** — `docs/pages/6-argument-parse-picker.md` (and `docs/_zh_CN` and `docs/dev` copies) now document the `picker` feature API; the `parser` section of `docs/pages/other/features.md` was removed.
+ - **`mingling/Cargo.toml`** — Removed `parser = ["dep:size"]` from `[features]`, removed `size = { version = "0.5", optional = true }` from `[dependencies]`, and removed `"parser"` from the dev-dependency and example feature lists.
+ - **`mingling/src/lib.rs`** — Removed `#[cfg(feature = "parser")] pub mod parser;` and the prelude's `#[cfg(feature = "parser")] pub use crate::parser::AsPicker;`.
+ - **`mingling/src/parser/` directory** — Entirely deleted (including `args.rs`, `picker.rs`, `picker/builtin.rs`, `picker/bools.rs`, `picker/path.rs`, `picker/path/rule.rs`, and `test.rs`).
+ - **`Cargo.lock`** and per-example `Cargo.lock` files — Removed the `size` package entry and added `arg-picker` / `arg-picker-macros` where the `picker` feature is enabled.
+
+ **Migration guide:**
+
+ - **Replace the `parser` feature with `picker`** in `Cargo.toml`:
+ ```toml
+ # Old:
+ features = ["parser"]
+ # New:
+ features = ["picker"]
+ ```
+ - **Replace API usage** with the `arg-picker` equivalents from the `picker` feature:
+ | Legacy `parser` API | New `picker` API |
+ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+ | `prev.pick(())` | `prev.pick_or_default(&arg![T])` (or `prev.pick(&arg![T])`) |
+ | `prev.pick(flag)` | `prev.pick(&arg![name: T, 'n'])` |
+ | `prev.pick_or(flag, default)` | `prev.pick_or(&arg![name: T, 'n'], \|\| default)` |
+ | `prev.pick_or_route(flag, route)` + `.unpack()` | `prev.pick_or_route(&arg![T], \|\| route.to_chain())` + `.to_result()` + `route!` |
+ | `args.pick_argument(flag)` | Use `arg_picker::picker::PickerArg` / the `arg!` macro |
+ | `impl Pickable for T { ... }` | `impl SinglePickable for T { fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { ... } }` |
+ | `impl PickableEnum for T {}` on an `EnumTag` enum | Implement `SinglePickable` manually with a `match` |
+ | `.after(...)` | `.post(...)` |
+ | `.unpack()` | `.unwrap()` |
+ | `usize` size-string parsing (`"25MiB"`) | Not directly supported by `arg-picker`; parse manually (e.g., via `size` directly or a custom `SinglePickable`) |
+ | `PathCheckRule` / `PathsChecker` / `PathChecker` | Use the new path wrapper types in `mingling::picker::value` (`FilePath`, `DirPath`, `NoPath`, `RecursiveFiles`, etc.) |
+ | `Yes` / `True` | Use `mingling::picker::value::Flag` (flag-based) and explicit value checks for confirmations |
+ - **Remove any `use mingling::parser::...;` imports.** All `parser` items are gone. Use the `picker` feature's module (`mingling::picker`, `arg_picker::prelude::arg`, etc.).
+ - **If you used the `usize` size-string parsing** (e.g., `preved.pick::<usize>("--size").unpack()` with inputs like `"25mib"`), implement a custom `SinglePickable` or use the `size` crate directly.
+ - **If you used `AsPicker`** on `Vec<String>` / `&[String]`, use `arg_picker::picker::IntoPicker` (or `EntryPicker`, for program entry types) instead.
+
+ _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
+
+### 0.4.0 (2026-08-16)
#### Fixes:
@@ -377,6 +677,108 @@ None
The `ArgumentSplitter` trait is a public API addition, so downstream code can now reuse the same argument-splitting logic that the REPL uses for parsing input lines.
+14. **[`confirm`]** Added the `Confirmer` resource and its confirmation predicates:
+
+ - **`mingling::res::Confirmer`** — A new resource type for interactive confirmation prompts. It caches the confirmed state to avoid repeated prompts, and is typically registered via [`ConfirmerSetup`] and injected into functions through Mingling's resource injection system.
+
+ - **`Confirmer::new()`** — Creates a new `Confirmer` instance in the unconfirmed state.
+ - **`Confirmer::new_confirmed()`** — Creates a `Confirmer` in the confirmed state; `ask`/`try_ask` return `true` directly without prompting.
+ - **`set_confirmed(&mut self)`** — Marks the confirmer as confirmed; subsequent `ask`/`try_ask` calls return `true` directly.
+ - **`ask<P: ConfirmerPredicate>(&self, ask: impl AsRef<str>) -> bool`** — Prompts the user at most **one** time. Returns `false` if the user provides an unrecognizable answer, `true` if already confirmed.
+ - **`try_ask<P: ConfirmerPredicate>(&self, ask: impl AsRef<str>, count: impl Into<ConfirmerCount>) -> Option<bool>`** — Prompts the user up to `count` times. Returns `Some(true)` for confirmation, `Some(false)` for rejection, and `None` if the maximum attempts are exhausted without a parseable answer. The prompt is written to stderr.
+
+ - **`mingling::confirm::ConfirmerCount`** — Specifies the maximum number of attempts: `Loop` (0, indefinite) or `Max(usize)` (positive integer). `From` impls are provided for all integer types (`i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`, `u32`, `u64`, `u128`, `usize`); `0` maps to `Loop`, negative values clamp to `Max(usize::MAX)`.
+
+ - **`mingling::confirm::ConfirmerPredicate`** — Defines how to parse user confirmation input. Implementors provide `is_yes(str: &str) -> Option<bool>`: `Some(true)` for yes, `Some(false)` for no, `None` for unparseable input (requiring re-entry).
+
+ - **`mingling::confirm::YesConfirm`** — Accepts `"y"`/`"yes"` as yes and `"n"`/`"no"` as no. Case-insensitive with leading/trailing whitespace trimming.
+
+ - **`mingling::confirm::TrueConfirm`** — Accepts `"true"`/`"t"` as yes and `"false"`/`"f"` as no. Case-insensitive with leading/trailing whitespace trimming.
+
+ The `Confirmer` resource derives `Debug`, `Default`, `Clone`, `Copy`. The `confirm` module also houses `ConfirmerCount`, `ConfirmerPredicate`, `YesConfirm`, and `TrueConfirm` at `mingling::confirm::*`.
+
+15. **[`setups`]** Added the `ConfirmSetup` and `StandardInputArgsSetup` program setups:
+
+ ### `ConfirmSetup`
+ - **`mingling::setup::ConfirmSetup`** — A `ProgramSetup` that registers a `ResConfirm` resource and installs a pre-dispatch hook checking the user's `confirmation` config mode. When `program.user_context.confirmation == ConfirmationMode::Skip`, the hook marks the `ResConfirm` as confirmed via `modify_res`, so all `ask`/`try_ask` calls return `true` without prompting.
+
+ - Registered via `program.with_setup(ConfirmSetup)`.
+ - Applies uniformly to all subcommands of the entire program; it does not support per-command overrides.
+
+ ### `StandardInputArgsSetup`
+ - **`mingling::setup::StandardInputArgsSetup`** — A `ProgramSetup` that reads piped/redirected standard input and appends the split arguments to the end of the program's argument list.
+
+ - A `pre_dispatch` hook checks whether stdin is a terminal via `IsTerminal`; if it is **not** a terminal (i.e., there is piped or redirected input), it reads all of stdin to the end, converts it to UTF-8 (strict first, lossy fallback), splits it via `ArgumentSplitter::split_args()` (whitespace, single/double quotes, backslash escaping), and appends the resulting arguments to `ctx.arguments` (a `&mut Vec<String>`, per the `pre_dispatch` hook's mutable-arguments semantics from **BREAKING CHANGE #6** in this release).
+ - Empty input produces no arguments.
+ - **Note:** the setup does **not** validate input — stdin content is treated as trusted arguments appended directly, so untrusted input can inject arbitrary arguments. It also has no per-subcommand granularity; if different subcommands need different stdin behavior, do not use this setup.
+
+16. **[`osc94`]** **[`setups:osc94`]** Added the `ResOSC94` resource and `OSC94Setup` for managing terminal `OSC 9;4` protocol status:
+
+ ### `ResOSC94` resource
+ - **`mingling::res::ResOSC94`** — A new resource type providing support for the [OSC 9;4 protocol](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences), which allows sending task progress notifications via ANSI escape sequences. It is typically registered via [`OSC94Setup`] and injected into functions through Mingling's resource injection system.
+
+ - **`ResOSC94::get_mut(&self) -> OSC94Guard`** — Returns an [`OSC94Guard`] with an initial state of [`OSC94State::Clean`]. If the current environment supports the `OSC 9;4` protocol, state changes will be sent to the terminal in real time.
+
+ Derives `Debug`, `Default`, `Clone`, `Copy`.
+
+ ### `OSC94Guard`
+ - **`mingling::osc94::OSC94Guard`** — A guard for modifying process state, obtained via [`ResOSC94::get_mut`]. When the guard is dropped, the process state is automatically restored to [`OSC94State::Clean`], so no manual cleanup is needed.
+
+ - **`set_clean_state(&mut self)`** — Sets the process state to Clean, indicating the process has finished or is in a normal, problem-free state.
+ - **`set_error_state(&mut self)`** — Sets the process state to Error, indicating an error occurred during process execution.
+ - **`set_warn_state(&mut self)`** — Sets the process state to Warn, indicating a warning occurred but hasn't reached error level.
+ - **`set_unknown_state(&mut self)`** — Sets the process state to Unknown, indicating the process state cannot be determined or has not been defined.
+ - **`set_progress(&mut self, progress: f32)`** — Sets the progress value (should be between `0.0` and `1.0`; values outside this range are not clamped, but it is recommended to keep them within range).
+ - **`state(&self) -> OSC94State`** — Returns the current process state.
+ - **`progress(&self) -> f32`** — Returns the actual progress value only when the state is `OSC94State::Normal`; otherwise returns `0.0`.
+
+ ### `OSC94State` enum
+ - **`mingling::osc94::OSC94State`** — Represents the `OSC 9;4` protocol message state:
+
+ - **`Clean`** — Clears/hides progress (used when task completes), corresponding to state code `0`.
+ - **`Normal(f32)`** — Normal state, state code `1`, requires a progress value (0-100).
+ - **`Error`** — Error state, state code `2` (usually displayed in red).
+ - **`Unknown`** — Uncertain state, state code `3` (shown as an indeterminate animation for unknown progress).
+ - **`Warn`** — Warning state, state code `4` (usually displayed in yellow).
+
+ - **`state_code(&self) -> u8`** — Returns the state code for the `OSC 9;4` protocol.
+ - **`progress(&self) -> f32`** — Returns the progress value (0-100) for the `Normal` state, clamped to the valid range.
+ - **`to_escape_sequence(&self) -> String`** — Converts the message into the corresponding `OSC 9;4` escape sequence string.
+ - **`send(&self)`** — Sends the `OSC 9;4` message to the terminal via stdout. Panics if the stdout stream cannot be flushed.
+
+ Implements `Display` (formats as the escape sequence), `From<OSC94State> for String`, and `From<&OSC94State> for String`. Derives `Debug`, `Clone`, `Copy`, `PartialEq`.
+
+ ### `OSC94Setup`
+ - **`mingling::setup::OSC94Setup`** — A `ProgramSetup` that registers an `ResOSC94` resource in the program's resource store, with its `is_support` flag determined at setup time by inspecting environment variables. The support check looks at:
+
+ - **`TERM_PROGRAM`** — `ghostty`, `WezTerm`, `iTerm.app`
+ - **`WT_SESSION`** — Windows Terminal
+ - **`VTE_VERSION`** — VTE-based terminals (such as GNOME Terminal, Konsole, etc.)
+ - **`TERM`** — terminal emulators containing `xterm`
+
+ Registered via `program.with_setup(OSC94Setup)`.
+
+ Usage example:
+
+ ```rust,ignore
+ use mingling::{macros::command, res::ResOSC94, setup::OSC94Setup};
+
+ fn main() {
+ let mut program = ThisProgram::new();
+ program.with_setup(OSC94Setup);
+ program.exec_and_exit();
+ }
+
+ #[command]
+ fn hello(osc: &ResOSC94) {
+ let mut guard = osc.get_mut();
+ guard.set_progress(0.5);
+ // ... do work ...
+ guard.set_warn_state();
+ // ... guard is dropped, state automatically restored to Clean
+ }
+ ```
+
#### **BREAKING CHANGES** (API CHANGES):
1. **[`macros`]** **[BREAKING]** Renamed the `extra_macros` feature to `extras`. All feature-gated macro re-exports in `mingling/src/lib.rs` (and throughout the codebase) have been updated from `#[cfg(feature = "extra_macros")]` to `#[cfg(feature = "extras")]`.