use std::path::{Path, PathBuf};
use crate::cache::DEFAULT_CACHE_DIR_NAME;
use crate::models::{FileInfo, Package, PackageType, PackageUid};
pub(super) struct ContainmentResourceAssigner {
pub(super) package_type: PackageType,
pub(super) find_root: fn(&Package) -> Option<PathBuf>,
pub(super) excluded_root_children: &'static [&'static str],
pub(super) skip_nested_roots: bool,
}
impl ContainmentResourceAssigner {
fn assign(&self, files: &mut [FileInfo], packages: &[Package]) {
let roots: Vec<(PathBuf, PackageUid)> = packages
.iter()
.filter(|package| package.package_type == Some(self.package_type))
.filter_map(|package| {
(self.find_root)(package).map(|root| (root, package.package_uid.clone()))
})
.collect();
if roots.is_empty() {
return;
}
for file in files.iter_mut() {
let path = Path::new(&file.path);
for (root, package_uid) in &roots {
if !path.starts_with(root)
|| is_excluded_child(path, root, self.excluded_root_children)
{
continue;
}
if self.skip_nested_roots
&& roots.iter().any(|(other_root, _)| {
other_root != root
&& other_root.starts_with(root)
&& path.starts_with(other_root)
})
{
continue;
}
if !file.for_packages.contains(package_uid) {
file.for_packages.push(package_uid.clone());
}
}
}
}
}
fn is_excluded_child(path: &Path, root: &Path, excluded: &[&str]) -> bool {
if excluded.is_empty() {
return false;
}
path.strip_prefix(root)
.ok()
.and_then(|relative| relative.components().next())
.is_some_and(|component| excluded.iter().any(|name| component.as_os_str() == *name))
}
pub(super) static RESOURCE_ASSIGNERS: &[ContainmentResourceAssigner] = &[
ContainmentResourceAssigner {
package_type: PackageType::Cargo,
find_root: super::cargo_resource_assign::cargo_package_root,
excluded_root_children: &["target"],
skip_nested_roots: false,
},
ContainmentResourceAssigner {
package_type: PackageType::Composer,
find_root: super::composer_resource_assign::composer_package_root,
excluded_root_children: &["vendor", DEFAULT_CACHE_DIR_NAME],
skip_nested_roots: true,
},
ContainmentResourceAssigner {
package_type: PackageType::Gem,
find_root: super::ruby_resource_assign::ruby_package_root,
excluded_root_children: &[DEFAULT_CACHE_DIR_NAME],
skip_nested_roots: true,
},
];
pub(super) fn assign_resources_for(
package_type: PackageType,
files: &mut [FileInfo],
packages: &[Package],
) {
for assigner in RESOURCE_ASSIGNERS {
if assigner.package_type == package_type {
assigner.assign(files, packages);
return;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn each_package_type_is_registered_at_most_once() {
let mut seen = HashSet::new();
for assigner in RESOURCE_ASSIGNERS {
assert!(
seen.insert(assigner.package_type),
"package type {:?} registered more than once in RESOURCE_ASSIGNERS",
assigner.package_type
);
}
}
}