aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/func
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src/func')
-rw-r--r--mingling_macros/src/func/dispatcher.rs26
-rw-r--r--mingling_macros/src/func/gen_program.rs107
-rw-r--r--mingling_macros/src/func/program_comp_gen.rs4
-rw-r--r--mingling_macros/src/func/program_final_gen.rs122
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(),