diff options
Diffstat (limited to 'CHANGELOG.md')
| -rw-r--r-- | CHANGELOG.md | 466 |
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 |
