use crate::utils::error::ContextCreatorError;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedPath {
pub path: PathBuf,
pub is_external: bool,
pub confidence: f32,
}
pub trait ModuleResolver: Send + Sync {
fn resolve_import(
&self,
module_path: &str,
from_file: &Path,
base_dir: &Path,
) -> Result<ResolvedPath, ContextCreatorError>;
fn get_file_extensions(&self) -> Vec<&'static str>;
fn is_external_module(&self, module_path: &str) -> bool {
module_path.starts_with('@') || !module_path.starts_with('.') || module_path.contains("node_modules") ||
module_path.contains("site-packages") ||
module_path.contains("vendor")
}
}
pub struct ResolverUtils;
impl ResolverUtils {
pub fn find_with_extensions(base_path: &Path, extensions: &[&str]) -> Option<PathBuf> {
if base_path.exists() && base_path.is_file() {
return Some(base_path.to_path_buf());
}
for ext in extensions {
let with_ext = base_path.with_extension(ext);
if with_ext.exists() && with_ext.is_file() {
return Some(with_ext);
}
}
if base_path.exists() && base_path.is_dir() {
for index_name in &["index", "mod", "__init__"] {
for ext in extensions {
let index_path = base_path.join(format!("{index_name}.{ext}"));
if index_path.exists() && index_path.is_file() {
return Some(index_path);
}
}
}
}
None
}
pub fn module_to_path(module_path: &str) -> PathBuf {
PathBuf::from(module_path.replace('.', "/").replace("::", "/"))
}
pub fn resolve_relative(
import_path: &str,
from_file: &Path,
extensions: &[&str],
) -> Option<PathBuf> {
let from_dir = from_file.parent()?;
let clean_path = import_path
.trim_start_matches("./")
.trim_start_matches("../");
let mut current_dir = from_dir.to_path_buf();
let up_count = import_path.matches("../").count();
for _ in 0..up_count {
current_dir = current_dir.parent()?.to_path_buf();
}
let target = current_dir.join(clean_path);
Self::find_with_extensions(&target, extensions)
}
pub fn is_within_project(path: &Path, base_dir: &Path) -> bool {
path.canonicalize()
.ok()
.and_then(|p| base_dir.canonicalize().ok().map(|b| p.starts_with(b)))
.unwrap_or(false)
}
}