diff --git a/rocks-bin/src/lib.rs b/rocks-bin/src/lib.rs index bd765c9..872d835 100644 --- a/rocks-bin/src/lib.rs +++ b/rocks-bin/src/lib.rs @@ -23,6 +23,7 @@ use uninstall::Uninstall; use update::Update; use upload::Upload; use url::Url; +use which::Which; pub mod build; pub mod check; @@ -51,6 +52,7 @@ pub mod unpack; pub mod update; pub mod upload; pub mod utils; +pub mod which; /// A fast and efficient Lua package manager. #[derive(Parser)] @@ -180,6 +182,6 @@ pub enum Commands { Update(Update), /// Upload a rockspec to the public rocks repository. Upload(Upload), - /// [UNIMPLEMENTED] Tell which file corresponds to a given module name. - Which, + /// Tell which file corresponds to a given module name. + Which(Which), } diff --git a/rocks-bin/src/main.rs b/rocks-bin/src/main.rs index 79ac2bd..bd111e1 100644 --- a/rocks-bin/src/main.rs +++ b/rocks-bin/src/main.rs @@ -7,7 +7,7 @@ use rocks::{ doc, download, fetch, format, info, install, install_lua, install_rockspec, list, outdated, path, pin, project, purge, remove, run, run_lua, search, test, uninstall, unpack, update, upload::{self}, - Cli, Commands, + which, Cli, Commands, }; use rocks_lib::{ config::ConfigBuilder, @@ -83,6 +83,6 @@ async fn main() { Commands::Uninstall(uninstall_data) => { uninstall::uninstall(uninstall_data, config).await.unwrap() } - Commands::Which => unimplemented!(), + Commands::Which(which_args) => which::which(which_args, config).unwrap(), } } diff --git a/rocks-bin/src/which.rs b/rocks-bin/src/which.rs new file mode 100644 index 0000000..78a3635 --- /dev/null +++ b/rocks-bin/src/which.rs @@ -0,0 +1,19 @@ +use clap::Args; +use eyre::Result; +use rocks_lib::{config::Config, package::PackageReq, rockspec::LuaModule, which}; + +#[derive(Args)] +pub struct Which { + /// The module to search for. + module: LuaModule, + /// Only search in these packages. + packages: Option>, +} + +pub fn which(args: Which, config: Config) -> Result<()> { + let path = which::Which::new(args.module, &config) + .packages(args.packages.unwrap_or_default()) + .search()?; + print!("{}", path.display()); + Ok(()) +} diff --git a/rocks-lib/resources/test/sample-tree/5.1/51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4-neorg@8.0.0-1/lib/bat/baz.so b/rocks-lib/resources/test/sample-tree/5.1/51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4-neorg@8.0.0-1/lib/bat/baz.so new file mode 100644 index 0000000..e69de29 diff --git a/rocks-lib/resources/test/sample-tree/5.1/51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4-neorg@8.0.0-1/src/foo/bar.lua b/rocks-lib/resources/test/sample-tree/5.1/51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4-neorg@8.0.0-1/src/foo/bar.lua new file mode 100644 index 0000000..e69de29 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 0000000..e69de29 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 0000000..e69de29 diff --git a/rocks-lib/resources/test/sample-tree/5.1/lock.json b/rocks-lib/resources/test/sample-tree/5.1/lock.json index 2243890..1f575b6 100644 --- a/rocks-lib/resources/test/sample-tree/5.1/lock.json +++ b/rocks-lib/resources/test/sample-tree/5.1/lock.json @@ -1,12 +1,12 @@ { "version": "1.0.0", "rocks": { - "d7164c4921869877c7f29fce3f14b8ea78e0aec0d0c36123a3a920a894785ea5": { + "51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4": { "name": "neorg", "version": "8.0.0-1", "pinned": false, "dependencies": [ - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769" ], "constraint": null, "binaries": [], @@ -16,7 +16,7 @@ "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" } }, - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0": { + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769": { "name": "lua-cjson", "version": "2.1.0-1", "pinned": false, @@ -29,12 +29,12 @@ "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" } }, - "aa0a5bcd396f3b8e59fa9dd8059a06c3bf4952f48473214d96fc46895edb59f0": { + "760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d": { "name": "neorg", "version": "8.8.1-1", "pinned": false, "dependencies": [ - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769" ], "binaries": [], "constraint": null, @@ -46,7 +46,7 @@ } }, "entrypoints": [ - "d7164c4921869877c7f29fce3f14b8ea78e0aec0d0c36123a3a920a894785ea5", - "aa0a5bcd396f3b8e59fa9dd8059a06c3bf4952f48473214d96fc46895edb59f0" + "51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4", + "760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d" ] } diff --git a/rocks-lib/src/lib.rs b/rocks-lib/src/lib.rs index 1a2eaac..74cf694 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 cef02c3..50190c6 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/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap index 7e2dd6d..61d40a4 100644 --- a/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap +++ b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap @@ -1,23 +1,11 @@ --- source: rocks-lib/src/lockfile/mod.rs +assertion_line: 865 expression: lockfile --- { "version": "1.0.0", "rocks": { - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0": { - "name": "lua-cjson", - "version": "2.1.0-1", - "pinned": false, - "dependencies": [], - "constraint": null, - "binaries": [], - "source": "luarocks_rockspec+https://luarocks.org/", - "hashes": { - "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", - "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" - } - }, "49667dff980cc30d5c45496ad9b2af4cecf4259f3dab55a76f6aa7ab6cdeadb7": { "name": "test1", "version": "0.1.0-1", @@ -33,25 +21,27 @@ expression: lockfile "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" } }, - "a9f137d1dad1af603e33935a3f8722dfbb2aebeac03bec5ed0b6e9cc5828c7f3": { - "name": "test2", - "version": "0.1.0-1", - "pinned": true, - "dependencies": [], - "constraint": ">=1.0.0", + "51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4": { + "name": "neorg", + "version": "8.0.0-1", + "pinned": false, + "dependencies": [ + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769" + ], + "constraint": null, "binaries": [], - "source": "test+foo_bar", + "source": "luarocks_rockspec+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" } }, - "aa0a5bcd396f3b8e59fa9dd8059a06c3bf4952f48473214d96fc46895edb59f0": { + "760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d": { "name": "neorg", "version": "8.8.1-1", "pinned": false, "dependencies": [ - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769" ], "constraint": null, "binaries": [], @@ -61,13 +51,24 @@ expression: lockfile "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" } }, - "d7164c4921869877c7f29fce3f14b8ea78e0aec0d0c36123a3a920a894785ea5": { - "name": "neorg", - "version": "8.0.0-1", + "a9f137d1dad1af603e33935a3f8722dfbb2aebeac03bec5ed0b6e9cc5828c7f3": { + "name": "test2", + "version": "0.1.0-1", + "pinned": true, + "dependencies": [], + "constraint": ">=1.0.0", + "binaries": [], + "source": "test+foo_bar", + "hashes": { + "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", + "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" + } + }, + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769": { + "name": "lua-cjson", + "version": "2.1.0-1", "pinned": false, - "dependencies": [ - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" - ], + "dependencies": [], "constraint": null, "binaries": [], "source": "luarocks_rockspec+https://luarocks.org/", @@ -78,7 +79,7 @@ expression: lockfile } }, "entrypoints": [ - "aa0a5bcd396f3b8e59fa9dd8059a06c3bf4952f48473214d96fc46895edb59f0", - "d7164c4921869877c7f29fce3f14b8ea78e0aec0d0c36123a3a920a894785ea5" + "51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4", + "760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d" ] } diff --git a/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap index 6676a69..c758429 100644 --- a/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap +++ b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap @@ -1,15 +1,18 @@ --- source: rocks-lib/src/lockfile/mod.rs +assertion_line: 817 expression: lockfile --- { "version": "1.0.0", "rocks": { - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0": { - "name": "lua-cjson", - "version": "2.1.0-1", + "51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4": { + "name": "neorg", + "version": "8.0.0-1", "pinned": false, - "dependencies": [], + "dependencies": [ + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769" + ], "constraint": null, "binaries": [], "source": "luarocks_rockspec+https://luarocks.org/", @@ -18,12 +21,12 @@ expression: lockfile "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" } }, - "aa0a5bcd396f3b8e59fa9dd8059a06c3bf4952f48473214d96fc46895edb59f0": { + "760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d": { "name": "neorg", "version": "8.8.1-1", "pinned": false, "dependencies": [ - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769" ], "constraint": null, "binaries": [], @@ -33,13 +36,11 @@ expression: lockfile "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" } }, - "d7164c4921869877c7f29fce3f14b8ea78e0aec0d0c36123a3a920a894785ea5": { - "name": "neorg", - "version": "8.0.0-1", + "e7c4c9fcd3cb6fe33c1c0a2ab8cdbbe8ee81d11334277d1f5631c80317770769": { + "name": "lua-cjson", + "version": "2.1.0-1", "pinned": false, - "dependencies": [ - "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" - ], + "dependencies": [], "constraint": null, "binaries": [], "source": "luarocks_rockspec+https://luarocks.org/", @@ -50,7 +51,7 @@ expression: lockfile } }, "entrypoints": [ - "aa0a5bcd396f3b8e59fa9dd8059a06c3bf4952f48473214d96fc46895edb59f0", - "d7164c4921869877c7f29fce3f14b8ea78e0aec0d0c36123a3a920a894785ea5" + "51c01417a2b1c1269e2ce71e688feb25bb7ea36c090b78f2e07df991ea8f42e4", + "760d318b1b0581e098a6c97ca1504a7bb2c15f259909c89ec4c5ad7fa46a155d" ] } diff --git a/rocks-lib/src/rockspec/build/builtin.rs b/rocks-lib/src/rockspec/build/builtin.rs index f17aee1..c1cfd00 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 d309801..2b78491 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 0000000..58020b4 --- /dev/null +++ b/rocks-lib/src/which/mod.rs @@ -0,0 +1,163 @@ +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" + ); + let result = Which::new(LuaModule::from_str("foo.bar").unwrap(), &config) + .package("lua-cjson".parse().unwrap()) + .search(); + assert!(matches!(result, Err(WhichError::ModuleNotFound(_)))); + let result = Which::new(LuaModule::from_str("foo.bar").unwrap(), &config) + .package("neorg@8.1.1-1".parse().unwrap()) + .search(); + assert!(matches!(result, Err(WhichError::ModuleNotFound(_)))); + let result = Which::new(LuaModule::from_str("foo.bar").unwrap(), &config) + .package("neorg@8.0.0-1".parse().unwrap()) + .search(); + assert!(result.is_ok()); + } +}