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}