diff options
Diffstat (limited to 'mingling_macros/src/func')
| -rw-r--r-- | mingling_macros/src/func/dispatcher.rs | 26 | ||||
| -rw-r--r-- | mingling_macros/src/func/gen_program.rs | 107 | ||||
| -rw-r--r-- | mingling_macros/src/func/program_comp_gen.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/func/program_final_gen.rs | 122 |
4 files changed, 111 insertions, 148 deletions
diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs index 4e7c42f..ef02618 100644 --- a/mingling_macros/src/func/dispatcher.rs +++ b/mingling_macros/src/func/dispatcher.rs @@ -99,7 +99,7 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { command_name, } => { let command_name_str = command_name.value(); - let pascal = dotted_to_pascal_case(&command_name_str); + let pascal = just_fmt::pascal_case!(&command_name_str); let command_struct = Ident::new(&format!("CMD{pascal}"), command_name.span()); let pack = Ident::new(&format!("Entry{pascal}"), command_name.span()); (command_name, command_struct, pack, cmd_attrs, Vec::new()) @@ -121,6 +121,12 @@ pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { ::mingling::macros::pack!(#(#entry_attrs)* #pack = Vec<String>); + impl From<#pack> for crate::Entry { + fn from(value: #pack) -> Self { + crate::Entry::new(value.inner) + } + } + #comp_entry #dispatch_tree_entry @@ -178,21 +184,3 @@ fn get_dispatch_tree_entry( ) -> TokenStream2 { quote! {} } - -/// Converts a dotted command name (e.g. "remote.add") to `PascalCase` (e.g. "`RemoteAdd`"). -/// -/// Each segment is split by `.`, the first character of each segment is uppercased, -/// and the segments are joined. This is used by the abbreviated `dispatcher!` syntax -/// (when `Command => Entry` is omitted) to auto-derive struct names. -#[cfg(feature = "extra_macros")] -pub(crate) fn dotted_to_pascal_case(s: &str) -> String { - s.split('.') - .map(|segment| { - let mut chars = segment.chars(); - match chars.next() { - None => String::new(), - Some(c) => c.to_uppercase().to_string() + chars.as_str(), - } - }) - .collect() -} diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs index c5358bc..d50041e 100644 --- a/mingling_macros/src/func/gen_program.rs +++ b/mingling_macros/src/func/gen_program.rs @@ -15,33 +15,100 @@ pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream { #[cfg(not(feature = "comp"))] let comp_gen = quote! {}; + // When pathf is enabled, load the type_using.rs generated by the build script + // and emit its use statements so types from submodules are in scope. + #[cfg(feature = "pathf")] + let pathf_uses: Vec<proc_macro2::TokenStream> = { + let uses = load_pathf_uses(); + if uses.is_empty() { + // The file might not exist yet — emit a clear hint + let hint: proc_macro2::TokenStream = syn::parse_quote! { + compile_error!( + "pathf: `{}` not found or empty.\n\ + Make sure `build.rs` calls `mingling::build::analyze_and_build_type_mapping().unwrap();`\n\ + with features [\"build\", \"pathf\"] enabled." + ); + }; + vec![hint] + } else { + uses + } + }; + #[cfg(not(feature = "pathf"))] + let pathf_uses: Vec<proc_macro2::TokenStream> = Vec::new(); + + #[cfg(feature = "pathf")] + let super_use = quote! {}; + + #[cfg(not(feature = "pathf"))] + let super_use = quote! { + use super::*; + }; + TokenStream::from(quote! { - /// Alias for the current program type `crate::ThisProgram` - pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; - - impl ::mingling::Routable<crate::ThisProgram> for ::mingling::ChainProcess<crate::ThisProgram> - { - fn to_chain(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - match self { - ::mingling::ChainProcess::Ok((any, _)) => { - ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain)) + pub use __this_program_impl::*; + + #[doc(hidden)] + pub mod __this_program_impl { + #super_use + #(#pathf_uses)* + + /// Alias for the current program type `ThisProgram` + pub type Next = ::mingling::ChainProcess<ThisProgram>; + + ::mingling::macros::pack!(Entry = Vec<String>); + + impl ::mingling::Routable<ThisProgram> for ::mingling::ChainProcess<ThisProgram> + { + fn to_chain(self) -> ::mingling::ChainProcess<ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain)) + } + other => other, } - other => other, } - } - fn to_render(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - match self { - ::mingling::ChainProcess::Ok((any, _)) => { - ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer)) + fn to_render(self) -> ::mingling::ChainProcess<ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer)) + } + other => other, } - other => other, } } - } - #comp_gen - ::mingling::macros::program_fallback_gen!(); - ::mingling::macros::program_final_gen!(); + #comp_gen + ::mingling::macros::program_fallback_gen!(); + ::mingling::macros::program_final_gen!(); + } }) } + +/// Loads `type_using.rs` generated by the pathf build script and returns each +/// `use ...;` line as a token stream, ready to be emitted in the generated output. +#[cfg(feature = "pathf")] +fn load_pathf_uses() -> Vec<proc_macro2::TokenStream> { + let out_dir = match std::env::var("OUT_DIR") { + Ok(d) => d, + Err(_) => return Vec::new(), + }; + let crate_name = match std::env::var("CARGO_PKG_NAME") { + Ok(n) => n, + Err(_) => return Vec::new(), + }; + let path = std::path::Path::new(&out_dir) + .join(&crate_name) + .join("type_using.rs"); + let content = match std::fs::read_to_string(&path) { + Ok(c) => c, + Err(_) => return Vec::new(), + }; + content + .lines() + .map(|line| line.trim().to_string()) + .filter(|line| !line.is_empty()) + .filter_map(|line| line.parse::<proc_macro2::TokenStream>().ok()) + .collect() +} diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs index b6b2546..2fbb0e0 100644 --- a/mingling_macros/src/func/program_comp_gen.rs +++ b/mingling_macros/src/func/program_comp_gen.rs @@ -14,7 +14,7 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) + ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest::new((ctx, suggest))) } Err(_) => std::process::exit(1), } @@ -32,7 +32,7 @@ pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { match read_ctx { Ok(ctx) => { let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) + ::mingling::Routable::<crate::ThisProgram>::to_render(CompletionSuggest::new((ctx, suggest))) } Err(_) => std::process::exit(1), } diff --git a/mingling_macros/src/func/program_final_gen.rs b/mingling_macros/src/func/program_final_gen.rs index d3af3b3..0eed1db 100644 --- a/mingling_macros/src/func/program_final_gen.rs +++ b/mingling_macros/src/func/program_final_gen.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use proc_macro::TokenStream; use quote::quote; @@ -37,48 +35,12 @@ fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, pr (struct_ident, variant_ident) } -/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`. -fn load_pathf_map() -> Option<std::collections::HashMap<String, String>> { - if !cfg!(feature = "pathf") { - return None; - } - let out_dir = std::env::var("OUT_DIR").ok()?; - let crate_name = std::env::var("CARGO_PKG_NAME").ok()?; - let path = std::path::Path::new(&out_dir) - .join(&crate_name) - .join("type_using.rs"); - let content = std::fs::read_to_string(&path).ok()?; - Some( - content - .lines() - .filter_map(|line| { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("use ") { - let path = rest.strip_suffix(';').unwrap_or(rest); - if let Some((_mod, type_name)) = path.rsplit_once("::") { - return Some((type_name.to_string(), path.to_string())); - } - } - None - }) - .collect(), - ) -} - -/// Resolves a type name to its full path token stream using the pathf mapping. -pub(crate) fn resolve_type( - name: &str, - map: &std::collections::HashMap<String, String>, -) -> proc_macro2::TokenStream { - if let Some(full_path) = map.get(name) { - syn::parse_str::<proc_macro2::TokenStream>(full_path).unwrap_or_else(|_| { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - }) - } else { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - } +/// Helper: convert a string ident into a token stream for the generated code. +/// Types are expected to be in scope (e.g. via pathf glob re-exports), so bare +/// idents suffice. +fn ident_tokens(name: &str) -> proc_macro2::TokenStream { + let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); + quote! { #ident } } #[allow(clippy::too_many_lines)] @@ -126,45 +88,6 @@ pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) .collect(); - let pathf_map: Option<HashMap<String, String>> = load_pathf_map(); - - #[cfg(feature = "pathf")] - let pathf_hint: proc_macro2::TokenStream = if pathf_map.is_none() { - quote! { - compile_error!( -"Cannot load type mapping computed by `pathf`. -If not yet configured, execute `mingling::build::analyze_and_build_type_mapping()` in your `build.rs`. - -fn main() { - // Require features: [\"builds\", \"pathf\"] - mingling::build::analyze_and_build_type_mapping().unwrap(); - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\__ Write to `build.rs` - -} - "); - } - } else { - quote! {} - }; - - #[cfg(not(feature = "pathf"))] - let pathf_hint: proc_macro2::TokenStream = quote! {}; - - let pathf_map: HashMap<String, String> = if cfg!(feature = "pathf") { - pathf_map.unwrap_or_default() - } else { - HashMap::new() - }; - - let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") { - pathf_map - .values() - .map(|path| format!("use {};", path).parse().unwrap_or_default()) - .collect() - } else { - Vec::new() - }; - #[cfg(feature = "structural_renderer")] let structural_renderer_tokens: Vec<proc_macro2::TokenStream> = structural_renderers .iter() @@ -177,13 +100,9 @@ fn main() { any: ::mingling::AnyOutput<Self::Enum>, setting: &::mingling::StructuralRendererSetting, ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { - #[allow(unused_imports)] - #(#pathf_uses)* match any.member_id() { #(#structural_renderer_tokens)* _ => { - // Non-structural types: render ResultEmpty (which implements - // StructuralData + Serialize) instead of producing nothing. let mut r = ::mingling::RenderResult::default(); ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?; Ok(r) @@ -222,8 +141,8 @@ fn main() { }) .collect(); - let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries, &pathf_map); - let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries, &pathf_map); + let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries); + let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries); quote! { #get_nodes_fn @@ -243,8 +162,6 @@ fn main() { #[cfg(feature = "comp")] let comp = quote! { fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { - #[allow(unused_imports)] - #(#pathf_uses)* match any.member_id() { #(#completion_tokens)* _ => ::mingling::Suggest::FileCompletion, @@ -266,12 +183,10 @@ fn main() { } else { let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + let downcast_ty = ident_tokens(&variant_ident.to_string()); + let resolved_struct = ident_tokens(&struct_ident.to_string()); quote! { Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; <#resolved_struct as ::mingling::Renderer>::render(value) } @@ -290,12 +205,10 @@ fn main() { // Build do_chain function (async and sync versions) let chain_arms_async: Vec<_> = chain_tokens.iter().map(|entry| { let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + let downcast_ty = ident_tokens(&variant_ident.to_string()); + let resolved_struct = ident_tokens(&struct_ident.to_string()); quote! { Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; let fut = async { <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value).await }; ::std::boxed::Box::pin(fut) @@ -307,12 +220,10 @@ fn main() { .iter() .map(|entry| { let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + let downcast_ty = ident_tokens(&variant_ident.to_string()); + let resolved_struct = ident_tokens(&struct_ident.to_string()); quote! { Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value) } @@ -370,8 +281,6 @@ fn main() { }; let expanded = quote! { - #pathf_hint - #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(#repr_type)] #[allow(nonstandard_style)] @@ -392,6 +301,7 @@ fn main() { type ErrorDispatcherNotFound = ErrorDispatcherNotFound; type ErrorRendererNotFound = ErrorRendererNotFound; type ResultEmpty = ResultEmpty; + fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput<Self::Enum> { ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string())) } @@ -404,8 +314,6 @@ fn main() { #render_fn #do_chain_fn fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - #[allow(unused_imports)] - #(#pathf_uses)* match any.member_id() { #(#help_tokens)* _ => ::mingling::RenderResult::default(), |
