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 {
11 ($(#[$meta:meta])* $vis:vis fn $name:ident() -> $pattern:expr) => {
12 $(#[$meta])*
13 $vis fn $name(&self, content: &str, topic_defs: &HashMap<String, TopicRef>) -> Vec<TopicRef> {
14 let pattern = $pattern;
15 Self::extract_topic_refs(content, topic_defs, &pattern)
16 }
17 };
18}
19
20impl TopologyService {
21 pub(super) async fn parse_node_source(&self, source_path: &Path) -> Result<(Vec<TopicRef>, Vec<TopicRef>)> {
23 let content = tokio::fs::read_to_string(source_path)
24 .await
25 .context(format!("Failed to read source file: {}", source_path.display()))?;
26
27 let topic_defs = self.extract_topic_definitions(&content);
28
29 let publishes = self.extract_publish_calls(&content, &topic_defs);
30
31 let subscribes = self.extract_subscribe_calls(&content, &topic_defs);
32
33 Ok((publishes, subscribes))
34 }
35
36 pub fn extract_topic_definitions(&self, content: &str) -> HashMap<String, TopicRef> {
40 let mut topics = HashMap::new();
41
42 let topic_pattern =
43 Regex::new(r#"pub\s+const\s+([A-Z_]+):\s*Topic<([^>]+)>\s*=\s*Topic::new\("([^"]+)"\)"#).unwrap();
44
45 for caps in topic_pattern.captures_iter(content) {
46 let const_name = caps.get(1).map(|m| m.as_str()).unwrap_or("");
47 let message_type = caps.get(2).map(|m| m.as_str().trim()).unwrap_or("");
48 let topic_path = caps.get(3).map(|m| m.as_str()).unwrap_or("");
49
50 topics.insert(
51 const_name.to_string(),
52 TopicRef {
53 path: topic_path.to_string(),
54 message_type: Some(message_type.to_string()),
55 },
56 );
57 }
58
59 topics
60 }
61
62 extract_topic_refs_fn!(
63 pub fn extract_publish_calls() -> Regex::new(r"publish_to\s*\(\s*(?:[a-z_]+::)?([A-Z_][A-Z0-9_]*)\s*,").unwrap()
67 );
68
69 extract_topic_refs_fn!(
70 pub fn extract_subscribe_calls() -> Regex::new(r"subscribe\s*(?:::\s*<[^>]+>\s*)?\(\s*(?:[a-z_]+::)?([A-Z_][A-Z0-9_]*)\s*\)").unwrap()
74 );
75
76 fn extract_topic_refs(content: &str, topic_defs: &HashMap<String, TopicRef>, pattern: &Regex) -> Vec<TopicRef> {
80 let mut refs = Vec::new();
81 let mut seen = HashSet::new();
82
83 for caps in pattern.captures_iter(content) {
84 if let Some(const_name) = caps.get(1) {
85 let const_name = const_name.as_str();
86 if let Some(topic) = topic_defs.get(const_name) {
87 if seen.insert(topic.path.clone()) {
88 refs.push(topic.clone());
89 }
90 }
91 }
92 }
93
94 refs
95 }
96}