#[macro_use]
extern crate display_derive;
extern crate failure;
extern crate flate2;
extern crate itertools;
#[macro_use]
extern crate log;
extern crate rayon;
extern crate tar;
extern crate version_compare;
extern crate xz2;
pub mod db;
mod error;
pub mod package;
use std::fmt::{self, Formatter, Display};
use std::path::Path;
use version_compare::Version;
use db::Db;
use error::Result;
use package::MetaPackage;
pub struct Alpm {
pub local_db: Db,
pub sync_dbs: Vec<Db>
}
impl Alpm {
pub fn new(location: impl AsRef<Path> + Sync) -> Result<Alpm> {
let (local, sync) = rayon::join(
|| Db::local_db(&location),
|| Db::sync_dbs(&location)
);
Ok(Alpm {
local_db: local?,
sync_dbs: sync?
})
}
pub fn foreign_pkgs<'a>(&'a self) -> impl Iterator<Item = &'a MetaPackage> {
debug!("computing foreign packages");
self.local_db.packages.iter()
.filter(move |package| !is_pkg_foreign(&self.sync_dbs, &package.name) )
}
}
fn is_pkg_foreign(sync_dbs: &[Db], pkg_name: &str) -> bool {
sync_dbs.iter()
.any(|db| db.pkg(&pkg_name).is_some() )
}
#[derive(Debug)]
pub struct PkgSpec {
pub name: String,
version: String,
release: String,
pub arch: Option<String>
}
impl PkgSpec {
pub fn split_specifier(specifier: &str) -> Option<PkgSpec> {
let specifier = if let Some(indx) = specifier.find('/') {
&specifier[..indx]
} else {
specifier
};
let mut parts = specifier.rsplit('-');
let rel = parts.next();
let version = parts.next();
let name: Vec<&str> = parts.rev().collect();
let name = name.join("-");
if let None = rel {
None
} else if let None = version {
None
} else {
Some(PkgSpec {
name: name,
version: version.unwrap().to_string(),
release: rel.unwrap().to_string(),
arch: None
})
}
}
pub fn split_pkgname(name: &str) -> Option<PkgSpec> {
if let Some(indx) = name.rfind('-') {
let pkgspec = &name[..indx];
if let Some(mut pkgspec) = PkgSpec::split_specifier(pkgspec) {
pkgspec.arch = Some(name[indx + 1..].to_string());
Some(pkgspec)
} else {
None
}
} else {
None
}
}
pub fn version_str(&self) -> String {
format!("{}-{}", self.version, self.release)
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
enum Comp {
Eq,
Lt,
Le,
Ge,
Gt,
}
impl Comp {
fn make_inclusive(self) -> Comp {
match self {
Comp::Lt => Comp::Le,
Comp::Gt => Comp::Ge,
_ => self
}
}
}
impl Display for Comp {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", match self {
Comp::Eq => "=",
Comp::Lt => "<",
Comp::Le => "<=",
Comp::Ge => ">=",
Comp::Gt => ">"
})
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct Provide {
pub name: String,
version: Option<String>,
comp: Comp,
}
impl Provide {
pub fn parse(data: impl AsRef<str>) -> Option<Provide> {
let data = data.as_ref();
let mut comp = Comp::Eq;
let mut data = data.splitn(2, |c| {
if c == '<' {
comp = Comp::Lt;
true
} else if c == '>' {
comp = Comp::Gt;
true
} else if c == '=' {
comp = Comp::Eq;
true
} else {
false
}
});
let name = data.next()?.to_string();
let version = if let Some(version) = data.next() {
if version.chars().nth(0) == Some('=') {
comp = comp.make_inclusive();
Some(version[1..].to_string())
} else {
Some(version.to_string())
}
} else { None };
Some(Provide { name, version, comp })
}
pub fn satisfies(&self, other: &Provide) -> bool {
self.name == other.name && if let Some(ref other_ver) = other.version() {
if let Some(ref ver) = self.version() {
match other.comp {
Comp::Eq => ver >= other_ver,
Comp::Lt => ver < other_ver,
Comp::Le => ver <= other_ver,
Comp::Ge => ver >= other_ver,
Comp::Gt => ver > other_ver,
}
} else {
false
}
} else {
true
}
}
pub fn version(&self) -> Option<Version> {
self.version.as_ref().and_then(|ver| {
Version::from(ver)
})
}
}
impl Display for Provide {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match &self.version {
Some(version) => write!(f, "{}{}{}", self.name, self.comp, version),
None => write!(f, "{}", self.name)
}
}
}
#[cfg(test)]
mod tests {
use std::fs::create_dir_all;
use std::path::Path;
use std::process::Command;
use {Db, package::Package, PkgSpec, Provide};
#[test]
fn test_pkgload() {
let test_dir = Path::join(Path::new(env!("CARGO_MANIFEST_DIR")), "tests");
create_dir_all(&test_dir).unwrap(); let wget = Command::new("curl")
.arg("-O")
.arg("https://sgp.mirror.pkgbuild.com/community/os/x86_64/ascii-3.18-1-x86_64.pkg.tar.xz")
.arg("--output")
.arg("ascii-3.18-1-x86_64.pkg.tar.xz")
.current_dir(&test_dir)
.spawn().unwrap()
.wait().unwrap();
if wget.success() {
let filename = Path::join(&test_dir, "ascii-3.18-1-x86_64.pkg.tar.xz");
let pkg = Package::load(filename).unwrap();
assert_eq!(pkg.meta.name, "ascii".to_string());
assert_eq!(pkg.meta.version().unwrap().as_str(), "3.18-1");
}
}
#[test]
fn test_split_specifier() {
let pkgspec = PkgSpec::split_specifier("pacman-5.1.1-2").unwrap();
assert_eq!(&pkgspec.name, "pacman");
assert_eq!(&pkgspec.version, "5.1.1");
assert_eq!(pkgspec.release, 2.to_string());
assert!(pkgspec.arch.is_none());
let pkgspec = PkgSpec::split_specifier("pithos-git-1.4.1-1/").unwrap();
assert_eq!(&pkgspec.name, "pithos-git");
assert_eq!(&pkgspec.version, "1.4.1");
assert_eq!(pkgspec.release, 1.to_string());
assert!(pkgspec.arch.is_none());
let pkgspec = PkgSpec::split_specifier("acorn-5.7.2-1/desc").unwrap();
assert_eq!(&pkgspec.name, "acorn");
assert_eq!(&pkgspec.version, "5.7.2");
assert_eq!(pkgspec.release, 1.to_string());
assert!(pkgspec.arch.is_none());
}
#[test]
fn test_split_pkgname() {
let pkgspec = PkgSpec::split_pkgname("pacman-5.1.1-2-x86_64").unwrap();
assert_eq!(&pkgspec.name, "pacman");
assert_eq!(&pkgspec.version, "5.1.1");
assert_eq!(pkgspec.release, 2.to_string());
assert_eq!(&pkgspec.arch.unwrap(), "x86_64");
let pkgspec = PkgSpec::split_pkgname("pithos-git-1.4.1-1-any").unwrap();
assert_eq!(&pkgspec.name, "pithos-git");
assert_eq!(&pkgspec.version, "1.4.1");
assert_eq!(pkgspec.release, 1.to_string());
assert_eq!(&pkgspec.arch.unwrap(), "any");
}
#[test]
fn test_localdb_parse() {
let _db = Db::local_db("/var/lib/pacman").unwrap();
}
#[test]
fn test_syncdb_parse() {
let _dbs = Db::sync_dbs("/var/lib/pacman").unwrap();
}
#[test]
#[ignore]
fn test_is_package_provided() {
let db = Db::local_db("/var/lib/pacman").unwrap();
assert!(db.provides(&Provide { name: "java-environment".to_string(), version: Some("8".to_string()) }));
}
}