use std::io::Read;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::result::Result as StdResult;
use failure::err_msg;
use tar::Archive;
use version_compare::Version;
use xz2::read::XzDecoder;
use Provide;
use error::{ParseError, Result};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Package {
pub filename: PathBuf,
pub meta: MetaPackage
}
impl Package {
pub fn load(filename: impl AsRef<Path>) -> Result<Package> {
let tarball_file = File::open(&filename)?;
let xz_decoder = XzDecoder::new(tarball_file);
let mut tar_decoder = Archive::new(xz_decoder);
for entry in tar_decoder.entries()? {
let mut entry = entry?;
if entry.path()? == Path::new(".PKGINFO") {
let mut data = String::new();
entry.read_to_string(&mut data)?;
let meta_package = MetaPackage::parse_pkginfo(data)?;
let package = Package {
filename: filename.as_ref().to_path_buf(),
meta: meta_package
};
return Ok(package);
}
}
Err(err_msg("tarball missing a .PKGINFO entry"))
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct MetaPackage {
pub name: String,
version: String,
pub description: String,
pub url: String,
pub packager: String,
pub architecture: String,
pub groups: Vec<String>,
pub licenses: Vec<String>,
pub depends: Vec<String>,
pub optdepends: Vec<String>,
pub makedepends: Vec<String>,
pub conflicts: Vec<String>,
pub replaces: Vec<String>,
pub provides: Vec<Provide>,
pub backups: Vec<String>
}
macro_rules! empty_meta_pkg {
() => {
MetaPackage {
name: String::new(),
version: String::new(),
description: String::new(),
url: String::new(),
packager: String::new(),
architecture: String::new(),
groups: Vec::new(),
licenses: Vec::new(),
depends: Vec::new(),
optdepends: Vec::new(),
makedepends: Vec::new(),
conflicts: Vec::new(),
replaces: Vec::new(),
provides: Vec::new(),
backups: Vec::new()
}
}
}
impl MetaPackage {
pub(crate) fn parse_pkginfo(pkginfo_data: String) -> StdResult<MetaPackage, ParseError> {
let mut pkg = empty_meta_pkg!();
let mut line_num = 0;
for line in pkginfo_data.lines() {
line_num += 1;
let line = line.trim();
if line.starts_with("#") || line.len() == 0 {
continue
}
let mut pair = line.splitn(2, '=');
let key = pair.next()
.ok_or(ParseError::MissingKey(line_num))?
.trim().to_string();
let value = pair.next()
.ok_or(ParseError::MissingValue(line_num, key.to_string()))?
.trim().to_string();
macro_rules! load_value {
($var:expr) => (if $var.len() == 0 {
$var = value;
} else {
return Err(ParseError::MultipleKeys(line_num, key.to_string()));
})
}
match key.as_str() {
"pkgname" => load_value!(pkg.name),
"pkgbase" => {}, "pkgver" => load_value!(pkg.version),
"pkgdesc" => load_value!(pkg.description),
"group" => pkg.groups.push(value),
"url" => load_value!(pkg.url),
"builddate" => {}, "packager" => load_value!(pkg.packager),
"size" => {}, "arch" => load_value!(pkg.architecture),
"license" => pkg.licenses.push(value),
"depend" => pkg.depends.push(value),
"optdepend" => pkg.optdepends.push(value),
"makedepend" => pkg.makedepends.push(value),
"conflict" => pkg.conflicts.push(value),
"replaces" => pkg.replaces.push(value),
"provides" => pkg.provides.push(Provide::parse(&value)
.expect(&format!("Invalid Provide: {}", value))),
"backup" => pkg.backups.push(value),
"force" => {}, "makepkgopt" => {}, &_ => return Err(ParseError::UnknownKey(line_num, key.to_string()))
}
}
Ok(pkg)
}
pub(crate) fn parse_pkgdesc(desc_data: String) -> StdResult<MetaPackage, ParseError> {
let mut pkg = empty_meta_pkg!();
let mut line_num: u32 = 0;
for entry in desc_data.split("\n\n") {
line_num += 1;
let mut lines = entry.lines();
if let Some(key) = lines.next() {
line_num += 1;
if key == "" {
continue;
}
macro_rules! load_value {
($field:ident, $required:expr) => {
if let Some(val) = lines.next() {
line_num += 1;
pkg.$field = val.to_string();
} else if !$required {
line_num += 1;
} else {
return Err(ParseError::MissingValue(line_num, key.to_string()))
}
}
}
macro_rules! load_value_all {
($field:ident) => {
pkg.$field = lines.map(|line| line.to_string() ).collect()
}
}
match key {
"%NAME%" => load_value!(name, true),
"%VERSION%" => load_value!(version, true),
"%FILENAME%" => {} "%BASE%" => {}, "%DESC%" => load_value!(description, false),
"%GROUPS%" => load_value_all!(groups),
"%URL%" => load_value!(url, false),
"%LICENSE%" => load_value_all!(licenses),
"%ARCH%" => load_value!(architecture, true),
"%BUILDDATE%" => {}, "%INSTALLDATE%" => {}, "%REASON%" => {}, "%PACKAGER%" => load_value!(packager, true),
"%SIZE%" => {}, "%CSIZE%" => {}, "%ISIZE%" => {}, "%MD5SUM%" => {}, "%SHA256SUM%" => {}, "%PGPSIG%" => {}, "%VALIDATION%" => {}, "%REPLACES%" => load_value_all!(replaces),
"%DEPENDS%" => load_value_all!(depends),
"%OPTDEPENDS%" => load_value_all!(optdepends),
"%MAKEDEPENDS%" => load_value_all!(makedepends),
"%CHECKDEPENDS%" => {}, "%CONFLICTS%" => load_value_all!(conflicts),
"%PROVIDES%" => pkg.provides = lines
.map(|line| Provide::parse(line)
.expect(&format!("Invalid Provide: {}", line)))
.collect(),
"%DELTAS%" => {}, "%FILES%" => {}, &_ => return Err(ParseError::UnknownKey(line_num, key.to_string()))
}
}
}
Ok(pkg)
}
pub fn version(&self) -> Option<Version> {
Version::from(&self.version)
}
pub fn satisfies(&self, other: &Provide) -> bool {
let self_provide = Provide::parse(format!("{}={}", self.name, self.version))
.expect(&format!("{:?} does not have a name", self));
self_provide.satisfies(other) || self.provides.iter()
.any(|provide| provide.satisfies(other) )
}
}