use anyhow::{Result, bail};
use pkgsrc::archive::{BinaryPackage, SummaryOptions};
use pkgsrc::metadata::FileRead;
use pkgsrc::pkgdb::PkgDB;
use rayon::prelude::*;
use regex::Regex;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(name = "pkg_info", about = "An example pkg_info(8) command")]
pub struct OptArgs {
#[structopt(short = "K", long = "pkg-dbdir")]
pkg_dbdir: Option<String>,
#[structopt(short = "X", long = "summary")]
sumout: bool,
#[structopt(short = "a", long = "all")]
all: bool,
#[structopt(short = "j", long = "jobs", default_value = "0")]
jobs: usize,
#[structopt(short = "c", long = "file-cksum")]
file_cksum: bool,
#[structopt(parse(from_os_str))]
packages: Vec<PathBuf>,
}
fn output_default<P: FileRead>(pkg: &P) -> Result<()> {
println!("{:<19} {}", pkg.pkgname(), pkg.comment()?);
Ok(())
}
const SUMMARY_BUILD_VARS: &[&str] = &[
"BUILD_DATE",
"CATEGORIES",
"HOMEPAGE",
"LICENSE",
"MACHINE_ARCH",
"OPSYS",
"OS_VERSION",
"PKGPATH",
"PKGTOOLS_VERSION",
"PKG_OPTIONS",
"PREV_PKGPATH",
"PROVIDES",
"REQUIRES",
"SUPERSEDES",
];
fn output_summary<P: FileRead>(pkg: &P) -> Result<()> {
let contents = pkg.contents()?;
let comment = pkg.comment()?;
let desc = pkg.desc()?;
for line in contents.lines() {
if let Some(name) = line.strip_prefix("@name ") {
println!("PKGNAME={}", name);
} else if let Some(dep) = line.strip_prefix("@pkgdep ") {
println!("DEPENDS={}", dep);
} else if let Some(cfl) = line.strip_prefix("@pkgcfl ") {
println!("CONFLICTS={}", cfl);
}
}
println!("COMMENT={}", comment);
if let Some(size) = pkg.size_pkg()? {
println!("SIZE_PKG={}", size.trim());
}
if let Some(build_info) = pkg.build_info()? {
for line in build_info.lines() {
if let Some(var) = line.split('=').next() {
if SUMMARY_BUILD_VARS.contains(&var) {
println!("{}", line);
}
}
}
}
for line in desc.lines() {
println!("DESCRIPTION={}", line);
}
println!();
Ok(())
}
fn extract_summary(path: &Path, opts: &SummaryOptions) -> Result<String> {
let pkg = BinaryPackage::open(path)?;
let summary = pkg.to_summary_with_opts(opts)?;
Ok(summary.to_string())
}
fn process_installed(cmd: &OptArgs) -> Result<()> {
let pkgm: Option<Regex> = if cmd.packages.len() == 1 {
Some(Regex::new(cmd.packages[0].to_string_lossy().as_ref())?)
} else {
None
};
let dbpath = cmd
.pkg_dbdir
.clone()
.unwrap_or_else(|| "/opt/pkg/.pkgdb".to_string());
let pkgdb = PkgDB::open(Path::new(&dbpath))?;
for pkg in pkgdb {
let pkg = pkg?;
if let Some(m) = &pkgm {
if !m.is_match(pkg.pkgname()) {
continue;
}
}
if cmd.sumout {
output_summary(&pkg)?;
} else {
output_default(&pkg)?;
}
}
Ok(())
}
fn process_binary_packages(cmd: &OptArgs) -> Result<()> {
if cmd.jobs > 0 {
rayon::ThreadPoolBuilder::new()
.num_threads(cmd.jobs)
.build_global()
.ok();
}
let summary_opts = SummaryOptions {
compute_file_cksum: cmd.file_cksum,
};
let results: Vec<_> = cmd
.packages
.par_iter()
.map(|path| (path, extract_summary(path, &summary_opts)))
.collect();
for (path, result) in results {
match result {
Ok(summary) => println!("{}\n", summary),
Err(e) => eprintln!("Error processing {}: {}", path.display(), e),
}
}
Ok(())
}
fn main() -> Result<()> {
let cmd = OptArgs::from_args();
if cmd.sumout {
if !cmd.packages.is_empty() {
return process_binary_packages(&cmd);
}
if cmd.all {
return process_installed(&cmd);
}
bail!("missing package name(s)");
}
process_installed(&cmd)
}