use crate::models::{DatasourceId, FileInfo, Package, PackageData, TopLevelDependency};
type DirectoryMergeOutput = (Option<Package>, Vec<TopLevelDependency>, Vec<usize>);
struct HuggingfaceSource<'a> {
file_index: usize,
datafile_path: String,
package_data: &'a PackageData,
}
fn is_huggingface_datasource(datasource_id: DatasourceId) -> bool {
matches!(
datasource_id,
DatasourceId::HuggingfaceModelCard
| DatasourceId::HuggingfaceConfigJson
| DatasourceId::HuggingfaceModelIndexJson
)
}
pub fn assemble_huggingface_packages(
files: &[FileInfo],
file_indices: &[usize],
) -> Vec<DirectoryMergeOutput> {
let mut sources: Vec<HuggingfaceSource> = Vec::new();
for &file_index in file_indices {
let file = &files[file_index];
for package_data in &file.package_data {
if package_data
.datasource_id
.is_some_and(is_huggingface_datasource)
{
sources.push(HuggingfaceSource {
file_index,
datafile_path: file.path.clone(),
package_data,
});
}
}
}
if sources.is_empty() {
return Vec::new();
}
let anchor_position = choose_anchor(&sources);
let anchor = &sources[anchor_position];
let mut package = Package::from_package_data(anchor.package_data, anchor.datafile_path.clone());
let mut affected_indices = vec![anchor.file_index];
let mut pending_dependencies = collect_dependencies(anchor.package_data, &anchor.datafile_path);
for (position, source) in sources.iter().enumerate() {
if position == anchor_position {
continue;
}
package.update(source.package_data, source.datafile_path.clone());
affected_indices.push(source.file_index);
pending_dependencies.extend(collect_dependencies(
source.package_data,
&source.datafile_path,
));
}
affected_indices.sort_unstable();
affected_indices.dedup();
let for_package_uid = Some(package.package_uid.clone());
let dependencies = pending_dependencies
.into_iter()
.map(|(dependency, datafile_path, datasource_id)| {
TopLevelDependency::from_dependency(
&dependency,
datafile_path,
datasource_id,
for_package_uid.clone(),
)
})
.collect();
vec![(Some(package), dependencies, affected_indices)]
}
fn choose_anchor(sources: &[HuggingfaceSource]) -> usize {
let config_with_identity = sources.iter().position(|source| {
source.package_data.purl.is_some()
&& matches!(
source.package_data.datasource_id,
Some(DatasourceId::HuggingfaceConfigJson)
| Some(DatasourceId::HuggingfaceModelIndexJson)
)
});
if let Some(position) = config_with_identity {
return position;
}
sources
.iter()
.position(|source| source.package_data.purl.is_some())
.unwrap_or(0)
}
fn collect_dependencies(
package_data: &PackageData,
datafile_path: &str,
) -> Vec<(crate::models::Dependency, String, DatasourceId)> {
let Some(datasource_id) = package_data.datasource_id else {
return Vec::new();
};
package_data
.dependencies
.iter()
.filter(|dependency| {
dependency.purl.is_some() || dependency.extracted_requirement.is_some()
})
.cloned()
.map(|dependency| (dependency, datafile_path.to_string(), datasource_id))
.collect()
}