use anyhow::{Result, anyhow};
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::io::Read;
#[derive(Debug, Clone)]
pub struct ModuleLoader {
module_cache: Arc<Mutex<HashMap<String, Arc<Module>>>>,
#[allow(dead_code)]
base_dir: PathBuf,
}
#[derive(Debug, Clone)]
pub struct Module {
#[allow(dead_code)]
pub exports: HashMap<String, serde_json::Value>,
#[allow(dead_code)]
pub path: PathBuf,
}
impl ModuleLoader {
pub fn new(base_dir: PathBuf) -> Self {
Self {
module_cache: Arc::new(Mutex::new(HashMap::new())),
base_dir,
}
}
pub fn from_current_dir() -> Result<Self> {
let base_dir: _ = std::env::current_dir()
.map_err(|e| anyhow!("Failed to get current directory: {}", e))?;
Ok(Self::new(base_dir))
}
#[allow(dead_code)]
pub fn resolve_module(&self, module_name: &str) -> Result<PathBuf> {
if module_name.starts_with("./") || module_name.starts_with("../") {
return self.resolve_relative_module(module_name);
}
if module_name.starts_with('/') {
return Ok(PathBuf::from(module_name));
}
if self.is_builtin_module(module_name) {
return self.resolve_builtin_module(module_name);
}
let relative_result: _ = self.resolve_relative_module(module_name);
if relative_result.is_ok() {
return relative_result;
}
self.resolve_node_modules(module_name)
}
#[allow(dead_code)]
fn resolve_relative_module(&self, module_name: &str) -> Result<PathBuf> {
let mut path = self.base_dir.clone();
let relative_part: _ = if module_name.starts_with("./") {
&module_name[2..]
} else if module_name.starts_with("../") {
&module_name[3..]
} else {
module_name
};
path = path.join(relative_part);
if path.exists() {
if path.is_dir() {
let index_path: _ = path.join("index.js");
if index_path.exists() {
return Ok(index_path);
}
}
return Ok(path);
}
let mut js_path = path.clone();
if !path.extension().is_some() {
js_path.set_extension("js");
}
if js_path.exists() {
return Ok(js_path);
}
Err(anyhow!("Module not found: {}", module_name))
}
#[allow(dead_code)]
fn is_builtin_module(&self, module_name: &str) -> bool {
matches!(module_name, "path" | "fs" | "os" | "crypto" | "buffer")
}
#[allow(dead_code)]
fn resolve_builtin_module(&self, module_name: &str) -> Result<PathBuf> {
Ok(PathBuf::from(format!("__builtin__/{}", module_name)))
}
#[allow(dead_code)]
fn resolve_node_modules(&self, module_name: &str) -> Result<PathBuf> {
let mut current_dir = self.base_dir.clone();
loop {
let node_modules: _ = current_dir.join("node_modules").join(module_name);
if node_modules.exists() {
let package_json: _ = node_modules.join("package.json");
if package_json.exists() {
let content: _ = fs::read_to_string(&package_json)
.map_err(|e| anyhow!("Failed to read package.json: {}", e))?;
let package: serde_json::Value = serde_json::from_str(&content)
.map_err(|e| anyhow!("Failed to parse package.json: {}", e))?;
if let Some(main) = package["main"].as_str() {
let mut main_path = node_modules.clone();
main_path = main_path.join(main);
if !main_path.extension().is_some() {
main_path.set_extension("js");
}
if main_path.exists() {
return Ok(main_path);
}
}
}
let index_path: _ = node_modules.join("index.js");
if index_path.exists() {
return Ok(index_path);
}
if node_modules.is_dir() {
let index_path: _ = node_modules.join("index.js");
if index_path.exists() {
return Ok(index_path);
}
}
}
if !current_dir.pop() {
break; }
if current_dir == Path::new("/") || current_dir.parent().is_none() {
break;
}
}
Err(anyhow!("Module not found: {}", module_name))
}
#[allow(dead_code)]
pub fn load_module(&self, module_name: &str) -> Result<Arc<Module>> {
{
let cache: _ = self.module_cache.lock().unwrap();
if let Some(cached_module) = cache.get(module_name) {
return Ok(cached_module.clone());
}
}
let module_path: _ = self.resolve_module(module_name)?;
if module_name == "path" {
let builtin_module: _ = self.create_builtin_path_module();
{
let mut cache = self.module_cache.lock().unwrap();
cache.insert(module_name.to_string(), builtin_module.clone());
}
return Ok(builtin_module);
}
let content: _ = fs::read_to_string(&module_path)
.map_err(|e| anyhow!("Failed to read module file {}: {}", module_name, e))?;
let module: _ = self.parse_module_content(&content, &module_path)?;
{
let mut cache = self.module_cache.lock().unwrap();
cache.insert(module_name.to_string(), Arc::new(Mutex::new(module)));
}
let cache: _ = self.module_cache.lock().unwrap();
Ok(cache.get(module_name).unwrap().clone())
}
#[allow(dead_code)]
fn create_builtin_path_module(&self) -> Arc<Module> {
let mut exports = HashMap::new();
exports.insert(
"join".to_string(),
serde_json::Value::String("function".to_string()),
);
exports.insert(
"resolve".to_string(),
serde_json::Value::String("function".to_string()),
);
exports.insert(
"dirname".to_string(),
serde_json::Value::String("function".to_string()),
);
exports.insert(
"basename".to_string(),
serde_json::Value::String("function".to_string()),
);
exports.insert(
"extname".to_string(),
serde_json::Value::String("function".to_string()),
);
Arc::new(Mutex::new(Module {
exports,
path: PathBuf::from("__builtin__/path"),
}))
}
#[allow(dead_code)]
fn parse_module_content(&self, content: &str, _path: &Path) -> Result<Module> {
let mut exports = HashMap::new();
if let Some(pos) = content.find("module.exports") {
let rest: _ = &content[pos + "module.exports".len()..];
if let Some(_equals_pos) = rest.find('=') {
exports.insert(
"default".to_string(),
serde_json::Value::Object(serde_json::Map::new()),
);
}
}
let lines: Vec<&str> = content.lines().collect();
for line in lines {
let trimmed: _ = line.trim();
if trimmed.starts_with("exports.") {
if let Some(equals_pos) = trimmed.find('=') {
let export_name: _ = &trimmed["exports.".len()..equals_pos].trim();
let _value_str: _ = &trimmed[equals_pos + 1..].trim();
let value: _ = serde_json::Value::String(_value_str.to_string());
exports.insert(export_name.to_string(), value);
}
}
}
Ok(Module {
exports,
path: _path.to_path_buf(),
})
}
#[allow(dead_code)]
pub fn clear_cache(&self) {
let mut cache = self.module_cache.lock().unwrap();
cache.clear();
}
#[allow(dead_code)]
pub fn get_cached_modules(&self) -> Vec<String> {
let cache: _ = self.module_cache.lock().unwrap();
cache.keys().cloned().collect()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_loader_creation() {
let temp_dir: _ = TempDir::new().unwrap();
let loader: _ = ModuleLoader::new(temp_dir.path().to_path_buf());
assert!(loader.module_cache.lock().unwrap().is_empty());
}
#[test]
fn test_resolve_relative_module() {
let temp_dir: _ = TempDir::new().unwrap();
let loader: _ = ModuleLoader::new(temp_dir.path().to_path_buf());
let module_file: _ = temp_dir.path().join("test.js");
std::fs::write(&module_file, "module.exports = { test: true };").unwrap();
let resolved: _ = loader.resolve_relative_module("./test.js").unwrap();
assert!(resolved.ends_with("test.js"));
}
#[test]
fn test_load_module() {
let temp_dir: _ = TempDir::new().unwrap();
let loader: _ = ModuleLoader::new(temp_dir.path().to_path_buf());
let module_file: _ = temp_dir.path().join("math.js");
std::fs::write(
&module_file,
"
exports.add = (a, b) => a + b;
exports.PI = 3.14159;
",
)
.unwrap();
let module: _ = loader.load_module("./math.js").unwrap();
assert!(module.exports.contains_key("add"));
assert!(module.exports.contains_key("PI"));
}
#[test]
fn test_module_caching() {
let temp_dir: _ = TempDir::new().unwrap();
let loader: _ = ModuleLoader::new(temp_dir.path().to_path_buf());
let module_file: _ = temp_dir.path().join("test.js");
std::fs::write(&module_file, "module.exports = { value: 42 };").unwrap();
let module1: _ = loader.load_module("./test.js").unwrap();
let module2: _ = loader.load_module("./test.js").unwrap();
assert_eq!(Arc::ptr_eq(&module1, &module2), true);
}
#[test]
fn test_builtin_module() {
let temp_dir: _ = TempDir::new().unwrap();
let loader: _ = ModuleLoader::new(temp_dir.path().to_path_buf());
let module: _ = loader.load_module("path").unwrap();
assert!(module.exports.contains_key("join"));
assert!(module.exports.contains_key("resolve"));
}
}