Skip to main content

pacsea/logic/
distro.rs

1//! Distro-related logic helpers (filtering and labels).
2
3/// What: Determine whether results from a repository should be visible under current toggles.
4///
5/// Inputs:
6/// - `repo`: Name of the repository associated with a package result.
7/// - `app`: Application state providing the filter toggles for official repos.
8///
9/// Output:
10/// - `true` when the repository passes the active filters; otherwise `false`.
11///
12/// Details:
13/// - Normalizes repository names and applies special-handling for EOS/CachyOS/Artix/BlackArch classification helpers.
14/// - Repos listed in `repos.conf` use dynamic toggles (`results_filter_show_<canonical>` in `settings.conf`).
15/// - Unknown repositories are only allowed when every official filter is enabled simultaneously.
16#[must_use]
17pub fn repo_toggle_for(repo: &str, app: &crate::state::AppState) -> bool {
18    let r = repo.to_lowercase();
19    if r == "core" {
20        app.results_filter_show_core
21    } else if r == "extra" {
22        app.results_filter_show_extra
23    } else if r == "multilib" {
24        app.results_filter_show_multilib
25    } else if crate::index::is_eos_repo(&r) {
26        app.results_filter_show_eos
27    } else if crate::index::is_cachyos_repo(&r) {
28        app.results_filter_show_cachyos
29    } else if crate::index::is_artix_omniverse(&r) {
30        app.results_filter_show_artix_omniverse
31    } else if crate::index::is_artix_universe(&r) {
32        app.results_filter_show_artix_universe
33    } else if crate::index::is_artix_lib32(&r) {
34        app.results_filter_show_artix_lib32
35    } else if crate::index::is_artix_galaxy(&r) {
36        app.results_filter_show_artix_galaxy
37    } else if crate::index::is_artix_world(&r) {
38        app.results_filter_show_artix_world
39    } else if crate::index::is_artix_system(&r) {
40        app.results_filter_show_artix_system
41    } else if crate::index::is_artix_repo(&r) {
42        // Fallback for any other Artix repo (shouldn't happen, but safe)
43        app.results_filter_show_artix
44    } else if crate::index::is_blackarch_repo(&r) {
45        app.results_filter_show_blackarch
46    } else if let Some(filter_key) = app.repo_results_filter_by_name.get(&r) {
47        app.results_filter_dynamic
48            .get(filter_key)
49            .copied()
50            .unwrap_or(true)
51    } else {
52        // Unknown official repo: include only when all official filters are enabled
53        app.results_filter_show_core
54            && app.results_filter_show_extra
55            && app.results_filter_show_multilib
56            && app.results_filter_show_eos
57            && app.results_filter_show_cachyos
58            && app.results_filter_show_artix
59            && app.results_filter_show_artix_omniverse
60            && app.results_filter_show_artix_universe
61            && app.results_filter_show_artix_lib32
62            && app.results_filter_show_artix_galaxy
63            && app.results_filter_show_artix_world
64            && app.results_filter_show_artix_system
65            && app.results_filter_show_blackarch
66    }
67}
68
69/// What: Produce a human-friendly label for an official package entry.
70///
71/// Inputs:
72/// - `repo`: Repository reported by the package source.
73/// - `name`: Package name used to detect Manjaro naming conventions.
74/// - `owner`: Optional upstream owner string available from package metadata.
75///
76/// Output:
77/// - Returns a display label describing the ecosystem the package belongs to.
78///
79/// Details:
80/// - Distinguishes `EndeavourOS`, `CachyOS`, `Artix Linux` repos (with specific labels for each Artix repo:
81///   `OMNI`, `UNI`, `LIB32`, `GALAXY`, `WORLD`, `SYSTEM`), `BlackArch`, and detects `Manjaro` branding by name/owner heuristics.
82/// - Falls back to the raw repository string when no special classification matches.
83#[must_use]
84pub fn label_for_official(repo: &str, name: &str, owner: &str) -> String {
85    let r = repo.to_lowercase();
86    if crate::index::is_eos_repo(&r) {
87        "EOS".to_string()
88    } else if crate::index::is_cachyos_repo(&r) {
89        "CachyOS".to_string()
90    } else if crate::index::is_artix_omniverse(&r) {
91        "OMNI".to_string()
92    } else if crate::index::is_artix_universe(&r) {
93        "UNI".to_string()
94    } else if crate::index::is_artix_lib32(&r) {
95        "LIB32".to_string()
96    } else if crate::index::is_artix_galaxy(&r) {
97        "GALAXY".to_string()
98    } else if crate::index::is_artix_world(&r) {
99        "WORLD".to_string()
100    } else if crate::index::is_artix_system(&r) {
101        "SYSTEM".to_string()
102    } else if crate::index::is_artix_repo(&r) {
103        // Fallback for any other Artix repo (shouldn't happen, but safe)
104        "Artix".to_string()
105    } else if crate::index::is_blackarch_repo(&r) {
106        "BlackArch".to_string()
107    } else if crate::index::is_manjaro_name_or_owner(name, owner) {
108        "Manjaro".to_string()
109    } else {
110        repo.to_string()
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use std::collections::HashMap;
117
118    use super::*;
119    use crate::state::AppState;
120
121    #[test]
122    /// What: Validate canonical repository toggles deny disabled repositories while permitting enabled ones.
123    ///
124    /// Inputs:
125    /// - `app`: Application state with `core` enabled and other official toggles disabled.
126    ///
127    /// Output:
128    /// - `repo_toggle_for` allows `core` entries but rejects `extra` and `multilib`.
129    ///
130    /// Details:
131    /// - Ensures the per-repository gate respects the individual boolean flags.
132    fn repo_toggle_respects_individual_flags() {
133        let app = AppState {
134            results_filter_show_core: true,
135            results_filter_show_extra: false,
136            results_filter_show_multilib: false,
137            results_filter_show_eos: false,
138            results_filter_show_cachyos: false,
139            results_filter_show_artix: false,
140            results_filter_show_artix_omniverse: false,
141            results_filter_show_artix_universe: false,
142            results_filter_show_artix_lib32: false,
143            results_filter_show_artix_galaxy: false,
144            results_filter_show_artix_world: false,
145            results_filter_show_artix_system: false,
146            ..Default::default()
147        };
148
149        assert!(repo_toggle_for("core", &app));
150        assert!(!repo_toggle_for("extra", &app));
151        assert!(!repo_toggle_for("multilib", &app));
152    }
153
154    #[test]
155    /// What: Ensure unknown official repositories require every official toggle to be enabled.
156    ///
157    /// Inputs:
158    /// - `app`: Application state with all official flags on, then one flag disabled.
159    ///
160    /// Output:
161    /// - Unknown repository accepted when fully enabled and rejected once any flag is turned off.
162    ///
163    /// Details:
164    /// - Exercises the fallback clause guarding unfamiliar repositories.
165    fn repo_toggle_unknown_only_with_full_whitelist() {
166        let mut app = AppState {
167            results_filter_show_core: true,
168            results_filter_show_extra: true,
169            results_filter_show_multilib: true,
170            results_filter_show_eos: true,
171            results_filter_show_cachyos: true,
172            results_filter_show_artix: true,
173            results_filter_show_artix_omniverse: true,
174            results_filter_show_artix_universe: true,
175            results_filter_show_artix_lib32: true,
176            results_filter_show_artix_galaxy: true,
177            results_filter_show_artix_world: true,
178            results_filter_show_artix_system: true,
179            ..Default::default()
180        };
181
182        assert!(repo_toggle_for("unlisted", &app));
183
184        app.results_filter_show_multilib = false;
185        assert!(!repo_toggle_for("unlisted", &app));
186    }
187
188    #[test]
189    /// What: Confirm label helper emits ecosystem-specific aliases for recognised repositories.
190    ///
191    /// Inputs:
192    /// - Repository/name permutations covering `EndeavourOS`, `CachyOS`, `Artix Linux` (with specific repo labels), `Manjaro`, and a generic repo.
193    ///
194    /// Output:
195    /// - Labels reduce to `EOS`, `CachyOS`, `OMNI`, `UNI` (for specific Artix repos), `Manjaro`, and the original repo name respectively.
196    ///
197    /// Details:
198    /// - Validates the Manjaro heuristic via package name and the repo classification helpers.
199    /// - Confirms specific Artix repos return their specific labels (OMNI, UNI, etc.) rather than the generic "Artix" label.
200    fn label_for_official_prefers_special_cases() {
201        assert_eq!(label_for_official("endeavouros", "pkg", ""), "EOS");
202        assert_eq!(label_for_official("cachyos-extra", "pkg", ""), "CachyOS");
203        assert_eq!(label_for_official("omniverse", "pkg", ""), "OMNI");
204        assert_eq!(label_for_official("universe", "pkg", ""), "UNI");
205        assert_eq!(label_for_official("blackarch", "pkg", ""), "BlackArch");
206        assert_eq!(label_for_official("extra", "manjaro-kernel", ""), "Manjaro");
207        assert_eq!(label_for_official("core", "glibc", ""), "core");
208    }
209
210    #[test]
211    /// What: Validate `BlackArch` toggle routes through its dedicated flag.
212    ///
213    /// Inputs:
214    /// - `app`: Application state with `BlackArch` enabled, then disabled.
215    ///
216    /// Output:
217    /// - `repo_toggle_for("blackarch", ...)` respects the `results_filter_show_blackarch` flag.
218    ///
219    /// Details:
220    /// - Exercises the `BlackArch` branch in `repo_toggle_for`.
221    fn repo_toggle_blackarch_flag() {
222        let mut app = AppState {
223            results_filter_show_blackarch: true,
224            ..Default::default()
225        };
226        assert!(repo_toggle_for("blackarch", &app));
227        assert!(repo_toggle_for("BlackArch", &app));
228
229        app.results_filter_show_blackarch = false;
230        assert!(!repo_toggle_for("blackarch", &app));
231    }
232
233    #[test]
234    /// What: Ensure unknown-repo fallback includes `BlackArch` in its all-on requirement.
235    ///
236    /// Inputs:
237    /// - `app`: Application state with all official flags on, then `BlackArch` disabled.
238    ///
239    /// Output:
240    /// - Unknown repo rejected when `BlackArch` flag is off.
241    ///
242    /// Details:
243    /// - Verifies the extended conjunction in the fallback clause.
244    fn repo_toggle_unknown_includes_blackarch_flag() {
245        let mut app = AppState {
246            results_filter_show_core: true,
247            results_filter_show_extra: true,
248            results_filter_show_multilib: true,
249            results_filter_show_eos: true,
250            results_filter_show_cachyos: true,
251            results_filter_show_artix: true,
252            results_filter_show_artix_omniverse: true,
253            results_filter_show_artix_universe: true,
254            results_filter_show_artix_lib32: true,
255            results_filter_show_artix_galaxy: true,
256            results_filter_show_artix_world: true,
257            results_filter_show_artix_system: true,
258            results_filter_show_blackarch: true,
259            ..Default::default()
260        };
261        assert!(repo_toggle_for("unlisted", &app));
262
263        app.results_filter_show_blackarch = false;
264        assert!(!repo_toggle_for("unlisted", &app));
265    }
266
267    #[test]
268    /// What: Repos mapped via `repos.conf` respect `results_filter_dynamic`.
269    ///
270    /// Inputs:
271    /// - `app`: Custom repo name `myvendor` with canonical filter `vendor_pkgs` toggled off.
272    ///
273    /// Output:
274    /// - `repo_toggle_for("myvendor", ...)` is `false` when dynamic map disables the filter.
275    ///
276    /// Details:
277    /// - Builtin branches take precedence; this exercises the dynamic `repos.conf` path only.
278    fn repo_toggle_respects_repos_conf_dynamic_filter() {
279        let mut by_name = HashMap::new();
280        by_name.insert("myvendor".to_string(), "vendor_pkgs".to_string());
281        let mut dynamic = HashMap::new();
282        dynamic.insert("vendor_pkgs".to_string(), false);
283        let app = AppState {
284            repo_results_filter_by_name: by_name,
285            results_filter_dynamic: dynamic,
286            ..Default::default()
287        };
288        assert!(!repo_toggle_for("myvendor", &app));
289        assert!(!repo_toggle_for("MyVendor", &app));
290    }
291}