use crate::manifest::Manifest;
use chrono::NaiveDate;
use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
};
type PackageName = String;
type TargetTriple = String;
type DatesSet = HashSet<NaiveDate>;
type PackagesAvailability = HashMap<PackageName, DatesSet>;
#[derive(Debug, Default)]
pub struct AvailabilityData {
data: HashMap<TargetTriple, PackagesAvailability>,
}
#[derive(Debug, serde::Serialize)]
pub struct AvailabilityRow<'a> {
pub package_name: &'a str,
pub availability_list: Vec<bool>,
pub last_available: Option<NaiveDate>,
_hidden: (),
}
impl AvailabilityData {
pub fn add_manifest(&mut self, manifest: Manifest) {
let reverse_renames: HashMap<_, _> = manifest
.renames
.iter()
.map(|(key, value)| (&value.to, key))
.collect();
for (package_name, info) in manifest.packages {
let package_name = reverse_renames
.get(&package_name)
.map(|name| String::clone(name))
.unwrap_or(package_name);
for (target_triple, target_info) in info.targets {
if target_info.available {
self.data
.entry(target_triple.clone())
.or_default()
.entry(package_name.clone())
.or_default()
.insert(manifest.date);
}
}
}
}
pub fn add_manifests(&mut self, manifests: impl IntoIterator<Item = Manifest>) {
manifests
.into_iter()
.for_each(|manifest| self.add_manifest(manifest));
}
pub fn get_available_targets(&self) -> HashSet<&'_ str> {
self.data
.keys()
.filter(|target| target != &"*")
.map(AsRef::as_ref)
.collect()
}
pub fn get_available_packages<'a>(&'a self) -> HashSet<&'a str> {
self.data
.iter()
.flat_map(|(_, per_target)| per_target.keys())
.map(AsRef::as_ref)
.collect()
}
pub fn get_availability_row<'a, I>(
&self,
target: &str,
pkg: &'a str,
dates: I,
) -> Option<AvailabilityRow<'a>>
where
I: IntoIterator,
I::Item: Borrow<NaiveDate>,
{
if self.data.get(target).and_then(|t| t.get(pkg)).is_none() {
return None;
}
let available_dates = self.available_dates(target, pkg);
let availability_list = dates
.into_iter()
.map(|date| available_dates.contains(date.borrow()))
.collect();
Some(AvailabilityRow {
package_name: pkg,
availability_list,
last_available: available_dates.into_iter().max(),
_hidden: (),
})
}
fn available_dates(&self, target: &str, pkg: &str) -> HashSet<NaiveDate> {
let available_on_target = self.data.get(target).and_then(|packages| packages.get(pkg));
let available_on_wildcard = self.data.get("*").and_then(|packages| packages.get(pkg));
match (available_on_target, available_on_wildcard) {
(Some(x), Some(y)) => x.union(y).cloned().collect(),
(Some(x), None) | (None, Some(x)) => x.iter().cloned().collect(),
(None, None) => HashSet::new(),
}
}
pub fn last_available(&self, target: &str, pkg: &str) -> Option<NaiveDate> {
self.available_dates(target, pkg).into_iter().max()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::Manifest;
#[test]
fn check() {
let data = r#"date = "2018-09-03"
[pkg.rust-src.target."*"]
available = true
[pkg.ahaha.target.lol]
available = true
"#;
let parsed_manifest: Manifest = toml::from_str(data).unwrap();
let mut availability: AvailabilityData = Default::default();
availability.add_manifest(parsed_manifest);
let all_packages = availability.get_available_packages();
assert_eq!(2, all_packages.len());
assert!(all_packages.contains("rust-src"));
assert!(all_packages.contains("ahaha"));
let all_targets = availability.get_available_targets();
assert_eq!(1, all_targets.len());
assert!(all_targets.contains("lol"));
let package_exists = availability
.get_availability_row("*", "rust-src", vec![NaiveDate::from_ymd(2018, 9, 3)])
.unwrap();
assert_eq!("rust-src", package_exists.package_name);
assert_eq!(vec!(true), package_exists.availability_list);
let package_exists = availability.get_availability_row(
"lol",
"rust-src",
vec![NaiveDate::from_ymd(2018, 9, 3)],
);
assert!(package_exists.is_none());
let package_exists = availability
.get_availability_row("lol", "ahaha", vec![NaiveDate::from_ymd(2018, 9, 3)])
.unwrap();
assert_eq!("ahaha", package_exists.package_name);
assert_eq!(vec!(true), package_exists.availability_list);
}
#[test]
fn check_rename() {
let data = r#"date = "2018-09-03"
[pkg.ahaha.target.lol]
available = true
[renames.kek]
to = "ahaha"
"#;
let parsed_manifest: Manifest = toml::from_str(data).unwrap();
let mut availability: AvailabilityData = Default::default();
availability.add_manifest(parsed_manifest);
let all_packages = availability.get_available_packages();
assert_eq!(1, all_packages.len());
assert!(all_packages.contains("kek"));
}
}