diff options
Diffstat (limited to 'CHANGELOG.md')
| -rw-r--r-- | CHANGELOG.md | 642 |
1 files changed, 640 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ba50c..ed8d388 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,453 @@ None ## Contents -### 0.4.0 (Unreleased) +### 0.5.0 (Unreleased) + +#### Fixes: + +1. **[`macros:gen_program`]** Fixed the empty `do_chain` fallback generated by `program_final_gen` to respect the `async` feature. When a program has no chains registered, the synthesized `do_chain` previously always emitted the synchronous signature `fn do_chain(...) -> ChainProcess<Self::Enum>`, which fails to compile under the `async` feature with E0053 (method signature does not match the `ProgramCollect` trait, which requires a `Pin<Box<dyn Future<Output = ChainProcess> + Send>>` return in async mode). The generator now checks the compile-time `ASYNC_ENABLED` flag for the empty-chain case, mirroring the non-empty branch: when async is enabled it emits the boxed-future signature with a `Box::pin(async { panic!(...) })` body, and otherwise emits the synchronous signature. This fixes programs that declare zero chains (relying solely on entry/fallback behavior) when built with the `async` feature. + +#### 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`). + +2. **[`macros:completion`]** Reworked the `#[completion]` attribute macro to accept a relaxed signature and fixed several code-generation details: + + **Relaxed function signature:** + - **Context parameter is now optional.** Previously, the completion function was required to have exactly one parameter of type `&ShellContext`. Now the first parameter (if present) may be `&ShellContext`, an owned `ShellContext`, or **any type implementing `From<&ShellContext>`**. The macro binds the shell context to the declared parameter type via `<#ty as From<&ShellContext>>::from(ctx)`, so identity `From` covers `&ShellContext` itself and `From<&Self>` covers owned `ShellContext` (a new `impl From<&Self> for ShellContext` added in `mingling_core/src/comp/shell_ctx.rs`). With **no parameters at all**, the completion function simply ignores the shell context. + - **Resource injection after the context.** `extract_resources_from_args` now starts after the context parameter (index 0 when present, index 0 when absent). A completion function with no context parameter cannot inject resources — the macro emits a compile error in that case. + - **Return type is now `Into<Suggest>`.** Previously the function had to return `Suggest` exactly. Now any type implementing `Into<Suggest>` is valid — `Suggest` itself, `Vec<String>`, `Vec<(String, String)>` (suggestion + description), `&[&str]`, or a set of `SuggestItem`s. A `()` return (or no return type) is also accepted and mapped to an empty `Suggest`. + - **`SuggestItem` gains `From<&str>`** (in `mingling_core/src/comp/suggest.rs`), and the blanket `From<T> for Suggest where T: IntoIterator` was widened from `T::Item: Into<String>` to `T::Item: Into<SuggestItem>`, so iterators of `&str`, `String`, or `SuggestItem` all convert to `Suggest` uniformly. + + **Generated `Completion::comp` signature:** The generated `fn comp` now always returns `::mingling::Suggest` and always binds the ambient `ctx: &ShellContext` parameter (which the caller passes via `Completion::comp(&ctx)`), ignoring it when the user function takes no context. The generated body: + + - Declares `let _ = ctx;` when the function takes no context parameter (keeps the parameter used). + - Declares `let __ctx: #ty = <#ty as From<&ShellContext>>::from(ctx);` when a context parameter is present, then passes `__ctx` as the first argument. + - Wraps the user body; for `()` returns, evaluates the body then returns `Suggest::new()`; otherwise evaluates the body and converts the result via `Into::into`. + + **`ShellContext` is now `Clone`** (derive added in `mingling_core/src/comp/shell_ctx.rs`) and implements `From<&Self> for ShellContext`, enabling owned-context completion signatures. + + _No behavioral change for existing code that already used the classic `fn(ctx: &ShellContext) -> Suggest` form — the identity `From` and `Into` impls preserve that path exactly. + +#### **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. + +6. **[`macros:completion`]** **[BREAKING]** Changed the `#[completion]` attribute macro's context-parameter semantics: completion functions now take the **owned** `ShellContext` (or any `From<&ShellContext>` type) by value, and `&ShellContext` is no longer accepted. + + ### What changed + + Previously, the completion function's context parameter could be `&ShellContext` (the classic form) or an owned `ShellContext` / any `From<&ShellContext>` type. Now the reference form is rejected: reference parameters (`&T` / `&mut T`) are reserved exclusively for **resource injection**, matching `#[chain]` semantics, so the parser in `mingling_macros/src/attr/completion.rs` was reworked to classify each parameter as either: + + - **Owned (non-reference) parameter** — a _shell source_: derived from `&ShellContext` via `<#ty as From<&ShellContext>>::from(ctx)`. This covers `ShellContext` itself (via its new `Clone`-based `From` impl), framework state types, and any user-defined type derived from the shell context. Multiple owned parameters are allowed; each gets its own derived binding (`__ctx_derived_{idx}`). + - **`&T` / `&mut T` reference parameter** — a _resource injection_, identical to the parameter position used by `#[chain]`. Requires a simple-identifier binding. `&ShellContext` specifically is rejected with a compile error: "`&ShellContext` is not supported; use the owned `ShellContext` (or any other type implementing `From<&ShellContext>`) as a value parameter". + + A helper `is_shell_context_path(ty)` detects a path whose last segment is `ShellContext` (covering `ShellContext` and `mingling::ShellContext` alike). + + Previously, resource injection only started _after_ the first (context) parameter, and a completion function with no context parameter could not inject resources (compile error). Now, ownership of the parameter — not its position — determines its role: owned parameters are shell sources, references are resources, and they may be freely interleaved. The "no context → no resources" restriction is gone entirely. + + The generated `Completion::comp` body now emits: + + 1. A derived-binding statement for each owned parameter. + 2. The immut-resource binding statements (for `&T` injections). + 3. The mut-resource wrapper / call (for `&mut T` injections). + 4. The return statement applying the `Into<Suggest>` conversion (`()` → empty `Suggest`). + + **Migration guide:** + + - Change every `ctx: &ShellContext` parameter to `ctx: ShellContext`. The owned type behaves identically for reads; only the declared parameter type changes. + - Code that previously relied on `&ShellContext` in the _middle_ of the signature no longer needs special treatment: owned parameters anywhere are treated as shell sources. + - `_ctx: &ShellContext` (unused parameter) becomes `_ctx: ShellContext`. + + _All internal call sites, examples, docs, and tests updated_ (e.g., `mingling_cli` completion handlers, `example-completion`, `example-enum-tag`, `GETTING-STARTED.md`, `docs/pages/advanced/1-completion.md`, `docs/_zh_CN/pages/advanced/1-completion.md`, and `mingling/src/example_docs.rs`). + +7. **[`build`]** **[BREAKING]** Replaced the `build` / `builds` build-time feature system with compile-time macro-driven build steps. The `build` feature, `builds` feature, `build_advanced` preset, `build_full` preset, `mingling::build` module, and the entire `build.rs`-based workflow have been removed. Build steps (completion script generation and pathf type-mapping analysis) now run automatically as a side effect of `gen_program!()` expansion via new `build_comp!()` / `build_pathf!()` macros. + + ### What changed + + Previously, build-time functionality was enabled through the `build` feature (and preset groups `build_advanced` / `build_full`, plus the deprecated `builds` alias). Users needed a `[build-dependencies.mingling]` entry in `Cargo.toml` and a hand-written `build.rs` that called `mingling::build::build_comp_scripts(...)` (for completion scripts) and `mingling::build::analyze_and_build_type_mapping()` (for pathf). These functions were gated behind the `build` + `comp` / `build` + `pathf` feature combinations and read `OUT_DIR` to locate the output directory. + + Now, the build steps are integrated directly into macro expansion: + + - **`gen_program!()` automatically invokes `build_comp!()`** (when the `comp` feature is enabled) and **`build_pathf!()`** (when the `pathf` feature is enabled) at the very start of its expansion. These macros run the build logic as a compile-time side effect and expand to nothing. + - **`build_comp!()`** is a proc macro (re-exported as `mingling::macros::build_comp`) that generates completion scripts into `{target_directory}/mingling/`. It accepts an optional string literal for the binary name; without an argument it defaults to `CARGO_PKG_NAME`. On failure it emits a `compile_error!`. + - **`build_pathf!()`** is a proc macro (re-exported as `mingling::macros::build_pathf`) that runs the pathf type-mapping analysis, writing mapping files into `{target_directory}/mingling/{CARGO_PKG_NAME}/`. On failure it emits a `compile_error!`. + - **No `build.rs` is required anymore.** Build logic runs from proc-macro expansion, so no `[build-dependencies.mingling]` entry, no `build` feature, and no `build.rs` file are needed. + + **Removed API:** + + - **`build` feature** — Removed from `mingling/Cargo.toml` and `mingling_core/Cargo.toml`. The `build = ["mingling_core/build"]` feature mapping and the `mingling_core/build` feature have been deleted. + - **`builds` feature** — Removed (deprecated alias, mapped to `mingling_core/build`). + - **`build_advanced` / `build_full` preset features** — Removed from `mingling/Cargo.toml` feature groups. + - **`mingling::build` module** — Removed entirely from `mingling_core`: + - `mingling_core/src/build.rs` and the `mingling_core/src/build/` directory deleted. + - `mingling_core/src/docs/build.md` deleted. + - `mingling_core/src/lib.rs` no longer gates `pub mod build` behind the `build` feature. + - **Build functions** — Removed: `build_comp_scripts`, `build_comp_script`, `build_comp_script_to`, `build_comp_script_to_file`, `analyze_and_build_type_mapping`, `analyze_and_build_type_mapping_for`, `analyze`. + - **`MINGLING_BUILD` / `MINGLING_BUILDS` / `MINGLING_BUILD_ADVANCED` / `MINGLING_BUILD_FULL` feature constants** — Removed from `mingling/src/features.rs`. + - **`mingling_core` dependencies** — Removed `just_template` (comp) and `mingling_pathf` (pathf) from `mingling_core/Cargo.toml`; these moved to `mingling_macros` as optional dependencies gated behind the `comp` / `pathf` features. + - **`mingling_macros` feature wiring** — Changed `comp = []` to `comp = ["dep:just_template", "dep:mingling_pathf"]` and `pathf = []` to `pathf = ["dep:mingling_pathf"]`; `mingling` crate's `pathf` feature no longer forwards to `mingling_core/pathf`. + - **`mingling::build::pathf` error re-exports** — `mingling_core::error` no longer re-exports `mingling_pathf::error::*`. + + **New internal infrastructure:** + + - **`mingling_macros/src/build.rs`** — New module hosting `comp_build_impl` (behind `comp`) and `pathf_build_impl` (behind `pathf`), which parse the macro input and delegate to the build logic, converting errors into `compile_error!` token streams. + - **`mingling_macros/src/build/comp.rs`** — Moved from `mingling_core/src/build/comp.rs` (with the shell templates, which moved from `mingling_core/tmpls/comps/` to `mingling_macros/tmpls/comps/`). Contains a private copy of `ShellFlag` (since the macros crate cannot depend on `mingling_core`); the template files are identical. Scripts are written to `{target_directory}/mingling/` resolved via the new `mingling_pathf::build_output_dir()`. + - **`mingling_macros/src/build/pathf.rs`** — New module providing `output_dir()` (`{target_directory}/mingling/{CARGO_PKG_NAME}`) and `analyze_and_build_type_mapping()` delegating to `mingling_pathf`. + - **`mingling_pathf::build_output_dir()`** — New public function resolving `{target_directory}/mingling/` via `cargo metadata` (from `CARGO_MANIFEST_DIR`). + - **`mingling_pathf::target_directory()`** — New public function running `cargo metadata` (`no_deps`) from a crate directory and returning the target directory. + - **`MinglingPathfinderError::CargoMetadata(String)`** — New error variant added to `mingling_pathf`'s error enum. + - **`cargo_metadata` dependency** — Added to `mingling_pathf` (workspace, version `0.23.1`) and to the root workspace `Cargo.toml`. + + **`gen_program!()` changes** (`mingling_macros/src/func/gen_program.rs`): + - Emits `::mingling::macros::build_comp!();` at the start of the expansion when `comp` is enabled (and `::mingling::macros::build_pathf!();` when `pathf` is enabled). + - The pathf `use`-statement loading now runs the analysis inline via `crate::build::pathf::analyze_and_build_type_mapping()` (so the mapping exists when the `use` statements are read) and loads `type_using.rs` from `crate::build::pathf::output_dir()`. + - The `load_pathf_uses` function now reads from `{target_directory}/mingling/{CARGO_PKG_NAME}/type_using.rs` instead of `{OUT_DIR}/{CARGO_PKG_NAME}/type_using.rs`. + - The empty-uses `compile_error!` hint was reworded: it no longer mentions `build.rs` or the `build` feature; it now says the analyzer found no types and suggests ensuring the `pathf` feature is enabled and `gen_program!()` is called in a crate with a `src/` directory. + - `mingling_pathf::analyze_and_build_type_mapping` no longer emits `cargo:rerun-if-changed=src/` / `cargo:rerun-if-env-changed=...` directives (there is no build script for Cargo to track). + + **Migration guide:** + + - **Delete `build.rs`** (and the `[build-dependencies]` block in `Cargo.toml`). Completion scripts are generated automatically when the `comp` feature is enabled; pathf analysis and any `[build-dependencies.mingling]`). If a feature list references only these, delete the whole section. + - **If your binary name differs from the crate name**, call `build_comp!()` manually with the binary name: + ```rust + // Features: ["comp"] + mingling::macros::build_comp!("mybin"); + ``` + This can be placed at module scope (e.g., in `src/lib.rs` or `src/main.rs`) alongside `gen_program!()`. + - **Remove any `mingling::build::...` imports.** + - **Example/build artifacts**: The completion scripts are now written to `{target_directory}/mingling/` rather than `{target_directory}/release/` or the `OUT_DIR`-derived path. Any scripts that copied them from the release directory must be updated (e.g., `.run/src/bin/install-mling.sh` now copies from `.temp/target/mingling/mling_comp.$comp`, and `.run/src/bin/install-mling.ps1` from `.temp/target/mingling/mling_comp.ps1`). + - **`mingling_cli`**: `build.rs` no longer calls `analyze_and_build_type_mapping` / `build_comp_scripts`; `mingling_cli/src/lib.rs` now invokes `mingling::macros::build_comp!("mling")` to generate scripts for the `mling` binary. `StateInstallBuild` / `StateInstallCopy` gained a `mingling_dir` field (`{target}/mingling/`) and the install copy step reads completion scripts from `{target}/mingling/` instead of `{target}/release/`. + - **Tests/examples**: Removed the `builds` feature from `mingling_core/tests/test-all`, `mingling_core/tests/test-comp`, and all pathf/completion examples, and deleted the corresponding `build.rs` files and `[build-dependencies]` blocks. + + _Behavioral note:_ the runtime behavior of programs is unchanged — completion scripts and pathf type mappings are still produced, just from compile-time macro expansion instead of a separate `build.rs` step. The output directory changed from an `OUT_DIR`-derived path (effectively `{target}/<profile>` style) to a dedicated `{target_directory}/mingling/` directory resolved via `cargo metadata`, which is deterministic regardless of build profile. + +8. **[`Cargo.toml`]** Removed the legacy `extra_macros` feature alias from `mingling/Cargo.toml`. The `extras` feature (introduced in 0.4.0, BREAKING CHANGE #1) is now the sole name for this feature; the deprecated alias is gone. + +9. **[`setups:dirs`]** **[BREAKING]** Simplified the `DirectoryEnvironmentSetup` type — it is no longer generic over the program collect type `C` and no longer requires `DirectoryEnvironmentSetup::<C>::default()` to construct. + + ### What changed + + Previously, `DirectoryEnvironmentSetup` was a generic struct `DirectoryEnvironmentSetup<C>` (carrying `PhantomData<C>`) that had to be constructed via `DirectoryEnvironmentSetup::<C>::default()` before calling `Program::with_setup`. Now the struct is unit-like (`pub struct DirectoryEnvironmentSetup;`), so it can be constructed directly as a value with no `::default()` call and no generic parameter. + + Additionally, the `setup` method's `program` parameter was retyped from `crate::Program<C>` to `mingling_core::Program<C>` for cleanliness. + + ### Removed / changed API + - **`DirectoryEnvironmentSetup<C>`** → **`DirectoryEnvironmentSetup`** — The struct no longer has a generic parameter (previously `DirectoryEnvironmentSetup<C>` with `PhantomData<C>`). + - **`impl<C> Default for DirectoryEnvironmentSetup<C>`** — Removed. The unit struct uses the derived/implicit `Default`, and more importantly construction is now just the plain value `DirectoryEnvironmentSetup`, not `DirectoryEnvironmentSetup::<C>::default()`. + - **`impl<C> ProgramSetup<C> for DirectoryEnvironmentSetup<C>`** → **`impl<C> ProgramSetup<C> for DirectoryEnvironmentSetup`** — The `ProgramSetup` impl is now on the unit type. + + ### Migration guide + - Replace `program.with_setup(DirectoryEnvironmentSetup::<ThisProgram>::default())` with `program.with_setup(DirectoryEnvironmentSetup)`. + - Any type annotations referencing `DirectoryEnvironmentSetup<C>` must drop the generic argument. + + _No behavioral changes — the setup still registers the same four directory resources (`ResCurrentDir`, `ResCurrentExe`, `ResHomeDir`, `ResTempDir`) in the program's resource store. The type simplification is purely ergonomic. + +10. **[`setups:exit_code`]** **[BREAKING]** Simplified the `ExitCodeSetup` type — it is no longer generic over the program collect type `C` and no longer requires `ExitCodeSetup::<C>::default()` to construct. + + ### What changed + + Previously, `ExitCodeSetup` was a generic struct `ExitCodeSetup<C>` (carrying `PhantomData<C>`) that had to be constructed via `ExitCodeSetup::<C>::default()` before calling `Program::with_setup`. Now the struct is unit-like (`pub struct ExitCodeSetup;`), so it can be constructed directly as a value with no `::default()` call and no generic parameter. + + Additionally, the `setup` method's `program` parameter was retyped from `crate::Program<C>` to `mingling_core::Program<C>` for cleanliness. + + ### Removed / changed API + - **`ExitCodeSetup<C>`** → **`ExitCodeSetup`** — The struct no longer has a generic parameter (previously `ExitCodeSetup<C>` with `PhantomData<C>`). + - **`impl<C> Default for ExitCodeSetup<C>`** — Removed. The unit struct uses the derived/implicit `Default`, and more importantly construction is now just the plain value `ExitCodeSetup`, not `ExitCodeSetup::<C>::default()`. + - **`impl<C> ProgramSetup<C> for ExitCodeSetup<C>`** → **`impl<C> ProgramSetup<C> for ExitCodeSetup`** — The `ProgramSetup` impl is now on the unit type. + + ### Migration guide + - Replace `program.with_setup(ExitCodeSetup::<ThisProgram>::default())` (or `ExitCodeSetup::default()`) with `program.with_setup(ExitCodeSetup)`. + - Any type annotations referencing `ExitCodeSetup<C>` must drop the generic argument. + + _No behavioral changes — the setup still registers the same `ResExitCode` resource (initialised to `0`) and installs the same program-finish hook that overrides the program's exit code when the resource holds a non-zero value. The type simplification is purely ergonomic._ + +--- + +## Contents + +### 0.4.0 (2026-08-16) #### Fixes: @@ -323,6 +770,162 @@ None The suggestion collection was also reworked: it now uses a `BTreeSet<SuggestItem>` for natural ordering and deduplication (replacing the previous `Vec<String>` + manual `sort()`/`dedup()`), carries both the suggested token and the fully-qualified owner node path used for the description lookup, and returns `Suggest::Suggest(suggestions)` directly. `entry_description` returns `None` for intermediate trie segments that have no entry of their own or entries without a registered `Description`, in which case suggestions fall back to plain `SuggestItem::new(token)`. The final empty-suggestions fallback to `file_suggest()` is unchanged. +12. **[`core:render`]** Reworked the `RenderResult` immediate-output mechanism from a single boolean flag into a general **print-hook** system, enabling multiple user-defined hooks to be invoked with the content and output mode of every write. + + **`RenderResultPrint` struct** — Added a new public struct bundling the emitted text content and its output mode: + + - **`content: String`** — The raw text written. For `println`/`eprintln` it includes the trailing newline; for `print`/`eprint` it is exactly the given text. + - **`mode: RenderResultMode`** — The output mode (`Stdout` or `Stderr`) the content was written with, telling the hook where the content belongs. + + Derives `Debug`, `Clone`, `PartialEq`, `Eq`. + + **`print_hook` field** — Replaced `immediate_output: bool` with `print_hook: PrintHook` (a `Vec<Box<dyn FnMut(RenderResultPrint)>>` inside an `Option`). The default is `None`, meaning content is only buffered and output uniformly at the end (e.g. via `std_print`). + + **`bind_print_hook()` method** — New method that pushes a user-provided hook onto the hook list; multiple hooks can be bound and are invoked in binding order. Returns `&mut Self` for chaining. + + **`immediate_output()` behavior change** — Now calls `bind_print_hook()` with a hook that flushes content to stdout/stderr in real time (functionally identical to the old boolean behavior, but implemented via the hook mechanism). No longer `const`. + + **`emit()` private helper** — Iterates bound hooks and invokes each with a `RenderResultPrint { content, mode }` value. + + **Write methods updated** — `print`, `println`, `eprint`, and `eprintln` now call `self.emit(&text, Stdout/Stderr)` (after formatting the trailing newline for `println`/`eprintln`) instead of checking the `immediate_output` flag. + + **`append_other()` semantics** — Now checks whether _self has hooks_ and _other has none_; when true, other's buffered content is emitted through self's hooks while being appended. The other's hooks and `exit_code` are **not** transferred — only its buffered content is merged. + + **Manual trait impls** — Since hooks are opaque closures that cannot be cloned or meaningfully compared, hand-written impls replaced the derives: + + - **`Clone`** — Clones the buffered content and exit code but drops the print hooks (creates a result with no hooks). + - **`PartialEq` / `Eq`** — Compares only the render buffer and exit code; hooks are ignored. + - **`Debug`** — Prints the buffered content, exit code, and the _number_ of bound hooks (`print_hooks: Vec::len`), avoiding attempting to format opaque closures. + + `Default` is still derived (all fields default to empty/`None`). + +13. **[`core:utils`]** Added a new `utils` module to `mingling_core` with the `ArgumentSplitter` trait, providing a reusable implementation of shell-style command-line argument splitting. The trait is implemented for `str` and internally for `String`, and provides a `split_args()` method that splits a string input into a `Vec<String>` of arguments, respecting single quotes, double quotes, and backslash escaping. + + **Trait definition:** + + ```rust + pub trait ArgumentSplitter { + /// Splits the string into a `Vec<String>` of arguments, respecting + /// single quotes, double quotes, and backslash escaping. + fn split_args(&self) -> Vec<String>; + } + + impl ArgumentSplitter for str { /* ... */ } + impl ArgumentSplitter for String { /* ... */ } + ``` + + **Splitting rules** (identical semantics to the previously private `splitter` module): + + - **Whitespace separation** — Arguments are separated by spaces (`' '`); consecutive spaces collapse and empty tokens are dropped. + - **Single/double quotes** — Text inside `'...'` or `"..."` is treated as a single argument; the quote characters are stripped. Within quoted segments, backslash escapes the next character (so `"b c"` produces `b c`, and `"b\"c"` produces `b"c`). + - **Backslash escaping** (outside quotes) — A backslash takes the next character literally (e.g., `b\ c` produces `b c`; `b\"c` produces `b"c`); a trailing backslash with no following character is ignored/lost. + + **Integration:** The previously private `split_input` / `split_input_string` functions in `mingling_core::program::repl_exec::splitter` have been removed; `repl_exec` now uses `readline.split_args()` on the input string. The `utils` module is exported as `mingling_core::utils`. + + 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")]`. @@ -433,6 +1036,41 @@ None _This is a pure deletion change with no behavioral replacement. If downstream code used the `Title`, `Lower`, or `Upper` variants, it needs to switch to other naming styles (such as `Pascal`, `Kebab`, `Snake`, or `Dot`)._ +6. **[`core:hook`]** **[BREAKING]** Changed `HookPreDispatchInfo.arguments` from `&'a [String]` (immutable slice) to `&'a mut Vec<String>` (mutable reference), and updated the `pre_dispatch` hook signature accordingly. The `pre_dispatch` hook can now **rewrite the program's command-line arguments before they are matched against registered dispatchers**, enabling argument normalization, injection, or filtering at the hook level. + + **Type changes:** + + - `HookPreDispatchInfo.arguments: &'a [String]` → `arguments: &'a mut Vec<String>` + - `ProgramHook<C>::pre_dispatch` field type: `Box<dyn for<'a> Fn(&HookPreDispatchInfo<'a>) -> ProgramControls<C>>` → `Box<dyn for<'a> Fn(&mut HookPreDispatchInfo<'a>) -> ProgramControls<C>>` + - `ProgramHookBuilder::on_pre_dispatch<F, R>` bound: `F: for<'a> Fn(&HookPreDispatchInfo<'a>) -> R` → `F: for<'a> Fn(&mut HookPreDispatchInfo<'a>) -> R` + - `Program::run_hook_pre_dispatch` parameter: `&HookPreDispatchInfo` → `&mut HookPreDispatchInfo` + + **Execution pipeline changes:** + + - `exec()` now clones `program.args` into a local mutable `Vec<String>` and passes `&mut` to `exec_with_args`, so hooks rewriting arguments do not mutate the program's stored args (the rewritten copy is what gets dispatched). + - `exec_with_args` now takes `args: &mut Vec<String>` instead of `args: &[String]`, and passes `&mut *args` into `run_hook_pre_dispatch`. + - `ProgramOnceExec::once_exec` takes the program args via `std::mem::take` into a local mutable `Vec<String>` and passes `&mut` through to `exec_with_args`, ensuring the same mutable-args semantics in the `once_exec` path. + - `ReplExec::exec` and `exec_once` in `repl_exec.rs` now pass `&mut Vec<String>` through the same execution path. + + **Migration guide:** + + - Any `pre_dispatch` hook closures must now accept `&mut HookPreDispatchInfo` instead of `&HookPreDispatchInfo`: + + ```rust + // Before + .on_pre_dispatch(|info: &HookPreDispatchInfo| { ... }) + + // After + .on_pre_dispatch(|info: &mut HookPreDispatchInfo| { + // info.arguments is now &mut Vec<String> — can be mutated + }) + ``` + + - References to `info.arguments` inside hooks that previously treated it as `&[String]` will need to adapt to `&mut Vec<String>` (e.g., `info.arguments.as_slice()` instead of `info.arguments` in comparison positions, or explicit dereference / indexing changes). + - While most `&[String]`-style usages are transparently compatible via `Deref`, code that relied on the immutability guarantee of the slice (e.g., passing `info.arguments` to functions expecting `&[String]` via coercion) may need `info.arguments.as_slice()` or `&*info.arguments`. + + _Behavioral change: hooks can now rewrite the argument list before dispatch — e.g., inserting default flags, removing deprecated options, or expanding shorthand syntax. Hook authors should be careful to preserve the program's expected argument layout when mutating the list._ + --- ### Release 0.3.0 (2026-07-27) |
