diff --git a/rocks-lib/resources/test/sample-tree/5.1/760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d-neorg@8.8.1-1/lib/bat/baz.so b/rocks-lib/resources/test/sample-tree/5.1/760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d-neorg@8.8.1-1/lib/bat/baz.so new file mode 100644 index 00000000..e69de29b diff --git a/rocks-lib/resources/test/sample-tree/5.1/760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d-neorg@8.8.1-1/src/foo/bar.lua b/rocks-lib/resources/test/sample-tree/5.1/760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d-neorg@8.8.1-1/src/foo/bar.lua new file mode 100644 index 00000000..e69de29b diff --git a/rocks-lib/src/lib.rs b/rocks-lib/src/lib.rs index 1a2eaacb..74cf6944 100644 --- a/rocks-lib/src/lib.rs +++ b/rocks-lib/src/lib.rs @@ -14,6 +14,7 @@ pub mod remote_package_db; pub mod rockspec; pub mod tree; pub mod upload; +pub mod which; pub(crate) mod remote_package_source; diff --git a/rocks-lib/src/lockfile/mod.rs b/rocks-lib/src/lockfile/mod.rs index cef02c39..50190c60 100644 --- a/rocks-lib/src/lockfile/mod.rs +++ b/rocks-lib/src/lockfile/mod.rs @@ -524,6 +524,19 @@ impl Lockfile

{ .cloned() } + /// Find all rocks that match the requirement + pub(crate) fn find_rocks(&self, req: &PackageReq) -> Vec { + match self.list().get(req.name()) { + Some(packages) => packages + .iter() + .rev() + .filter(|package| req.version_req().matches(package.version())) + .map(|package| package.id()) + .collect_vec(), + None => Vec::default(), + } + } + /// Validate the integrity of an installed package with the entry in this lockfile. pub(crate) fn validate_integrity( &self, diff --git a/rocks-lib/src/rockspec/build/builtin.rs b/rocks-lib/src/rockspec/build/builtin.rs index f17aee18..c1cfd00a 100644 --- a/rocks-lib/src/rockspec/build/builtin.rs +++ b/rocks-lib/src/rockspec/build/builtin.rs @@ -22,14 +22,22 @@ pub struct LuaModule(String); impl LuaModule { pub fn to_lua_path(&self) -> PathBuf { - self.to_pathbuf(".lua") + self.to_file_path(".lua") + } + + pub fn to_lua_init_path(&self) -> PathBuf { + self.to_path_buf().join("init.lua") } pub fn to_lib_path(&self) -> PathBuf { - self.to_pathbuf(&format!(".{}", lua_lib_extension())) + self.to_file_path(&format!(".{}", lua_lib_extension())) + } + + fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.0.replace('.', std::path::MAIN_SEPARATOR_STR)) } - fn to_pathbuf(&self, extension: &str) -> PathBuf { + fn to_file_path(&self, extension: &str) -> PathBuf { PathBuf::from(self.0.replace('.', std::path::MAIN_SEPARATOR_STR) + extension) } diff --git a/rocks-lib/src/tree/mod.rs b/rocks-lib/src/tree/mod.rs index d3098019..2b784911 100644 --- a/rocks-lib/src/tree/mod.rs +++ b/rocks-lib/src/tree/mod.rs @@ -138,23 +138,12 @@ impl Tree { } pub fn match_rocks(&self, req: &PackageReq) -> io::Result { - match self.list()?.get(req.name()) { - Some(packages) => { - let mut found_packages = packages - .iter() - .rev() - .filter(|package| req.version_req().matches(package.version())) - .map(|package| package.id()) - .collect_vec(); - - Ok(match found_packages.len() { - 0 => RockMatches::NotFound(req.clone()), - 1 => RockMatches::Single(found_packages.pop().unwrap()), - 2.. => RockMatches::Many(found_packages), - }) - } - None => Ok(RockMatches::NotFound(req.clone())), - } + let mut found_packages = self.lockfile()?.find_rocks(req); + Ok(match found_packages.len() { + 0 => RockMatches::NotFound(req.clone()), + 1 => RockMatches::Single(found_packages.pop().unwrap()), + 2.. => RockMatches::Many(found_packages), + }) } pub fn match_rocks_and(&self, req: &PackageReq, filter: F) -> io::Result diff --git a/rocks-lib/src/which/mod.rs b/rocks-lib/src/which/mod.rs new file mode 100644 index 00000000..bcf55695 --- /dev/null +++ b/rocks-lib/src/which/mod.rs @@ -0,0 +1,151 @@ +use std::{io, path::PathBuf}; + +use bon::{builder, Builder}; +use itertools::Itertools; +use thiserror::Error; + +use crate::{ + config::{Config, LuaVersion, LuaVersionUnset}, + package::PackageReq, + rockspec::LuaModule, + tree::Tree, +}; + +/// A rocks module finder. +#[derive(Builder)] +#[builder(start_fn = new, finish_fn(name = _build, vis = ""))] +pub struct Which<'a> { + #[builder(start_fn)] + module: LuaModule, + #[builder(start_fn)] + config: &'a Config, + #[builder(field)] + packages: Vec, +} + +impl WhichBuilder<'_, State> +where + State: which_builder::State, +{ + pub fn package(mut self, package: PackageReq) -> Self { + self.packages.push(package); + self + } + + pub fn packages(mut self, packages: impl IntoIterator) -> Self { + self.packages.extend(packages); + self + } + + pub fn search(self) -> Result + where + State: which_builder::IsComplete, + { + do_search(self._build()) + } +} + +#[derive(Error, Debug)] +pub enum WhichError { + #[error(transparent)] + Io(#[from] io::Error), + #[error(transparent)] + LuaVersionUnset(#[from] LuaVersionUnset), + #[error("lua module {0} not found.")] + ModuleNotFound(LuaModule), +} + +fn do_search(which: Which<'_>) -> Result { + let config = which.config; + let tree = Tree::new(config.tree().clone(), LuaVersion::from(config)?)?; + let lockfile = tree.lockfile()?; + let local_packages = if which.packages.is_empty() { + lockfile + .list() + .into_iter() + .flat_map(|(_, pkgs)| pkgs) + .collect_vec() + } else { + which + .packages + .iter() + .flat_map(|req| { + lockfile + .find_rocks(req) + .into_iter() + .map(|id| lockfile.get(&id).unwrap()) + .cloned() + .collect_vec() + }) + .collect_vec() + }; + local_packages + .into_iter() + .filter_map(|pkg| { + let rock_layout = tree.rock_layout(&pkg); + let lib_path = rock_layout.lib.join(which.module.to_lib_path()); + if lib_path.is_file() { + return Some(lib_path); + } + let lua_path = rock_layout.src.join(which.module.to_lua_path()); + if lua_path.is_file() { + return Some(lua_path); + } + let lua_path = rock_layout.src.join(which.module.to_lua_init_path()); + if lua_path.is_file() { + return Some(lua_path); + } + None + }) + .next() + .ok_or(WhichError::ModuleNotFound(which.module)) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::config::{ConfigBuilder, LuaVersion}; + use assert_fs::prelude::PathCopy; + use std::{path::PathBuf, str::FromStr}; + + #[tokio::test] + async fn test_which() { + let tree_path = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-tree"); + let temp = assert_fs::TempDir::new().unwrap(); + temp.copy_from(&tree_path, &["**"]).unwrap(); + let tree_path = temp.to_path_buf(); + let config = ConfigBuilder::new() + .tree(Some(tree_path.clone())) + .lua_version(Some(LuaVersion::Lua51)) + .build() + .unwrap(); + + let result = Which::new(LuaModule::from_str("foo.bar").unwrap(), &config) + .search() + .unwrap(); + assert_eq!(result.file_name().unwrap().to_string_lossy(), "bar.lua"); + assert_eq!( + result + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy(), + "foo" + ); + let result = Which::new(LuaModule::from_str("bat.baz").unwrap(), &config) + .search() + .unwrap(); + assert_eq!(result.file_name().unwrap().to_string_lossy(), "baz.so"); + assert_eq!( + result + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy(), + "bat" + ); + } +}