rustpm 0.2.2

A fast, friendly APT frontend with kernel, desktop, and sources management
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(|| {
        // Inst pkg [(oldver, repo, arch)] (newver repo [arch])
        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
}