Skip to content

Commit

Permalink
feat(cargo-binstall): implement cargo-binstall detection
Browse files Browse the repository at this point in the history
Fixes #372.
  • Loading branch information
tversteeg committed Feb 1, 2024
1 parent e12fb51 commit a56f765
Show file tree
Hide file tree
Showing 24 changed files with 148 additions and 34 deletions.
12 changes: 3 additions & 9 deletions src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,9 @@ impl Packages {
.split(|c| c == ';' || c == '|' || c == '&' || c == '\r' || c == '\n')
// Then try to find the proper package manager for each line, this also filters out
// lines that are not related to the package manager
.filter_map(|line| {
// Filter out matches with less than 4 characters, it's impossible to install a
// package that we can catch like that
if line.len() < 4 {
return None;
}

// Attempt to find a matching package manager with the line
PackageManager::from_line(line).map(|manager| (line, manager))
.flat_map(|line| {
// Attempt to find a matching package managers with the line
PackageManager::from_line_iter(line).map(move |manager| (line, manager))
})
// Parse the packages in the line with the package manager supplied
.flat_map(|(line, package_manager)| package_manager.catch(line))
Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/apt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("apt install test").unwrap();
let manager = PackageManager::single_from_line("apt install test").unwrap();
assert_eq!(manager, PackageManager::from(Apt));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/brew.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("brew install test").unwrap();
let manager = PackageManager::single_from_line("brew install test").unwrap();
assert_eq!(manager, PackageManager::from(Brew));
}

Expand Down
5 changes: 3 additions & 2 deletions src/package_manager/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("cargo install test").unwrap();
assert_eq!(manager, PackageManager::from(Cargo));
assert!(PackageManager::from_line_iter("cargo binstall test")
.any(|manager| manager == PackageManager::from(Cargo)));
}

#[test]
Expand All @@ -82,6 +82,7 @@ mod tests {
catch!(PackageManager::from(Cargo), "cargo uninstall test test2" => ());
catch!(PackageManager::from(Cargo), "cargo install ." => ());
catch!(PackageManager::from(Cargo), "cargo install --path test" => ());
catch!(PackageManager::from(Cargo), "cargo binstall test" => ());

// Flags that should be captured
catch!(PackageManager::from(Cargo), "cargo install --git https://test.com/test.git" => "https://test.com/test.git" ["--git"]);
Expand Down
102 changes: 102 additions & 0 deletions src/package_manager/cargo_binstall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use super::{CaptureFlag, PackageInstalledMethod, PackageManagerTrait};
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct CargoBinstall;

impl PackageManagerTrait for CargoBinstall {
fn full_name(self) -> &'static str {
"Cargo B(inary)Install"
}

fn commands(self) -> Vec<&'static str> {
vec!["cargo"]
}

fn sub_commands(self) -> Vec<&'static str> {
vec!["binstall"]
}

fn install_command(self) -> &'static str {
"cargo binstall --quiet"
}

fn needs_root(self) -> bool {
false
}

#[cfg(not(target_os = "windows"))]
fn is_installed(self, package: &str) -> PackageInstalledMethod {
PackageInstalledMethod::Script(format!(
"cargo install --list | grep 'v[0-9]' | grep -q {}",
package
))
}
#[cfg(target_os = "windows")]
fn is_installed(self, package: &str) -> PackageInstalledMethod {
PackageInstalledMethod::Script(format!("cargo install --list | findstr {}", package))
}

fn known_flags_with_values(self) -> Vec<&'static str> {
vec![
"--log-level",
"--github-token",
"--root-certificates",
"--min-tls-version",
"--registry",
"--index",
"--root",
"--install-path",
"--disable-strategies",
"--strategies",
"--rate-limit",
"--pkg-url",
"--pkg-fmt",
"--bin-dir",
"--manifest-path",
]
}

fn capture_flags(self) -> Vec<CaptureFlag> {
vec![
CaptureFlag::Single("--git"),
CaptureFlag::DynamicValue("--version"),
CaptureFlag::DynamicValue("--targets"),
]
}

fn invalidating_flags(self) -> Vec<&'static str> {
vec![]
}
}

#[cfg(test)]
mod tests {
use super::CargoBinstall;
use crate::{catch, package_manager::PackageManager};

#[test]
fn test_package_manager() {
assert!(PackageManager::from_line_iter("cargo binstall test")
.any(|manager| manager == PackageManager::from(CargoBinstall)));
}

#[test]
fn test_catch() {
// Regular invocation
catch!(PackageManager::from(CargoBinstall), "cargo binstall test" => "test");
catch!(PackageManager::from(CargoBinstall), "cargo binstall [email protected]" => "[email protected]");

// Multiple
catch!(PackageManager::from(CargoBinstall), "cargo binstall test test2" => "test", "test2");
catch!(PackageManager::from(CargoBinstall), "cargo binstall test install test-install" => "test", "install", "test-install");

// Shouldn't match
catch!(PackageManager::from(CargoBinstall), "cargo uninstall test test2" => ());
catch!(PackageManager::from(CargoBinstall), "cargo binstall ." => ());
catch!(PackageManager::from(CargoBinstall), "cargo install test" => ());

// Flags that should be captured
catch!(PackageManager::from(CargoBinstall), "cargo binstall --git https://test.com/test.git" => "https://test.com/test.git" ["--git"]);
}
}
2 changes: 1 addition & 1 deletion src/package_manager/chocolatey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("choco install test").unwrap();
let manager = PackageManager::single_from_line("choco install test").unwrap();
assert_eq!(manager, PackageManager::from(Chocolatey));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/dnf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("dnf install test").unwrap();
let manager = PackageManager::single_from_line("dnf install test").unwrap();
assert_eq!(manager, PackageManager::from(Dnf));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/gem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("gem install test").unwrap();
let manager = PackageManager::single_from_line("gem install test").unwrap();
assert_eq!(manager, PackageManager::from(Gem));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("go get test").unwrap();
let manager = PackageManager::single_from_line("go get test").unwrap();
assert_eq!(manager, PackageManager::from(Go));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/guix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("guix install emplace").unwrap();
let manager = PackageManager::single_from_line("guix install emplace").unwrap();
assert_eq!(manager, PackageManager::from(Guix));
}

Expand Down
3 changes: 3 additions & 0 deletions src/package_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod test_macro;
mod apt;
mod brew;
mod cargo;
mod cargo_binstall;
mod chocolatey;
mod dnf;
mod gem;
Expand All @@ -25,6 +26,7 @@ mod zypper;
pub use apt::Apt;
pub use brew::Brew;
pub use cargo::Cargo;
pub use cargo_binstall::CargoBinstall;
pub use chocolatey::Chocolatey;
pub use dnf::Dnf;
pub use gem::Gem;
Expand Down Expand Up @@ -56,6 +58,7 @@ pub enum PackageManager {
Apt,
Brew,
Cargo,
CargoBinstall,
Chocolatey,
Dnf,
Gem,
Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/nix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("nix-env -iA nixos.test").unwrap();
let manager = PackageManager::single_from_line("nix-env -iA nixos.test").unwrap();
assert_eq!(manager, PackageManager::from(Nix));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("npm install test").unwrap();
let manager = PackageManager::single_from_line("npm install test").unwrap();
assert_eq!(manager, PackageManager::from(Npm));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/pacman.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("pacman -S test").unwrap();
let manager = PackageManager::single_from_line("pacman -S test").unwrap();
assert_eq!(manager, PackageManager::from(Pacman));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/pip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("pip install test").unwrap();
let manager = PackageManager::single_from_line("pip install test").unwrap();
assert_eq!(manager, PackageManager::from(Pip));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/pip3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("pip3 install test").unwrap();
let manager = PackageManager::single_from_line("pip3 install test").unwrap();
assert_eq!(manager, PackageManager::from(Pip3));
}

Expand Down
4 changes: 2 additions & 2 deletions src/package_manager/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("pkg install test").unwrap();
let manager = PackageManager::single_from_line("pkg install test").unwrap();
assert_eq!(manager, PackageManager::from(Pkg));

assert!(PackageManager::from_line("qpkg install test").is_none());
assert!(PackageManager::single_from_line("qpkg install test").is_none());
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/rua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("rua install test").unwrap();
let manager = PackageManager::single_from_line("rua install test").unwrap();
assert_eq!(manager, PackageManager::from(Rua));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/rustup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("rustup component add test").unwrap();
let manager = PackageManager::single_from_line("rustup component add test").unwrap();
assert_eq!(manager, PackageManager::from(Rustup));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/scoop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("scoop install test").unwrap();
let manager = PackageManager::single_from_line("scoop install test").unwrap();
assert_eq!(manager, PackageManager::from(Scoop));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/snap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("snap install test").unwrap();
let manager = PackageManager::single_from_line("snap install test").unwrap();
assert_eq!(manager, PackageManager::from(Snap));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/yay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("yay -S test").unwrap();
let manager = PackageManager::single_from_line("yay -S test").unwrap();
assert_eq!(manager, PackageManager::from(Yay));
}

Expand Down
2 changes: 1 addition & 1 deletion src/package_manager/zypper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod tests {

#[test]
fn test_package_manager() {
let manager = PackageManager::from_line("zypper install test").unwrap();
let manager = PackageManager::single_from_line("zypper install test").unwrap();
assert_eq!(manager, PackageManager::from(Zypper));
}

Expand Down
20 changes: 17 additions & 3 deletions src/package_manager_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use strum::IntoEnumIterator;
impl PackageManager {
/// Whether the line contains a package manager.
pub fn detects_line(line: &str) -> bool {
Self::from_line(line).is_some()
Self::single_from_line(line).is_some()
}

/// Try to find the proper package manager corresponding to a line.
pub fn from_line(line: &str) -> Option<Self> {
/// Try to find the best matching package manager corresponding to a line.
pub fn single_from_line(line: &str) -> Option<Self> {
// Iterate over all enum variations
Self::iter().find(|manager| {
// Iterate over all commands
Expand All @@ -28,6 +28,20 @@ impl PackageManager {
})
}

/// Get all possible package manager corresponding to a line.
pub fn from_line_iter(line: &str) -> impl Iterator<Item = Self> + '_ {
// Iterate over all enum variations
Self::iter().filter(move |manager| {
// Iterate over all commands
manager
.os_commands()
.into_iter()
// Find the command that's in the file, use an extra space to only match full
// package names
.any(|command| Self::line_contains_command(line, &command))
})
}

/// Check whether a package is already installed.
pub fn package_is_installed(self, package: &Package) -> Result<bool> {
match self.is_installed(package.name()) {
Expand Down

0 comments on commit a56f765

Please sign in to comment.