Skip to main content

codesynapse_core/ts_extract/
sln.rs

1use 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        // Parse Project("...") = "Name", "Path", "{GUID}"
21        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        // Parse ProjectDependencies = postProject sections for dependency edges
76        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}