feat: integrate package manager with module loader
- Auto-add .lux_packages/ to module search paths - Find project root by looking for lux.toml - Enable importing modules from installed packages Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
167
src/modules.rs
167
src/modules.rs
@@ -72,20 +72,30 @@ pub struct ModuleLoader {
|
||||
|
||||
impl ModuleLoader {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
let mut loader = Self {
|
||||
search_paths: vec![PathBuf::from(".")],
|
||||
cache: HashMap::new(),
|
||||
loading: HashSet::new(),
|
||||
}
|
||||
};
|
||||
|
||||
// Add package paths if in a project with lux.toml
|
||||
loader.add_package_paths();
|
||||
|
||||
loader
|
||||
}
|
||||
|
||||
/// Create a loader with custom search paths
|
||||
pub fn with_paths(paths: Vec<PathBuf>) -> Self {
|
||||
Self {
|
||||
let mut loader = Self {
|
||||
search_paths: paths,
|
||||
cache: HashMap::new(),
|
||||
loading: HashSet::new(),
|
||||
}
|
||||
};
|
||||
|
||||
// Add package paths if in a project with lux.toml
|
||||
loader.add_package_paths();
|
||||
|
||||
loader
|
||||
}
|
||||
|
||||
/// Add a search path
|
||||
@@ -93,16 +103,67 @@ impl ModuleLoader {
|
||||
self.search_paths.push(path);
|
||||
}
|
||||
|
||||
/// Add package search paths from .lux_packages directory
|
||||
fn add_package_paths(&mut self) {
|
||||
// Find project root by looking for lux.toml
|
||||
if let Some(project_root) = Self::find_project_root() {
|
||||
let packages_dir = project_root.join(".lux_packages");
|
||||
|
||||
if packages_dir.exists() {
|
||||
// Add the packages directory itself
|
||||
self.search_paths.push(packages_dir.clone());
|
||||
|
||||
// Add each installed package directory
|
||||
if let Ok(entries) = fs::read_dir(&packages_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
self.search_paths.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the project root by looking for lux.toml
|
||||
fn find_project_root() -> Option<PathBuf> {
|
||||
let mut current = std::env::current_dir().ok()?;
|
||||
|
||||
loop {
|
||||
if current.join("lux.toml").exists() {
|
||||
return Some(current);
|
||||
}
|
||||
|
||||
if !current.pop() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a module path to a file path
|
||||
fn resolve_path(&self, module_path: &str) -> Option<PathBuf> {
|
||||
// Convert module path (e.g., "std/list") to file path (e.g., "std/list.lux")
|
||||
let relative_path = format!("{}.lux", module_path);
|
||||
|
||||
for search_path in &self.search_paths {
|
||||
// Try direct path (e.g., search_path/module_path.lux)
|
||||
let full_path = search_path.join(&relative_path);
|
||||
if full_path.exists() {
|
||||
return Some(full_path);
|
||||
}
|
||||
|
||||
// For packages, try module_path/lib.lux (e.g., .lux_packages/http/lib.lux)
|
||||
let lib_path = search_path.join(module_path).join("lib.lux");
|
||||
if lib_path.exists() {
|
||||
return Some(lib_path);
|
||||
}
|
||||
|
||||
// Also try module_path/src/lib.lux for common package layout
|
||||
let src_lib_path = search_path.join(module_path).join("src").join("lib.lux");
|
||||
if src_lib_path.exists() {
|
||||
return Some(src_lib_path);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
@@ -634,4 +695,102 @@ mod tests {
|
||||
|
||||
assert_eq!(format!("{}", result), "200");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_package_lib_lux_entry_point() {
|
||||
// Test that packages with lib.lux are correctly resolved
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a package structure: mypackage/lib.lux
|
||||
let pkg_dir = dir.path().join("mypackage");
|
||||
fs::create_dir_all(&pkg_dir).unwrap();
|
||||
|
||||
let lib_content = r#"
|
||||
pub fn getValue(): Int = 42
|
||||
"#;
|
||||
fs::write(pkg_dir.join("lib.lux"), lib_content).unwrap();
|
||||
|
||||
// The loader should find mypackage via lib.lux
|
||||
let mut loader = ModuleLoader::with_paths(vec![dir.path().to_path_buf()]);
|
||||
let module = loader.load_module("mypackage");
|
||||
|
||||
assert!(module.is_ok(), "Should find package via lib.lux");
|
||||
let module = module.unwrap();
|
||||
assert!(module.exports.contains("getValue"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_package_src_lib_lux_entry_point() {
|
||||
// Test that packages with src/lib.lux are correctly resolved
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a package structure: mypackage/src/lib.lux
|
||||
let src_dir = dir.path().join("anotherpackage").join("src");
|
||||
fs::create_dir_all(&src_dir).unwrap();
|
||||
|
||||
let lib_content = r#"
|
||||
pub fn compute(): Int = 100
|
||||
"#;
|
||||
fs::write(src_dir.join("lib.lux"), lib_content).unwrap();
|
||||
|
||||
// The loader should find anotherpackage via src/lib.lux
|
||||
let mut loader = ModuleLoader::with_paths(vec![dir.path().to_path_buf()]);
|
||||
let module = loader.load_module("anotherpackage");
|
||||
|
||||
assert!(module.is_ok(), "Should find package via src/lib.lux");
|
||||
let module = module.unwrap();
|
||||
assert!(module.exports.contains("compute"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lux_packages_directory_resolution() {
|
||||
use crate::interpreter::Interpreter;
|
||||
use crate::typechecker::TypeChecker;
|
||||
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
||||
// Create a lux.toml to make this a "project"
|
||||
fs::write(
|
||||
dir.path().join("lux.toml"),
|
||||
"[project]\nname = \"testproj\"\nversion = \"0.1.0\"\n[dependencies]\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create .lux_packages/mylib/lib.lux
|
||||
let pkg_dir = dir.path().join(".lux_packages").join("mylib");
|
||||
fs::create_dir_all(&pkg_dir).unwrap();
|
||||
fs::write(
|
||||
pkg_dir.join("lib.lux"),
|
||||
"pub fn helper(): Int = 999\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create main.lux that imports mylib
|
||||
let main_source = r#"
|
||||
import mylib
|
||||
|
||||
let result = mylib.helper()
|
||||
"#;
|
||||
|
||||
// Change to the project directory to test find_project_root
|
||||
let original_dir = std::env::current_dir().unwrap();
|
||||
std::env::set_current_dir(dir.path()).unwrap();
|
||||
|
||||
let mut loader = ModuleLoader::new();
|
||||
let main_path = dir.path().join("main.lux");
|
||||
let program = loader.load_source(main_source, Some(&main_path)).unwrap();
|
||||
|
||||
let mut checker = TypeChecker::new();
|
||||
checker
|
||||
.check_program_with_modules(&program, &loader)
|
||||
.unwrap();
|
||||
|
||||
let mut interp = Interpreter::new();
|
||||
let result = interp.run_with_modules(&program, &loader).unwrap();
|
||||
|
||||
// Restore original directory
|
||||
std::env::set_current_dir(original_dir).unwrap();
|
||||
|
||||
assert_eq!(format!("{}", result), "999");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user