diff --git a/Cargo.lock b/Cargo.lock index ee0c4eba..746a5a24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1205,6 +1205,9 @@ dependencies = [ "console", "lazy_static", "linked-hash-map", + "pest", + "pest_derive", + "serde", "similar", ] @@ -1753,6 +1756,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "pest_meta" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -2097,6 +2145,7 @@ dependencies = [ "octocrab", "rocks-lib", "rustyline", + "serde_json", "spdx", "spinners", "termcolor", @@ -3000,6 +3049,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unarray" version = "0.1.4" diff --git a/rocks-bin/Cargo.toml b/rocks-bin/Cargo.toml index ac56d63c..feba0394 100644 --- a/rocks-bin/Cargo.toml +++ b/rocks-bin/Cargo.toml @@ -12,6 +12,7 @@ itertools = "0.12.1" nucleo = "0.4.1" octocrab = "0.36.0" rustyline = "14.0.0" +serde_json = "1.0.118" spdx = "0.10.4" spinners = "4.1.1" termcolor = "1.4.1" diff --git a/rocks-bin/src/list.rs b/rocks-bin/src/list.rs new file mode 100644 index 00000000..9ded0c73 --- /dev/null +++ b/rocks-bin/src/list.rs @@ -0,0 +1,43 @@ +use clap::Args; +use eyre::{OptionExt as _, Result}; +use itertools::Itertools as _; +use rocks_lib::{config::Config, tree::Tree}; +use text_trees::{FormatCharacters, StringTreeNode, TreeFormatting}; + +#[derive(Args)] +pub struct ListCmd { + #[arg(long)] + outdated: bool, + #[arg(long)] + porcelain: bool, +} + +pub fn list_installed(list_data: ListCmd, config: &Config) -> Result<()> { + let tree = Tree::new( + &config.tree, + config + .lua_version + .as_ref() + .ok_or_eyre("lua version not supplied!")?, + )?; + let available_rocks = tree.list(); + + // TODO(vhyrro): Add `outdated` support. + + if list_data.porcelain { + println!("{}", serde_json::to_string(&available_rocks)?); + } else { + let formatting = TreeFormatting::dir_tree(FormatCharacters::box_chars()); + for (name, versions) in available_rocks.into_iter().sorted() { + let mut tree = StringTreeNode::new(name.to_owned()); + + for version in versions { + tree.push(version); + } + + println!("{}", tree.to_string_with_format(&formatting)?); + } + } + + Ok(()) +} diff --git a/rocks-bin/src/main.rs b/rocks-bin/src/main.rs index c8b0823a..b0227794 100644 --- a/rocks-bin/src/main.rs +++ b/rocks-bin/src/main.rs @@ -5,6 +5,7 @@ use rocks_lib::config::{Config, LuaVersion}; mod build; mod download; +mod list; mod rockspec; mod search; mod unpack; @@ -108,7 +109,7 @@ enum Commands { /// Check syntax of a rockspec. Lint, /// List currently installed rocks. - List, + List(list::ListCmd), /// Compile package in current directory using a rockspec. Make, /// Auto-write a rockspec for a new version of the rock. @@ -176,6 +177,7 @@ async fn main() { rockspec::write_rockspec(rockspec_data).await.unwrap() } Commands::Build(build_data) => build::build(build_data, &config).unwrap(), + Commands::List(list_data) => list::list_installed(list_data, &config).unwrap(), _ => unimplemented!(), }, None => { diff --git a/rocks-lib/Cargo.toml b/rocks-lib/Cargo.toml index 069e41e2..938ed3ca 100644 --- a/rocks-lib/Cargo.toml +++ b/rocks-lib/Cargo.toml @@ -29,6 +29,7 @@ tempdir = "0.3.7" vfs = "0.12.0" walkdir = "2.4.0" zip = "0.6.6" +insta = { version = "1.39.0", features = ["redactions", "yaml"] } [dev-dependencies] httptest = { version = "0.15.5" } diff --git a/rocks-lib/src/tree/list.rs b/rocks-lib/src/tree/list.rs new file mode 100644 index 00000000..a98b1fcd --- /dev/null +++ b/rocks-lib/src/tree/list.rs @@ -0,0 +1,26 @@ +use std::collections::HashMap; + +use itertools::Itertools; +use walkdir::WalkDir; + +use super::Tree; + +impl<'a> Tree<'a> { + pub fn list(&self) -> HashMap> { + WalkDir::new(self.root()) + .min_depth(1) + .max_depth(1) + .into_iter() + .map(|rock_directory| { + let rock_dir = rock_directory.unwrap(); + let (name, version) = rock_dir + .file_name() + .to_str() + .unwrap() + .split_once('@') + .unwrap(); + (name.to_string(), version.to_string()) + }) + .into_group_map() + } +} diff --git a/rocks-lib/src/tree/mod.rs b/rocks-lib/src/tree/mod.rs index c8c95976..850fe321 100644 --- a/rocks-lib/src/tree/mod.rs +++ b/rocks-lib/src/tree/mod.rs @@ -3,6 +3,8 @@ use std::path::PathBuf; use crate::config::LuaVersion; use eyre::Result; +mod list; + /// A tree is a collection of files where installed rocks are located. /// /// `rocks` diverges from the traditional hierarchy employed by luarocks. @@ -22,6 +24,7 @@ pub struct Tree<'a> { } /// Change-agnostic way of referencing various paths for a rock. +#[derive(Debug, PartialEq)] pub struct RockLayout { pub etc: PathBuf, pub lib: PathBuf, @@ -63,3 +66,58 @@ impl<'a> Tree<'a> { Ok(RockLayout { etc, lib, src }) } } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use insta::{assert_yaml_snapshot, sorted_redaction}; + + use crate::{config::LuaVersion, tree::RockLayout}; + + use super::Tree; + + #[test] + fn rock_layout() { + let tree_path = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-tree"); + + let tree = Tree::new(&tree_path, &LuaVersion::Lua51).unwrap(); + + let neorg = tree + .rock(&"neorg".to_string(), &"8.0.0-1".to_string()) + .unwrap(); + + assert_eq!( + neorg, + RockLayout { + etc: tree_path.join("5.1/neorg@8.0.0-1/etc"), + lib: tree_path.join("5.1/neorg@8.0.0-1/lib"), + src: tree_path.join("5.1/neorg@8.0.0-1/src"), + } + ); + + let lua_cjson = tree + .rock(&"lua-cjson".to_string(), &"2.1.0.9-1".to_string()) + .unwrap(); + + assert_eq!( + lua_cjson, + RockLayout { + etc: tree_path.join("5.1/lua-cjson@2.1.0.9-1/etc"), + lib: tree_path.join("5.1/lua-cjson@2.1.0.9-1/lib"), + src: tree_path.join("5.1/lua-cjson@2.1.0.9-1/src"), + } + ); + } + + #[test] + fn tree_list() { + let tree_path = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/sample-tree"); + + let tree = Tree::new(&tree_path, &LuaVersion::Lua51).unwrap(); + + assert_yaml_snapshot!(tree.list(), { "." => sorted_redaction() }) + } +} diff --git a/rocks-lib/src/tree/snapshots/rocks_lib__tree__tests__tree_list.snap b/rocks-lib/src/tree/snapshots/rocks_lib__tree__tests__tree_list.snap new file mode 100644 index 00000000..fb20ca2b --- /dev/null +++ b/rocks-lib/src/tree/snapshots/rocks_lib__tree__tests__tree_list.snap @@ -0,0 +1,9 @@ +--- +source: rocks-lib/src/tree/mod.rs +expression: tree.list() +--- +lua-cjson: + - 2.1.0.9-1 +neorg: + - 8.8.1-1 + - 8.0.0-1