Skip to main content

arch_toolkit/install/
detect.rs

1//! Detection of AUR helpers and privilege escalation tools on the system.
2
3use crate::types::install::{AurHelper, PrivilegeTool};
4
5use super::shell::command_on_path;
6
7/// What: Detect the preferred AUR helper available on `PATH`.
8///
9/// Inputs: None.
10///
11/// Output:
12/// - `Some(AurHelper::Paru)` when `paru` is available.
13/// - `Some(AurHelper::Yay)` when only `yay` is available.
14/// - `None` when neither helper is installed.
15///
16/// Details:
17/// - Preference order (paru first, yay fallback) matches Pacsea's install flow.
18/// - Detection is explicit: command builders never call this implicitly, so
19///   callers can override the choice (e.g., from user configuration).
20///
21/// # Example
22///
23/// ```no_run
24/// use arch_toolkit::install::detect_aur_helper;
25///
26/// match detect_aur_helper() {
27///     Some(helper) => println!("Using AUR helper: {helper}"),
28///     None => eprintln!("No AUR helper (paru/yay) found."),
29/// }
30/// ```
31#[must_use]
32pub fn detect_aur_helper() -> Option<AurHelper> {
33    if command_on_path(AurHelper::Paru.binary_name()) {
34        Some(AurHelper::Paru)
35    } else if command_on_path(AurHelper::Yay.binary_name()) {
36        Some(AurHelper::Yay)
37    } else {
38        None
39    }
40}
41
42/// What: Detect the preferred privilege escalation tool available on `PATH`.
43///
44/// Inputs: None.
45///
46/// Output:
47/// - `Some(PrivilegeTool::Doas)` when `doas` is available.
48/// - `Some(PrivilegeTool::Sudo)` when only `sudo` is available.
49/// - `None` when neither tool is installed.
50///
51/// Details:
52/// - Preference order (doas first, sudo fallback) matches Pacsea's `Auto`
53///   privilege mode: sudo is present on most systems by default, so an
54///   installed doas signals a deliberate user choice.
55/// - Callers with an explicit user configuration should honor it via
56///   [`is_privilege_tool_available`] instead of calling this.
57/// - Password handling is intentionally out of scope for arch-toolkit.
58///
59/// # Example
60///
61/// ```no_run
62/// use arch_toolkit::install::detect_privilege_tool;
63///
64/// match detect_privilege_tool() {
65///     Some(tool) => println!("Privilege tool: {tool}"),
66///     None => eprintln!("No privilege tool (sudo/doas) found."),
67/// }
68/// ```
69#[must_use]
70pub fn detect_privilege_tool() -> Option<PrivilegeTool> {
71    if command_on_path(PrivilegeTool::Doas.binary_name()) {
72        Some(PrivilegeTool::Doas)
73    } else if command_on_path(PrivilegeTool::Sudo.binary_name()) {
74        Some(PrivilegeTool::Sudo)
75    } else {
76        None
77    }
78}
79
80/// What: Check whether a specific AUR helper is available on `PATH`.
81///
82/// Inputs:
83/// - `helper`: The helper to check.
84///
85/// Output:
86/// - `true` when the helper binary is executable on `PATH`.
87///
88/// Details:
89/// - Useful when the caller has a configured preference and wants to verify it.
90#[must_use]
91pub fn is_aur_helper_available(helper: AurHelper) -> bool {
92    command_on_path(helper.binary_name())
93}
94
95/// What: Check whether a specific privilege tool is available on `PATH`.
96///
97/// Inputs:
98/// - `tool`: The tool to check.
99///
100/// Output:
101/// - `true` when the tool binary is executable on `PATH`.
102///
103/// Details:
104/// - Useful when the caller has a configured preference and wants to verify it.
105#[must_use]
106pub fn is_privilege_tool_available(tool: PrivilegeTool) -> bool {
107    command_on_path(tool.binary_name())
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    /// What: Verify detection functions return consistent results with availability checks.
116    ///
117    /// Inputs:
118    /// - Current system `PATH` (environment-dependent).
119    ///
120    /// Output:
121    /// - When detection returns a helper/tool, its availability check agrees.
122    ///
123    /// Details:
124    /// - Cannot assert specific tools exist (CI environments differ), so this
125    ///   validates internal consistency and preference ordering instead.
126    fn detection_consistency() {
127        if let Some(helper) = detect_aur_helper() {
128            assert!(is_aur_helper_available(helper));
129            // Preference: if paru is available, it must be chosen over yay.
130            if is_aur_helper_available(AurHelper::Paru) {
131                assert_eq!(helper, AurHelper::Paru);
132            }
133        }
134        if let Some(tool) = detect_privilege_tool() {
135            assert!(is_privilege_tool_available(tool));
136            // Preference: if doas is available, it must be chosen over sudo.
137            if is_privilege_tool_available(PrivilegeTool::Doas) {
138                assert_eq!(tool, PrivilegeTool::Doas);
139            }
140        }
141    }
142}