pray-core 1.16.0

Core library for Prayfile, the package manager for the language placed before inference
Documentation
use crate::lockfile::Lockfile;
use crate::manifest::{ManifestPackage, ManifestSource};
use crate::registry::{resolve_local_registry_package_root, resolve_registry_package_root};
use crate::resolve_context::{PackageResolutionContext, ResolveOptions};
use crate::resolve_git_sources::{resolve_git_package_root, GitSourceCheckout};
use crate::{PrayError, PrayResult};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub(crate) struct PackageRootResolution {
    pub root: PathBuf,
    pub signer_fingerprint: Option<String>,
    pub registry_latest_version: Option<String>,
}

pub(crate) fn resolve_package_root(
    project_root: &Path,
    sources: &BTreeMap<String, ManifestSource>,
    git_sources: &BTreeMap<String, GitSourceCheckout>,
    user_config: &crate::config::PrayConfig,
    declaration: &ManifestPackage,
    lockfile: Option<&Lockfile>,
    options: &ResolveOptions,
) -> PrayResult<PackageRootResolution> {
    if let Some(local_path) = user_config.local.package.get(&declaration.name) {
        return Ok(PackageRootResolution {
            root: project_root.join(local_path),
            signer_fingerprint: None,
            registry_latest_version: None,
        });
    }
    if let Some(path) = &declaration.path {
        return Ok(PackageRootResolution {
            root: project_root.join(path),
            signer_fingerprint: None,
            registry_latest_version: None,
        });
    }
    if let Some(tarball) = &declaration.tarball {
        return Ok(PackageRootResolution {
            root: crate::resolve_tarball::resolve_tarball_package_root(
                project_root,
                tarball,
                options,
            )?,
            signer_fingerprint: None,
            registry_latest_version: None,
        });
    }
    let source_name = implied_source_name(declaration, sources)?;
    if let Some(source_name) = source_name {
        let source = sources
            .get(&source_name)
            .ok_or_else(|| PrayError::Resolution(format!("unknown source: {source_name}")))?;
        let context = PackageResolutionContext::from_lockfile(lockfile, &declaration.name, options);
        if let Some(local_path) = user_config.local.source.get(&source_name) {
            let source_root = project_root.join(local_path);
            let resolved = resolve_local_registry_package_root(
                project_root,
                &format!("local:{source_name}"),
                &source_root,
                declaration,
                &context,
            )?;
            return Ok(PackageRootResolution {
                root: resolved.root,
                signer_fingerprint: resolved.signer_fingerprint,
                registry_latest_version: resolved.registry_latest_version,
            });
        }
        if source.kind == "path" {
            let slug = declaration.name.replace('/', "-");
            return Ok(PackageRootResolution {
                root: project_root.join(&source.url).join(slug),
                signer_fingerprint: None,
                registry_latest_version: None,
            });
        }
        if source.kind == "registry" || source.kind == "static index" || source.kind == "pray_ssh" {
            let resolved =
                resolve_registry_package_root(project_root, &source.url, declaration, &context)?;
            return Ok(PackageRootResolution {
                root: resolved.root,
                signer_fingerprint: resolved.signer_fingerprint,
                registry_latest_version: resolved.registry_latest_version,
            });
        }
        if source.kind == "git" {
            let resolved = resolve_git_package_root(
                project_root,
                &source_name,
                &source.url,
                git_sources,
                declaration,
                &context,
            )?;
            return Ok(PackageRootResolution {
                root: resolved.root,
                signer_fingerprint: resolved.signer_fingerprint,
                registry_latest_version: resolved.registry_latest_version,
            });
        }
        return Err(PrayError::Unsupported(format!(
            "source kind {} not implemented yet",
            source.kind
        )));
    }
    if declaration.git.is_some() || declaration.oci.is_some() {
        return Err(PrayError::Unsupported(
            "remote sources are not implemented yet".to_string(),
        ));
    }
    let slug = declaration.name.replace('/', "-");
    Ok(PackageRootResolution {
        root: project_root.join(slug),
        signer_fingerprint: None,
        registry_latest_version: None,
    })
}

pub(crate) fn implied_source_name(
    declaration: &ManifestPackage,
    sources: &BTreeMap<String, ManifestSource>,
) -> PrayResult<Option<String>> {
    if let Some(name) = &declaration.source {
        return Ok(Some(name.clone()));
    }
    if let Some(namespace) = declaration.name.split_once('/').map(|(name, _)| name) {
        if sources.contains_key(namespace) {
            return Ok(Some(namespace.to_string()));
        }
    }
    match sources.len() {
        0 => Ok(None),
        1 => Ok(sources.keys().next().cloned()),
        _ => Err(PrayError::Resolution(format!(
            "package {} requires source: when multiple sources are declared and the package namespace does not match a source",
            declaration.name
        ))),
    }
}