rebecca-core 0.3.0

Core planning, safety, scanning, and history models for Rebecca.
Documentation
use std::sync::OnceLock;

use crate::error::{RebeccaError, Result};

use super::policy::{
    CACHEDIR_TAG_POLICY, PROJECT_ARTIFACT_POLICIES, all_project_artifact_policies,
    policy_for_definition, policy_for_dir_name, policy_matches_selector,
};
use super::{ProjectArtifactDefinition, ProjectArtifactPolicy};

const PRUNED_SCAN_DIRS: &[&str] = &[
    ".git",
    ".hg",
    ".svn",
    ".trash",
    "$recycle.bin",
    "library",
    "applications",
];

pub fn project_artifact_definitions() -> &'static [ProjectArtifactDefinition] {
    static DEFINITIONS: OnceLock<Vec<ProjectArtifactDefinition>> = OnceLock::new();

    DEFINITIONS
        .get_or_init(|| {
            PROJECT_ARTIFACT_POLICIES
                .iter()
                .map(|policy| policy.definition)
                .collect()
        })
        .as_slice()
}

pub fn all_project_artifact_definitions() -> impl Iterator<Item = ProjectArtifactDefinition> {
    project_artifact_definitions()
        .iter()
        .copied()
        .chain([CACHEDIR_TAG_POLICY.definition])
}

pub fn project_artifact_definition_for_dir_name(name: &str) -> Option<ProjectArtifactDefinition> {
    policy_for_dir_name(name).map(|policy| policy.definition)
}

pub fn validate_project_artifact_selectors(selectors: &[String]) -> Result<()> {
    for selector in selectors {
        if selector.trim().is_empty() {
            return Err(RebeccaError::InvalidProjectArtifactSelector(
                "selector cannot be empty".to_string(),
            ));
        }

        let known =
            all_project_artifact_policies().any(|policy| policy_matches_selector(policy, selector));
        if !known {
            return Err(RebeccaError::InvalidProjectArtifactSelector(
                selector.clone(),
            ));
        }
    }

    Ok(())
}

pub fn project_artifact_matches_selectors(
    definition: ProjectArtifactDefinition,
    selectors: &[String],
) -> bool {
    let Some(policy) = policy_for_definition(definition) else {
        return false;
    };
    project_artifact_policy_matches_selectors(policy, selectors)
}

pub(crate) fn project_artifact_policy_matches_selectors(
    policy: &ProjectArtifactPolicy,
    selectors: &[String],
) -> bool {
    selectors.is_empty()
        || selectors
            .iter()
            .any(|selector| policy_matches_selector(policy, selector))
}

pub(super) fn policy_for_directory_name(name: &str) -> Option<&'static ProjectArtifactPolicy> {
    policy_for_dir_name(name)
}

pub(super) fn is_known_project_artifact_dir_name(name: &str) -> bool {
    policy_for_dir_name(name).is_some()
}

pub(super) fn should_prune_scan_dir(name: &str) -> bool {
    PRUNED_SCAN_DIRS
        .iter()
        .any(|pruned| pruned.eq_ignore_ascii_case(name))
}