cargo-tally 1.0.22

Cargo subcommand for drawing graphs of the number of dependencies on a crate over time
Documentation
use crate::cratemap::CrateMap;
use crate::user::User;
use anyhow::{bail, Result};
use cargo_tally::arena::Slice;
use cargo_tally::dependency::DependencyKind;
use cargo_tally::feature::{
    CrateFeature, DefaultFeatures, FeatureEnables, FeatureId, FeatureNames,
};
use cargo_tally::id::{CrateId, DependencyId, VersionId};
use cargo_tally::timestamp::DateTime;
use cargo_tally::version::{Version, VersionReq};
use cargo_tally::{DbDump, Dependency, Release};
use db_dump::crate_owners::OwnerId;
use std::cell::RefCell;
use std::collections::{BTreeMap as Map, BTreeSet as Set};
use std::iter::FromIterator;
use std::mem;
use std::path::Path;

pub(crate) fn load(path: impl AsRef<Path>) -> Result<(DbDump, CrateMap)> {
    let mut crates = CrateMap::new();
    let mut users: Map<User, OwnerId> = Map::new();
    let mut teams: Map<User, OwnerId> = Map::new();
    let mut owners: Map<OwnerId, Vec<CrateId>> = Map::new();
    let mut releases: Vec<Release> = Vec::new();
    let mut dependencies: Vec<Dependency> = Vec::new();
    let mut release_features: Vec<Vec<(FeatureId, Vec<CrateFeature>, Vec<CrateFeature>)>> =
        Vec::new();
    let mut dep_renames: Map<DependencyId, String> = Map::new();
    let mut dep_renames_resolve: Map<(VersionId, FeatureId), CrateId> = Map::new();
    let feature_names = RefCell::new(FeatureNames::new());

    db_dump::Loader::new()
        .crates(|row| {
            let crate_id = CrateId::from(row.id);
            crates.insert(crate_id, row.name);
        })
        .users(|row| {
            users.insert(User::new(row.gh_login), OwnerId::User(row.id));
        })
        .teams(|row| {
            if let Some(team) = row.login.strip_prefix("github:") {
                if team.contains(':') {
                    let team = team.replace(':', "/");
                    teams.insert(User::new(team), OwnerId::Team(row.id));
                }
            }
        })
        .crate_owners(|row| {
            owners
                .entry(row.owner_id)
                .or_insert_with(Vec::new)
                .push(CrateId::from(row.crate_id));
        })
        .versions(|row| {
            if row.yanked {
                return;
            }
            let crate_id = CrateId::from(row.crate_id);
            let mut features = Vec::new();
            if !row.features.is_empty() {
                let mut feature_names = feature_names.borrow_mut();
                for (feature, raw_enables) in &row.features {
                    let feature_id = feature_names.id(feature);
                    let mut enables = Vec::new();
                    let mut weak_enables = Vec::new();
                    for feature in raw_enables {
                        let crate_id;
                        let mut crate_feature_vec = &mut enables;
                        let mut feature = feature.as_str();
                        if let Some(slash) = feature.find('/') {
                            let mut crate_name = &feature[..slash];
                            if let Some(crate_name_weak) = crate_name.strip_suffix('?') {
                                crate_name = crate_name_weak;
                                crate_feature_vec = &mut weak_enables;
                            }
                            crate_id = feature_names.id(crate_name);
                            feature = &feature[slash + 1..];
                        } else {
                            crate_id = FeatureId::CRATE;
                        }
                        let feature_id = feature_names.id(feature);
                        crate_feature_vec.push(CrateFeature {
                            crate_id: CrateId(crate_id.0),
                            feature_id,
                        });
                    }
                    features.push((feature_id, enables, weak_enables));
                }
            }
            releases.push(Release {
                id: VersionId::from(row.id),
                crate_id,
                num: Version(row.num),
                created_at: DateTime::from(row.created_at),
                features: {
                    release_features.push(features);
                    Slice::EMPTY
                },
            });
        })
        .dependencies(|row| {
            let dependency_id = DependencyId::from(row.id);
            let version_id = VersionId::from(row.version_id);
            let crate_id = CrateId::from(row.crate_id);
            let feature_id = if row.optional {
                FeatureId::TBD
            } else {
                FeatureId::CRATE
            };
            let mut default_features = row.default_features;
            let mut features = Set::new();
            if !row.features.is_empty() {
                let mut feature_names = feature_names.borrow_mut();
                for feature in &row.features {
                    let feature_id = feature_names.id(feature);
                    if feature_id == FeatureId::DEFAULT {
                        default_features = true;
                    } else {
                        features.insert(feature_id);
                    }
                }
            }
            if let Some(explicit_name) = row.explicit_name {
                let mut feature_names = feature_names.borrow_mut();
                dep_renames_resolve
                    .insert((version_id, feature_names.id(&explicit_name)), crate_id);
                dep_renames.insert(dependency_id, explicit_name);
            }
            dependencies.push(Dependency {
                id: dependency_id,
                version_id,
                crate_id,
                req: VersionReq::from(row.req),
                feature_id,
                default_features: DefaultFeatures(default_features),
                features: Slice::from_iter(features),
                kind: DependencyKind::from(row.kind),
            });
        })
        .load(path)?;

    let mut feature_names = mem::take(&mut *feature_names.borrow_mut());
    let mut feature_buffer = Vec::new();
    for (release, mut features) in releases.iter_mut().zip(release_features) {
        for (feature, enables, weak_enables) in &mut features {
            for crate_features in [&mut *enables, &mut *weak_enables] {
                for feature in crate_features {
                    let feature_id = FeatureId(feature.crate_id.0);
                    feature.crate_id = if feature_id == FeatureId::CRATE {
                        release.crate_id
                    } else if let Some(crate_id) =
                        dep_renames_resolve.get(&(release.id, feature_id))
                    {
                        *crate_id
                    } else if let Some(crate_id) = {
                        let name = feature_names.name(feature_id);
                        crates.id(name)
                    } {
                        crate_id
                    } else {
                        bail!(
                            "{} v{} depends on {} which is not found",
                            crates.name(release.crate_id).unwrap(),
                            release.num,
                            feature_names.name(feature_id),
                        );
                    };
                }
            }
            feature_buffer.push(FeatureEnables {
                id: *feature,
                enables: Slice::new(enables),
                weak_enables: Slice::new(weak_enables),
            });
        }
        release.features = Slice::new(&feature_buffer);
        feature_buffer.clear();
    }
    for dep in &mut dependencies {
        if dep.feature_id == FeatureId::TBD {
            dep.feature_id = feature_names.id(match dep_renames.get(&dep.id) {
                Some(explicit_name) => explicit_name,
                None => crates.name(dep.crate_id).unwrap(),
            });
        }
    }

    let db_dump = DbDump {
        releases,
        dependencies,
        features: feature_names,
    };

    crates.owners = owners;
    crates.users = users;
    crates.users.extend(teams);

    Ok((db_dump, crates))
}