aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-22 20:41:00 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-22 20:41:00 +0800
commit5169b9a462a7a3854b8320c8d9e78985a34c5f15 (patch)
treeb0c029bb7dbf4e0cb39794f43c3b3166d539e24b /mingling_macros/src
parentd7c9ad94113cca2f782666e37a0aa4fb7b8d7d86 (diff)
Support resource injection in #[help] and #[completion
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/completion.rs150
-rw-r--r--mingling_macros/src/help.rs101
2 files changed, 187 insertions, 64 deletions
diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/completion.rs
index 720ac49..452c418 100644
--- a/mingling_macros/src/completion.rs
+++ b/mingling_macros/src/completion.rs
@@ -1,11 +1,12 @@
+use crate::res_injection::{ResourceInjection, generate_immut_resource_bindings};
use proc_macro::TokenStream;
use quote::quote;
-use syn::{Ident, ItemFn, TypePath, parse_macro_input};
+use syn::spanned::Spanned;
+use syn::{FnArg, Ident, ItemFn, Pat, PatType, Type, TypePath, parse_macro_input};
#[cfg(feature = "comp")]
pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
- // Parse the attribute arguments (e.g., HelloEntry or crate::EntryFine from #[completion(crate::EntryFine)])
-
+ // Parse the attribute arguments such as HelloEntry or crate::EntryFine from #[completion(crate::EntryFine)]
use crate::get_global_set;
let previous_type_path: TypePath = if attr.is_empty() {
return syn::Error::new(
@@ -24,8 +25,6 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// Validate the function is not async
if input_fn.sig.asyncness.is_some() {
- use syn::spanned::Spanned;
-
return syn::Error::new(input_fn.sig.span(), "Completion function cannot be async")
.to_compile_error()
.into();
@@ -36,22 +35,44 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
let inputs = &sig.inputs;
let output = &sig.output;
- // Check that the function has exactly one parameter
- if inputs.len() != 1 {
- use syn::spanned::Spanned;
-
+ // Must have at least one parameter ctx
+ if inputs.is_empty() {
return syn::Error::new(
inputs.span(),
- "Completion function must have exactly one parameter",
+ "Completion function must have at least one parameter: `ctx: &ShellContext`",
)
.to_compile_error()
.into();
}
+ // Extract the first param pattern and type for the ctx parameter
+ let first_arg = &inputs[0];
+ let (ctx_pat, _ctx_type) = match first_arg {
+ FnArg::Typed(PatType { pat, ty, .. }) => {
+ let param_pat = (**pat).clone();
+ (param_pat, (**ty).clone())
+ }
+ FnArg::Receiver(_) => {
+ return syn::Error::new(
+ first_arg.span(),
+ "Completion function cannot have self parameter",
+ )
+ .to_compile_error()
+ .into();
+ }
+ };
+
+ // Extract resources from params 2 through N, skipping ctx
+ let resources = match extract_resources_from_args(sig, 1) {
+ Ok(r) => r,
+ Err(e) => return e.to_compile_error().into(),
+ };
+
// Get the function body
let fn_body = &input_fn.block;
+ let fn_body_stmts = &fn_body.stmts;
- // Get function attributes (excluding the completion attribute)
+ // Get function attributes excluding the completion attribute
let mut fn_attrs = input_fn.attrs.clone();
fn_attrs.retain(|attr| !attr.path().is_ident("completion"));
@@ -68,7 +89,43 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
);
let struct_name = Ident::new(&internal_name, fn_name.span());
+ let program_type = crate::default_program_path();
+ let has_resources = !resources.is_empty();
+ let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect();
+
+ // Generate immutable resource bindings
+ let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), &program_type);
+
+ // Build the comp method body with resource injection
+ // Use modify_res for mutable resources same pattern as renderer.rs
+ let wrapped_body = if mut_resources.is_empty() {
+ quote! { #(#fn_body_stmts)* }
+ } else {
+ let mut wrapped = quote! { #(#fn_body_stmts)* };
+ for res in mut_resources.iter().rev() {
+ let var_name = &res.var_name;
+ let inner_type = &res.inner_type;
+ wrapped = quote! {
+ ::mingling::this::<#program_type>().modify_res(|#var_name: &mut #inner_type| {
+ #wrapped
+ })
+ };
+ }
+ wrapped
+ };
+
+ let comp_body = if has_resources {
+ quote! {
+ #(#immut_resource_stmts)*
+ #wrapped_body
+ }
+ } else {
+ quote! { #(#fn_body_stmts)* }
+ };
+
// Generate the struct and implementation
+ // The `comp` trait method only takes `ctx` as the first parameter; resources are injected internally
+
let expanded = quote! {
#(#fn_attrs)*
#[doc(hidden)]
@@ -78,8 +135,8 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
impl ::mingling::Completion for #struct_name {
type Previous = #previous_type_path;
- fn comp(#inputs) #output {
- #fn_body
+ fn comp(#ctx_pat: &::mingling::ShellContext) #output {
+ #comp_body
}
}
@@ -100,3 +157,70 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
expanded.into()
}
+
+/// Extract resource injection parameters from function arguments (skipping the first N params).
+fn extract_resources_from_args(
+ sig: &syn::Signature,
+ skip: usize,
+) -> syn::Result<Vec<ResourceInjection>> {
+ let mut resources = Vec::new();
+ for arg in sig.inputs.iter().skip(skip) {
+ match arg {
+ FnArg::Typed(PatType { pat, ty, .. }) => {
+ let var_name = match &**pat {
+ Pat::Ident(pat_ident) => pat_ident.ident.clone(),
+ _ => {
+ return Err(syn::Error::new(
+ pat.span(),
+ "Resource injection parameter must be a simple identifier",
+ ));
+ }
+ };
+
+ let full_type = *(*ty).clone();
+
+ let (inner_type, is_ref, is_mut) = match &full_type {
+ Type::Reference(ref_type) => match &*ref_type.elem {
+ Type::Path(type_path) => {
+ let is_mut = ref_type.mutability.is_some();
+ (type_path.clone(), true, is_mut)
+ }
+ _ => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "Reference resource type must be a type path",
+ ));
+ }
+ },
+ Type::Path(_) => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "Resource injection parameter must be a reference (`&T` or `&mut T`)",
+ ));
+ }
+ _ => {
+ return Err(syn::Error::new(
+ ty.span(),
+ "Resource injection type must be a type path or reference",
+ ));
+ }
+ };
+
+ resources.push(ResourceInjection {
+ var_name,
+ full_type,
+ inner_type,
+ is_ref,
+ is_mut,
+ });
+ }
+ FnArg::Receiver(_) => {
+ return Err(syn::Error::new(
+ arg.span(),
+ "Resource injection parameter cannot be self",
+ ));
+ }
+ }
+ }
+ Ok(resources)
+}
diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/help.rs
index e9e91cf..e904b16 100644
--- a/mingling_macros/src/help.rs
+++ b/mingling_macros/src/help.rs
@@ -1,44 +1,10 @@
use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
-use syn::{
- FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input,
-};
+use syn::{Ident, ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input};
use crate::get_global_set;
-
-/// Extracts the previous type and parameter name from function arguments
-fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> {
- // The function should have exactly one parameter
- if sig.inputs.len() != 1 {
- return Err(syn::Error::new(
- sig.inputs.span(),
- "Help function must have exactly one parameter (the entry type)",
- ));
- }
-
- // First and only parameter is the entry type
- let arg = &sig.inputs[0];
- match arg {
- FnArg::Typed(PatType { pat, ty, .. }) => {
- // Extract the pattern (parameter name)
- let param_pat = (**pat).clone();
-
- // Extract the type
- match &**ty {
- Type::Path(type_path) => Ok((param_pat, type_path.clone())),
- _ => Err(syn::Error::new(
- ty.span(),
- "Parameter type must be a type path",
- )),
- }
- }
- FnArg::Receiver(_) => Err(syn::Error::new(
- arg.span(),
- "Help function cannot have self parameter",
- )),
- }
-}
+use crate::res_injection::{extract_args_info, generate_immut_resource_bindings};
/// Validates the return type is () or empty
fn validate_return_type(sig: &Signature) -> syn::Result<()> {
@@ -65,8 +31,8 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
.into();
}
- // Extract the entry type and parameter name from function arguments
- let (prev_param, entry_type) = match extract_previous_info(&input_fn.sig) {
+ // Extract the entry type, parameter name, and resource injection params
+ let (prev_param, entry_type, resources) = match extract_args_info(&input_fn.sig) {
Ok(info) => info,
Err(e) => return e.to_compile_error().into(),
};
@@ -78,8 +44,9 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
// Get the function body
let fn_body = &input_fn.block;
+ let fn_body_stmts = &fn_body.stmts;
- // Get function attributes (excluding the help attribute)
+ // Get function attributes excluding the help attribute
let mut fn_attrs = input_fn.attrs.clone();
fn_attrs.retain(|attr| !attr.path().is_ident("help"));
@@ -89,13 +56,52 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
// Get function name
let fn_name = &input_fn.sig.ident;
- // Generate internal name using snake_case for the chain macro
+ // Get original inputs to keep the original function
+
+ let original_inputs = input_fn.sig.inputs.clone();
+
+ // Generate internal name using snake_case
let internal_name = format!(
"__internal_help_{}",
just_fmt::snake_case!(fn_name.to_string())
);
let struct_name = Ident::new(&internal_name, fn_name.span());
+ let program_type = crate::default_program_path();
+ let has_resources = !resources.is_empty();
+ let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect();
+
+ // Generate immutable resource bindings
+ let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), &program_type);
+
+ // Build the render_help body with resource injection
+ // Use modify_res for mutable resources same pattern as renderer.rs
+
+ let wrapped_body = if mut_resources.is_empty() {
+ quote! { #(#fn_body_stmts)* }
+ } else {
+ let mut wrapped = quote! { #(#fn_body_stmts)* };
+ for res in mut_resources.iter().rev() {
+ let var_name = &res.var_name;
+ let inner_type = &res.inner_type;
+ wrapped = quote! {
+ ::mingling::this::<#program_type>().modify_res(|#var_name: &mut #inner_type| {
+ #wrapped
+ })
+ };
+ }
+ wrapped
+ };
+
+ let help_render_body = if has_resources {
+ quote! {
+ #(#immut_resource_stmts)*
+ #wrapped_body
+ }
+ } else {
+ quote! { #(#fn_body_stmts)* }
+ };
+
// Register the help request mapping
let help_entry = build_help_entry(&struct_name, &entry_type);
let entry_str = help_entry.to_string();
@@ -115,23 +121,16 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
type Entry = #entry_type;
fn render_help(#prev_param: Self::Entry, __renderer_inner_result: &mut ::mingling::RenderResult) {
- // Create a local wrapper function that includes `__renderer_inner_result` parameter
- // This allows r_println! to access `__renderer_inner_result`
- #[allow(non_snake_case)]
- fn help_wrapper(#prev_param: #entry_type, __renderer_inner_result: &mut ::mingling::RenderResult) {
- #fn_body
- }
-
- // Call the wrapper function
- help_wrapper(#prev_param, __renderer_inner_result);
+ #help_render_body
}
}
::mingling::macros::register_help!(#entry_type, #struct_name);
- // Keep the original function for internal use (without `__renderer_inner_result` parameter)
+ // Keep the original function for internal use with original params without __renderer_inner_result
+
#(#fn_attrs)*
- #vis fn #fn_name(#prev_param: #entry_type) {
+ #vis fn #fn_name(#original_inputs) {
let mut dummy_r = ::mingling::RenderResult::default();
let __renderer_inner_result = &mut dummy_r;
#fn_body