mecha10_dev/services/topology/
source_scan.rs1use anyhow::{Context, Result};
4use regex::Regex;
5use std::collections::{HashMap, HashSet};
6use std::path::Path;
7
8use super::{TopicRef, TopologyService};
9
10macro_rules! extract_topic_refs_fn {
16 ($(#[$meta:meta])* $vis:vis fn $name:ident() -> $pattern:expr) => {
17 $(#[$meta])*
18 $vis fn $name(&self, content: &str, topic_defs: &HashMap<String, TopicRef>) -> Vec<TopicRef> {
19 let pattern = $pattern;
20 Self::extract_topic_refs(content, topic_defs, &pattern)
21 }
22 };
23}
24
25impl TopologyService {
26 pub(super) async fn parse_node_source(&self, source_path: &Path) -> Result<(Vec<TopicRef>, Vec<TopicRef>)> {
28 let content = tokio::fs::read_to_string(source_path)
29 .await
30 .context(format!("Failed to read source file: {}", source_path.display()))?;
31
32 let topic_defs = self.extract_topic_definitions(&content);
33
34 let publishes = self.extract_publish_calls(&content, &topic_defs);
35
36 let subscribes = self.extract_subscribe_calls(&content, &topic_defs);
37
38 Ok((publishes, subscribes))
39 }
40
41 pub fn extract_topic_definitions(&self, content: &str) -> HashMap<String, TopicRef> {
45 let mut topics = HashMap::new();
46
47 let topic_pattern =
48 Regex::new(r#"pub\s+const\s+([A-Z_]+):\s*Topic<([^>]+)>\s*=\s*Topic::new\("([^"]+)"\)"#).unwrap();
49
50 for caps in topic_pattern.captures_iter(content) {
51 let const_name = caps.get(1).map(|m| m.as_str()).unwrap_or("");
52 let message_type = caps.get(2).map(|m| m.as_str().trim()).unwrap_or("");
53 let topic_path = caps.get(3).map(|m| m.as_str()).unwrap_or("");
54
55 topics.insert(
56 const_name.to_string(),
57 TopicRef {
58 path: topic_path.to_string(),
59 message_type: Some(message_type.to_string()),
60 },
61 );
62 }
63
64 topics
65 }
66
67 extract_topic_refs_fn!(
68 pub fn extract_publish_calls() -> Regex::new(r"publish_to\s*\(\s*(?:[a-z_]+::)?([A-Z_][A-Z0-9_]*)\s*,").unwrap()
72 );
73
74 extract_topic_refs_fn!(
75 pub fn extract_subscribe_calls() -> Regex::new(r"subscribe\s*(?:::\s*<[^>]+>\s*)?\(\s*(?:[a-z_]+::)?([A-Z_][A-Z0-9_]*)\s*\)").unwrap()
79 );
80
81 fn extract_topic_refs(content: &str, topic_defs: &HashMap<String, TopicRef>, pattern: &Regex) -> Vec<TopicRef> {
85 let mut refs = Vec::new();
86 let mut seen = HashSet::new();
87
88 for caps in pattern.captures_iter(content) {
89 if let Some(const_name) = caps.get(1) {
90 let const_name = const_name.as_str();
91 if let Some(topic) = topic_defs.get(const_name) {
92 if seen.insert(topic.path.clone()) {
93 refs.push(topic.clone());
94 }
95 }
96 }
97 }
98
99 refs
100 }
101}