aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-16 22:31:52 +0800
committer魏曹先生 <1992414357@qq.com>2026-05-16 22:31:52 +0800
commit60b91db3df168532f9143f0cafb7b5166e1dc78b (patch)
treeddb10f909a46033af16d725ab0dae8385227b985
parent05d07bbc627b59fd07a8764a972b4aac63a46f53 (diff)
Accept paths for program name parameters in macros
All proc macros (`pack!`, `dispatcher!`, `#[chain]`, `#[program_setup]`, `#[dispatcher_clap]`, `#[derive(Groupped)]`) now parse program names as `syn::Path` instead of bare `Ident`, allowing use of paths like `crate::MyProgram` or `my_crate::MyProgram`. The default program name `ThisProgram` is no longer re-exported or required as an import — generated code references `crate::ThisProgram` directly.
-rw-r--r--CHANGELOG.md9
-rw-r--r--mingling_macros/src/chain.rs12
-rw-r--r--mingling_macros/src/dispatcher.rs40
-rw-r--r--mingling_macros/src/dispatcher_clap.rs77
-rw-r--r--mingling_macros/src/help.rs2
-rw-r--r--mingling_macros/src/pack.rs53
-rw-r--r--mingling_macros/src/program_setup.rs4
-rw-r--r--mling/src/cli/install.rs2
-rw-r--r--mling/src/cli/list.rs2
-rw-r--r--mling/src/cli/namespace_mgr.rs5
-rw-r--r--mling/src/cli/read.rs5
11 files changed, 98 insertions, 113 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba9c212..044e7d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -115,6 +115,15 @@ this::<ThisProgram>().modify_res::<ExitCode>(|code| {
1. **\[core\]** The signature of `exec` has been changed to `exec(self) -> i32` (previously was `exec(self)`)
+2. **\[macros\]** All proc macros that accept a program/group name parameter (e.g. `pack!`, `dispatcher!`, `#[chain]`, `#[program_setup]`, `#[dispatcher_clap]`, `#[derive(Groupped)]`) now parse the name as a `syn::Path` instead of a bare `Ident`. This means:
+ - You can now use paths like `crate::MyProgram` or `my_crate::MyProgram` in addition to plain `MyProgram`.
+ - The default program name `ThisProgram` is no longer re-exported or required as an import — generated code references `crate::ThisProgram` directly.
+ - If you previously imported `ThisProgram` from `crate` only for macro use, that import is no longer needed and can be removed.
+
+```rust
+use crate::ThisProgram; // Can be removed if not used directly
+```
+
---
### Release 0.1.7 (2026-05-04)
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs
index 3c0cca5..fd1db65 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -5,8 +5,6 @@ use syn::{
FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input,
};
-use crate::DEFAULT_PROGRAM_NAME;
-
/// Extracted information about a resource injection parameter
struct ResourceInjection {
var_name: Ident,
@@ -137,12 +135,10 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse the attribute arguments (e.g., MyProgram from #[chain(MyProgram)])
// If no argument is provided, use ThisProgram
let (group_name, use_crate_prefix) = if attr.is_empty() {
- (
- Ident::new(DEFAULT_PROGRAM_NAME, proc_macro2::Span::call_site()),
- true,
- )
+ (crate::default_program_path(), true)
} else {
- (parse_macro_input!(attr as Ident), false)
+ let path: syn::Path = parse_macro_input!(attr as syn::Path);
+ (quote! { #path }, false)
};
// Parse the function item
@@ -235,7 +231,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
let program_type = if use_crate_prefix {
crate::default_program_path()
} else {
- quote! { #group_name }
+ group_name.clone()
};
// Check for async fn + &mut combination, which is not supported
diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs
index a74db6d..ba36545 100644
--- a/mingling_macros/src/dispatcher.rs
+++ b/mingling_macros/src/dispatcher.rs
@@ -11,11 +11,10 @@ use syn::{Ident, Result as SynResult, Token};
#[cfg(feature = "dispatch_tree")]
use crate::COMPILE_TIME_DISPATCHERS;
-use crate::DEFAULT_PROGRAM_NAME;
enum DispatcherChainInput {
Explicit {
- group_name: Ident,
+ group_name: syn::Path,
command_name: syn::LitStr,
command_struct: Ident,
pack: Ident,
@@ -29,10 +28,10 @@ enum DispatcherChainInput {
impl Parse for DispatcherChainInput {
fn parse(input: ParseStream) -> SynResult<Self> {
- let lookahead = input.lookahead1();
-
- if lookahead.peek(Ident) && input.peek2(Token![,]) && input.peek3(syn::LitStr) {
- let group_name = input.parse()?;
+ if (input.peek(Ident) || input.peek(Token![crate]))
+ && (input.peek2(Token![::]) || input.peek2(Token![,]))
+ {
+ let group_name = input.parse::<syn::Path>()?;
input.parse::<Token![,]>()?;
let command_name = input.parse()?;
input.parse::<Token![,]>()?;
@@ -46,7 +45,7 @@ impl Parse for DispatcherChainInput {
command_struct,
pack,
})
- } else if lookahead.peek(syn::LitStr) {
+ } else if input.peek(syn::LitStr) {
// Default format: "command_name", CommandStruct => ChainStruct
let command_name = input.parse()?;
input.parse::<Token![,]>()?;
@@ -60,7 +59,7 @@ impl Parse for DispatcherChainInput {
pack,
})
} else {
- Err(lookahead.error())
+ Err(input.lookahead1().error())
}
}
}
@@ -77,23 +76,29 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput);
// Determine if we're using default or explicit group
- let (group_name, command_name, command_struct, pack, use_default) = match dispatcher_input {
+ let (command_name, command_struct, pack, _use_default, group_path) = match dispatcher_input {
DispatcherChainInput::Explicit {
group_name,
command_name,
command_struct,
pack,
- } => (group_name, command_name, command_struct, pack, false),
+ } => (
+ command_name,
+ command_struct,
+ pack,
+ false,
+ quote! { #group_name },
+ ),
DispatcherChainInput::Default {
command_name,
command_struct,
pack,
} => (
- Ident::new(DEFAULT_PROGRAM_NAME, proc_macro2::Span::call_site()),
command_name,
command_struct,
pack,
true,
+ crate::default_program_path(),
),
};
@@ -104,22 +109,13 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
let dispatch_tree_entry = get_dispatch_tree_entry(&command_name_str, &command_struct, &pack);
let expanded = {
- let program_ident = if use_default {
- Ident::new(DEFAULT_PROGRAM_NAME, proc_macro2::Span::call_site())
- } else {
- group_name.clone()
- };
- let program_path = if use_default {
- crate::default_program_path()
- } else {
- quote! { #group_name }
- };
+ let program_path = group_path;
quote! {
#[derive(Debug, Default)]
pub struct #command_struct;
- ::mingling::macros::pack!(#program_ident, #pack = Vec<String>);
+ ::mingling::macros::pack!(#program_path, #pack = Vec<String>);
#comp_entry
#dispatch_tree_entry
diff --git a/mingling_macros/src/dispatcher_clap.rs b/mingling_macros/src/dispatcher_clap.rs
index b32883e..bb40404 100644
--- a/mingling_macros/src/dispatcher_clap.rs
+++ b/mingling_macros/src/dispatcher_clap.rs
@@ -65,7 +65,7 @@ enum DispatcherClapInput {
},
/// `(Program, "cmd", Disp, ...)`
Explicit {
- group_name: Ident,
+ group_name: syn::Path,
command_name: LitStr,
dispatcher_struct: Ident,
options: ClapOptions,
@@ -76,9 +76,11 @@ impl Parse for DispatcherClapInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
- if lookahead.peek(Ident) && input.peek2(Token![,]) && input.peek3(syn::LitStr) {
+ if (input.peek(Ident) || input.peek(Token![crate]))
+ && (input.peek2(Token![::]) || input.peek2(Token![,]))
+ {
// Explicit format: Program, "cmd", Disp, ...
- let group_name: Ident = input.parse()?;
+ let group_name: syn::Path = input.parse()?;
input.parse::<Token![,]>()?;
let command_name: LitStr = input.parse()?;
input.parse::<Token![,]>()?;
@@ -132,44 +134,35 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream
let struct_name = &input_struct.ident;
// Determine the program name and other fields
- let (command_name_str, dispatcher_struct, options, program_ident, program_path) =
- match &attr_input {
- DispatcherClapInput::Default {
- command_name,
- dispatcher_struct,
- options,
- } => {
- let path = crate::default_program_path();
- (
- command_name.value(),
- dispatcher_struct.clone(),
- ClapOptions {
- error_struct: options.error_struct.clone(),
- help_enabled: options.help_enabled,
- },
- Ident::new(DEFAULT_PROGRAM_NAME, proc_macro2::Span::call_site()),
- path,
- )
- }
- DispatcherClapInput::Explicit {
- group_name,
- command_name,
- dispatcher_struct,
- options,
- } => {
- let path = quote! { #group_name };
- (
- command_name.value(),
- dispatcher_struct.clone(),
- ClapOptions {
- error_struct: options.error_struct.clone(),
- help_enabled: options.help_enabled,
- },
- group_name.clone(),
- path,
- )
- }
- };
+ let (command_name_str, dispatcher_struct, options, program_path) = match &attr_input {
+ DispatcherClapInput::Default {
+ command_name,
+ dispatcher_struct,
+ options,
+ } => (
+ command_name.value(),
+ dispatcher_struct.clone(),
+ ClapOptions {
+ error_struct: options.error_struct.clone(),
+ help_enabled: options.help_enabled,
+ },
+ crate::default_program_path(),
+ ),
+ DispatcherClapInput::Explicit {
+ group_name,
+ command_name,
+ dispatcher_struct,
+ options,
+ } => (
+ command_name.value(),
+ dispatcher_struct.clone(),
+ ClapOptions {
+ error_struct: options.error_struct.clone(),
+ help_enabled: options.help_enabled,
+ },
+ quote! { #group_name },
+ ),
+ };
// Generate the `begin` method body
let begin_body = if let Some(ref error_struct) = options.error_struct {
@@ -198,7 +191,7 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream
// Generate the error pack type
let error_pack = options.error_struct.as_ref().map(|error_struct| {
quote! {
- ::mingling::macros::pack!(#program_ident, #error_struct = String);
+ ::mingling::macros::pack!(#program_path, #error_struct = String);
}
});
diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/help.rs
index c89aad2..6667ae0 100644
--- a/mingling_macros/src/help.rs
+++ b/mingling_macros/src/help.rs
@@ -158,7 +158,7 @@ pub fn register_help(input: TokenStream) -> TokenStream {
// Parse the input as a comma-separated list of arguments
let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
- // Check that we have exactly two elements
+ // Check if there are exactly two elements
if input_parsed.len() != 2 {
return syn::Error::new(
input_parsed.span(),
diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/pack.rs
index 1c0bb7b..657f1bb 100644
--- a/mingling_macros/src/pack.rs
+++ b/mingling_macros/src/pack.rs
@@ -3,11 +3,9 @@ use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, Result as SynResult, Token, Type};
-use crate::DEFAULT_PROGRAM_NAME;
-
enum PackInput {
Explicit {
- group_name: Ident,
+ group_name: syn::Path,
type_name: Ident,
inner_type: Type,
},
@@ -19,12 +17,18 @@ enum PackInput {
impl Parse for PackInput {
fn parse(input: ParseStream) -> SynResult<Self> {
- // Try to parse as explicit format first: GroupName, TypeName = InnerType
- let lookahead = input.lookahead1();
-
- if lookahead.peek(Ident) && input.peek2(Token![,]) {
- // Explicit format: GroupName, TypeName = InnerType
- let group_name = input.parse()?;
+ // Look ahead to determine format:
+ // - `Path, TypeName = InnerType` → Explicit
+ // - `TypeName = InnerType` → Default
+ //
+ // Both start with an ident. We peek at the second token:
+ // if it's a `,` or `::`, it's explicit; if it's `=`, it's default.
+
+ if (input.peek(Ident) || input.peek(Token![crate]))
+ && (input.peek2(Token![,]) || input.peek2(Token![::]))
+ {
+ // Explicit format: Path, TypeName = InnerType
+ let group_name = input.parse::<syn::Path>()?;
input.parse::<Token![,]>()?;
let type_name = input.parse()?;
input.parse::<Token![=]>()?;
@@ -35,7 +39,7 @@ impl Parse for PackInput {
type_name,
inner_type,
})
- } else if lookahead.peek(Ident) && input.peek2(Token![=]) {
+ } else if input.peek(Ident) && input.peek2(Token![=]) {
// Default format: TypeName = InnerType
let type_name = input.parse()?;
input.parse::<Token![=]>()?;
@@ -46,14 +50,12 @@ impl Parse for PackInput {
inner_type,
})
} else {
- Err(lookahead.error())
+ Err(input.lookahead1().error())
}
}
}
pub fn pack(input: TokenStream) -> TokenStream {
- let default_program_path = crate::default_program_path();
-
// Parse the input
let pack_input = syn::parse_macro_input!(input as PackInput);
@@ -63,16 +65,11 @@ pub fn pack(input: TokenStream) -> TokenStream {
group_name,
type_name,
inner_type,
- } => (group_name, type_name, inner_type, false),
+ } => (quote! { #group_name }, type_name, inner_type, false),
PackInput::Default {
type_name,
inner_type,
- } => (
- Ident::new(DEFAULT_PROGRAM_NAME, proc_macro2::Span::call_site()),
- type_name,
- inner_type,
- true,
- ),
+ } => (crate::default_program_path(), type_name, inner_type, true),
};
// Generate the struct definition
@@ -211,13 +208,13 @@ pub fn pack(input: TokenStream) -> TokenStream {
#default_impl
#register_impl
- impl Into<mingling::AnyOutput<#default_program_path>> for #type_name {
- fn into(self) -> mingling::AnyOutput<#default_program_path> {
+ impl Into<mingling::AnyOutput<#group_name>> for #type_name {
+ fn into(self) -> mingling::AnyOutput<#group_name> {
mingling::AnyOutput::new(self)
}
}
- impl From<#type_name> for mingling::ChainProcess<#default_program_path> {
+ impl From<#type_name> for mingling::ChainProcess<#group_name> {
fn from(value: #type_name) -> Self {
mingling::AnyOutput::new(value).route_chain()
}
@@ -225,19 +222,19 @@ pub fn pack(input: TokenStream) -> TokenStream {
impl #type_name {
/// Converts the wrapper type into a `ChainProcess` for chaining operations.
- pub fn to_chain(self) -> mingling::ChainProcess<#default_program_path> {
+ pub fn to_chain(self) -> mingling::ChainProcess<#group_name> {
mingling::AnyOutput::new(self).route_chain()
}
/// Converts the wrapper type into a `ChainProcess` for rendering operations.
- pub fn to_render(self) -> mingling::ChainProcess<#default_program_path> {
+ pub fn to_render(self) -> mingling::ChainProcess<#group_name> {
mingling::AnyOutput::new(self).route_renderer()
}
}
- impl ::mingling::Groupped<#default_program_path> for #type_name {
- fn member_id() -> #default_program_path {
- #default_program_path::#type_name
+ impl ::mingling::Groupped<#group_name> for #type_name {
+ fn member_id() -> #group_name {
+ #group_name::#type_name
}
}
}
diff --git a/mingling_macros/src/program_setup.rs b/mingling_macros/src/program_setup.rs
index dd32581..383d632 100644
--- a/mingling_macros/src/program_setup.rs
+++ b/mingling_macros/src/program_setup.rs
@@ -53,8 +53,8 @@ pub fn setup_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
let (program_name, use_crate_prefix) = if attr.is_empty() {
(crate::default_program_path(), true)
} else {
- let ident: Ident = parse_macro_input!(attr as Ident);
- (quote! { #ident }, false)
+ let path: syn::Path = parse_macro_input!(attr as syn::Path);
+ (quote! { #path }, false)
};
// Parse the function item
diff --git a/mling/src/cli/install.rs b/mling/src/cli/install.rs
index dc2cf57..59d1d97 100644
--- a/mling/src/cli/install.rs
+++ b/mling/src/cli/install.rs
@@ -4,7 +4,7 @@ use mingling::{
parser::Picker,
};
-use crate::{ThisProgram, project_installer::install_all};
+use crate::project_installer::install_all;
dispatcher!("install", InstallCommand => InstallEntry);
diff --git a/mling/src/cli/list.rs b/mling/src/cli/list.rs
index d4088ce..49712b8 100644
--- a/mling/src/cli/list.rs
+++ b/mling/src/cli/list.rs
@@ -6,7 +6,7 @@ use mingling::{
};
use serde::Serialize;
-use crate::{ThisProgram, namespace_manager::list_namespaces};
+use crate::namespace_manager::list_namespaces;
dispatcher!("ls-namespace", ListInstalledCommand => ListInstalledEntry);
diff --git a/mling/src/cli/namespace_mgr.rs b/mling/src/cli/namespace_mgr.rs
index 4f70c15..0f5a115 100644
--- a/mling/src/cli/namespace_mgr.rs
+++ b/mling/src/cli/namespace_mgr.rs
@@ -4,10 +4,7 @@ use mingling::{
parser::{Picker, Yes},
};
-use crate::{
- ThisProgram,
- namespace_manager::{list_namespaces, remove_namespace, set_namespace_trusted},
-};
+use crate::namespace_manager::{list_namespaces, remove_namespace, set_namespace_trusted};
dispatcher!("trust", TrustNamespaceCommand => TrustNamespaceEntry);
dispatcher!("untrust", UntrustNamespaceCommand => UntrustNamespaceEntry);
diff --git a/mling/src/cli/read.rs b/mling/src/cli/read.rs
index 3bd9fc2..e9cd6bb 100644
--- a/mling/src/cli/read.rs
+++ b/mling/src/cli/read.rs
@@ -7,10 +7,7 @@ use mingling::{
};
use serde::Serialize;
-use crate::{
- ThisProgram,
- project_solver::{BinaryItem, solve_current_dir},
-};
+use crate::project_solver::{BinaryItem, solve_current_dir};
dispatcher!("show-target-dir", ReadTargetDirCommand => ReadTargetDirEntry);
dispatcher!("show-workspace-root", ReadWorkspaceRootCommand => ReadWorkspaceRootEntry);