aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src/lib.rs')
-rw-r--r--mingling_macros/src/lib.rs839
1 files changed, 120 insertions, 719 deletions
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index d0f603a..a85648f 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -13,7 +13,7 @@
//! ┌──────────────────────────────────────────────────────────────────┐
//! │ Phase 1: Declaration │
//! │ │
-//! │ dispatcher! pack! node! #[derive(Groupped)] │
+//! │ dispatcher! pack! node! #[derive(Grouped)] │
//! │ │ │ │ │ │
//! │ V V V V │
//! │ Declares Wraps a Builds Makes a type │
@@ -55,7 +55,7 @@
//! | `pack_err!` | Creates an error struct with automatic `name` field |
//! | `pack_err_structural!` | Like `pack_err!` but also derives `StructuralData` for structured output |
//! | `entry!` | Creates a packed entry from string literals |
-//! | [`#[derive(Groupped)]`](derive@Groupped) | Makes a type recognizable by the framework's type registry |
+//! | [`#[derive(Grouped)]`](derive@Grouped) | Makes a type recognizable by the framework's type registry |
//! | `#[derive(StructuralData)]` | Marks a type as eligible for structured output (JSON/YAML/etc.) |
//! | [`#[derive(EnumTag)]`](derive@EnumTag) | Adds enum variant metadata (name, description) |
//!
@@ -141,40 +141,48 @@
//! }
//! ```
-use proc_macro::TokenStream;
+#[cfg(feature = "extra_macros")]
use quote::quote;
+
+#[cfg(feature = "extra_macros")]
+use syn::parse_macro_input;
+
+use proc_macro::TokenStream;
use std::collections::BTreeSet;
use std::sync::Mutex;
use std::sync::OnceLock;
-use syn::parse_macro_input;
-mod chain;
+mod attr;
+mod derive;
+mod func;
+mod systems;
+
+mod extensions;
+mod utils;
+
+// Bring all sub-modules into scope at the old paths so that existing
+// references (e.g. `chain::chain_attr`, `renderer::renderer_attr`)
+// continue to work without any `use`-path changes.
#[cfg(feature = "comp")]
-mod completion;
-#[cfg(feature = "dispatch_tree")]
-mod dispatch_tree_gen;
-mod dispatcher;
+use attr::completion;
#[cfg(feature = "clap")]
-mod dispatcher_clap;
+use attr::dispatcher_clap;
#[cfg(feature = "extra_macros")]
-mod entry;
-mod enum_tag;
+use attr::program_setup;
+use attr::{chain, help, renderer};
+use derive::{enum_tag, grouped};
#[cfg(feature = "extra_macros")]
-mod group_impl;
-mod groupped;
-mod help;
-mod node;
-mod pack;
+use func::entry;
#[cfg(feature = "extra_macros")]
-mod pack_err;
+pub(crate) use func::group as group_impl;
#[cfg(feature = "extra_macros")]
-mod program_setup;
-mod renderer;
-mod res_injection;
-#[cfg(feature = "structural_renderer")]
-mod structural_data;
+use func::pack_err;
#[cfg(feature = "comp")]
-mod suggest;
+use func::suggest;
+use func::{dispatcher, node, pack};
+use systems::res_injection;
+#[cfg(feature = "structural_renderer")]
+pub(crate) use systems::structural_data;
pub(crate) fn default_program_path() -> proc_macro2::TokenStream {
quote::quote! { crate::ThisProgram }
@@ -262,44 +270,6 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool {
entry.contains(&format!(":: {variant_name} =>"))
}
-/// Registers an outside-type as a member of a program group without modifying its definition.
-///
-/// This macro allows you to use outside-types from external crates (like `std::io::Error`)
-/// within the Mingling framework by generating a `Groupped` implementation and registering
-/// the type's simple name as an enum variant.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// group!(std::io::Error);
-/// group!(ParseIntError);
-/// ```
-///
-/// The type is registered under the default program (`crate::ThisProgram`).
-///
-/// # How it works
-///
-/// The macro generates a module containing:
-/// - A `use` import for the program path and the outside-type
-/// - An `impl Groupped<Program>` for the outside-type
-/// - A `register_type!` call with the type's simple name
-///
-/// The type's simple name (e.g. `Error`) is used as the enum variant in the generated
-/// program enum, just like `#[derive(Groupped)]` or `pack!`.
-///
-/// # Example
-///
-/// ```rust,ignore
-/// use mingling::macros::group;
-///
-/// // Register std::io::Error as a group member
-/// group!(std::io::Error);
-/// ```
-///
-/// After expansion, the type can be used in chains and renderers like any
-/// `#[derive(Groupped)]` type.
-///
-/// This macro is only available with the `extra_macros` feature.
#[cfg(feature = "extra_macros")]
#[proc_macro]
pub fn group(input: TokenStream) -> TokenStream {
@@ -402,7 +372,7 @@ pub fn node(input: TokenStream) -> TokenStream {
/// - `AsRef<String>`, `AsMut<String>`
/// - `Default` if `String: Default`
/// - `Into<AnyOutput<ThisProgram>>`, `Into<ChainProcess<ThisProgram>>`
-/// - Implements `Groupped<ThisProgram>` with `member_id()` returning the enum variant
+/// - Implements `Grouped<ThisProgram>` with `member_id()` returning the enum variant
///
/// The struct is also registered via `register_type!` so that `gen_program!`
/// can include it in the program enum.
@@ -438,7 +408,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// Creates an error struct with a `name: String` field and optional `info: Type` field.
///
-/// This macro provides a concise way to define error types that implement `Groupped`
+/// This macro provides a concise way to define error types that implement `Grouped`
/// and are registered for inclusion in the program enum.
///
/// The `name` field is automatically set to the snake_case version of the struct name
@@ -461,7 +431,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// For `pack_err!(ErrorNotFound)`:
///
/// ```rust,ignore
-/// #[derive(::mingling::Groupped)]
+/// #[derive(::mingling::Grouped)]
/// pub struct ErrorNotFound {
/// name: String,
/// }
@@ -478,7 +448,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// For `pack_err!(ErrorNotDir = PathBuf)`:
///
/// ```rust,ignore
-/// #[derive(::mingling::Groupped)]
+/// #[derive(::mingling::Grouped)]
/// pub struct ErrorNotDir {
/// name: String,
/// info: PathBuf,
@@ -521,20 +491,25 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
pack_err::pack_err_structural(input)
}
-/// Early-returns an error from a `Result`, converting the `Ok` branch to a
-/// `ChainProcess`.
+/// Early-returns the error from a `Result`, converting the `Ok` branch to the
+/// next chain process value.
///
/// This macro is equivalent to:
/// ```rust,ignore
/// match expr {
/// Ok(r) => r,
-/// Err(e) => return ::mingling::Groupped::to_chain(e),
+/// Err(e) => return ::mingling::Routable::to_chain(e),
/// }
/// ```
///
-/// It is useful inside chain functions where you have a `Result<SomeType, SomeType>`
-/// where both types implement `Groupped` and want to propagate the error case
-/// as an early return via `Groupped::to_chain()`.
+/// It is useful inside chain functions where you have a `Result<SuccessType, ErrorType>`
+/// where both types implement `Routable` and you want to propagate the error case
+/// as an early return via `Routable::to_chain()`.
+///
+/// The key difference from a simple `?` operator is that `route!` converts **both**
+/// the success and error types into the chain process — the `Ok` value is unwrapped
+/// directly, while the `Err` value is converted via `Routable::to_chain()` and
+/// returned early.
///
/// # Example
///
@@ -542,7 +517,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
/// use mingling::macros::{chain, route};
///
/// #[chain]
-/// fn process(prev: SomeEntry) -> ChainProcess<ThisProgram> {
+/// fn process(prev: SomeEntry) -> Next {
/// let value = route!(current_dir().map_err(|e| ErrorEntry::new(e.to_string_lossy().to_string())));
/// // value is the PathBuf from current_dir()
/// value.to_chain()
@@ -555,7 +530,7 @@ pub fn route(input: TokenStream) -> TokenStream {
let expanded = quote! {
match #expr {
Ok(r) => r,
- Err(e) => return ::mingling::Groupped::to_chain(e),
+ Err(e) => return ::mingling::Routable::to_chain(e),
}
};
TokenStream::from(expanded)
@@ -613,7 +588,7 @@ pub fn route(input: TokenStream) -> TokenStream {
#[proc_macro]
pub fn empty_result(_input: TokenStream) -> TokenStream {
let expanded = quote! {
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
+ <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
};
TokenStream::from(expanded)
}
@@ -867,10 +842,16 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
///
/// - The function must have at least **one** parameter (the previous type in the chain).
/// - The first parameter must be taken **by move**.
-/// - The function must return `Next` (the type alias generated by `gen_program!`, which equals `ChainProcess<ProgramName>`).
+/// - The function may return `Next`, `ChainProcess<ProgramName>`, `()`, or omit the return type.
+/// - The original function signature is preserved unchanged.
/// - With the `async` feature, async functions are supported; without it, async functions are rejected.
#[proc_macro_attribute]
pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
+ // Extension point: if attr contains extension identifiers like `routeify`,
+ // re-dispatch as #[ext1] #[ext2] #[chain] fn ...
+ if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "chain") {
+ return redispatch;
+ }
chain::chain_attr(attr, item)
}
@@ -938,6 +919,9 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro_attribute]
pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream {
+ if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "renderer") {
+ return redispatch;
+ }
renderer::renderer_attr(attr, item)
}
@@ -992,6 +976,9 @@ pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream {
#[cfg(feature = "comp")]
#[proc_macro_attribute]
pub fn completion(attr: TokenStream, item: TokenStream) -> TokenStream {
+ if let Some(redispatch) = extensions::try_redispatch_completion(attr.clone(), &item) {
+ return redispatch;
+ }
completion::completion_attr(attr, item)
}
@@ -1234,7 +1221,7 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream {
///
/// - The function must have exactly one parameter (the entry type to provide help for).
/// - The parameter type must be a single-segment type path (e.g., `MyEntry`, not `other::MyEntry`).
-/// - The function must return `RenderResult`.
+/// - The function may return `RenderResult`, `()`, or any type that implements `Into<RenderResult>`.
/// - The function cannot be async.
///
/// # See also
@@ -1245,14 +1232,38 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream {
///
/// [`BasicProgramSetup`]: https://docs.rs/mingling/latest/mingling/setup/struct.BasicProgramSetup.html
#[proc_macro_attribute]
-pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
+pub fn help(attr: TokenStream, item: TokenStream) -> TokenStream {
+ if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "help") {
+ return redispatch;
+ }
help::help_attr(item)
}
-/// Derive macro for automatically implementing the `Groupped` trait on a struct.
+/// Extension attribute macro that transforms `expr?` into `route!(expr)`.
+///
+/// Designed for use with `#[chain(routeify, ...)]` to enable concise error
+/// routing in chain functions using the `?` operator syntax.
///
-/// The `#[derive(Groupped)]` macro:
-/// 1. Implements `Groupped<crate::ThisProgram>`.
+/// # Example
+///
+/// ```rust,ignore
+/// #[chain(routeify)]
+/// fn handle_calc(args: EntryCalculate) -> Next {
+/// let a = args.pick(&arg![f32]).to_result()?;
+/// let op = args.pick(&arg![Operator]).to_result()?;
+/// StateCalculate { number_a: a, operator: op, ... }.to_chain()
+/// }
+/// ```
+#[cfg(feature = "extra_macros")]
+#[proc_macro_attribute]
+pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream {
+ extensions::routeify::routeify_impl(attr, item)
+}
+
+/// Derive macro for automatically implementing the `Grouped` trait on a struct.
+///
+/// The `#[derive(Grouped)]` macro:
+/// 1. Implements `Grouped<crate::ThisProgram>`.
/// 2. Registers the type via `register_type!` so it's included in the program enum.
/// 3. Generates `Into<AnyOutput<Group>>` and `Into<ChainProcess<Group>>` conversions.
/// 4. Adds `to_chain()` and `to_render()` methods to the struct.
@@ -1260,7 +1271,7 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
/// # Syntax
///
/// ```rust,ignore
-/// #[derive(Groupped)]
+/// #[derive(Grouped)]
/// struct MyStruct {
/// // ...
/// }
@@ -1269,9 +1280,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::macros::Groupped;
+/// use mingling::macros::Grouped;
///
-/// #[derive(Groupped)]
+/// #[derive(Grouped)]
/// struct Greeting {
/// name: String,
/// }
@@ -1279,9 +1290,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
///
/// This is equivalent to using `pack!` but works with custom structs that
/// have named fields. For simple wrappers, prefer `pack!`.
-#[proc_macro_derive(Groupped, attributes(group))]
-pub fn derive_groupped(input: TokenStream) -> TokenStream {
- groupped::derive_groupped(input)
+#[proc_macro_derive(Grouped, attributes(group))]
+pub fn derive_grouped(input: TokenStream) -> TokenStream {
+ grouped::derive_grouped(input)
}
/// Derive macro for automatically implementing the `EnumTag` trait on an enum
@@ -1350,18 +1361,18 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream {
structural_data::derive_structural_data(input)
}
-/// Derive macro for implementing both `Groupped` and `serde::Serialize` on a struct.
+/// Derive macro for implementing both `Grouped` and `serde::Serialize` on a struct.
///
/// **This macro is only available with the `structural_renderer` feature.**
///
-/// This is identical to `#[derive(Groupped)]` but also adds `#[derive(serde::Serialize)]`
+/// This is identical to `#[derive(Grouped)]` but also adds `#[derive(serde::Serialize)]`
/// to the struct, which is required for the structural renderer to serialize output
/// to formats like JSON, YAML, TOML, or RON.
///
/// # Syntax
///
/// ```rust,ignore
-/// #[derive(GrouppedSerialize)]
+/// #[derive(GroupedSerialize)]
/// struct Info {
/// name: String,
/// age: i32,
@@ -1371,19 +1382,19 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::GrouppedSerialize;
+/// use mingling::GroupedSerialize;
/// use serde::Serialize;
///
-/// #[derive(GrouppedSerialize)]
+/// #[derive(GroupedSerialize)]
/// struct Info {
/// name: String,
/// age: i32,
/// }
/// ```
#[cfg(feature = "structural_renderer")]
-#[proc_macro_derive(GrouppedSerialize, attributes(group))]
-pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
- groupped::derive_groupped_serialize(input)
+#[proc_macro_derive(GroupedSerialize, attributes(group))]
+pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream {
+ grouped::derive_grouped_serialize(input)
}
/// Generates the program enum and all collected types, chains, and renderers.
@@ -1435,119 +1446,20 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
/// gen_program!();
/// ```
#[proc_macro]
-pub fn gen_program(_input: TokenStream) -> TokenStream {
- #[cfg(feature = "comp")]
- let comp_gen = quote! {
- ::mingling::macros::program_comp_gen!();
- };
-
- #[cfg(not(feature = "comp"))]
- let comp_gen = quote! {};
-
- TokenStream::from(quote! {
- pub type Next = ::mingling::ChainProcess<crate::ThisProgram>;
-
- #comp_gen
- ::mingling::macros::program_fallback_gen!();
- ::mingling::macros::program_final_gen!();
- })
+pub fn gen_program(input: TokenStream) -> TokenStream {
+ func::gen_program::gen_program_impl(input)
}
-/// Internal macro used by `gen_program!` to generate completion infrastructure.
-///
-/// **This macro is only available with the `comp` feature.**
-///
-/// This is an internal macro and should not be called directly by user code.
-/// It generates a completion dispatcher, the `CompletionContext` type, and
-/// the execution/render logic for shell completion.
-///
-/// The generated module `__completion_gen` contains:
-/// - A `__comp` dispatcher that routes completion requests
-/// - A `__exec_completion` chain that processes `CompletionContext` into `CompletionSuggest`
-/// - A `__render_completion` renderer that outputs completion suggestions
-#[proc_macro]
#[cfg(feature = "comp")]
-pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
- #[cfg(feature = "async")]
- let fn_exec_comp = quote! {
- #[doc(hidden)]
- #[::mingling::macros::chain]
- pub async fn __exec_completion(prev: CompletionContext) -> Next {
- use ::mingling::Groupped;
-
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
- match read_ctx {
- Ok(ctx) => {
- let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- crate::CompletionSuggest::new((ctx, suggest)).to_render()
- }
- Err(_) => std::process::exit(1),
- }
- }
- };
-
- #[cfg(not(feature = "async"))]
- let fn_exec_comp = quote! {
- #[doc(hidden)]
- #[::mingling::macros::chain]
- pub fn __exec_completion(prev: CompletionContext) -> Next {
- use ::mingling::Groupped;
-
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
- match read_ctx {
- Ok(ctx) => {
- let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- crate::CompletionSuggest::new((ctx, suggest)).to_render()
- }
- Err(_) => std::process::exit(1),
- }
- }
- };
-
- #[cfg(feature = "dispatch_tree")]
- let internal_dispatcher_comp = quote! {
- use __internal_completion_mod::__internal_dispatcher_comp;
- };
-
- #[cfg(not(feature = "dispatch_tree"))]
- let internal_dispatcher_comp = quote! {};
-
- let comp_dispatcher = quote! {
- #[doc(hidden)]
- mod __internal_completion_mod {
- use ::mingling::Groupped;
- ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext);
- ::mingling::macros::pack!(
- CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest)
- );
- }
- #internal_dispatcher_comp
- use __internal_completion_mod::CompletionContext;
- use __internal_completion_mod::CompletionSuggest;
- pub use __internal_completion_mod::CMDCompletion;
-
- #fn_exec_comp
-
- ::mingling::macros::register_type!(CompletionContext);
-
- #[allow(unused)]
- #[doc(hidden)]
- #[::mingling::macros::renderer]
- pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult {
- let result = ::mingling::RenderResult::default();
- let (ctx, suggest) = prev.inner;
- ::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(ctx, suggest);
- result
- }
- };
-
- TokenStream::from(comp_dispatcher)
+#[proc_macro]
+pub fn program_comp_gen(input: TokenStream) -> TokenStream {
+ func::gen_program::program_comp_gen_impl(input)
}
/// Registers a type into the global packed types registry for inclusion in
/// the program enum generated by `gen_program!`.
///
-/// This macro is called internally by `pack!` and `#[derive(Groupped)]`(`macro.derive_groupped.html`)
+/// This macro is called internally by `pack!` and `#[derive(Grouped)]`(`macro.derive_grouped.html`)
/// and is generally not needed in user code. However, it can be used for manual
/// registration if you are implementing custom type registration outside of
/// the standard macros.
@@ -1566,115 +1478,29 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
/// Panics if the global `PACKED_TYPES` mutex is poisoned.
#[proc_macro]
pub fn register_type(input: TokenStream) -> TokenStream {
- let type_ident = parse_macro_input!(input as syn::Ident);
- let entry_str = type_ident.to_string();
-
- get_global_set(&PACKED_TYPES)
- .lock()
- .unwrap()
- .insert(entry_str);
-
- TokenStream::new()
+ func::gen_program::register_type_impl(input)
}
-/// Registers a chain mapping from a previous type to a chain struct.
-///
-/// This macro is called internally by `#[chain]`(macro.chain.html) and is
-/// generally not needed in user code. It inserts entries into the global
-/// `CHAINS` and `CHAINS_EXIST` registries.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// register_chain!(PreviousType, ChainStruct);
-/// ```
-///
-/// The `PreviousType` is the input type of the chain step, and `ChainStruct`
-/// is the generated struct that implements the `Chain` trait.
#[proc_macro]
pub fn register_chain(input: TokenStream) -> TokenStream {
- chain::register_chain(input)
+ func::gen_program::register_chain_impl(input)
}
-/// Registers a renderer mapping from a type to a renderer struct.
-///
-/// This macro is called internally by `#[renderer]`(macro.renderer.html) and is
-/// generally not needed in user code. It inserts entries into the global
-/// `RENDERERS`, `RENDERERS_EXIST` and (with `structural_renderer` feature)
-/// `STRUCTURAL_RENDERERS` registries.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// register_renderer!(PreviousType, RendererStruct);
-/// ```
-///
-/// The `PreviousType` is the input type of the renderer, and `RendererStruct`
-/// is the generated struct that implements the `Renderer` trait.
#[proc_macro]
pub fn register_renderer(input: TokenStream) -> TokenStream {
- renderer::register_renderer(input)
+ func::gen_program::register_renderer_impl(input)
}
-/// Internal macro used by `gen_program!` to generate fallback types.
-///
-/// This macro generates the fallback wrapper types that are essential
-/// for error handling in the Mingling pipeline:
-///
-/// - **`ErrorRendererNotFound`** — Wraps a `String` (the name of the missing renderer).
-/// Used when no matching renderer is found for a given output type.
-/// - **`ErrorDispatcherNotFound`** — Wraps `Vec<String>` (the unrecognized command args).
-/// Used when no matching dispatcher is found for user input.
-/// - **`ResultEmpty`** — Wraps `()` (the unit type).
-/// Used when the chain returns an empty result.
-///
-/// Users can (and should) write `#[renderer]` functions for these types
-/// to provide meaningful error messages.
-///
-/// This macro is called automatically by `gen_program!` and should not
-/// be called directly by user code.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// // Called internally by gen_program!:
-/// program_fallback_gen!();
-/// ```
-///
-/// # Generated code equivalent
-///
-/// ```rust,ignore
-/// pack!(ErrorRendererNotFound = String);
-/// pack!(ErrorDispatcherNotFound = Vec<String>);
-/// pack!(ResultEmpty = ());
-/// ```
#[proc_macro]
-pub fn program_fallback_gen(_input: TokenStream) -> TokenStream {
- #[cfg(feature = "structural_renderer")]
- let pack_empty = quote! {
- #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Groupped, Default)]
- pub struct ResultEmpty;
- };
-
- #[cfg(not(feature = "structural_renderer"))]
- let pack_empty = quote! {
- #[derive(::mingling::Groupped, Default)]
- pub struct ResultEmpty;
- };
-
- let expanded = quote! {
- ::mingling::macros::pack!(ErrorRendererNotFound = String);
- ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>);
- #pack_empty
- };
- TokenStream::from(expanded)
+pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
+ func::gen_program::program_fallback_gen_impl(input)
}
/// Internal macro used by `gen_program!` to generate the final program enum
/// and its `ProgramCollect` implementation.
///
/// This is the core code generation macro that:
-/// 1. Collects all registered types (from `pack!`, `#[derive(Groupped)]`, etc.) and
+/// 1. Collects all registered types (from `pack!`, `#[derive(Grouped)]`, etc.) and
/// creates an enum with each type as a variant.
/// 2. Generates the `Display` implementation for the enum.
/// 3. Generates the `ProgramCollect` implementation that dispatches to all
@@ -1719,434 +1545,9 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream {
/// pub fn new() -> Program<MyProgram> { Program::new() }
/// }
/// ```
-///
-/// # Panics
-///
-// Feature detection: baked into the proc-macro binary at compile time
-#[cfg(feature = "async")]
-const ASYNC_ENABLED: bool = true;
-#[cfg(not(feature = "async"))]
-const ASYNC_ENABLED: bool = false;
-
-/// Parses an entry of the format `StructName => EnumVariant,` into a pair of idents.
-fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, proc_macro2::Ident) {
- let s = entry.to_string();
- let arrow_idx = s
- .find("=>")
- .unwrap_or_else(|| panic!("Entry missing '=>': {s}"));
- let struct_str = s[..arrow_idx].trim();
- let variant_str = s[arrow_idx + 2..].trim().trim_end_matches(',').trim();
- let struct_ident = proc_macro2::Ident::new(struct_str, proc_macro2::Span::call_site());
- let variant_ident = proc_macro2::Ident::new(variant_str, proc_macro2::Span::call_site());
- (struct_ident, variant_ident)
-}
-
-/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`.
-/// Always compiled; returns empty map when pathf feature is not enabled.
-fn load_pathf_map() -> std::collections::HashMap<String, String> {
- if !cfg!(feature = "pathf") {
- return std::collections::HashMap::new();
- }
- let out_dir = std::env::var("OUT_DIR").ok();
- let crate_name = std::env::var("CARGO_PKG_NAME").ok();
- match (out_dir, crate_name) {
- (Some(dir), Some(name)) => {
- let path = std::path::Path::new(&dir).join(&name).join("type_using.rs");
- match std::fs::read_to_string(&path) {
- Ok(content) => 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(),
- Err(_) => std::collections::HashMap::new(),
- }
- }
- _ => std::collections::HashMap::new(),
- }
-}
-
-/// 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 }
- }
-}
-
-/// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.)
-/// are poisoned.
#[proc_macro]
-#[allow(clippy::too_many_lines)]
-pub fn program_final_gen(_input: TokenStream) -> TokenStream {
- let name = syn::Ident::new("ThisProgram", proc_macro2::Span::call_site());
-
- let packed_types = get_global_set(&PACKED_TYPES).lock().unwrap().clone();
-
- let renderers = get_global_set(&RENDERERS).lock().unwrap().clone();
- let chains = get_global_set(&CHAINS).lock().unwrap().clone();
- let renderer_exist = get_global_set(&RENDERERS_EXIST).lock().unwrap().clone();
- let chain_exist = get_global_set(&CHAINS_EXIST).lock().unwrap().clone();
-
- #[cfg(feature = "structural_renderer")]
- let structural_renderers = get_global_set(&STRUCTURAL_RENDERERS)
- .lock()
- .unwrap()
- .clone();
-
- #[cfg(feature = "comp")]
- let completions = get_global_set(&COMPLETIONS).lock().unwrap().clone();
-
- let packed_types: Vec<proc_macro2::TokenStream> = packed_types
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let chain_tokens: Vec<proc_macro2::TokenStream> = chains
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let renderer_exist_tokens: Vec<proc_macro2::TokenStream> = renderer_exist
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let chain_exist_tokens: Vec<proc_macro2::TokenStream> = chain_exist
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let pathf_map: std::collections::HashMap<String, String> = if cfg!(feature = "pathf") {
- load_pathf_map()
- } else {
- std::collections::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()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- #[cfg(feature = "structural_renderer")]
- let structural_render = quote! {
- fn structural_render(
- 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)
- }
- }
- }
- };
-
- #[cfg(not(feature = "structural_renderer"))]
- let structural_render = quote! {};
-
- #[cfg(feature = "dispatch_tree")]
- let compile_time_dispatchers: Vec<String> = get_global_set(&COMPILE_TIME_DISPATCHERS)
- .lock()
- .unwrap()
- .clone()
- .iter()
- .cloned()
- .collect();
-
- #[cfg(feature = "dispatch_tree")]
- let dispatch_tree_nodes = {
- let entries: Vec<(String, String, String)> = compile_time_dispatchers
- .iter()
- .filter_map(|entry| {
- let parts: Vec<&str> = entry.split(':').collect();
- if parts.len() == 3 {
- Some((
- parts[0].to_string(),
- parts[1].to_string(),
- parts[2].to_string(),
- ))
- } else {
- None
- }
- })
- .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);
-
- quote! {
- #get_nodes_fn
- #dispatch_trie_fn
- }
- };
-
- #[cfg(not(feature = "dispatch_tree"))]
- let dispatch_tree_nodes = 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 {
- #[allow(unused_imports)]
- #(#pathf_uses)*
- match any.member_id {
- #(#completion_tokens)*
- _ => ::mingling::Suggest::FileCompletion,
- }
- }
- };
-
- #[cfg(not(feature = "comp"))]
- let comp = quote! {};
-
- // Build render function arms from stored entries
- let render_fn =
- if renderer_tokens.is_empty() {
- quote! {
- fn render(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
- ::mingling::RenderResult::default()
- }
- }
- } 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);
- 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)
- }
- }
- }).collect();
- quote! {
- fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult {
- match any.member_id {
- #(#render_arms)*
- _ => ::mingling::RenderResult::default(),
- }
- }
- }
- };
-
- // 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);
- 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)
- }
- }
- }).collect();
-
- let chain_arms_sync: 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);
- 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)
- }
- }
- })
- .collect();
-
- let do_chain_fn = if chain_tokens.is_empty() {
- quote! {
- fn do_chain(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::ChainProcess<Self::Enum> {
- ::core::panic!("No chain found for type id")
- }
- }
- } else if ASYNC_ENABLED {
- quote! {
- fn do_chain(
- any: ::mingling::AnyOutput<Self::Enum>,
- ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::std::marker::Send>> {
- match any.member_id {
- #(#chain_arms_async)*
- _ => ::core::panic!("No chain found for type id: {:?}", any.type_id),
- }
- }
- }
- } else {
- quote! {
- fn do_chain(
- any: ::mingling::AnyOutput<Self::Enum>,
- ) -> ::mingling::ChainProcess<Self::Enum> {
- match any.member_id {
- #(#chain_arms_sync)*
- _ => ::core::panic!("No chain found for type id: {:?}", any.type_id),
- }
- }
- }
- };
-
- let help_tokens: Vec<proc_macro2::TokenStream> = get_global_set(&HELP_REQUESTS)
- .lock()
- .unwrap()
- .clone()
- .iter()
- .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap())
- .collect();
-
- let num_variants = packed_types.len();
- let repr_type = if u8::try_from(num_variants).is_ok() {
- quote! { u8 }
- } else if u16::try_from(num_variants).is_ok() {
- quote! { u16 }
- } else if u32::try_from(num_variants).is_ok() {
- quote! { u32 }
- } else {
- quote! { u128 }
- };
-
- let expanded = quote! {
- #[derive(Debug, PartialEq, Eq, Clone)]
- #[repr(#repr_type)]
- #[allow(nonstandard_style)]
- pub enum #name {
- #(#packed_types),*
- }
-
- impl ::std::fmt::Display for #name {
- fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- match self {
- #(#name::#packed_types => write!(f, stringify!(#packed_types)),)*
- }
- }
- }
-
- impl ::mingling::ProgramCollect for #name {
- type Enum = #name;
- 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()))
- }
- fn build_dispatcher_not_found(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> {
- ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args))
- }
- fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> {
- ::mingling::AnyOutput::new(ResultEmpty)
- }
- #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(),
- }
- }
- fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool {
- match any.member_id {
- #(#renderer_exist_tokens)*
- _ => false
- }
- }
- fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool {
- match any.member_id {
- #(#chain_exist_tokens)*
- _ => false
- }
- }
- #dispatch_tree_nodes
- #structural_render
- #comp
- }
-
- impl #name {
- /// Creates a new `Program<#name>` instance with default configuration.
- pub fn new() -> ::mingling::Program<#name> {
- ::mingling::Program::new()
- }
-
- /// Returns a static reference to the global `Program<#name>` singleton.
- pub fn this() -> &'static ::mingling::Program<#name> {
- &::mingling::this::<#name>()
- }
- }
- };
-
- // Clear all global registries to prevent stale state in Rust Analyzer
- get_global_set(&PACKED_TYPES).lock().unwrap().clear();
- get_global_set(&CHAINS).lock().unwrap().clear();
- get_global_set(&CHAINS_EXIST).lock().unwrap().clear();
- get_global_set(&RENDERERS).lock().unwrap().clear();
- get_global_set(&RENDERERS_EXIST).lock().unwrap().clear();
- get_global_set(&HELP_REQUESTS).lock().unwrap().clear();
- #[cfg(feature = "comp")]
- get_global_set(&COMPLETIONS).lock().unwrap().clear();
- #[cfg(feature = "dispatch_tree")]
- get_global_set(&COMPILE_TIME_DISPATCHERS)
- .lock()
- .unwrap()
- .clear();
- #[cfg(feature = "structural_renderer")]
- get_global_set(&STRUCTURAL_RENDERERS)
- .lock()
- .unwrap()
- .clear();
-
- TokenStream::from(expanded)
+pub fn program_final_gen(input: TokenStream) -> TokenStream {
+ func::gen_program::program_final_gen_impl(input)
}
/// Builds a `Suggest` instance with inline suggestion items.