Skip to content

Commit

Permalink
Add simplistic command-line interface and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vrmiguel authored and theory committed Nov 4, 2024
1 parent 800cf6d commit 735c6ec
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 31 deletions.
95 changes: 95 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
pub mod error;
pub mod operating_system;
pub mod package_type;

use std::str::FromStr;
use std::sync::LazyLock;

use operating_system::OperatingSystem;
use purl::GenericPurl;

use crate::error::{Error, Result};
use crate::package_type::{Cargo, Generic, PackageType, Pgxn, Postgres};

static SUPPORTED_PACKAGE_TYPES: LazyLock<Vec<Box<dyn PackageType>>> = LazyLock::new(|| {
vec![
Box::new(Pgxn),
Box::new(Generic),
Box::new(Cargo),
Box::new(Postgres),
]
});

/// Given a PURL, return the installation command to install the given package.
pub async fn resolve_package(purl: &str, os: OperatingSystem) -> Result<String> {
let purl = GenericPurl::<String>::from_str(purl)?;

let package_type = SUPPORTED_PACKAGE_TYPES
.iter()
.find(|supported_package_type| supported_package_type.name() == purl.package_type())
.ok_or_else(|| Error::UnknownPackage(purl.package_type().to_string()))?;

package_type
.resolve_package_for_operating_system(purl.name(), os)
.await
}

#[cfg(test)]
mod tests {
use crate::{operating_system::OperatingSystem, resolve_package};

#[tokio::test]
async fn resolves_generic_packages() {
assert_eq!(
resolve_package("pkg:generic/ripgrep", OperatingSystem::Debian)
.await
.unwrap(),
"sudo apt-get install -y rust-ripgrep"
);

assert_eq!(
resolve_package("pkg:generic/ripgrep", OperatingSystem::RedHat)
.await
.unwrap(),
"sudo dnf install -y rust-ripgrep"
);

assert_eq!(
resolve_package("pkg:generic/ripgrep", OperatingSystem::Mac)
.await
.unwrap(),
"brew install ripgrep"
);
}

#[tokio::test]
async fn resolves_other_packages() {
assert_eq!(
resolve_package("pkg:cargo/cargo-pgrx", OperatingSystem::Debian)
.await
.unwrap(),
"cargo install cargo-pgrx"
);

assert_eq!(
resolve_package("pkg:pgxn/pgtap", OperatingSystem::Debian)
.await
.unwrap(),
"pgxn install pgtap"
);

assert_eq!(
resolve_package("pkg:pgxn/temporal-tables", OperatingSystem::Debian)
.await
.unwrap(),
"pgxn install temporal-tables"
);

assert_eq!(
resolve_package("pkg:postgres/plpgsql", OperatingSystem::Debian)
.await
.unwrap(),
"pgxn install plpgsql"
);
}
}
52 changes: 21 additions & 31 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
pub mod error;
pub mod operating_system;
pub mod package_type;
use pgxn_deps::{error::Result, operating_system::OperatingSystem, resolve_package};

use std::{str::FromStr, sync::LazyLock};
use argh::FromArgs;

use package_type::{Cargo, Generic, PackageType, Pgxn, Postgres};
use purl::GenericPurl;
#[derive(FromArgs)]
/// Obtain the installation command for a given purl package, according to PGXNv2 specs
struct Command {
/// operating system to install the package on
#[argh(option)]
os: Option<OperatingSystem>,

use crate::error::{Error, Result};

static SUPPORTED_PACKAGE_TYPES: LazyLock<Vec<Box<dyn PackageType>>> = LazyLock::new(|| {
vec![
Box::new(Pgxn),
Box::new(Generic),
Box::new(Cargo),
Box::new(Postgres),
]
});

/// Given a PURL, return the installation command to install the given package.
pub async fn resolve_package(purl: &str) -> Result<String> {
let purl = GenericPurl::<String>::from_str(purl)?;

let package_type = SUPPORTED_PACKAGE_TYPES
.iter()
.find(|supported_package_type| supported_package_type.name() == purl.package_type())
.ok_or_else(|| Error::UnknownPackage(purl.package_type().to_string()))?;

package_type.resolve_package(purl.name()).await
/// a purl string
#[argh(positional)]
purl: String,
}

#[tokio::main]
async fn main() -> Result<()> {
resolve_package("pkg:cargo/cargo-pgrx").await?;
resolve_package("pkg:postgres/plpgsql").await?;
resolve_package("pkg:pgxn/pgtap").await?;
resolve_package("pkg:generic/curl").await?;
let command: Command = argh::from_env();

let operating_system = match command.os {
Some(os) => os,
None => OperatingSystem::detect()?,
};

let installation_command = resolve_package(&command.purl, operating_system).await?;

println!("{installation_command}");

Ok(())
}
16 changes: 16 additions & 0 deletions src/operating_system.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{
fs,
io::{BufRead, BufReader},
result::Result as StdResult,
str::FromStr,
};

use crate::error::{Error, Result};
Expand All @@ -13,6 +15,20 @@ pub enum OperatingSystem {
Windows,
}

impl FromStr for OperatingSystem {
type Err = String;

fn from_str(s: &str) -> StdResult<Self, Self::Err> {
match s.to_lowercase().as_str() {
"mac" | "osx" | "macos" => Ok(OperatingSystem::Mac),
"debian" => Ok(OperatingSystem::Debian),
"redhat" | "rhel" => Ok(OperatingSystem::RedHat),
"windows" | "win" => Ok(OperatingSystem::Windows),
_ => Err(format!("Invalid operating system: '{}'", s)),
}
}
}

impl OperatingSystem {
/// Package manager(s) for this operating system family
pub fn package_managers(&self) -> &[PackageManager] {
Expand Down

0 comments on commit 735c6ec

Please sign in to comment.