pacsea 0.8.2

A fast, friendly TUI for browsing and installing Arch and AUR packages with built-in news and security scanning
Documentation
//! Distro-related logic helpers (filtering and labels).

/// What: Determine whether results from a repository should be visible under current toggles.
///
/// Inputs:
/// - `repo`: Name of the repository associated with a package result.
/// - `app`: Application state providing the filter toggles for official repos.
///
/// Output:
/// - `true` when the repository passes the active filters; otherwise `false`.
///
/// Details:
/// - Normalizes repository names and applies special-handling for EOS/CachyOS/Artix/BlackArch classification helpers.
/// - Repos listed in `repos.conf` use dynamic toggles (`results_filter_show_<canonical>` in `settings.conf`).
/// - Unknown repositories are only allowed when every official filter is enabled simultaneously.
#[must_use]
pub fn repo_toggle_for(repo: &str, app: &crate::state::AppState) -> bool {
    let r = repo.to_lowercase();
    if r == "core" {
        app.results_filter_show_core
    } else if r == "extra" {
        app.results_filter_show_extra
    } else if r == "multilib" {
        app.results_filter_show_multilib
    } else if crate::index::is_eos_repo(&r) {
        app.results_filter_show_eos
    } else if crate::index::is_cachyos_repo(&r) {
        app.results_filter_show_cachyos
    } else if crate::index::is_artix_omniverse(&r) {
        app.results_filter_show_artix_omniverse
    } else if crate::index::is_artix_universe(&r) {
        app.results_filter_show_artix_universe
    } else if crate::index::is_artix_lib32(&r) {
        app.results_filter_show_artix_lib32
    } else if crate::index::is_artix_galaxy(&r) {
        app.results_filter_show_artix_galaxy
    } else if crate::index::is_artix_world(&r) {
        app.results_filter_show_artix_world
    } else if crate::index::is_artix_system(&r) {
        app.results_filter_show_artix_system
    } else if crate::index::is_artix_repo(&r) {
        // Fallback for any other Artix repo (shouldn't happen, but safe)
        app.results_filter_show_artix
    } else if crate::index::is_blackarch_repo(&r) {
        app.results_filter_show_blackarch
    } else if let Some(filter_key) = app.repo_results_filter_by_name.get(&r) {
        app.results_filter_dynamic
            .get(filter_key)
            .copied()
            .unwrap_or(true)
    } else {
        // Unknown official repo: include only when all official filters are enabled
        app.results_filter_show_core
            && app.results_filter_show_extra
            && app.results_filter_show_multilib
            && app.results_filter_show_eos
            && app.results_filter_show_cachyos
            && app.results_filter_show_artix
            && app.results_filter_show_artix_omniverse
            && app.results_filter_show_artix_universe
            && app.results_filter_show_artix_lib32
            && app.results_filter_show_artix_galaxy
            && app.results_filter_show_artix_world
            && app.results_filter_show_artix_system
            && app.results_filter_show_blackarch
    }
}

/// What: Produce a human-friendly label for an official package entry.
///
/// Inputs:
/// - `repo`: Repository reported by the package source.
/// - `name`: Package name used to detect Manjaro naming conventions.
/// - `owner`: Optional upstream owner string available from package metadata.
///
/// Output:
/// - Returns a display label describing the ecosystem the package belongs to.
///
/// Details:
/// - Distinguishes `EndeavourOS`, `CachyOS`, `Artix Linux` repos (with specific labels for each Artix repo:
///   `OMNI`, `UNI`, `LIB32`, `GALAXY`, `WORLD`, `SYSTEM`), `BlackArch`, and detects `Manjaro` branding by name/owner heuristics.
/// - Falls back to the raw repository string when no special classification matches.
#[must_use]
pub fn label_for_official(repo: &str, name: &str, owner: &str) -> String {
    let r = repo.to_lowercase();
    if crate::index::is_eos_repo(&r) {
        "EOS".to_string()
    } else if crate::index::is_cachyos_repo(&r) {
        "CachyOS".to_string()
    } else if crate::index::is_artix_omniverse(&r) {
        "OMNI".to_string()
    } else if crate::index::is_artix_universe(&r) {
        "UNI".to_string()
    } else if crate::index::is_artix_lib32(&r) {
        "LIB32".to_string()
    } else if crate::index::is_artix_galaxy(&r) {
        "GALAXY".to_string()
    } else if crate::index::is_artix_world(&r) {
        "WORLD".to_string()
    } else if crate::index::is_artix_system(&r) {
        "SYSTEM".to_string()
    } else if crate::index::is_artix_repo(&r) {
        // Fallback for any other Artix repo (shouldn't happen, but safe)
        "Artix".to_string()
    } else if crate::index::is_blackarch_repo(&r) {
        "BlackArch".to_string()
    } else if crate::index::is_manjaro_name_or_owner(name, owner) {
        "Manjaro".to_string()
    } else {
        repo.to_string()
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use super::*;
    use crate::state::AppState;

    #[test]
    /// What: Validate canonical repository toggles deny disabled repositories while permitting enabled ones.
    ///
    /// Inputs:
    /// - `app`: Application state with `core` enabled and other official toggles disabled.
    ///
    /// Output:
    /// - `repo_toggle_for` allows `core` entries but rejects `extra` and `multilib`.
    ///
    /// Details:
    /// - Ensures the per-repository gate respects the individual boolean flags.
    fn repo_toggle_respects_individual_flags() {
        let app = AppState {
            results_filter_show_core: true,
            results_filter_show_extra: false,
            results_filter_show_multilib: false,
            results_filter_show_eos: false,
            results_filter_show_cachyos: false,
            results_filter_show_artix: false,
            results_filter_show_artix_omniverse: false,
            results_filter_show_artix_universe: false,
            results_filter_show_artix_lib32: false,
            results_filter_show_artix_galaxy: false,
            results_filter_show_artix_world: false,
            results_filter_show_artix_system: false,
            ..Default::default()
        };

        assert!(repo_toggle_for("core", &app));
        assert!(!repo_toggle_for("extra", &app));
        assert!(!repo_toggle_for("multilib", &app));
    }

    #[test]
    /// What: Ensure unknown official repositories require every official toggle to be enabled.
    ///
    /// Inputs:
    /// - `app`: Application state with all official flags on, then one flag disabled.
    ///
    /// Output:
    /// - Unknown repository accepted when fully enabled and rejected once any flag is turned off.
    ///
    /// Details:
    /// - Exercises the fallback clause guarding unfamiliar repositories.
    fn repo_toggle_unknown_only_with_full_whitelist() {
        let mut app = AppState {
            results_filter_show_core: true,
            results_filter_show_extra: true,
            results_filter_show_multilib: true,
            results_filter_show_eos: true,
            results_filter_show_cachyos: true,
            results_filter_show_artix: true,
            results_filter_show_artix_omniverse: true,
            results_filter_show_artix_universe: true,
            results_filter_show_artix_lib32: true,
            results_filter_show_artix_galaxy: true,
            results_filter_show_artix_world: true,
            results_filter_show_artix_system: true,
            ..Default::default()
        };

        assert!(repo_toggle_for("unlisted", &app));

        app.results_filter_show_multilib = false;
        assert!(!repo_toggle_for("unlisted", &app));
    }

    #[test]
    /// What: Confirm label helper emits ecosystem-specific aliases for recognised repositories.
    ///
    /// Inputs:
    /// - Repository/name permutations covering `EndeavourOS`, `CachyOS`, `Artix Linux` (with specific repo labels), `Manjaro`, and a generic repo.
    ///
    /// Output:
    /// - Labels reduce to `EOS`, `CachyOS`, `OMNI`, `UNI` (for specific Artix repos), `Manjaro`, and the original repo name respectively.
    ///
    /// Details:
    /// - Validates the Manjaro heuristic via package name and the repo classification helpers.
    /// - Confirms specific Artix repos return their specific labels (OMNI, UNI, etc.) rather than the generic "Artix" label.
    fn label_for_official_prefers_special_cases() {
        assert_eq!(label_for_official("endeavouros", "pkg", ""), "EOS");
        assert_eq!(label_for_official("cachyos-extra", "pkg", ""), "CachyOS");
        assert_eq!(label_for_official("omniverse", "pkg", ""), "OMNI");
        assert_eq!(label_for_official("universe", "pkg", ""), "UNI");
        assert_eq!(label_for_official("blackarch", "pkg", ""), "BlackArch");
        assert_eq!(label_for_official("extra", "manjaro-kernel", ""), "Manjaro");
        assert_eq!(label_for_official("core", "glibc", ""), "core");
    }

    #[test]
    /// What: Validate `BlackArch` toggle routes through its dedicated flag.
    ///
    /// Inputs:
    /// - `app`: Application state with `BlackArch` enabled, then disabled.
    ///
    /// Output:
    /// - `repo_toggle_for("blackarch", ...)` respects the `results_filter_show_blackarch` flag.
    ///
    /// Details:
    /// - Exercises the `BlackArch` branch in `repo_toggle_for`.
    fn repo_toggle_blackarch_flag() {
        let mut app = AppState {
            results_filter_show_blackarch: true,
            ..Default::default()
        };
        assert!(repo_toggle_for("blackarch", &app));
        assert!(repo_toggle_for("BlackArch", &app));

        app.results_filter_show_blackarch = false;
        assert!(!repo_toggle_for("blackarch", &app));
    }

    #[test]
    /// What: Ensure unknown-repo fallback includes `BlackArch` in its all-on requirement.
    ///
    /// Inputs:
    /// - `app`: Application state with all official flags on, then `BlackArch` disabled.
    ///
    /// Output:
    /// - Unknown repo rejected when `BlackArch` flag is off.
    ///
    /// Details:
    /// - Verifies the extended conjunction in the fallback clause.
    fn repo_toggle_unknown_includes_blackarch_flag() {
        let mut app = AppState {
            results_filter_show_core: true,
            results_filter_show_extra: true,
            results_filter_show_multilib: true,
            results_filter_show_eos: true,
            results_filter_show_cachyos: true,
            results_filter_show_artix: true,
            results_filter_show_artix_omniverse: true,
            results_filter_show_artix_universe: true,
            results_filter_show_artix_lib32: true,
            results_filter_show_artix_galaxy: true,
            results_filter_show_artix_world: true,
            results_filter_show_artix_system: true,
            results_filter_show_blackarch: true,
            ..Default::default()
        };
        assert!(repo_toggle_for("unlisted", &app));

        app.results_filter_show_blackarch = false;
        assert!(!repo_toggle_for("unlisted", &app));
    }

    #[test]
    /// What: Repos mapped via `repos.conf` respect `results_filter_dynamic`.
    ///
    /// Inputs:
    /// - `app`: Custom repo name `myvendor` with canonical filter `vendor_pkgs` toggled off.
    ///
    /// Output:
    /// - `repo_toggle_for("myvendor", ...)` is `false` when dynamic map disables the filter.
    ///
    /// Details:
    /// - Builtin branches take precedence; this exercises the dynamic `repos.conf` path only.
    fn repo_toggle_respects_repos_conf_dynamic_filter() {
        let mut by_name = HashMap::new();
        by_name.insert("myvendor".to_string(), "vendor_pkgs".to_string());
        let mut dynamic = HashMap::new();
        dynamic.insert("vendor_pkgs".to_string(), false);
        let app = AppState {
            repo_results_filter_by_name: by_name,
            results_filter_dynamic: dynamic,
            ..Default::default()
        };
        assert!(!repo_toggle_for("myvendor", &app));
        assert!(!repo_toggle_for("MyVendor", &app));
    }
}