project_map_cli_rust/core/
query_engine.rs1use crate::core::graph::{ProjectGraph, NodeData, NodeType};
2use crate::error::Result;
3use petgraph::visit::Dfs;
4use std::path::Path;
5
6pub struct QueryEngine {
7 graph: ProjectGraph,
8}
9
10impl QueryEngine {
11 pub fn load(path: &Path) -> Result<Self> {
12 let graph = ProjectGraph::load(path)?;
13 Ok(Self { graph })
14 }
15
16 pub fn find_symbols(&self, query: &str) -> Vec<NodeData> {
17 let keywords: Vec<String> = query.to_lowercase()
18 .split_whitespace()
19 .map(|s| s.to_string())
20 .collect();
21
22 if keywords.is_empty() {
23 return Vec::new();
24 }
25
26 self.graph.graph.node_weights()
27 .filter(|n| {
28 if n.node_type != NodeType::Symbol {
29 return false;
30 }
31
32 let name_lower = n.name.to_lowercase();
33 let doc_lower = n.docstring.as_ref().map(|d| d.to_lowercase()).unwrap_or_default();
34
35 keywords.iter().all(|k| name_lower.contains(k) || doc_lower.contains(k))
37 })
38 .cloned()
39 .collect()
40 }
41
42 pub fn get_file_outline(&self, path: &str) -> Vec<NodeData> {
43 let file_node = self.graph.graph.node_indices()
44 .find(|i| self.graph.graph[*i].node_type == NodeType::File && self.graph.graph[*i].path == path);
45
46 if let Some(idx) = file_node {
47 self.graph.graph.neighbors_directed(idx, petgraph::Direction::Outgoing)
48 .map(|n| self.graph.graph[n].clone())
49 .collect()
50 } else {
51 Vec::new()
52 }
53 }
54
55 pub fn analyze_impact(&self, name: &str) -> Vec<NodeData> {
56 let node_idx = self.graph.graph.node_indices()
57 .find(|i| self.graph.graph[*i].name == name);
58
59 if let Some(start_node) = node_idx {
60 let mut dfs = Dfs::new(&self.graph.graph, start_node);
62 let mut results = Vec::new();
63 while let Some(nx) = dfs.next(&self.graph.graph) {
64 if nx != start_node {
65 results.push(self.graph.graph[nx].clone());
66 }
67 }
68 results
69 } else {
70 Vec::new()
71 }
72 }
73
74 pub fn check_blast_radius(&self, path: &str, symbol: &str) -> Vec<NodeData> {
75 let node_idx = self.graph.graph.node_indices()
76 .find(|i| {
77 let node = &self.graph.graph[*i];
78 node.path == path && node.name == symbol && node.node_type == NodeType::Symbol
79 });
80
81 let start_node = if let Some(idx) = node_idx {
82 idx
83 } else {
84 let file_node = self.graph.graph.node_indices()
86 .find(|i| {
87 let node = &self.graph.graph[*i];
88 node.path == path && node.node_type == NodeType::File
89 });
90 if let Some(idx) = file_node { idx } else { return Vec::new(); }
91 };
92
93 let mut results = Vec::new();
99 let mut stack = vec![start_node];
100 let mut visited = std::collections::HashSet::new();
101 visited.insert(start_node);
102
103 while let Some(current) = stack.pop() {
104 for neighbor in self.graph.graph.neighbors_directed(current, petgraph::Direction::Incoming) {
105 if visited.insert(neighbor) {
106 results.push(self.graph.graph[neighbor].clone());
107 stack.push(neighbor);
108 }
109 }
110 }
111
112 results
113 }
114
115 pub fn find_symbol_in_path(&self, path: &str, name: &str) -> Option<NodeData> {
116 self.graph.graph.node_weights()
117 .find(|n| n.node_type == NodeType::Symbol && n.path == path && n.name == name)
118 .cloned()
119 }
120}