loadsmith-manifest 0.2.0

Manifest and lockfile format, resolver, and profile state for the loadsmith mod-manager library
use loadsmith_core::{Checksum, PackageId, Version};
use std::collections::{HashMap, HashSet};

#[derive(Debug, Clone)]
pub struct Diff<'a, T, U> {
    pub added: Vec<&'a U>,
    pub removed: Vec<&'a T>,
    pub changed: Vec<(&'a T, &'a U)>,
}

pub trait Diffable {
    fn checksum(&self) -> Option<&Checksum>;
    fn version(&self) -> &Version;
}

impl<'a, T: Diffable, U: Diffable> Diff<'a, T, U> {
    pub(crate) fn compute(
        old_map: HashMap<&'a PackageId, &'a T>,
        new_map: HashMap<&'a PackageId, &'a U>,
    ) -> Self {
        let all_ids = old_map.keys().chain(new_map.keys()).collect::<HashSet<_>>();

        let mut added = Vec::new();
        let mut removed = Vec::new();
        let mut changed = Vec::new();

        for id in all_ids {
            let old = old_map.get(id);
            let new = new_map.get(id);

            match (old, new) {
                (Some(old), Some(new)) => {
                    if old.version() != new.version() || old.checksum() != new.checksum() {
                        changed.push((*old, *new));
                    }
                }
                (None, Some(new)) => {
                    added.push(*new);
                }
                (Some(old), None) => {
                    removed.push(*old);
                }
                (None, None) => unreachable!(),
            }
        }

        Self {
            added,
            removed,
            changed,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.added.is_empty() && self.removed.is_empty() && self.changed.is_empty()
    }

    pub fn to_add(&'a self) -> impl Iterator<Item = &'a U> {
        self.added
            .iter()
            .chain(self.changed.iter().map(|(_old, new)| new))
            .copied()
    }

    pub fn to_remove(&'a self) -> impl Iterator<Item = &'a T> {
        self.removed
            .iter()
            .chain(self.changed.iter().map(|(old, _new)| old))
            .copied()
    }
}