diff options
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/completion.rs | 132 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher_chain.rs | 38 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 67 |
3 files changed, 132 insertions, 105 deletions
diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/completion.rs index cf66f13..23b509f 100644 --- a/mingling_macros/src/completion.rs +++ b/mingling_macros/src/completion.rs @@ -5,91 +5,7 @@ use proc_macro::TokenStream; use quote::quote; -use syn::spanned::Spanned; -use syn::{ - FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input, -}; - -/// Extracts the previous type from function arguments -fn extract_previous_type(sig: &Signature) -> syn::Result<TypePath> { - // The function should have exactly one parameter: ShellContext - if sig.inputs.len() != 1 { - return Err(syn::Error::new( - sig.inputs.span(), - "Completion function must have exactly one parameter (ShellContext)", - )); - } - - let arg = &sig.inputs[0]; - match arg { - FnArg::Typed(PatType { ty, .. }) => { - match &**ty { - Type::Path(type_path) => { - // Check if it's ShellContext - let last_segment = type_path.path.segments.last().unwrap(); - if last_segment.ident != "ShellContext" { - return Err(syn::Error::new( - ty.span(), - "Parameter type must be ShellContext", - )); - } - Ok(type_path.clone()) - } - _ => Err(syn::Error::new( - ty.span(), - "Parameter type must be a type path", - )), - } - } - FnArg::Receiver(_) => Err(syn::Error::new( - arg.span(), - "Completion function cannot have self parameter", - )), - } -} - -/// Extracts the return type from the function signature -fn extract_return_type(sig: &Signature) -> syn::Result<TypePath> { - match &sig.output { - ReturnType::Type(_, ty) => match &**ty { - Type::Path(type_path) => { - // Check if it's Suggest - let last_segment = type_path.path.segments.last().unwrap(); - if last_segment.ident != "Suggest" { - return Err(syn::Error::new(ty.span(), "Return type must be Suggest")); - } - Ok(type_path.clone()) - } - _ => Err(syn::Error::new( - ty.span(), - "Return type must be a type path", - )), - }, - ReturnType::Default => Err(syn::Error::new( - sig.span(), - "Completion function must have a return type", - )), - } -} - -/// Extracts the parameter name from function arguments -fn extract_param_name(sig: &Signature) -> syn::Result<Pat> { - if sig.inputs.len() != 1 { - return Err(syn::Error::new( - sig.inputs.span(), - "Completion function must have exactly one parameter", - )); - } - - let arg = &sig.inputs[0]; - match arg { - FnArg::Typed(PatType { pat, .. }) => Ok((**pat).clone()), - FnArg::Receiver(_) => Err(syn::Error::new( - arg.span(), - "Completion function cannot have self parameter", - )), - } -} +use syn::{Ident, ItemFn, parse_macro_input}; #[cfg(feature = "comp")] pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -110,25 +26,28 @@ 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(); } - // Extract the parameter name - let param_name = match extract_param_name(&input_fn.sig) { - Ok(name) => name, - Err(e) => return e.to_compile_error().into(), - }; + // Get the function signature parts + let sig = &input_fn.sig; + let inputs = &sig.inputs; + let output = &sig.output; - // Extract and validate the parameter type (must be ShellContext) - if let Err(e) = extract_previous_type(&input_fn.sig) { - return e.to_compile_error().into(); - } + // Check that the function has exactly one parameter + if inputs.len() != 1 { + use syn::spanned::Spanned; - // Extract and validate the return type (must be Suggest) - if let Err(e) = extract_return_type(&input_fn.sig) { - return e.to_compile_error().into(); + return syn::Error::new( + inputs.span(), + "Completion function must have exactly one parameter", + ) + .to_compile_error() + .into(); } // Get the function body @@ -142,7 +61,7 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let vis = &input_fn.vis; // Get function name - let fn_name = &input_fn.sig.ident; + let fn_name = &sig.ident; // Generate struct name from function name using pascal_case let pascal_case_name = just_fmt::pascal_case!(fn_name.to_string()); @@ -156,20 +75,27 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { impl ::mingling::Completion for #struct_name { type Previous = #previous_type_ident; - fn comp(#param_name: ::mingling::ShellContext) -> ::mingling::Suggest { - // This is just to prevent warnings about imported ShellContext and Suggest - let _ = ShellContext::default(); - let _ = Suggest::file_comp(); + fn comp(#inputs) #output { #fn_body } } // Keep the original function for internal use #(#fn_attrs)* - #vis fn #fn_name(#param_name: ::mingling::ShellContext) -> ::mingling::Suggest { + #vis fn #fn_name(#inputs) #output { #fn_body } }; + let completion_entry = quote! { + Self::#previous_type_ident => <#struct_name as ::mingling::Completion>::comp(ctx), + }; + + let mut completions = crate::COMPLETIONS.lock().unwrap(); + let completion_str = completion_entry.to_string(); + if !completions.contains(&completion_str) { + completions.push(completion_str); + } + expanded.into() } diff --git a/mingling_macros/src/dispatcher_chain.rs b/mingling_macros/src/dispatcher_chain.rs index f531424..4038600 100644 --- a/mingling_macros/src/dispatcher_chain.rs +++ b/mingling_macros/src/dispatcher_chain.rs @@ -1,7 +1,7 @@ //! Dispatcher Chain and Dispatcher Render Macros //! //! This module provides macros for creating dispatcher chain and dispatcher render structs -//! with automatic implementations of the `DispatcherChain` trait. +//! with automatic implementations of the `Dispatcher` trait. use proc_macro::TokenStream; use quote::quote; @@ -60,6 +60,13 @@ impl Parse for DispatcherChainInput { } } +// NOTICE: This implementation contains significant code duplication between the explicit +// and default cases in both `dispatcher_chain` and `dispatcher_render` functions. +// The logic for handling default vs explicit group names and generating the appropriate +// code should be extracted into common helper functions to reduce redundancy. +// Additionally, the token stream generation patterns are nearly identical between +// the two main functions and could benefit from refactoring. + pub fn dispatcher_chain(input: TokenStream) -> TokenStream { // Parse the input let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput); @@ -87,6 +94,8 @@ pub fn dispatcher_chain(input: TokenStream) -> TokenStream { let command_name_str = command_name.value(); + let comp_entry = get_comp_entry(&pack); + let expanded = if use_default { // For default case, use ThisProgram quote! { @@ -95,6 +104,8 @@ pub fn dispatcher_chain(input: TokenStream) -> TokenStream { ::mingling::macros::pack!(ThisProgram, #pack = Vec<String>); + #comp_entry + impl ::mingling::Dispatcher<ThisProgram> for #command_struct { fn node(&self) -> ::mingling::Node { ::mingling::macros::node!(#command_name_str) @@ -115,6 +126,8 @@ pub fn dispatcher_chain(input: TokenStream) -> TokenStream { ::mingling::macros::pack!(#group_name, #pack = Vec<String>); + #comp_entry + impl ::mingling::Dispatcher<#group_name> for #command_struct { fn node(&self) -> ::mingling::Node { ::mingling::macros::node!(#command_name_str) @@ -159,6 +172,8 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream { let command_name_str = command_name.value(); + let comp_entry = get_comp_entry(&pack); + let expanded = if use_default { // For default case, use ThisProgram quote! { @@ -167,6 +182,8 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream { ::mingling::macros::pack!(ThisProgram, #pack = Vec<String>); + #comp_entry + impl ::mingling::Dispatcher for #command_struct { fn node(&self) -> ::mingling::Node { ::mingling::macros::node!(#command_name_str) @@ -187,6 +204,8 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream { ::mingling::macros::pack!(#group_name, #pack = Vec<String>); + #comp_entry + impl ::mingling::Dispatcher for #command_struct { fn node(&self) -> ::mingling::Node { ::mingling::macros::node!(#command_name_str) @@ -203,3 +222,20 @@ pub fn dispatcher_render(input: TokenStream) -> TokenStream { expanded.into() } + +#[cfg(feature = "comp")] +fn get_comp_entry(entry_name: &Ident) -> proc_macro2::TokenStream { + let comp_entry = quote! { + impl ::mingling::CompletionEntry for #entry_name { + fn get_input(self) -> Vec<String> { + self.inner.clone() + } + } + }; + comp_entry +} + +#[cfg(not(feature = "comp"))] +fn get_comp_entry(_entry_name: &Ident) -> proc_macro2::TokenStream { + quote! {} +} diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index d324ddc..7291f0e 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -24,10 +24,13 @@ mod suggest; use once_cell::sync::Lazy; use std::sync::Mutex; -// Global variable declarations for storing chain and renderer mappings +// Global variables #[cfg(feature = "general_renderer")] pub(crate) static GENERAL_RENDERERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); +#[cfg(feature = "comp")] +pub(crate) static COMPLETIONS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); + pub(crate) static PACKED_TYPES: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); pub(crate) static CHAINS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); pub(crate) static RENDERERS: Lazy<Mutex<Vec<String>>> = Lazy::new(|| Mutex::new(Vec::new())); @@ -107,6 +110,13 @@ pub fn gen_program(input: TokenStream) -> TokenStream { let mut packed_types = PACKED_TYPES.lock().unwrap().clone(); packed_types.push("DispatcherNotFound".to_string()); packed_types.push("RendererNotFound".to_string()); + + #[cfg(feature = "comp")] + { + packed_types.push("CompletionContext".to_string()); + packed_types.push("CompletionSuggest".to_string()); + } + packed_types.sort(); packed_types.dedup(); let renderers = RENDERERS.lock().unwrap().clone(); @@ -117,6 +127,9 @@ pub fn gen_program(input: TokenStream) -> TokenStream { #[cfg(feature = "general_renderer")] let general_renderers = GENERAL_RENDERERS.lock().unwrap().clone(); + #[cfg(feature = "comp")] + let completions = COMPLETIONS.lock().unwrap().clone(); + let packed_types: Vec<proc_macro2::TokenStream> = packed_types .iter() .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) @@ -164,6 +177,55 @@ pub fn gen_program(input: TokenStream) -> TokenStream { #[cfg(not(feature = "general_renderer"))] let general_render = quote! {}; + #[cfg(feature = "comp")] + let comp_dispatcher = quote! { + ::mingling::macros::dispatcher!(#name, "__comp", CompletionDispatcher => CompletionContext); + ::mingling::macros::pack!( + #name, + CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) + ); + + #[::mingling::macros::chain] + async fn __completion(prev: CompletionContext) -> NextProcess { + let read_ctx = ::mingling::ShellContext::try_from(prev.inner); + match read_ctx { + Ok(ctx) => { + let suggest = ::mingling::CompletionHelper::exec_completion::<#name>(&ctx); + CompletionSuggest::new((ctx, suggest)).to_render() + } + Err(_) => std::process::exit(1), + } + } + + #[::mingling::macros::renderer] + fn __render_completion(prev: CompletionSuggest) { + let (ctx, suggest) = prev.inner; + ::mingling::CompletionHelper::render_suggest::<#name>(ctx, suggest); + } + }; + + #[cfg(not(feature = "comp"))] + let comp_dispatcher = quote! {}; + + #[cfg(feature = "comp")] + let completion_tokens: Vec<proc_macro2::TokenStream> = completions + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + #[cfg(feature = "comp")] + let comp = quote! { + fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { + match any.member_id { + #(#completion_tokens)* + _ => ::mingling::Suggest::FileCompletion, + } + } + }; + + #[cfg(not(feature = "comp"))] + let comp = quote! {}; + let expanded = quote! { ::mingling::macros::pack!(#name, RendererNotFound = String); ::mingling::macros::pack!(#name, DispatcherNotFound = Vec<String>); @@ -176,6 +238,8 @@ pub fn gen_program(input: TokenStream) -> TokenStream { #(#packed_types),* } + #comp_dispatcher + impl ::std::fmt::Display for #name { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { match self { @@ -212,6 +276,7 @@ pub fn gen_program(input: TokenStream) -> TokenStream { } } #general_render + #comp } impl #name { |
