pray-core 1.11.0

Core library for Prayfile, the package manager for the language placed before inference
Documentation
use crate::manifest::{
    DestinationEntry, DestinationMode, ExportRole, Manifest, ManifestLocal, ManifestPackage,
    ManifestTarget,
};
use crate::{PrayError, PrayResult};

pub fn is_local_path_form(value: &str) -> bool {
    value.starts_with('.')
        || value.starts_with('/')
        || value.ends_with(".md")
        || value.ends_with(".txt")
        || value.ends_with(".markdown")
        || !value.contains('/')
}

pub fn destination_target_name(mode: DestinationMode, path: &str) -> String {
    let prefix = match mode {
        DestinationMode::Compose => "compose",
        DestinationMode::Tree => "tree",
        DestinationMode::Legacy => "legacy",
    };
    format!("{prefix}:{path}")
}

pub fn new_destination_target(mode: DestinationMode, path: &str) -> ManifestTarget {
    let mut target = ManifestTarget {
        name: destination_target_name(mode, path),
        mode,
        scoped: true,
        ..ManifestTarget::default()
    };
    match mode {
        DestinationMode::Compose => target.outputs.push(path.to_string()),
        DestinationMode::Tree => target.skills.push(path.to_string()),
        DestinationMode::Legacy => {}
    }
    target
}

pub fn upsert_package(manifest: &mut Manifest, package: ManifestPackage) -> PrayResult<()> {
    if let Some(existing) = manifest
        .packages
        .iter_mut()
        .find(|candidate| candidate.name == package.name)
    {
        if existing.constraint != package.constraint
            && existing.constraint != "*"
            && package.constraint != "*"
        {
            return Err(PrayError::Manifest(format!(
                "package {} declared with conflicting constraints ({} vs {})",
                package.name, existing.constraint, package.constraint
            )));
        }
        if existing.constraint == "*" && package.constraint != "*" {
            existing.constraint = package.constraint;
        }
        if existing.source.is_none() {
            existing.source = package.source;
        } else if package.source.is_some() && existing.source != package.source {
            return Err(PrayError::Manifest(format!(
                "package {} declared with conflicting sources",
                package.name
            )));
        }
        for export in package.exports {
            if !existing.exports.contains(&export) {
                existing.exports.push(export);
            }
        }
        for role in package.roles {
            if !existing.roles.contains(&role) {
                existing.roles.push(role);
            }
        }
        if package.file.is_some() {
            if existing.file.is_some() && existing.file != package.file {
                return Err(PrayError::Manifest(format!(
                    "package {} declared with conflicting file: destinations",
                    package.name
                )));
            }
            existing.file = package.file;
        }
        existing.bound = existing.bound || package.bound;
        existing.optional = existing.optional || package.optional;
        if existing.path.is_none() {
            existing.path = package.path;
        }
        if existing.git.is_none() {
            existing.git = package.git;
        }
        if existing.tag.is_none() {
            existing.tag = package.tag;
        }
        if existing.rev.is_none() {
            existing.rev = package.rev;
        }
        if existing.tarball.is_none() {
            existing.tarball = package.tarball;
        }
        if existing.oci.is_none() {
            existing.oci = package.oci;
        }
        for group in package.groups {
            if !existing.groups.contains(&group) {
                existing.groups.push(group);
            }
        }
        return Ok(());
    }
    manifest.packages.push(package);
    Ok(())
}

pub fn upsert_local(manifest: &mut Manifest, local: ManifestLocal) {
    if let Some(existing) = manifest
        .local
        .iter_mut()
        .find(|candidate| candidate.path == local.path)
    {
        existing.bound = existing.bound || local.bound;
        existing.optional = existing.optional || local.optional;
        if existing.position == "after" && local.position != "after" {
            existing.position = local.position;
        }
        return;
    }
    manifest.local.push(local);
}

pub fn bind_package_entry(target: &mut ManifestTarget, package_name: &str) {
    let entry = DestinationEntry::Package {
        name: package_name.to_string(),
    };
    if !target.entries.contains(&entry) {
        target.entries.push(entry);
    }
}

pub fn bind_local_entry(target: &mut ManifestTarget, path: &str) {
    let entry = DestinationEntry::Local {
        path: path.to_string(),
    };
    if !target.entries.contains(&entry) {
        target.entries.push(entry);
    }
}

pub fn role_for_destination(mode: DestinationMode) -> Option<ExportRole> {
    match mode {
        DestinationMode::Compose => Some(ExportRole::Fragment),
        DestinationMode::Tree => Some(ExportRole::Folder),
        DestinationMode::Legacy => None,
    }
}

pub fn package_bound_to_compose(package: &ManifestPackage, target: &ManifestTarget) -> bool {
    if target.scoped && target.mode == DestinationMode::Compose {
        return target.entries.iter().any(|entry| match entry {
            DestinationEntry::Package { name } => name == &package.name,
            DestinationEntry::Local { .. } => false,
        });
    }
    if package.bound || package.file.is_some() {
        return false;
    }
    true
}

pub fn package_bound_to_tree(package: &ManifestPackage, target: &ManifestTarget) -> bool {
    if target.scoped && target.mode == DestinationMode::Tree {
        return target.entries.iter().any(|entry| match entry {
            DestinationEntry::Package { name } => name == &package.name,
            DestinationEntry::Local { .. } => false,
        });
    }
    if package.bound || package.file.is_some() {
        return false;
    }
    true
}

pub fn export_kind_matches_role(kind: &str, role: ExportRole) -> bool {
    match role {
        ExportRole::Fragment => kind == "fragment",
        ExportRole::Folder => matches!(kind, "folder" | "skill"),
        ExportRole::File => kind == "file",
    }
}