codesynapse_core/ts_extract/
sln.rs1use super::make_file_node;
2use crate::error::Result;
3use crate::extract::{make_id, ImportNode, LanguageExtractor};
4use crate::types::{Edge, ExtractionFragment, Node};
5use std::collections::HashMap;
6use std::path::Path;
7
8pub struct SlnExtractor;
9
10impl SlnExtractor {
11 pub fn extract(source: &[u8], path: &Path) -> Result<ExtractionFragment> {
12 let (file_id, _, file_node) = make_file_node(path);
13 let mut fragment = ExtractionFragment {
14 nodes: vec![file_node],
15 edges: vec![],
16 };
17
18 let content = std::str::from_utf8(source).unwrap_or("");
19
20 let proj_re = regex::Regex::new(
22 r#"Project\("[^"]*"\)\s*=\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"\{([^}]+)\}""#,
23 );
24 let proj_re = match proj_re {
25 Ok(r) => r,
26 Err(_) => return Ok(fragment),
27 };
28
29 let mut guid_to_id: HashMap<String, String> = HashMap::new();
30
31 for cap in proj_re.captures_iter(content) {
32 let name = cap.get(1).map(|m| m.as_str()).unwrap_or("").trim();
33 let proj_path = cap.get(2).map(|m| m.as_str()).unwrap_or("").trim();
34 let guid = cap
35 .get(3)
36 .map(|m| m.as_str())
37 .unwrap_or("")
38 .trim()
39 .to_uppercase();
40
41 if name.is_empty() || guid.is_empty() {
42 continue;
43 }
44
45 let proj_id = make_id(&[&file_id, name]);
46 guid_to_id.insert(guid.clone(), proj_id.clone());
47
48 fragment.nodes.push(Node {
49 id: proj_id.clone(),
50 label: name.to_string(),
51 file_type: "code".to_string(),
52 source_file: path.to_string_lossy().to_string(),
53 source_location: None,
54 community: None,
55 rationale: None,
56 docstring: None,
57 metadata: {
58 let mut m = HashMap::new();
59 m.insert("path".to_string(), proj_path.to_string());
60 m.insert("guid".to_string(), guid.clone());
61 m
62 },
63 });
64 fragment.edges.push(Edge {
65 source: file_id.clone(),
66 target: proj_id,
67 relation: "contains".to_string(),
68 confidence: "EXTRACTED".to_string(),
69 source_file: Some(path.to_string_lossy().to_string()),
70 weight: 1.0,
71 context: None,
72 });
73 }
74
75 let dep_re = regex::Regex::new(r#"\{([A-F0-9\-]+)\}\s*=\s*\{([A-F0-9\-]+)\}"#);
77 if let Ok(dep_re) = dep_re {
78 let mut in_deps = false;
79 for line in content.lines() {
80 let trimmed = line.trim();
81 if trimmed.contains("ProjectDependencies") {
82 in_deps = true;
83 continue;
84 }
85 if trimmed == "EndProjectSection" {
86 in_deps = false;
87 continue;
88 }
89 if in_deps {
90 if let Some(cap) = dep_re.captures(trimmed) {
91 let dep_guid = cap.get(1).map(|m| m.as_str()).unwrap_or("").to_uppercase();
92 let src_guid = cap.get(2).map(|m| m.as_str()).unwrap_or("").to_uppercase();
93 if let (Some(dep_id), Some(src_id)) =
94 (guid_to_id.get(&dep_guid), guid_to_id.get(&src_guid))
95 {
96 fragment.edges.push(Edge {
97 source: src_id.clone(),
98 target: dep_id.clone(),
99 relation: "imports".to_string(),
100 confidence: "EXTRACTED".to_string(),
101 source_file: Some(path.to_string_lossy().to_string()),
102 weight: 1.0,
103 context: None,
104 });
105 }
106 }
107 }
108 }
109 }
110
111 Ok(fragment)
112 }
113}
114
115impl LanguageExtractor for SlnExtractor {
116 fn file_extensions(&self) -> Vec<&'static str> {
117 vec!["sln"]
118 }
119 fn extract(&self, source: &[u8], path: &Path) -> Result<ExtractionFragment> {
120 Self::extract(source, path)
121 }
122 fn resolve_imports(&self, _imports: &[ImportNode]) -> Vec<Edge> {
123 vec![]
124 }
125 fn collect_type_refs(&self, _fragment: &mut ExtractionFragment) {}
126}