aboutsummaryrefslogtreecommitdiff
path: root/CHANGELOG.md
diff options
context:
space:
mode:
Diffstat (limited to 'CHANGELOG.md')
-rw-r--r--CHANGELOG.md466
1 files changed, 455 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b90536e..514d186 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.3.0 (Unreleased)](#release-030-unreleased)
+- [Release 0.4.0 (Unreleased)](#release-040-unreleased)
+- [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)
- [Release 0.2.0 (2026-06-30)](#release-020-2026-06-30)
@@ -50,7 +51,9 @@ None
---
-### Release 0.3.0 (Unreleased)
+## Contents
+
+### 0.4.0 (Unreleased)
#### Fixes:
@@ -58,6 +61,154 @@ None
#### Optimizations:
+1. **[`pathf`]** Added `is_module` field to `AnalyzeItem` and a new constructor `AnalyzeItem::local_module(module, item_name)` which sets `is_module: true`. The `type_mapping_builder` now tracks whether an item is a module: when generating `type_using.rs`, module items produce `use path::to::module::*;` (glob import) instead of the standard `use path::to::TypeName;` direct import. Non-module items continue to use direct imports as before. The internal data structure changed from `Vec<(String, String)>` to `Vec<(String, String, bool)>` to carry the `is_module` flag through the pipeline.
+
+2. **[`macros:gen_program`]** Wrapped all code generated by `gen_program!()` inside a `__this_program_impl` module and re-exported it with `pub use __this_program_impl::*;`. This isolates the generated internal items (type aliases, trait implementations, and pathf-generated `use` statements) from the call site's module namespace, preventing name collisions and keeping generated machinery out of the caller's direct scope.
+
+ - The `Next` type alias, `Routable` impl for `ChainProcess<ThisProgram>`, and the `program_fallback_gen!()` / `program_final_gen!()` expansions are now all inside `pub mod __this_program_impl { ... }`, then re-exported publicly.
+ - Pathf integration: when the `pathf` feature is enabled, the `type_using.rs` file (generated by the build script) is loaded at compile time via `load_pathf_uses()` and emitted as `use ...;` statements **inside** the `__this_program_impl` module. Previously, pathf uses were injected via `include!()` inside the `ProgramCollect` impl block in `program_final_gen`; now they are loaded by `gen_program` itself and placed at the top of the hidden module. A `compile_error!` hint is emitted if the pathf file is missing or empty.
+ - When `pathf` is **disabled**, `__this_program_impl` emits `use super::*;` to bring the caller's parent scope types into the generated module, preserving existing behavior for projects that don't use pathf.
+ - Completion generation: removed `crate::` prefix from `CompletionSuggest` references in `program_comp_gen.rs`, since the generated code now lives inside `__this_program_impl` and no longer has a direct `crate` path to the user's crate root. The prefix became unnecessary because `CompletionSuggest` is expected to be in scope (e.g., via pathf glob re-exports or the `use super::*;` fallback).
+
+ _No behavioral change for downstream code — all public items are re-exported with the same names. The `__this_program_impl` module is `#[doc(hidden)]` and not part of the public API._
+
+3. **[`core:comp`]** Added `add_suggest()` and `add_suggest_with_description()` methods to `Suggest` for batch-adding suggestion items:
+
+ - **`add_suggest(&mut self, items: impl Into<Vec<String>>)`** — Wraps each item in `SuggestItem::Simple` and inserts it into the underlying `BTreeSet`.
+ - **`add_suggest_with_description(&mut self, items: impl Into<Vec<String>>, desc: impl Into<String>)`** — Wraps each item in `SuggestItem::WithDescription` using the provided description and inserts it into the set.
+
+ These methods enable ergonomic batch population of suggestion sets from collections of strings, complementing the existing `insert()` method.
+
+#### Features:
+
+1. **[`picker:value:paths`]** Added new path wrapper types to `arg_picker::value` for filesystem-aware argument parsing:
+
+ - **`FilePath`** — Wraps `PathBuf`, validated at parse time to exist and be a file.
+ - **`NoFilePath`** — Wraps `PathBuf`, validated at parse time to _not_ exist as a file.
+ - **`DirPath`** — Wraps `PathBuf`, validated at parse time to exist and be a directory.
+ - **`NoDirPath`** — Wraps `PathBuf`, validated at parse time to _not_ exist as a directory.
+ - **`SymlinkPath`** — Wraps `PathBuf`, validated at parse time to exist and be a symlink.
+ - **`NoSymlinkPath`** — Wraps `PathBuf`, validated at parse time to _not_ exist as a symlink.
+ - **`NoPath`** — Wraps `PathBuf`, validated at parse time to have no filesystem entry at all.
+ - **`RecursiveFiles`** — Wraps `Vec<PathBuf>`. If given a file path, returns a single-element list; if given a directory path, recursively collects all files (and symlinks) under it.
+
+ All single-path types implement `From<PathBuf>`, `From<&PathBuf>`, `AsRef<Path>`, `Deref<Target = PathBuf>`, `DerefMut`, and `Into<PathBuf>`. `RecursiveFiles` additionally provides `len()`, `is_empty()`, `iter()`, `From<Vec<RecursiveFiles>>` for merging multiple collections, and the `IntoRecursiveFiles` trait for ergonomic combination from `Vec<T>`, `&[T]`, and `[T; N]`.
+
+ Each type implements `SinglePickable`, performing filesystem validation at parse time and returning `NotFound` when the precondition is not met.
+
+2. **[`picker:parsing`]** Added convenience methods to the internal `repeat!`-generated tuple implementations for `PickArgParsed<T1, T2, ...>` structs in `arg_picker::picker::parse`:
+
+ - **`unwrap_or_default(self)`** — Returns the parsed values, using `Default::default()` for any missing required arguments. Panics if a route was selected.
+ - **`unwrap_or_else<F>(self, op: F)`** — Returns the parsed values, using the provided closure to generate default values for any missing required arguments. Panics if a route was selected.
+ - **`expect(self, msg: &str)`** — Returns the parsed values, or panics with the given message if a route was selected. Requires `Route: std::fmt::Debug`.
+
+ These methods provide ergonomic alternatives to `to_result()` + `unwrap()` / `unwrap_or_default()` / `unwrap_or_else()` / `expect()` chaining, reducing boilerplate when working with `PickArgParsed` tuples directly.
+
+3. **[`macros:command`]** Added the `#[command]` attribute macro (feature-gated behind `extras`) that converts a plain function with a `Vec<String>` parameter into a fully wired Mingling command. The macro:
+
+ - Calls `dispatcher!("command_name")` to register the dispatcher entry.
+ - Generates a `#[chain]` wrapper that bridges the entry type (`Entry{Pascal}`) to the original function.
+ - Preserves the original function unchanged (including attributes, extensions, visibility, and asyncness).
+
+ **Syntax variants:**
+
+ ```rust,ignore
+ // Simple form — auto-derives names from function name
+ #[command]
+ fn greet(args: Vec<String>) -> Next { /* ... */ }
+ // → dispatcher!("greet"), CMDGreet, EntryGreet
+
+ // Explicit node path
+ #[command(node = "hello.world")]
+ fn greet(args: Vec<String>) -> Next { /* ... */ }
+ // → dispatcher!("hello.world", CMDGreet => EntryGreet)
+
+ // Explicit name/entry overrides
+ #[command(name = MyDispatcher, entry = MyEntry)]
+ fn greet(args: Vec<String>) -> Next { /* ... */ }
+ // → dispatcher!("greet", MyDispatcher => MyEntry)
+ ```
+
+ **Extension attributes** (e.g. `buffer`, `routeify`) passed as bare paths in `#[command(...)]` are applied as `#[ext]` attributes **on the original function**, not on the generated chain wrapper. The chain wrapper always uses bare `#[::mingling::macros::chain]`.
+
+ **Resource injection:** Parameters after the first are treated as resource injections and passed through to the generated `#[chain]` wrapper unchanged.
+
+ **Hidden module:** Each `#[command]` generates a `#[doc(hidden)]` module `__command_{fn_name}_module` that re-exports all generated types (`CMD*`, `Entry*`, chain struct, dispatcher static) for pathf / external access.
+
+ Internally, the implementation:
+ - Parses `#[command(...)]` arguments via `CommandArgs` supporting `node`, `name`, `entry` keys and extension paths.
+ - Validates function constraints (no `self`, at least one parameter).
+ - Handles async functions (rejected without the `async` feature).
+ - Resolves default names via `just_fmt::dot_case!` / `just_fmt::pascal_case!`.
+ - Builds a chain wrapper that calls the original function with `entry.into()` for the first argument.
+
+4. **[`pathf:patterns`]** Added `CommandPattern` to the `pathf` pattern analyzer, matching functions annotated with `#[command]`. The pattern tracks the generated hidden module (`__command_{fn}_module`) and marks it as a local module item via `AnalyzeItem::local_module()`. The build system generates a glob re-export `use path::__command_{fn}_module::*;` to bring all generated types (`Entry*`, `CMD*`, chain struct, dispatcher static) into scope.
+
+5. **[`macros:dispatcher`]** Added a `From<pack_Type> for crate::Entry` implementation inside the `dispatcher!()` macro expansion. When the `dispatcher!()` macro generates the entry pack type (via `pack!(#pack = Vec<String>)`), it now also generates `impl From<#pack> for crate::Entry { fn from(value: #pack) -> Self { crate::Entry::new(value.inner) } }`. This allows pack types generated by `dispatcher!()` to be directly converted into `crate::Entry`, enabling ergonomic integration with program-level entry handling.
+
+6. **[`macros:gen_program`]** Added a `pack!(Entry = Vec<String>)` invocation inside the `__this_program_impl` module generated by `gen_program!()`. This creates a `Entry` pack type (aliasing a `Vec<String>` container) directly in the generated module, providing a default entry point type for the program that can be used by `dispatcher!()`-generated types and other chain infrastructure without requiring the user to define a separate entry pack type manually.
+
+7. **[`core`]** **[`comp`]** Added `Suggest::combine(self, other: impl Into<Suggest>) -> Self` method that merges two `Suggest` values. If both are `Suggest::Suggest`, their inner `BTreeSet`s are merged (all items from `other` are added into `self`). Otherwise, the first `Suggest::Suggest` (or `FileCompletion`) is returned unchanged, and the other value is discarded. This enables ergonomic aggregation of completion suggestions from multiple sources.
+
+#### **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")]`.
+
+ **Affected macros** (all previously gated behind `extra_macros`, now `extras`):
+ - `#[command]`
+ - `empty_result!`
+ - `entry!`
+ - `group!`
+ - `group_structural!` (also requires `structural_renderer`)
+ - `pack_err!`
+ - `pack_err_structural!` (also requires `structural_renderer`)
+ - `#[program_setup]`
+ - `render_route!`
+ - `#[renderify]`
+ - `route!`
+ - `#[routeify]`
+
+ **Migration guide:**
+ - Update `Cargo.toml` feature declarations from `extra_macros` to `extras`.
+ - If your code references `mingling::feature::MINGLING_EXTRA_MACROS`, update it to `mingling::feature::MINGLING_EXTRAS`.
+
+ _No behavioral changes — this is a pure feature rename. The `extras` feature provides identical functionality to the old `extra_macros` feature; all prelude and macros module re-exports remain the same under the new feature name._
+
+---
+
+### Release 0.3.0 (2026-07-27)
+
+> In detail, the changes in Mingling 0.3.0 are as follows:
+
+1. **Added `arg-picker`** — Mingling has never had a comfortable argument parsing solution. You either suffered with `parser` or went all-in on `clap`. So I wrote a smarter `arg-picker`. The API style is close to the original `parser`, but it's more type-safe, more robust, and more extensible. See the main text for details.
+
+2. **Made implicit behavior explicit**. For a long time, Mingling's attribute macros have been making **implicit** modifications to the original function — I have to admit, that's dirty. In the new version, I've removed **all** implicit modifications to the original function by attribute macros. In other words, `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` will no longer modify your original function in any way unless you explicitly specify it. Use the Extension Attribute (`#[chain(/* ... */)]`) mechanism to explicitly inject implicit behavior into your functions.
+
+3. I was originally planning to remove `r_println!` because I couldn't stand that `__renderer_inner_result` thing implicitly injected by the `#[renderer]` macro. But now I've rewritten it: `#[buffer]` injects an implicit `__render_result_buffer` value into the function, and then `r_println!` calls it. It's an extra step, but it also means: the dirt is your choice, not something I'm forcing on you :)
+
+4. **Finally**, a philosophical point. Mingling will move forward with a preference for **"selectively dirty"** over **"invisibly, forcibly dirty"** — this is the biggest direction going forward, building a more comfortable API on this foundation.
+
+#### Fixes:
+
+1. **[`pathf:patterns`]** Fixed pattern detection logic in `mingling_pathf` for multiple patterns to correctly detect opening bracket forms (e.g., `[chain`, `[renderer`, `[help`, `[completion`) in addition to the previously supported closing bracket forms (e.g., `chain]`, `renderer]`, `help]`, `completion]`). This ensures that attribute macro usages like `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` are properly detected regardless of which side of the attribute the pattern matcher examines.
+
+ - **`ChainPattern`**: Changed `content.contains("chain]")` → `content.contains("[chain") || content.contains("chain]")`
+ - **`RendererPattern`**: Changed `content.contains("renderer]")` → `content.contains("[renderer") || content.contains("renderer]")`
+ - **`HelpPattern`**: Changed `content.contains("help]")` → `content.contains("[help") || content.contains("help]")`
+ - **`CompletionPattern`**: Changed `content.contains("completion(")` → `content.contains("completion(") || content.contains("[completion")`
+
+2. **[`pathf:patterns`]** Updated `PackPattern` detection to recognize `pack_structural!` and `pack_err_structural!` macros, which were previously missed by the pattern matcher. The `contains` method now checks for these additional macro names alongside the existing `pack!` and `pack_err!` checks.
+
+3. **[`pathf:patterns`]** Added `is_foreign` field to `AnalyzeItem` struct in `mingling_pathf`, along with constructor helpers `AnalyzeItem::local()` and `AnalyzeItem::foreign()`. The `foreign()` constructor marks items resolved via `use` imports, so their `module` path is used as-is (rather than being prefixed with the file's module path) in `type_mapping_builder.rs`.
+
+ - **`GroupPattern`** updated to collect `use` imports at the file and inline-module level via `collect_use_imports()` and `collect_from_use_tree()`, and to resolve `group!(TypeName)` invocations against those imports: if a name matches an imported type, it is emitted as `AnalyzeItem::foreign()` with the full import path; if the `Alias = path::Type` form is used, it is emitted as `AnalyzeItem::local()` (the alias lives in-crate); otherwise it is `local()`.
+
+ - All other patterns (`BasicStructPattern`, `ChainPattern`, `CompletionPattern`, `DispatcherPattern`, `DispatcherClapPattern`, `GroupedDerivePattern`, `HelpPattern`, `PackPattern`, `RendererPattern`) updated to use `AnalyzeItem::local()` constructors, preserving existing behavior (all items are treated as local/in-crate).
+
+ - **`type_mapping_builder.rs`** updated to check `ai.is_foreign`: when true, the full path is built as `{module}::{item_name}` (no prefix from the file's own module path); when false, the existing logic applies (`{file_module_path}::{module}::{item_name}` or `{file_module_path}::{item_name}`).
+
+#### Optimizations:
+
1. **[`macros`]** Updated `route!` macro to use `Routable` trait instead of `Grouped` trait for error conversion, making the semantics clearer. The `route!` macro now calls `::mingling::Routable::to_chain(e)` on the error branch instead of `::mingling::Grouped::to_chain(e)`.
Additionally, `ChainProcess<ThisProgram>` now implements `Routable<ThisProgram>`, allowing `route!` to work with `Result<Ok, ChainProcess<ThisProgram>>` patterns — where the error side is already a `ChainProcess` value that should be routed directly:
@@ -66,11 +217,11 @@ None
The `Routable` trait is defined in `mingling_core::asset::routable` and provides unified routing capabilities (`to_chain` / `to_render`) for any type that can be dispatched into the program's pipeline. A blanket implementation is provided for all `T: Grouped<C> + Send`, ensuring backward compatibility — existing types that implement `Grouped` automatically implement `Routable`.
-2. **[`macros`]** Restructured the `mingling_macros` crate's internal module hierarchy. The previously flat module structure has been reorganized into a logical directory-based layout:
+2. **[`macros`]** Restructured the `mingling_macros` crate's internal module hierarchy. The previously flat module structure has been reorganized into a logical directory-based layout, with **each macro moved to its own dedicated `.rs` file**:
- - **`attr/`** — Attribute macro implementations (e.g., `#[chain]`, `#[renderer]`, `#[help]`, `#[completion]`, `#[dispatcher_clap]`, `#[program_setup]`)
- - **`derive/`** — Derive macro implementations (e.g., `#[derive(Grouped)]`, `#[derive(EnumTag)]`)
- - **`func/`** — Function-like macro implementations (e.g., `pack!`, `group!`, `dispatcher!`, `suggest!`, `entry!`, `node!`, `gen_program!` and its sub-macros)
+ - **`attr/`** — Attribute macro implementations (e.g., `#[chain]` → `attr/chain.rs`, `#[renderer]` → `attr/renderer.rs`, `#[help]` → `attr/help.rs`, `#[completion]` → `attr/completion.rs`, `#[dispatcher_clap]` → `attr/dispatcher_clap.rs`, `#[program_setup]` → `attr/program_setup.rs`)
+ - **`derive/`** — Derive macro implementations (e.g., `#[derive(Grouped)]` → `derive/grouped.rs`, `#[derive(EnumTag)]` → `derive/enum_tag.rs`)
+ - **`func/`** — Function-like macro implementations, each in its own file (e.g., `pack!` → `func/pack.rs`, `group!` → `func/group.rs`, `dispatcher!` → `func/dispatcher.rs`, `suggest!` → `func/suggest.rs`, `entry!` → `func/entry.rs`, `node!` → `func/node.rs`, `gen_program!` → `func/gen_program.rs`, and its sub-macros each in separate files)
- **`systems/`** — Cross-cutting systems (e.g., resource injection, dispatch tree generation, structural data derive support)
- **`extensions/`** — Extension point mechanism for attribute macros (unchanged)
- **`utils.rs`** — Shared utility module for future common helpers
@@ -89,6 +240,90 @@ None
_No migration is required for downstream code — the behavior of all four macros is unchanged. This is purely an internal code generation refactoring._
+4. **[`core`]** Changed the `exec_without_render` and `exec` methods' behavior: the `print!`-based output with manual stdout flushing has been replaced by a call to `result.std_print()`, which handles buffered output internally via the new `RenderResult` buffer system (introduced in **BREAKING CHANGE #6**). The `render_output` gating still applies — when `stdout_setting.render_output` is `false`, nothing is printed — but the `!result.is_empty()` guard has been removed: `std_print()` now handles empty results appropriately.
+
+ Previously, when `render_output` was true but `result.is_empty()` was also true, the old code would skip printing entirely and return the exit code from the result. Now, `std_print()` is called unconditionally when `render_output` is true, and the exit code is read from the result regardless of whether any output was printed. This ensures that programs which set a non-zero exit code without producing renderable output (e.g., via `ExitCodeSetup` or `ProgramControlUnit::OverrideExitCode`) will exit with the correct code.
+
+5. **[`core`]** Made `with_resource`, `with_dispatcher`, `with_dispatchers`, `with_hook`, and `with_setup` methods return `&mut Self` instead of `()`. These methods now return a mutable reference to the program instance, enabling ergonomic chaining:
+
+ ```rust
+ // Before — required separate statements
+ program.with_resource(ResConfig::new());
+ program.with_resource(ResDatabase::new());
+ program.with_setup(BasicProgramSetup);
+ program.with_hook(logging_hook);
+
+ // After — supports method chaining
+ program
+ .with_resource(ResConfig::new())
+ .with_resource(ResDatabase::new())
+ .with_setup(BasicProgramSetup)
+ .with_hook(logging_hook);
+ ```
+
+ Affected methods:
+ - `Program::with_resource(&mut self, res: Res) -> &mut Self` — Insert a resource into the program's global resource store.
+ - `Program::with_dispatcher(&mut self, dispatcher: Disp) -> &mut Self` — Add a single dispatcher to the program.
+ - `Program::with_dispatchers(&mut self, dispatchers: D) -> &mut Self` — Add multiple dispatchers to the program.
+ - `Program::with_hook(&mut self, hook: ProgramHook<C>) -> &mut Self` — Add a lifecycle hook to the program.
+ - `Program::with_setup(&mut self, setup: S) -> &mut Self` — Load and execute a program setup.
+
+ _No behavioral changes — all existing functionality is preserved. Downstream code that ignores the return value continues to work without modification._
+
+6. **[`core`]** **`StructuralData` trait now takes a generic parameter `C`.** The `StructuralData` trait and its sealed supertrait `StructuralDataSealed` (both under `::mingling::__private`) have been made generic over a program collector type `C: ProgramCollect<Enum = C>`. This change is necessary for `group_structural!` to bypass the orphan rule — by tying `StructuralData<C>` to `crate::ThisProgram` (which is defined in the user's crate), external types can implement `StructuralData<crate::ThisProgram>` without violating coherence.
+
+ **Migration guide (only relevant for manual `StructuralData` implementations):**
+
+ - All `impl StructuralData for MyType` must be updated to `impl StructuralData<crate::ThisProgram> for MyType`.
+ - All `impl StructuralDataSealed for MyType` must be updated to `impl StructuralDataSealed<crate::ThisProgram> for MyType`.
+ - All trait bounds `T: StructuralData` must be updated to `T: StructuralData<C>` with an additional `C: ProgramCollect<Enum = C>` bound.
+ - The `StructuralRenderer::render` method signature has changed from `render<T: StructuralData + Send>(...)` to `render<T, C>(...) where T: StructuralData<C> + Send, C: ProgramCollect<Enum = C>`.
+
+ **Internal changes:**
+
+ - `StructuralDataSealed` in `mingling_core::__private` now takes a `C` type parameter with `C: ProgramCollect<Enum = C>`.
+ - `StructuralData` in `mingling_core::renderer::structural::structural_data` now takes a `C` type parameter with `C: ProgramCollect<Enum = C>`.
+ - Both traits remain under `::mingling::__private`, so this change does **not** affect the public API surface.
+ - `StructuralRenderer::render` now takes an additional generic parameter `C: ProgramCollect<Enum = C>`.
+ - All derive macro and `pack_structural!` / `pack_err_structural!` / `group_structural!` implementations have been updated to emit `impl StructuralDataSealed<crate::ThisProgram>` and `impl StructuralData<crate::ThisProgram>` instead of the non-generic form.
+ - Test code has been updated to use `MockProgramCollect` where appropriate, and integration tests now use `crate::ThisProgram` and call `gen_program!()`.
+
+ _No behavioral changes — this is purely a type-system refactoring to enable `group_structural!` to work with external types. Since both traits are defined in `::mingling::__private`, this change has **no impact on the public API** — end users interact with `StructuralData` only through auto-generated derive macros and `pack_structural!`/`group_structural!` macros, which are automatically updated. Only users with manual `impl StructuralData` blocks (an advanced/rare case) need to update their code.
+
+7. **[`core`]** **`RenderResult` now derives `Clone` and `Eq` in addition to `Default`, `Debug`, and `PartialEq`.** Added `Clone` and `Eq` derive macros to the `RenderResult` struct in `mingling_core/src/renderer/render_result.rs`. These additions enable `RenderResult` values to be explicitly cloned and support equality comparisons that are both reflexive and transitive.
+
+ - **`Clone`** — Allows a `RenderResult` to be duplicated via `.clone()`, which is useful for scenarios where the same render output needs to be reused or stored in multiple locations.
+ - **`Eq`** — Enables `RenderResult` to be used in contexts that require full equivalence (e.g., `assert_eq!` with `Eq` bounds, `HashMap`/`HashSet` keys when combined with `Hash`).
+
+ _No migration is required — these are purely additive derives that expand the type's capabilities without affecting existing behavior._
+
+8. **[`core`]** Added the `build` feature (renamed from `builds`) to `mingling_core` and `mingling`. The old `builds` feature has been deprecated in favor of `build`, with a backward-compatibility alias retained in `mingling/Cargo.toml`:
+
+- **`mingling_core/Cargo.toml`**: Renamed the feature from `builds` to `build`.
+- **`mingling/Cargo.toml`**: Changed the feature dependency from `mingling_core/builds` to `mingling_core/build`. A deprecated `builds` feature alias is kept as `builds = ["mingling_core/build"]` with a note indicating it will be removed in a future breaking change.
+
+ _No behavioral changes — the `build` feature provides identical functionality to the old `builds` feature. Downstream code using `builds` continues to work via the alias, but should migrate to `build`._
+
+9. **[`core`]** Renamed `ResourceMarker` methods from public names (`res_clone`, `res_default`, `modify`) to doc-hidden internal names (`__resource_marker_clone`, `__resource_marker_default`, `__resource_marker_modify`). These methods are internal implementation details of the resource injection system and should not be called directly by user code. By prefixing with `__` and adding `#[doc(hidden)]`, they are still technically accessible but hidden from documentation and tooling, reducing API surface confusion.
+
+ - **`res_clone()`** → **`__resource_marker_clone()`** — Internal method for cloning a resource value during resource injection.
+ - **`res_default()`** → **`__resource_marker_default()`** — Internal method for creating a default resource value during resource injection.
+ - **`modify<C>()`** → **`__resource_marker_modify<C>()`** — Internal method for in-place modification of a resource during resource injection.
+
+ All internal usages within `global_resource.rs` and `lazy_resource.rs` have been updated to use the renamed methods. Test code has been updated accordingly.
+
+ A new module `mingling_core::asset::core_invokes` has been added to provide a centralized location for internal invocation helpers.
+
+10. **[`core:exec`]** Refactored the program execution pipeline (`exec` and `exec_with_args`) to use the `might_be_async` crate instead of manual `#[cfg(feature = "async")]` duplication. The previously separate sync and async implementations have been consolidated into a single `#[might_be_async::func]`-annotated function, with `might_be_async::invoke!()` wrapping the `C::do_chain(current)` call inside `exec_with_args` and the delegation from `exec` to `exec_with_args`.
+
+ The `exec` function no longer contains the full execution loop inline. Instead, it delegates to `exec_with_args` (which now also carries the `#[might_be_async::func]` annotation), reducing code duplication and centralizing the execution logic.
+
+ - **`exec`**: Changed from separate `#[cfg(feature = "async")]` and `#[cfg(not(feature = "async"))]` implementations to a single `#[might_be_async::func]` function that calls `might_be_async::invoke!(exec_with_args(program, &program.args))`.
+ - **`exec_with_args`**: Changed from separate implementations to a single `#[might_be_async::func]` function. The `C::do_chain(current)` call is now wrapped with `might_be_async::invoke!(C::do_chain(current))` to support both sync and async chain execution.
+ - **Removed**: The `error.rs` submodule import remains, but the separate sync/async code blocks in the function bodies have been eliminated.
+
+ _No behavioral changes. All existing functionality — hooks, help handling, chain execution, renderer dispatch, and exit code management — is preserved identically._
+
#### Features:
1. **[`core`]** Added `RenderResult::new()` method for creating a new `RenderResult` with default values (empty text and exit code 0). This provides a more explicit and discoverable constructor compared to `RenderResult::default()`, making it clearer when a fresh result is being created for use with `write!`/`writeln!`.
@@ -265,13 +500,143 @@ None
}
```
- The `#[routeify]` macro is feature-gated behind `extra_macros` and re-exported as `mingling::macros::routeify`.
+ The `#[routeify]` macro is feature-gated behind `extras` and re-exported as `mingling::macros::routeify`.
Internal changes:
- Added `mingling_macros/src/extensions/routeify.rs` with `routeify_impl` implementation.
- Updated `try_redispatch_simple` and `try_redispatch_completion` to emit `#[#exts]` instead of bare `#exts`, ensuring proper attribute syntax in the re-dispatched token stream.
- Registered `#[proc_macro_attribute] pub fn routeify` in `mingling_macros/src/lib.rs`.
+12. **[`core`]** **[`macros`]** Added the `r_append!` macro and `RenderResult::append_other()` method for appending the contents of one `RenderResult` to another. The `append_other()` method on `RenderResult` merges the buffered content (text and output modes) from another `RenderResult` into the current one. If the destination result has `immediate_output` enabled but the source does not, the source's content will be immediately flushed to the appropriate output stream (stdout/stderr) while also being appended to the render buffer. The `exit_code` of the source result is **not** transferred — only the buffered content and the `immediate_output` flag are merged.
+
+ The `r_append!` macro supports two usage forms:
+ - **Explicit buffer** — `r_append!(dst, src)` appends the contents of `src` into the `dst` `RenderResult`.
+ - **Implicit buffer** — `r_append!(src)` appends the contents of `src` into the implicit `__render_result_buffer` (available inside `#[buffer]` functions).
+
+ The macro is re-exported as `mingling::macros::r_append` and included in `mingling::prelude::*`.
+
+13. **[`core`]** **[`macros`]** Added `From<F>` implementation for `RenderResult` where `F: FnOnce() -> RenderResult`. This enables `RenderResult` to be constructed from a closure or function pointer that returns a `RenderResult`, enabling ergonomic composition of render buffers.
+
+ This is particularly useful with the `#[buffer]` extension attribute, which creates a separate buffer function that can be called from within a `#[renderer(buffer)]` function using `r_append!`:
+
+ ```rust
+ use mingling::macros::{buffer, r_append, r_println};
+
+ // A standalone buffer function
+ #[buffer]
+ fn print_sth() {
+ r_println!("ok");
+ }
+
+ #[renderer(buffer)]
+ fn render_greet(_: ResultGreet) {
+ r_append!(print_sth); // appends `a`'s RenderResult content into `p`'s buffer
+ }
+ ```
+
+ Under the hood, `r_append!` (when used in implicit buffer mode inside a `#[buffer]` function) calls `append_other` on the current render buffer. The `From<F>` implementation allows the buffer function's return value (a `RenderResult`) to be seamlessly converted into the parent's buffer via `append_other`. This enables clean separation of render logic into reusable buffer functions that can be composed together.
+
+14. **[`core`]** **[`macros`]** Added the `render_route!` macro and `#[renderify]` extension attribute macro, providing error routing to the rendering pipeline (as opposed to `route!`/`#[routeify]` which route to the chain pipeline).
+
+ The `render_route!` macro is conceptually similar to `route!`, but instead of routing errors through `Routable::to_chain()` (returning `ChainProcess`), it routes them directly to the renderer via `crate::ThisProgram::render(AnyOutput::new(e))` (returning `RenderResult`). This makes it suitable for use in `#[renderer]` and `#[help]` functions where the return type is `RenderResult`.
+
+ ```rust,ignore
+ use mingling::macros::{renderer, render_route};
+
+ #[renderer]
+ fn render_something(prev: SomeType) -> RenderResult {
+ let data = render_route!(fetch_data().map_err(|e| ErrorEntry::new(e.to_string())))?;
+ // ... render data
+ Ok(RenderResult::new())
+ }
+ ```
+
+ The `#[renderify]` extension attribute is the rendering-pipeline counterpart to `#[routeify]`. It transforms `expr?` into `render_route!(expr)` (instead of `route!(expr)`), enabling concise error routing in renderer and help functions using the `?` operator syntax.
+
+ ```rust,ignore
+ #[renderer(renderify)]
+ fn render_greeting(prev: Greeting) -> RenderResult {
+ let data = load_data()?; // expands to render_route!(load_data())
+ r_println!("{data}");
+ Ok(RenderResult::new())
+ }
+ ```
+
+ The `#[renderify]` macro can be used:
+ - **Standalone** — as a direct attribute: `#[renderify] fn render(...) { ... }`
+ - **As an extension** — via the extension point system: `#[renderer(renderify)] fn render(...) { ... }` or `#[help(renderify)] fn help(...) { ... }`
+
+ When used as a renderer/help extension, the `renderify` identifier is detected by the extension point mechanism, stripped from the attribute arguments, and `#[renderify]` is applied as an outer attribute on top of `#[renderer]`/`#[help]` — just like `routeify` works with `#[chain]`.
+
+ Both `render_route!` and `#[renderify]` are feature-gated behind `extras` and re-exported as `mingling::macros::render_route` and `mingling::macros::renderify` respectively.
+
+ Internal changes:
+ - Added `mingling_macros/src/extensions/renderify.rs` with `renderify_impl` implementation.
+ - Registered `#[proc_macro] pub fn render_route` and `#[proc_macro_attribute] pub fn renderify` in `mingling_macros/src/lib.rs`.
+
+15. **[`macros`]** Added the `#[mlint(...)]` marker attribute macro — a no-op attribute that passes its attached item through unchanged. The attribute content is ignored by `rustc` and reserved for the Mingling lint (`mlint`) tooling system.
+
+ The `#[mlint]` attribute is registered as a `#[proc_macro_attribute]` and re-exported as `mingling::macros::mlint`. It supports three styles of lint configuration:
+
+ ```rust,ignore
+ #[mlint(allow(MLINT_SOME_LINT))]
+ #[mlint(warn(MLINT_SOME_LINT))]
+ #[mlint(deny(MLINT_SOME_LINT))]
+ fn some_item() {}
+ ```
+
+ Since the attribute is a no-op at compile time, it has no effect on code generation, type checking, or runtime behavior. Its purpose is to serve as a structured annotation that `mlint` tooling can parse from the AST. The attribute is feature-gated behind `extras`.
+
+16. **[`core`]** Added `RendererInvoker<T, C>` and `ChainInvoker<T, C>` types to `mingling_core::asset::core_invokes`, providing a mechanism for selectively invoking renderer and chain pipelines for specific types from within chain/renderer functions via resource injection.
+
+ These types are designed to be created **only** through the resource injection system (via `ResourceMarker::__resource_marker_default`), and attempting to invoke them without being properly injected will panic. They are marked `#[non_exhaustive]` with private fields, so they cannot be constructed by user code.
+
+ ### `RendererInvoker<T, C>`
+
+ Allows invoking the renderer pipeline for a specific type `T`. Use cases include:
+ - **Reusing** — calling another renderer's output from within a `#[renderer]` or `#[chain]` function
+ - **Bypassing** — directly rendering intermediate values without going through the chain pipeline
+
+ ```rust,ignore
+ #[renderer(buffer)]
+ fn render_foo(_: ResultFoo, renderer: &RendererInvoker<Bar>) {
+ let bar = Bar::default();
+ r_append!(renderer.invoke(bar));
+ }
+ ```
+
+ **Methods:**
+ - `invoke(&self, value: T) -> RenderResult` — Invokes the renderer for value `T`, returning the rendered output. Does **not** execute program hooks (by design — this is for bypassing/reusing, not flow control).
+
+ ### `ChainInvoker<T, C>`
+
+ Allows executing chain steps for a specific type `T`. Use cases include:
+ - **Sub-routing** — dispatching to a sub-chain from within a chain handler
+ - **Re-entering** — re-processing a value through the chain pipeline
+
+ ```rust,ignore
+ #[chain]
+ fn handle_foo(_: EntryFoo, chain: &ChainInvoker<StateBar>) -> Next {
+ let bar = Bar::default();
+ // Execute one step of the chain
+ let next = chain.invoke_once(bar);
+ // ... handle the result
+ next
+ }
+ ```
+
+ **Methods:**
+ - `invoke_once(&self, value: T) -> ChainProcess<C>` — Executes a **single step** of chain processing for value `T`. If no chain exists for the type, it converts the value into a `ChainProcess::Ok` with `NextProcess::Chain` routing. Does **not** execute program hooks.
+ - `invoke_to_last(&self, value: T) -> ChainProcess<C>` — Continuously executes the chain for value `T` until it is routed to a renderer or can no longer continue. Each step calls `C::do_chain(any)`. If a step produces a `ChainProcess::Ok` with `NextProcess::Chain` and the next type has no chain handler, it stops and returns that state. Non-chain results (e.g., `ChainProcess::Err`) are returned immediately.
+ - `invoke_to_result(&self, value: T) -> RenderResult` — Convenience method that runs the chain to completion via `invoke_to_last` and then renders the final result. If the final result lacks a renderer or is an error, returns an empty `RenderResult`.
+
+ Both types implement `ResourceMarker` (via `__resource_marker_clone`, `__resource_marker_default`, and `__resource_marker_modify`) which:
+ - `__resource_marker_default` creates instances with `create_by_res_injection: true`, allowing invocation.
+ - `__resource_marker_clone` preserves the `create_by_res_injection` flag.
+ - `__resource_marker_modify` is a no-op (these invokers are not meant to be modified at runtime).
+
+ The types are re-exported from `mingling_core` and exposed to the `mingling` crate root.
+
#### **BREAKING CHANGES** (API CHANGES):
1. **[`macros:renderer`]** **[`macros:help`]** Removed `r_println!` and `r_print!` macros from being implicitly injected by `#[renderer]` and `#[help]` macros. These macros still exist, but must now be used **explicitly** — either with an explicit buffer argument, or via the `#[buffer]` extension attribute that re-enables implicit buffer injection.
@@ -367,6 +732,85 @@ None
_No behavioral changes — this is a pure API migration from `Grouped` to `Routable` for routing methods._
+6. **[`core`]** **[BREAKING]** Removed the `Deref<Target = str>` implementation from `RenderResult`. Previously, `RenderResult` implemented `Deref<Target = str>`, delegating to the internal `render_text: String` field. This implementation has been removed as part of the internal refactoring of `RenderResult` from a single `String` field to a `Vec<(String, RenderResultMode)>` buffer.
+
+ The `RenderResult` struct has been restructured internally:
+
+ - **Removed:** `render_text: String` — replaced by a buffered storage format
+ - **Added:** `render_buffer: Vec<(String, RenderResultMode)>` — a list of (text, output-mode) pairs
+ - **Added:** `immediate_output: bool` — flag for real-time flushing
+
+ **New `RenderResultMode` enum** (`Stdout` / `Stderr`) has been added to distinguish output streams at the buffer level. Both variants are re-exported as `mingling::RenderResultMode`.
+
+ **New methods added to `RenderResult`:**
+
+ - `append_to_buffer(text, mode)` / `append_line_to_buffer(text, mode)` — Append text with an explicit output mode (`Stdout` or `Stderr`)
+ - `eprint(text)` / `eprintln(text)` — Append text marked for stderr output
+ - `immediate_output()` — Enable real-time output flushing
+ - `std_print()` — Flush all buffered content to stdout/stderr
+ - `len()` / `is_empty()` — Character count and emptiness check (based on the buffer)
+ - `trim_buffer(self) -> RenderResult` — Trim whitespace from the buffer ends, returning a new `RenderResult`
+
+ **`r_eprint!` and `r_eprintln!` macros** have been added to `mingling_macros` and re-exported via `mingling::macros::r_eprint` / `mingling::macros::r_eprintln` and `mingling::prelude::*`. These work analogously to `r_print!` / `r_println!` but target stderr output (calling `RenderResult::eprint` / `RenderResult::eprintln` under the hood). Both support implicit buffer mode (inside `#[buffer]` functions) and explicit buffer mode (passing a `RenderResult` as the first argument).
+
+ The `Display` implementation for `RenderResult` is now: `write!(f, "{}", render_result_to_string(self).trim())` — this trims leading and trailing whitespace from the rendered output, making formatting more predictable and avoiding stray newlines or spaces in display contexts.
+
+ **Migration guide:**
+
+ Code that relied on `Deref<Target = str>` (e.g., `&*result`, `result.as_ref()`, or passing `&RenderResult` where `&str` was expected) must be updated to use one of the following:
+
+ ```rust
+ // Before — relied on Deref
+ fn takes_str(s: &str) { /* ... */ }
+ takes_str(&result);
+
+ // After — convert explicitly
+ let result_string: String = result.to_string();
+ takes_str(&result_string);
+
+ // Or use the Display impl directly
+ println!("{}", result);
+ ```
+
+ The `is_empty()` method now checks the buffer length (in characters) rather than checking `render_text.is_empty()`. The `Display` implementation no longer adds a trailing newline (previously `writeln!` was used; now `write!` is used) — existing code that relied on the trailing newline in `Display` may need adjustment. Additionally, the `to_string()` call on `RenderResult` now trims leading and trailing whitespace from the rendered text via the `Display` implementation, whereas previously the raw content was preserved without trimming.
+
+ All examples and internal usages have been updated across the codebase to reflect these changes (e.g., `repl_basic_setup` now calls `println!("{}", r.result)` instead of `println!("{}", r.result.trim())`, since `Display` no longer adds a trailing newline).
+
+7. **[`any`]** **[`macros`]** Made `AnyOutput`'s `type_id` and `member_id` fields private (`pub(crate)`) and added public accessor methods `type_id()` and `member_id()`. Added the `unsafe fn new_bare<T>(value: T, member_id: G) -> Self` constructor that bypasses the `Grouped` trait, allowing manual specification of `member_id` without requiring the concrete type to implement `Grouped`.
+
+ - **`type_id`** field changed from `pub` to `pub(crate)` — accessible via `type_id()` accessor.
+ - **`member_id`** field changed from `pub` to `pub(crate)` — accessible via `member_id()` accessor (requires `G: Copy`).
+ - **`new_bare`** — Unsafe constructor that takes a raw `member_id` value without invoking `Grouped::member_id()`. The caller must ensure the provided `member_id` correctly corresponds to the concrete type `T`.
+ - Updated all internal `match any.member_id { ... }` patterns in `gen_program.rs` to use `match any.member_id() { ... }` instead.
+ - Updated the panic message in `do_chain` (both sync and async) from `any.type_id` to `any.type_id()`.
+ - Updated the example-hook `main.rs` to call `info.output.member_id()` instead of accessing `info.output.member_id` directly.
+ - Added `Copy` derive to the generated enum to enable `member_id()`'s `Copy` requirement on the enum type.
+
+ _No behavioral changes for existing code — the accessor methods provide the same values as the previously-public fields._
+
+8. **[`any`]** **[`macros`]** **[BREAKING]** Marked `Grouped` trait as `unsafe trait`. The `Grouped` trait has always been inherently unsafe — the `member_id()` return value must exactly correspond to the variant registered by `register_type!` for the concrete type, otherwise dispatching on that type will result in **undefined behavior**. This unsoundness has existed since the trait's inception but was previously unenforced at the type system level.
+
+ By making `Grouped` an `unsafe trait`, implementors must now explicitly acknowledge this safety contract with `unsafe impl Grouped<...> for ...`. This change makes the existing safety invariant visible to developers and enables soundness warnings at compile time.
+
+ **Changes made:**
+
+ - **`Grouped` trait** in `mingling_core/src/any/group.rs` changed from `pub trait Grouped<Group>` to `pub unsafe trait Grouped<Group>`, with a safety doc comment explaining that manually implementing the trait with an incorrect `member_id` leads to undefined behavior.
+
+ - **Derive macros** (`#[derive(Grouped)]`, `#[derive(GroupedSerialize)]`) now generate `unsafe impl` instead of `impl`, with a SAFETY comment stating that the derive macro guarantees correctness because the `Ident` used in `register_type!` matches the `Ident` returned by `member_id()`.
+
+ - **`pack!`, `pack_structural!`, `group!`, `group_structural!`** macros now generate `unsafe impl` instead of `impl`, with analogous SAFETY comments.
+
+ - **All manual test implementations** of `Grouped` across the codebase (in `any.rs` tests, `hook.rs` tests, `mock.rs`) updated to `unsafe impl` with SAFETY comments explaining why they are safe in their test contexts.
+
+ - **`MockProgramCollect::member_id()`** changed from `MockProgramCollect::Foo` to `panic!("Attempting to read an unsafe enum type")` to prevent accidental execution in production paths.
+
+ **Migration guide:**
+
+ - Existing code that uses `Grouped` only through the derive macro or `pack!`/`group!` macros is automatically migrated — no changes needed.
+ - Code with **manual** `impl Grouped<...> for ...` blocks must add `unsafe` before `impl` and verify that the `member_id()` return value correctly corresponds to the type's registered variant. Only proceed if the correspondence is guaranteed.
+
+ _This is a breaking change only for code with manual `Grouped` implementations._
+
---
## Release 0.2.2 (2026-07-10)
@@ -454,7 +898,7 @@ None
- `test-basic`: Basic type tests with default features (Node, Flag, RenderResult, NextProcess, StringVec)
- `test-comp`: ShellContext, Suggest, SuggestItem, is_completing with `comp + builds` features
- `test-structural-renderer`: StructuralRenderer output in various formats with `structural_renderer_full + parser` features
- - `test-repl`: ResREPL and basic types with `repl + extra_macros` features
+ - `test-repl`: ResREPL and basic types with `repl + extras` features
- `test-dispatch-tree`: Basic types with `dispatch_tree` feature
- `test-all`: Comprehensive testing with all feature combinations (ShellContext, Suggest, ResREPL, StructuralRenderer, Hooks, basic types, etc.)
@@ -618,7 +1062,7 @@ impl ErrorNotDir {
}
```
-This macro is only available with the `extra_macros` feature.
+This macro is only available with the `extras` feature.
9. **[`mingling`]** Added `Groupped` trait to the `mingling::prelude` module, so it can now be imported via `use mingling::prelude::*` without needing to separately import the trait from the `mingling` crate root.
@@ -637,7 +1081,7 @@ An aliased syntax is also supported for descriptive variant naming:
group!(IoError = std::io::Error);
```
-This macro is only available with the `extra_macros` feature.
+This macro is only available with the `extras` feature.
11. **[`macros`]** `#[help]` and `#[completion]` now support resource injection parameters, consistent with `#[chain]` and `#[renderer]`. Specific changes:
@@ -1028,7 +1472,7 @@ fn render(prev: Previous) { // Implicitly introduces `__renderer_inner_result`
}
```
-5. **[`macros`]** Moved the `entry!`, `route!`, `#[program_setup]` macros into the `extra_macros` feature
+5. **[`macros`]** Moved the `entry!`, `route!`, `#[program_setup]` macros into the `extras` feature
6. **[`macros`]** The `crate::Next` generated by `gen_program!()` now requires explicit import into the project