aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md38
-rw-r--r--mingling/src/lib.rs9
-rw-r--r--mingling_macros/src/extensions.rs4
-rw-r--r--mingling_macros/src/extensions/renderify.rs48
-rw-r--r--mingling_macros/src/lib.rs76
5 files changed, 175 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16ca38a..9977b9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -366,6 +366,44 @@ None
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 `extra_macros` 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`.
+
#### **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.
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 457f393..f08e9e4 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -123,8 +123,17 @@ pub mod macros {
pub use mingling_macros::register_renderer;
#[doc(hidden)]
pub use mingling_macros::register_type;
+ /// `render_route! { /* ... */ }` - Routes errors to the rendering pipeline.
+ /// Similar to `route!`, but used in `#[renderer]` and `#[help]` functions
+ /// where the return type is `RenderResult` instead of `ChainProcess`.
+ #[cfg(feature = "extra_macros")]
+ pub use mingling_macros::render_route;
/// `#[renderer]` - Used to generate a struct implementing the `Renderer` trait via a method
pub use mingling_macros::renderer;
+ /// `#[renderify]` - An extension attribute macro that transforms `expr?` into `render_route!(expr)`.
+ /// Can be used standalone or as a renderer/help extension: `#[renderer(renderify, ...)]`, `#[help(renderify, ...)]`.
+ #[cfg(feature = "extra_macros")]
+ pub use mingling_macros::renderify;
/// `route! { /* ... */ }` - Used to generate a route that either returns a successful result or early returns an error.
#[cfg(feature = "extra_macros")]
pub use mingling_macros::route;
diff --git a/mingling_macros/src/extensions.rs b/mingling_macros/src/extensions.rs
index 022761b..c2fdb30 100644
--- a/mingling_macros/src/extensions.rs
+++ b/mingling_macros/src/extensions.rs
@@ -13,6 +13,10 @@ use syn::{Ident, Token};
#[cfg(feature = "extra_macros")]
pub(crate) mod routeify;
+/// Extension: `#[renderify]` — transforms `expr?` into `render_route!(expr)`.
+#[cfg(feature = "extra_macros")]
+pub(crate) mod renderify;
+
/// Extension: `#[buffer]` — wraps a unit-returning function to return `RenderResult`.
pub(crate) mod buffer;
diff --git a/mingling_macros/src/extensions/renderify.rs b/mingling_macros/src/extensions/renderify.rs
new file mode 100644
index 0000000..9be8acc
--- /dev/null
+++ b/mingling_macros/src/extensions/renderify.rs
@@ -0,0 +1,48 @@
+//! The `#[renderify]` extension — transforms `expr?` into `render_route!(expr)`.
+//!
+//! Designed as an extension for the Mingling attribute macro system, intended
+//! to be used with `#[renderer(renderify)]`, `#[help(renderify)]`,
+//! or standalone as `#[renderify]`.
+//!
+//! # How it works
+//!
+//! The macro parses the function AST and replaces every `Expr::Try` node with an
+//! equivalent `render_route!(expr)` invocation, which routes errors to the
+//! rendering pipeline via `crate::ThisProgram::render(AnyOutput::new(e))`.
+
+use proc_macro::TokenStream;
+use quote::ToTokens;
+use syn::spanned::Spanned;
+use syn::visit_mut::VisitMut;
+use syn::{Expr, ItemFn, parse_macro_input};
+
+struct RenderifyTransform;
+
+impl VisitMut for RenderifyTransform {
+ fn visit_expr_mut(&mut self, expr: &mut Expr) {
+ syn::visit_mut::visit_expr_mut(self, expr);
+
+ if let Expr::Try(try_expr) = expr {
+ let inner = &*try_expr.expr;
+ let inner_tokens = inner.to_token_stream();
+
+ // Set the span of the generated `render_route` ident to the `?` token's span,
+ // so that rust-analyzer resolves the `?` position to the `render_route!` macro
+ // instead of the standard Try trait, showing the render_route macro's docs on hover.
+ let q_span = try_expr.question_token.span();
+ let route_ident = proc_macro2::Ident::new("render_route", q_span);
+
+ if let Ok(macro_expr) = syn::parse2::<Expr>(quote::quote! {
+ ::mingling::macros::#route_ident!(#inner_tokens)
+ }) {
+ *expr = macro_expr;
+ }
+ }
+ }
+}
+
+pub(crate) fn renderify_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ let mut input_fn = parse_macro_input!(item as ItemFn);
+ RenderifyTransform.visit_item_fn_mut(&mut input_fn);
+ input_fn.to_token_stream().into()
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 5d8d7e0..da699da 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -590,6 +590,56 @@ pub fn route(input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}
+/// Routes errors to the rendering pipeline instead of the chain pipeline.
+///
+/// This macro is similar to [`route!`] but instead of routing errors through
+/// `Routable::to_chain()` (which returns `ChainProcess`), it routes them
+/// directly to the renderer via `crate::ThisProgram::render(AnyOutput::new(e))`
+/// (which returns `RenderResult`).
+///
+/// This is useful in `#[renderer]` and `#[help]` functions where the return
+/// type is `RenderResult` rather than `ChainProcess`.
+///
+/// # Syntax
+///
+/// ```rust,ignore
+/// render_route!(expr)
+/// ```
+///
+/// Where `expr` is an expression of type `Result<T, E>`.
+///
+/// # Interaction with `#[routeify]`
+///
+/// When `#[routeify]` is used on a `#[renderer]` or `#[help]` function (e.g.
+/// `#[renderer(routeify)]` or `#[help(routeify)]`), every `expr?` is automatically
+/// replaced with `render_route!(expr)` instead of `route!(expr)`.
+///
+/// # Example
+///
+/// ```rust,ignore
+/// use mingling::macros::{renderer, render_route};
+/// use std::io::Write;
+///
+/// #[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())
+/// }
+/// ```
+#[cfg(feature = "extra_macros")]
+#[proc_macro]
+pub fn render_route(input: TokenStream) -> TokenStream {
+ let expr = parse_macro_input!(input as syn::Expr);
+ let expanded = quote! {
+ match #expr {
+ Ok(r) => r,
+ Err(e) => return crate::ThisProgram::render(::mingling::AnyOutput::new(e)),
+ }
+ };
+ TokenStream::from(expanded)
+}
+
/// Creates an empty result value wrapped in `ChainProcess` for early return
/// from a chain function.
///
@@ -1314,6 +1364,32 @@ pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream {
extensions::routeify::routeify_impl(attr, item)
}
+/// Extension attribute macro that transforms `expr?` into `render_route!(expr)`.
+///
+/// Designed for use with `#[renderer(renderify, ...)]` or `#[help(renderify, ...)]`
+/// to enable concise error routing in renderer and help functions using the `?`
+/// operator syntax.
+///
+/// Unlike `#[routeify]` which routes errors to the chain pipeline via `route!`,
+/// `#[renderify]` routes errors to the rendering pipeline via `render_route!`,
+/// which matches the `RenderResult` return type of renderer and help functions.
+///
+/// # Example
+///
+/// ```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())
+/// }
+/// ```
+#[cfg(feature = "extra_macros")]
+#[proc_macro_attribute]
+pub fn renderify(attr: TokenStream, item: TokenStream) -> TokenStream {
+ extensions::renderify::renderify_impl(attr, item)
+}
+
/// Wraps a unit-returning function to produce a `RenderResult`.
///
/// The `#[buffer]` attribute macro injects a local `__render_result_buffer`