use regex::Regex;
use std::sync::OnceLock;
#[derive(Debug, Clone, PartialEq)]
pub enum ChangeKind {
Install,
Remove,
Upgrade,
Configure,
}
#[derive(Debug, Clone)]
pub struct PackageChange {
pub kind: ChangeKind,
pub name: String,
pub old_version: Option<String>,
pub new_version: Option<String>,
pub arch: Option<String>,
pub size_bytes: Option<u64>,
}
static INST_RE: OnceLock<Regex> = OnceLock::new();
static REMV_RE: OnceLock<Regex> = OnceLock::new();
static CONF_RE: OnceLock<Regex> = OnceLock::new();
fn inst_re() -> &'static Regex {
INST_RE.get_or_init(|| {
Regex::new(r"^Inst (\S+)(?: \[([^\]]+)\])?(?: \((\S+) [^)]+\))?").unwrap()
})
}
fn remv_re() -> &'static Regex {
REMV_RE.get_or_init(|| {
Regex::new(r"^Remv (\S+)(?: \[([^\]]+)\])?").unwrap()
})
}
fn conf_re() -> &'static Regex {
CONF_RE.get_or_init(|| {
Regex::new(r"^Conf (\S+)").unwrap()
})
}
pub fn parse_dry_run_output(output: &str) -> Vec<PackageChange> {
let mut changes = Vec::new();
for line in output.lines() {
if let Some(caps) = inst_re().captures(line) {
let name = caps[1].to_string();
let old_ver = caps.get(2).map(|m| m.as_str().to_string());
let new_ver = caps.get(3).map(|m| m.as_str().to_string());
let kind = if old_ver.is_some() {
ChangeKind::Upgrade
} else {
ChangeKind::Install
};
changes.push(PackageChange {
kind,
name,
old_version: old_ver,
new_version: new_ver,
arch: None,
size_bytes: None,
});
} else if let Some(caps) = remv_re().captures(line) {
let name = caps[1].to_string();
let old_ver = caps.get(2).map(|m| m.as_str().to_string());
changes.push(PackageChange {
kind: ChangeKind::Remove,
name,
old_version: old_ver,
new_version: None,
arch: None,
size_bytes: None,
});
} else if let Some(caps) = conf_re().captures(line) {
changes.push(PackageChange {
kind: ChangeKind::Configure,
name: caps[1].to_string(),
old_version: None,
new_version: None,
arch: None,
size_bytes: None,
});
}
}
changes
}