context_creator/core/semantic/
resolver.rs1use crate::utils::error::ContextCreatorError;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, PartialEq)]
8pub struct ResolvedPath {
9 pub path: PathBuf,
11 pub is_external: bool,
13 pub confidence: f32,
15}
16
17pub trait ModuleResolver: Send + Sync {
19 fn resolve_import(
21 &self,
22 module_path: &str,
23 from_file: &Path,
24 base_dir: &Path,
25 ) -> Result<ResolvedPath, ContextCreatorError>;
26
27 fn get_file_extensions(&self) -> Vec<&'static str>;
29
30 fn is_external_module(&self, module_path: &str) -> bool {
32 module_path.starts_with('@') || !module_path.starts_with('.') || module_path.contains("node_modules") ||
36 module_path.contains("site-packages") ||
37 module_path.contains("vendor")
38 }
39}
40
41pub struct ResolverUtils;
43
44impl ResolverUtils {
45 pub fn find_with_extensions(base_path: &Path, extensions: &[&str]) -> Option<PathBuf> {
47 if base_path.exists() && base_path.is_file() {
49 return Some(base_path.to_path_buf());
50 }
51
52 for ext in extensions {
54 let with_ext = base_path.with_extension(ext);
55 if with_ext.exists() && with_ext.is_file() {
56 return Some(with_ext);
57 }
58 }
59
60 if base_path.exists() && base_path.is_dir() {
62 for index_name in &["index", "mod", "__init__"] {
63 for ext in extensions {
64 let index_path = base_path.join(format!("{index_name}.{ext}"));
65 if index_path.exists() && index_path.is_file() {
66 return Some(index_path);
67 }
68 }
69 }
70 }
71
72 None
73 }
74
75 pub fn module_to_path(module_path: &str) -> PathBuf {
77 PathBuf::from(module_path.replace('.', "/").replace("::", "/"))
78 }
79
80 pub fn resolve_relative(
82 import_path: &str,
83 from_file: &Path,
84 extensions: &[&str],
85 ) -> Option<PathBuf> {
86 let from_dir = from_file.parent()?;
87
88 let clean_path = import_path
90 .trim_start_matches("./")
91 .trim_start_matches("../");
92
93 let mut current_dir = from_dir.to_path_buf();
94
95 let up_count = import_path.matches("../").count();
97 for _ in 0..up_count {
98 current_dir = current_dir.parent()?.to_path_buf();
99 }
100
101 let target = current_dir.join(clean_path);
102 Self::find_with_extensions(&target, extensions)
103 }
104
105 pub fn is_within_project(path: &Path, base_dir: &Path) -> bool {
107 path.canonicalize()
108 .ok()
109 .and_then(|p| base_dir.canonicalize().ok().map(|b| p.starts_with(b)))
110 .unwrap_or(false)
111 }
112}