mol-core 0.3.0

Core Libraray for mol crate
Documentation
use std::collections::HashMap;
use std::path::PathBuf;

use itertools::Itertools;

use crate::version::{Version, Versioned};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Package<T: Versioned> {
  pub path: PathBuf,
  pub name: String,
  pub version: Version<T>,
  pub dependencies: Vec<(String, String)>,
}

pub trait AsPackageGraph<T: Versioned> {
  fn as_package_graph(&self) -> PackageGraph<'_, T>;
}

#[derive(Debug, PartialEq)]
pub struct PackageGraph<'a, T: Versioned> {
  edges: Vec<(&'a str, &'a Package<T>)>,
  nodes: Vec<&'a Package<T>>,
}

impl<'a, T> PackageGraph<'a, T>
where
  T: Versioned,
{
  pub fn child_changes(&self, name: &'a str) -> Vec<&'a Package<T>> {
    self
      .edges
      .iter()
      .filter(|(p_name, _)| &name == p_name)
      .map(|(_, package)| *package)
      .collect()
  }

  fn stagger_scores(&self, scores: &mut HashMap<&'a str, isize>) {
    for (edge, target) in &self.edges {
      if let Some(value) = scores.get(&*target.name).copied() {
        if let Some(score) = scores.get_mut(&*edge) {
          *score += value;
        }
      }
    }
  }

  pub fn update_order(&self) -> Vec<&'a Package<T>> {
    let name_map: HashMap<&str, &'a Package<T>> = self
      .nodes
      .iter()
      .map(|package| (package.name.as_str(), *package))
      .collect();

    let base = self
      .nodes
      .iter()
      .map(|package| (package.name.as_str(), 0))
      .collect();

    let mut scores: HashMap<&'a str, isize> =
      self.edges.iter().fold(base, |mut acc, (parent, package)| {
        let value = acc
          .get(&*package.name)
          .map(|val| val + 1)
          .unwrap_or_else(|| 0);

        if let Some(score) = acc.get_mut(parent) {
          *score += value
        } else {
          acc.insert(parent, value);
        }

        acc
      });

    for _ in 0..self.nodes.len() {
      self.stagger_scores(&mut scores);
    }

    scores
      .into_iter()
      .sorted_by_key(|(_, score)| -*score)
      .filter(|(name, _)| name_map.contains_key(name))
      .map(|(name, _)| name_map[name])
      .collect()
  }
}

impl<T> AsPackageGraph<T> for Vec<Package<T>>
where
  T: Versioned,
{
  fn as_package_graph(&self) -> PackageGraph<'_, T> {
    let nodes: Vec<&Package<T>> = self.iter().collect();
    let edges: Vec<(&str, &Package<T>)> = self.iter().fold(vec![], |mut acc, package| {
      acc.extend(
        package
          .dependencies
          .iter()
          .map(|(dep, _)| (dep.as_str(), package)),
      );
      acc
    });

    PackageGraph { edges, nodes }
  }
}

#[cfg(test)]
mod tests {

  use super::*;
  use crate::semantic::Semantic;

  #[test]
  fn as_package_graph() {
    let packages: Vec<Package<Semantic>> = vec![
      Package {
        path: "".into(),
        name: "foo".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![],
      },
      Package {
        path: "".into(),
        name: "bar".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
      Package {
        path: "".into(),
        name: "baz".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
    ];

    let graph = packages.as_package_graph();

    assert_eq!(
      graph,
      PackageGraph {
        nodes: vec![&packages[0], &packages[1], &packages[2]],
        edges: vec![("foo", &packages[1]), ("foo", &packages[2])]
      }
    );
  }

  #[test]
  fn update_order() {
    let packages: Vec<Package<Semantic>> = vec![
      Package {
        path: "".into(),
        name: "foo".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![],
      },
      Package {
        path: "".into(),
        name: "bar".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
      Package {
        path: "".into(),
        name: "baz".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
    ];

    let graph = packages.as_package_graph();

    let update_order = graph.update_order();

    assert_eq!(update_order[0], &packages[0]);
  }

  #[test]
  fn update_order_deep() {
    let packages: Vec<Package<Semantic>> = vec![
      Package {
        path: "".into(),
        name: "pre_foo".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![],
      },
      Package {
        path: "".into(),
        name: "foo".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("pre_foo".to_owned(), "1".to_owned())],
      },
      Package {
        path: "".into(),
        name: "bar".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
      Package {
        path: "".into(),
        name: "baz".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
    ];

    let graph = packages.as_package_graph();

    let update_order = graph.update_order();

    let packages_ref: Vec<&Package<Semantic>> = packages.iter().collect();
    assert_eq!(update_order[..2], packages_ref[..2]);
  }

  #[test]
  fn child_changes() {
    let packages = vec![
      Package {
        path: "".into(),
        name: "foo".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![],
      },
      Package {
        path: "".into(),
        name: "bar".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
      Package {
        path: "".into(),
        name: "baz".to_owned(),
        version: "1.0.0".into(),
        dependencies: vec![("foo".to_owned(), "1".to_owned())],
      },
    ];

    let graph = packages.as_package_graph();

    let child_changes = graph.child_changes("foo");

    assert_eq!(
      child_changes,
      packages[1..].iter().collect::<Vec<&Package<Semantic>>>()
    );
  }
}