aboutsummaryrefslogtreecommitdiff
path: root/mingling_pathf
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_pathf')
-rw-r--r--mingling_pathf/src/patterns/dispatcher.rs85
-rw-r--r--mingling_pathf/src/patterns/dispatcher_clap.rs56
-rw-r--r--mingling_pathf/test/src/lib.rs69
-rw-r--r--mingling_pathf/test/src/test_files/test_dispatcher.rs12
-rw-r--r--mingling_pathf/test/src/test_files/test_dispatcher_clap.rs14
-rw-r--r--mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs8
6 files changed, 116 insertions, 128 deletions
diff --git a/mingling_pathf/src/patterns/dispatcher.rs b/mingling_pathf/src/patterns/dispatcher.rs
index 5090052..859f198 100644
--- a/mingling_pathf/src/patterns/dispatcher.rs
+++ b/mingling_pathf/src/patterns/dispatcher.rs
@@ -2,12 +2,12 @@
//! The `DispatcherPattern` matches invocations of the `dispatcher!` macro and
//! extracts the generated type names from its arguments. It supports:
//! - `Entry*` — the entry type (always generated)
-//! - `CMD*` — the dispatcher struct (always generated)
+//! - `__Dispatcher*` — the hidden dispatcher struct (always generated)
//! - `__internal_dispatcher_*` — the compile-time collected static (always generated)
//!
//! Supported forms:
-//! - Explicit: `dispatcher!("greet", CMDGreet => EntryGreet)`
-//! - Implicit: `dispatcher!("greet")` — infers `CMDGreet` and `EntryGreet`
+//! - Explicit: `dispatcher!("greet", EntryGreet)`
+//! - Implicit: `dispatcher!("greet")` — infers `EntryGreet`
//! - With braces: `dispatcher! { ... }`
//!
//! This pattern is used to track dispatcher types for code generation or analysis.
@@ -18,7 +18,7 @@ use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
/// Matches the `dispatcher!` macro, extracts:
/// - `Entry*` — the entry type (always)
-/// - `CMD*` — the dispatcher struct (always)
+/// - `__Dispatcher*` — the hidden dispatcher struct (always)
/// - `__internal_dispatcher_*` — the compile-time collected static (always)
#[derive(Default)]
pub struct DispatcherPattern;
@@ -90,22 +90,20 @@ fn macro_simple_name(m: &syn::ItemMacro) -> String {
/// Extracts all types generated by a `dispatcher!` call.
fn extract_all_types(tokens: &proc_macro2::TokenStream, module: &str) -> Vec<AnalyzeItem> {
- let (cmd_name, cmd_struct, entry_struct) = parse_dispatcher_args(tokens);
+ let (cmd_name, entry_struct) = parse_dispatcher_args(tokens);
let Some(cmd_name) = cmd_name else {
return Vec::new();
};
let mut items = Vec::new();
- // Entry type — always
- if let Some(ref entry) = entry_struct {
- items.push(AnalyzeItem::local(module.to_string(), entry.clone()));
- }
+ // Entry type — always (derived from the command name in the implicit form)
+ let entry = entry_struct.unwrap_or_else(|| format!("Entry{}", to_pascal_case(&cmd_name)));
+ items.push(AnalyzeItem::local(module.to_string(), entry));
- // CMD type — always
- if let Some(ref cmd) = cmd_struct {
- items.push(AnalyzeItem::local(module.to_string(), cmd.clone()));
- }
+ // Hidden dispatcher struct — always
+ let hidden_name = format!("__Dispatcher{}", to_pascal_case(&cmd_name));
+ items.push(AnalyzeItem::local(module.to_string(), hidden_name));
// __internal_dispatcher_* — the compile-time collected static
let internal_name = format!("__internal_dispatcher_{}", snake_case(&cmd_name));
@@ -114,53 +112,28 @@ fn extract_all_types(tokens: &proc_macro2::TokenStream, module: &str) -> Vec<Ana
items
}
-/// Parses dispatcher arguments and returns (`command_name`, `cmd_struct`, `entry_struct`).
-fn parse_dispatcher_args(
- tokens: &proc_macro2::TokenStream,
-) -> (Option<String>, Option<String>, Option<String>) {
+/// Parses dispatcher arguments and returns (`command_name`, `entry_struct`).
+fn parse_dispatcher_args(tokens: &proc_macro2::TokenStream) -> (Option<String>, Option<String>) {
let stream = tokens.to_string();
- // Explicit form: "name", CMDType => EntryType
- if let Some(arrow_idx) = stream.find("=>") {
- // Extract command name
- let before_arrow = &stream[..arrow_idx];
- let cmd_name = extract_string_literal(before_arrow);
-
- // Extract CMD type: the ident before `=>`
- let before_arrow_trimmed = before_arrow.trim();
- let cmd_type = before_arrow_trimmed
- .split(|c: char| c.is_whitespace() || c == ',')
- .filter_map(|s| {
- let s = s.trim();
- if s.starts_with('"') || s.is_empty() {
- None
- } else {
- Some(s.to_string())
- }
- })
- .next_back();
-
- // Extract entry type: after `=>`
- let after_arrow = stream[arrow_idx + 2..].trim();
- let entry_type = after_arrow
- .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '}')
- .next()
- .map(|s| s.trim().to_string())
- .filter(|s| !s.is_empty());
-
- return (cmd_name, cmd_type, entry_type);
- }
-
- // Implicit form: "name"
let Some(cmd_name) = extract_string_literal(&stream) else {
- return (None, None, None);
+ return (None, None);
};
- let pascal = to_pascal_case(&cmd_name);
- (
- Some(cmd_name),
- Some(format!("CMD{pascal}")),
- Some(format!("Entry{pascal}")),
- )
+
+ // Explicit form: "name", EntryType — the entry is the first bare
+ // identifier after the string literal. (The old `CMD => Entry` form is
+ // no longer supported.)
+ let after_lit = {
+ let start = stream.find('"').unwrap_or_default();
+ &stream[start + cmd_name.len() + 2..]
+ };
+ let entry_type = after_lit
+ .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '}' || c == '=')
+ .map(str::trim)
+ .find(|s| !s.is_empty() && !s.starts_with('"'))
+ .map(str::to_string);
+
+ (Some(cmd_name), entry_type)
}
/// Extracts the first string literal from a token string.
diff --git a/mingling_pathf/src/patterns/dispatcher_clap.rs b/mingling_pathf/src/patterns/dispatcher_clap.rs
index da9da3e..cd7bf67 100644
--- a/mingling_pathf/src/patterns/dispatcher_clap.rs
+++ b/mingling_pathf/src/patterns/dispatcher_clap.rs
@@ -2,16 +2,16 @@
//! The `DispatcherClapPattern` matches structs annotated with `#[dispatcher_clap(...)]` and
//! extracts key items for code generation or analysis:
//! - The entry struct name (always)
-//! - The dispatcher command struct (`CMD*`, always)
+//! - The hidden dispatcher struct (`__Dispatcher*`, always)
//! - The error type, if `error = ErrorType` is specified
//! - The help internal struct, if `help = true` is specified
//! - The `__internal_dispatcher_*` compile-time collected static (always)
//!
//! Supported forms:
-//! - `#[dispatcher_clap("greet", CMDGreet)] struct EntryGreet { ... }`
-//! - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet)] struct EntryGreet { ... }`
-//! - `#[dispatcher_clap("greet", CMDGreet, help = true)] struct EntryGreet { ... }`
-//! - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet, help = true)] struct EntryGreet { ... }`
+//! - `#[dispatcher_clap("greet")] struct EntryGreet { ... }`
+//! - `#[dispatcher_clap("greet", error = ErrorGreet)] struct EntryGreet { ... }`
+//! - `#[dispatcher_clap("greet", help = true)] struct EntryGreet { ... }`
+//! - `#[dispatcher_clap("greet", error = ErrorGreet, help = true)] struct EntryGreet { ... }`
use syn::Item;
@@ -19,16 +19,16 @@ use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
/// Match structs annotated with `#[dispatcher_clap(...)]`, extracting:
/// - The entry type (struct name, always)
-/// - The dispatcher struct (`CMD*`, always)
+/// - The hidden dispatcher struct (`__Dispatcher*`, always)
/// - The error type, if `error = ErrorType` is specified
/// - The help internal struct, if `help = true` is specified
/// - `__internal_dispatcher_*` — compile-time collected static (always)
///
/// Covers forms:
-/// - `#[dispatcher_clap("greet", CMDGreet)] struct EntryGreet { ... }`
-/// - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet)] struct EntryGreet { ... }`
-/// - `#[dispatcher_clap("greet", CMDGreet, help = true)] struct EntryGreet { ... }`
-/// - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet, help = true)] struct EntryGreet { ... }`
+/// - `#[dispatcher_clap("greet")] struct EntryGreet { ... }`
+/// - `#[dispatcher_clap("greet", error = ErrorGreet)] struct EntryGreet { ... }`
+/// - `#[dispatcher_clap("greet", help = true)] struct EntryGreet { ... }`
+/// - `#[dispatcher_clap("greet", error = ErrorGreet, help = true)] struct EntryGreet { ... }`
#[derive(Default)]
pub struct DispatcherClapPattern;
@@ -95,9 +95,10 @@ impl DispatcherClapPattern {
let args_str = args.map(|l| l.tokens.to_string()).unwrap_or_default();
let parsed = parse_dispatcher_clap_args(&args_str);
- // CMD type — always
- if let Some(ref cmd) = parsed.cmd_type {
- items.push(AnalyzeItem::local(module.to_string(), cmd.clone()));
+ // Hidden dispatcher struct — always (if the command name is given)
+ if let Some(ref cmd_name) = parsed.cmd_name {
+ let hidden_name = format!("__Dispatcher{}", to_pascal_case(cmd_name));
+ items.push(AnalyzeItem::local(module.to_string(), hidden_name));
}
// Error type — if error = TypeName
@@ -107,9 +108,9 @@ impl DispatcherClapPattern {
// Help internal struct — if help = true
if parsed.help_enabled
- && let Some(ref cmd) = parsed.cmd_type
+ && let Some(ref cmd_name) = parsed.cmd_name
{
- let help_fn = format!("__{}_help", just_fmt::snake_case!(cmd));
+ let help_fn = format!("__{}_help", just_fmt::snake_case!(cmd_name));
let help_struct = format!("__internal_help_{}", just_fmt::snake_case!(&help_fn));
items.push(AnalyzeItem::local(module.to_string(), help_struct));
}
@@ -128,15 +129,13 @@ impl DispatcherClapPattern {
struct ParsedClapArgs {
cmd_name: Option<String>,
- cmd_type: Option<String>,
error_type: Option<String>,
help_enabled: bool,
}
-/// Parse `#[dispatcher_clap("cmd", CMDType, error = ErrorType, help = true)]` arguments.
+/// Parse `#[dispatcher_clap("cmd", error = ErrorType, help = true)]` arguments.
fn parse_dispatcher_clap_args(args: &str) -> ParsedClapArgs {
let mut cmd_name = None;
- let mut cmd_type = None;
let mut error_type = None;
let mut help_enabled = false;
@@ -178,23 +177,30 @@ fn parse_dispatcher_clap_args(args: &str) -> ParsedClapArgs {
}
_ => {}
}
- } else {
- // Bare ident — the CMD type
- let clean = part.trim_end_matches([')', ']']).trim();
- if !clean.is_empty() && cmd_type.is_none() {
- cmd_type = Some(clean.to_string());
- }
}
+ // Bare idents (e.g. the old CMD struct argument) are ignored.
}
ParsedClapArgs {
cmd_name,
- cmd_type,
error_type,
help_enabled,
}
}
+/// Simple `pascal_case` conversion for deriving the hidden dispatcher name.
+fn to_pascal_case(s: &str) -> String {
+ s.split(['-', '_', '.'])
+ .filter(|s| !s.is_empty())
+ .map(|s| {
+ let mut c = s.chars();
+ c.next().map_or_else(String::new, |f| {
+ f.to_uppercase().collect::<String>() + c.as_str()
+ })
+ })
+ .collect()
+}
+
fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool {
attrs
.iter()
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index 81f3b3f..fff2597 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -258,17 +258,17 @@ fn test_dispatcher_analyze() {
let r = analyzer.analyze_file(file).unwrap();
let required: Vec<&str> = vec![
"::EntryGreet",
- "::CMDGreet",
+ "::__DispatcherGreet",
"::EntryRemoteAdd",
- "::CMDRemoteAdd",
+ "::__DispatcherRemoteAdd",
"::EntryDelete",
- "::CMDDelete",
+ "::__DispatcherDelete",
"::EntryRemoteRm",
- "::CMDRemoteRm",
+ "::__DispatcherRemoteRm",
"::sub::EntryGreet",
- "::sub::CMDGreet",
+ "::sub::__DispatcherGreet",
"::sub::EntryDelete",
- "::sub::CMDDelete",
+ "::sub::__DispatcherDelete",
// Dispatchers are always collected at compile time:
"::__internal_dispatcher_greet",
"::__internal_dispatcher_remote_add",
@@ -293,16 +293,18 @@ fn test_dispatcher_dispatch_tree() {
.join("src/test_files/test_dispatcher_dispatch_tree.rs");
// Dispatchers are always collected at compile time, so the analyzer
- // always extracts the `__internal_dispatcher_*` statics too:
- // 8 (Entry + CMD, root + sub) + 4 __internal (root + sub) = 12
+ // always extracts the hidden dispatcher structs and statics too:
+ // 8 (Entry + __Dispatcher, root + sub) + 4 __internal (root + sub) = 12
let r = pattern_analyzer::init().analyze_file(&file).unwrap();
assert_eq!(r.len(), 12);
assert!(r.contains("::EntryGreet"));
- assert!(r.contains("::CMDGreet"));
+ assert!(r.contains("::__DispatcherGreet"));
assert!(r.contains("::EntryDelete"));
- assert!(r.contains("::CMDDelete"));
+ assert!(r.contains("::__DispatcherDelete"));
assert!(r.contains("::sub::EntryGreet"));
- assert!(r.contains("::sub::CMDGreet"));
+ assert!(r.contains("::sub::__DispatcherGreet"));
+ assert!(r.contains("::sub::EntryDelete"));
+ assert!(r.contains("::sub::__DispatcherDelete"));
assert!(r.contains("::__internal_dispatcher_greet"));
assert!(r.contains("::__internal_dispatcher_delete"));
assert!(r.contains("::sub::__internal_dispatcher_greet"));
@@ -323,43 +325,43 @@ fn test_dispatcher_clap_analyze() {
"::EntryClap2",
"::EntryClap3",
"::EntryClap4",
- // Root: with CMD type
+ // Root: with command name only
"::EntryWithCmd",
- "::CMDGreet",
- // Root: with CMD + error
+ // Root: with error
"::EntryWithError",
- "::CMDDelete",
"::ErrorDelete",
- // Root: with CMD + help
+ // Root: with help
"::EntryWithHelp",
- "::CMDHelp",
- "::__internal_help_cmdhelp_help",
- // Root: with CMD + error + help
+ "::__internal_help_helpcmd_help",
+ // Root: with error + help
"::EntryFull",
- "::CMDFull",
"::ErrorFull",
- "::__internal_help_cmdfull_help",
+ "::__internal_help_full_help",
// Sub: entry types (bare dispatcher_clap)
"::sub::EntryClap1",
"::sub::EntryClap3",
- // Sub: with CMD type
+ // Sub: with command name only
"::sub::EntryWithCmd",
- "::sub::CMDGreet",
- // Sub: with CMD + error
+ // Sub: with error
"::sub::EntryWithError",
- "::sub::CMDDelete",
"::sub::ErrorDelete",
- // Sub: with CMD + help
+ // Sub: with help
"::sub::EntryWithHelp",
- "::sub::CMDHelp",
- "::sub::__internal_help_cmdhelp_help",
- // Dispatchers are always collected at compile time:
+ "::sub::__internal_help_helpcmd_help",
+ // Hidden dispatcher structs + statics are always collected:
+ "::__DispatcherGreet",
"::__internal_dispatcher_greet",
+ "::__DispatcherDelete",
"::__internal_dispatcher_delete",
+ "::__DispatcherHelpcmd",
"::__internal_dispatcher_helpcmd",
+ "::__DispatcherFull",
"::__internal_dispatcher_full",
+ "::sub::__DispatcherGreet",
"::sub::__internal_dispatcher_greet",
+ "::sub::__DispatcherDelete",
"::sub::__internal_dispatcher_delete",
+ "::sub::__DispatcherHelpcmd",
"::sub::__internal_dispatcher_helpcmd",
];
@@ -378,15 +380,22 @@ fn test_dispatcher_clap_dispatch_tree() {
.join("src/test_files/test_dispatcher_clap.rs");
// Dispatchers are always collected at compile time:
- // 26 (Entry/CMD/error/help items) + 4 __internal (root) + 3 __internal (sub, no "full") = 33
+ // 19 (entry/error/help items) + 14 (hidden structs + statics, root + sub) = 33
let r = pattern_analyzer::init().analyze_file(&file).unwrap();
assert_eq!(r.len(), 33);
+ assert!(r.contains("::__DispatcherGreet"));
assert!(r.contains("::__internal_dispatcher_greet"));
+ assert!(r.contains("::__DispatcherDelete"));
assert!(r.contains("::__internal_dispatcher_delete"));
+ assert!(r.contains("::__DispatcherHelpcmd"));
assert!(r.contains("::__internal_dispatcher_helpcmd"));
+ assert!(r.contains("::__DispatcherFull"));
assert!(r.contains("::__internal_dispatcher_full"));
+ assert!(r.contains("::sub::__DispatcherGreet"));
assert!(r.contains("::sub::__internal_dispatcher_greet"));
+ assert!(r.contains("::sub::__DispatcherDelete"));
assert!(r.contains("::sub::__internal_dispatcher_delete"));
+ assert!(r.contains("::sub::__DispatcherHelpcmd"));
assert!(r.contains("::sub::__internal_dispatcher_helpcmd"));
}
diff --git a/mingling_pathf/test/src/test_files/test_dispatcher.rs b/mingling_pathf/test/src/test_files/test_dispatcher.rs
index 48f5e4d..66e6a74 100644
--- a/mingling_pathf/test/src/test_files/test_dispatcher.rs
+++ b/mingling_pathf/test/src/test_files/test_dispatcher.rs
@@ -1,17 +1,17 @@
-mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet);
+mingling::macros::dispatcher!("greet", EntryGreet);
mingling::macros::dispatcher!("greet");
-mingling::macros::dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd);
+mingling::macros::dispatcher!("remote.add", EntryRemoteAdd);
mingling::macros::dispatcher!("remote.add");
-dispatcher!("delete", CMDDelete => EntryDelete);
+dispatcher!("delete", EntryDelete);
dispatcher!("delete");
-dispatcher!("remote.rm", CMDRemoteRm => EntryRemoteRm);
+dispatcher!("remote.rm", EntryRemoteRm);
dispatcher!("remote.rm");
pub mod sub {
- mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet);
+ mingling::macros::dispatcher!("greet", EntryGreet);
mingling::macros::dispatcher!("greet");
- dispatcher!("delete", CMDDelete => EntryDelete);
+ dispatcher!("delete", EntryDelete);
dispatcher!("delete");
}
diff --git a/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs b/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs
index 33d86e0..c1d5ef0 100644
--- a/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs
+++ b/mingling_pathf/test/src/test_files/test_dispatcher_clap.rs
@@ -22,25 +22,25 @@ pub struct EntryClap4 {
}
// With CMD type
-#[dispatcher_clap("greet", CMDGreet)]
+#[dispatcher_clap("greet", )]
struct EntryWithCmd {
name: String,
}
// With CMD + error
-#[dispatcher_clap("delete", CMDDelete, error = ErrorDelete)]
+#[dispatcher_clap("delete", error = ErrorDelete)]
struct EntryWithError {
id: u64,
}
// With CMD + help
-#[dispatcher_clap("helpcmd", CMDHelp, help = true)]
+#[dispatcher_clap("helpcmd", help = true)]
struct EntryWithHelp {
verbose: bool,
}
// With CMD + error + help
-#[dispatcher_clap("full", CMDFull, error = ErrorFull, help = true)]
+#[dispatcher_clap("full", error = ErrorFull, help = true)]
struct EntryFull {
all: bool,
}
@@ -56,17 +56,17 @@ pub mod sub {
value: String,
}
- #[dispatcher_clap("greet", CMDGreet)]
+ #[dispatcher_clap("greet", )]
struct EntryWithCmd {
name: String,
}
- #[dispatcher_clap("delete", CMDDelete, error = ErrorDelete)]
+ #[dispatcher_clap("delete", error = ErrorDelete)]
struct EntryWithError {
id: u64,
}
- #[dispatcher_clap("helpcmd", CMDHelp, help = true)]
+ #[dispatcher_clap("helpcmd", help = true)]
struct EntryWithHelp {
verbose: bool,
}
diff --git a/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs b/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs
index ac321ca..e319242 100644
--- a/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs
+++ b/mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs
@@ -1,7 +1,7 @@
-mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet);
-dispatcher!("delete", CMDDelete => EntryDelete);
+mingling::macros::dispatcher!("greet", EntryGreet);
+dispatcher!("delete", EntryDelete);
pub mod sub {
- mingling::macros::dispatcher!("greet", CMDGreet => EntryGreet);
- dispatcher!("delete", CMDDelete => EntryDelete);
+ mingling::macros::dispatcher!("greet", EntryGreet);
+ dispatcher!("delete", EntryDelete);
}