aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/group_impl.rs115
-rw-r--r--mingling_macros/src/lib.rs53
2 files changed, 168 insertions, 0 deletions
diff --git a/mingling_macros/src/group_impl.rs b/mingling_macros/src/group_impl.rs
new file mode 100644
index 0000000..1b765f3
--- /dev/null
+++ b/mingling_macros/src/group_impl.rs
@@ -0,0 +1,115 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse::{Parse, ParseStream};
+use syn::{Ident, Path, Result as SynResult, Token, TypePath};
+
+/// Input for the `group!` macro
+///
+/// # Syntax
+///
+/// ```rust,ignore
+/// // Explicit mode: specify both program path and type path
+/// group!(crate::ThisProgram, std::io::Error);
+///
+/// // Implicit mode: only type path, uses default `crate::ThisProgram` as program
+/// group!(std::io::Error);
+/// ```
+enum GroupInput {
+ Explicit {
+ program_path: Path,
+ type_path: TypePath,
+ },
+ Implicit {
+ type_path: TypePath,
+ },
+}
+
+impl Parse for GroupInput {
+ fn parse(input: ParseStream) -> SynResult<Self> {
+ // Parse the first path (could be program path or type path)
+ let first_path: Path = input.parse()?;
+
+ // If followed by a comma, it's explicit mode: Path, TypePath
+ if input.peek(Token![,]) {
+ input.parse::<Token![,]>()?;
+ let type_path: TypePath = input.parse()?;
+ Ok(GroupInput::Explicit {
+ program_path: first_path,
+ type_path,
+ })
+ } else {
+ // Otherwise it's implicit mode: just a type path
+ Ok(GroupInput::Implicit {
+ type_path: TypePath {
+ qself: None,
+ path: first_path,
+ },
+ })
+ }
+ }
+}
+
+/// Convert a type path into a valid module name segment
+///
+/// e.g. `std::io::Error` -> `internal_group_std_io_error`
+fn module_name_from_type(type_path: &TypePath) -> Ident {
+ let segments: Vec<String> = type_path
+ .path
+ .segments
+ .iter()
+ .map(|seg| seg.ident.to_string().to_lowercase())
+ .collect();
+ Ident::new(
+ &format!("internal_group_{}", segments.join("_")),
+ proc_macro2::Span::call_site(),
+ )
+}
+
+/// Get the last segment name of a type path (the simple type name)
+///
+/// e.g. `std::io::Error` -> `Error`
+fn type_simple_name(type_path: &TypePath) -> Ident {
+ type_path
+ .path
+ .segments
+ .last()
+ .expect("TypePath must have at least one segment")
+ .ident
+ .clone()
+}
+
+pub fn group_macro(input: TokenStream) -> TokenStream {
+ let input = syn::parse_macro_input!(input as GroupInput);
+
+ let (program_path, type_path) = match input {
+ GroupInput::Explicit {
+ program_path,
+ type_path,
+ } => (quote! { #program_path }, type_path),
+ GroupInput::Implicit { type_path } => (crate::default_program_path(), type_path),
+ };
+
+ // Use the type's simple name as the enum variant identifier
+ let type_name = type_simple_name(&type_path);
+ // Create a unique module name from the full type path
+ let module_name = module_name_from_type(&type_path);
+
+ // Generate the module with the Groupped implementation
+ let expanded = quote! {
+ #[allow(non_camel_case_types)]
+ mod #module_name {
+ use #program_path as __MinglingProgram;
+ use #type_path;
+
+ impl ::mingling::Groupped<__MinglingProgram> for #type_name {
+ fn member_id() -> __MinglingProgram {
+ __MinglingProgram::#type_name
+ }
+ }
+
+ ::mingling::macros::register_type!(#type_name);
+ }
+ };
+
+ expanded.into()
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 9880cd6..bb296ef 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -153,6 +153,8 @@ mod dispatcher_clap;
#[cfg(feature = "extra_macros")]
mod entry;
mod enum_tag;
+#[cfg(feature = "extra_macros")]
+mod group_impl;
mod groupped;
mod help;
mod node;
@@ -230,6 +232,56 @@ pub(crate) fn check_single_segment_type(
}
}
+/// 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
+///
+/// Two forms are supported:
+///
+/// ```rust,ignore
+/// // Explicit mode — specify both program path and outside-type:
+/// group!(crate::ThisProgram, std::io::Error);
+///
+/// // Implicit mode — uses default `crate::ThisProgram` as the program:
+/// group!(std::io::Error);
+/// ```
+///
+/// # 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);
+///
+/// // With explicit program path:
+/// group!(crate::MyProgram, serde_json::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 {
+ group_impl::group_macro(input)
+}
+
/// Creates a `Node` from a dot-separated path string.
///
/// Each segment is converted to kebab-case (unless it starts with `_`).
@@ -1801,6 +1853,7 @@ pub fn program_final_gen(input: TokenStream) -> TokenStream {
let expanded = quote! {
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(#repr_type)]
+ #[allow(nonstandard_style)]
pub enum #name {
#(#packed_types),*
}