diff --git a/src/modules.rs b/src/modules.rs index 7044c45..9d40bd8 100644 --- a/src/modules.rs +++ b/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) -> 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 { + 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 { // 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"); + } }