diff options
Diffstat (limited to 'mingling_pathf')
| -rw-r--r-- | mingling_pathf/src/patterns/dispatcher.rs | 85 | ||||
| -rw-r--r-- | mingling_pathf/src/patterns/dispatcher_clap.rs | 56 | ||||
| -rw-r--r-- | mingling_pathf/test/src/lib.rs | 69 | ||||
| -rw-r--r-- | mingling_pathf/test/src/test_files/test_dispatcher.rs | 12 | ||||
| -rw-r--r-- | mingling_pathf/test/src/test_files/test_dispatcher_clap.rs | 14 | ||||
| -rw-r--r-- | mingling_pathf/test/src/test_files/test_dispatcher_dispatch_tree.rs | 8 |
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); } |
