use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct ModulePathIndex {
module_to_file: HashMap<String, String>,
file_to_module: HashMap<String, String>,
}
impl ModulePathIndex {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, module_path: &str, file_path: &str) {
self.module_to_file
.insert(module_path.to_string(), file_path.to_string());
self.file_to_module
.insert(file_path.to_string(), module_path.to_string());
}
pub fn resolve(&self, module_path: &str) -> Option<String> {
self.module_to_file.get(module_path).cloned()
}
pub fn get_module_path(&self, file_path: &str) -> Option<String> {
self.file_to_module.get(file_path).cloned()
}
fn get_current_module(&self, file_path: &str) -> Option<String> {
self.file_to_module.get(file_path).cloned()
}
fn get_parent_module(module_path: &str) -> Option<String> {
module_path
.rfind("::")
.map(|last_colon_pos| module_path[..last_colon_pos].to_string())
}
}
pub fn resolve_module_path(
index: &ModulePathIndex,
current_file: &str,
module_path: &str,
) -> Option<String> {
if module_path == "self" {
return Some(current_file.to_string());
}
if let Some(rest) = module_path.strip_prefix("self::") {
if rest.is_empty() {
return Some(current_file.to_string());
}
return Some(current_file.to_string());
}
if module_path == "super" {
let current_module = index.get_current_module(current_file)?;
let parent_module = ModulePathIndex::get_parent_module(¤t_module)?;
return index.resolve(&parent_module);
}
if module_path.starts_with("super::") {
let current_module = index.get_current_module(current_file)?;
let mut target_module = current_module;
let mut super_count = 0;
for part in module_path.split("::") {
if part == "super" {
super_count += 1;
} else {
break;
}
}
for _ in 0..super_count {
match ModulePathIndex::get_parent_module(&target_module) {
Some(parent) => target_module = parent,
None => return None,
}
}
let remaining_parts: Vec<&str> = module_path.split("::").skip(super_count).collect();
if remaining_parts.is_empty() || remaining_parts.iter().all(|s| s.is_empty()) {
return index.resolve(&target_module);
}
let full_path = format!("{}::{}", target_module, remaining_parts.join("::"));
return index.resolve(&full_path);
}
index.resolve(module_path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_parent_module() {
assert_eq!(
ModulePathIndex::get_parent_module("crate::foo::bar"),
Some("crate::foo".to_string())
);
assert_eq!(
ModulePathIndex::get_parent_module("crate::foo"),
Some("crate".to_string())
);
assert_eq!(ModulePathIndex::get_parent_module("crate"), None);
}
}