aboutsummaryrefslogtreecommitdiff
path: root/mingling_pathf/src/patterns/dispatcher.rs
blob: 7bb076cd5dca8ed87178581c474771d48f34a216 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use syn::Item;

use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};

/// Matches the `dispatcher!` macro, extracts the entry type name.
///
/// Supported forms:
/// - `dispatcher!("greet", CMDGreet => EntryGreet)` — explicit
/// - `dispatcher!("greet")` — implicit, infers EntryType
/// - `dispatcher! { ... }` — with braces
pub struct DispatcherPattern;

impl AnalyzePattern for DispatcherPattern {
    fn contains(&self, content: &str) -> bool {
        content.contains("dispatcher!")
    }

    fn analyze(&self, content: &str) -> Vec<AnalyzeItem> {
        let Ok(syntax) = syn::parse_file(content) else {
            return Vec::new();
        };

        let mut items = Vec::new();

        for item in &syntax.items {
            match item {
                Item::Macro(m) => {
                    let macro_name = macro_simple_name(m);
                    if macro_name != "dispatcher" {
                        continue;
                    }
                    if let Some(entry_name) = extract_dispatcher_entry(&m.mac.tokens) {
                        items.push(AnalyzeItem {
                            module: String::new(),
                            item_name: entry_name,
                        });
                    }
                }
                Item::Mod(item_mod) => {
                    if let Some((_, nested)) = &item_mod.content {
                        for n in nested {
                            if let Item::Macro(m) = n {
                                if macro_simple_name(m) != "dispatcher" {
                                    continue;
                                }
                                if let Some(entry_name) = extract_dispatcher_entry(&m.mac.tokens) {
                                    items.push(AnalyzeItem {
                                        module: item_mod.ident.to_string(),
                                        item_name: entry_name,
                                    });
                                }
                            }
                        }
                    }
                }
                _ => {}
            }
        }

        items
    }
}

fn macro_simple_name(m: &syn::ItemMacro) -> String {
    m.mac
        .path
        .segments
        .last()
        .map(|s| s.ident.to_string())
        .unwrap_or_default()
}

/// Extracts the entry type name from the `dispatcher!` macro arguments.
///
/// Input examples:
/// - `"greet", CMDGreet => EntryGreet` → `EntryGreet`
/// - `"remote.add"` → `EntryRemoteAdd` (implicit inference)
fn extract_dispatcher_entry(tokens: &proc_macro2::TokenStream) -> Option<String> {
    let stream = tokens.to_string();

    // Explicit form: look for `=>`
    if let Some(arrow_idx) = stream.find("=>") {
        let after_arrow = stream[arrow_idx + 2..].trim();
        let entry_name = after_arrow
            .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '}')
            .next()?;
        if !entry_name.is_empty() {
            return Some(entry_name.trim().to_string());
        }
    }

    // Implicit form: infer from command name
    let stream = stream.trim();
    if let Some(start) = stream.find('"') {
        let rest = &stream[start + 1..];
        let cmd_name = rest.split('"').next()?;
        let last_segment = cmd_name.split('.').next_back()?;
        let entry = format!("Entry{}", to_pascal_case(last_segment));
        Some(entry)
    } else {
        None
    }
}

fn to_pascal_case(s: &str) -> String {
    s.split(['-', '_', '.'])
        .filter(|s| !s.is_empty())
        .map(|s| {
            let mut c = s.chars();
            match c.next() {
                None => String::new(),
                Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
            }
        })
        .collect()
}