1use std::process::Command;
5
6pub async fn which(command: &str) -> Option<String> {
9 which_impl(command).await
10}
11
12pub fn which_sync(command: &str) -> Option<String> {
14 which_impl_sync(command)
15}
16
17async fn which_impl(command: &str) -> Option<String> {
18 #[cfg(target_os = "windows")]
19 {
20 let output = Command::new("where.exe").arg(command).output().ok()?;
22
23 if !output.status.success() {
24 return None;
25 }
26
27 let stdout = String::from_utf8_lossy(&output.stdout);
28 let trimmed = stdout.trim();
29 if trimmed.is_empty() {
30 return None;
31 }
32
33 trimmed.split('\n').next().map(|s| s.trim().to_string())
35 }
36
37 #[cfg(not(target_os = "windows"))]
38 {
39 let output = Command::new("which").arg(command).output().ok()?;
41
42 if !output.status.success() {
43 return None;
44 }
45
46 let stdout = String::from_utf8_lossy(&output.stdout);
47 let trimmed = stdout.trim();
48 if trimmed.is_empty() {
49 return None;
50 }
51
52 Some(trimmed.to_string())
53 }
54}
55
56fn which_impl_sync(command: &str) -> Option<String> {
57 #[cfg(target_os = "windows")]
58 {
59 let output = Command::new("where.exe").arg(command).output().ok()?;
61
62 if !output.status.success() {
63 return None;
64 }
65
66 let stdout = String::from_utf8_lossy(&output.stdout);
67 let trimmed = stdout.trim();
68 if trimmed.is_empty() {
69 return None;
70 }
71
72 trimmed.split('\n').next().map(|s| s.trim().to_string())
74 }
75
76 #[cfg(not(target_os = "windows"))]
77 {
78 let output = Command::new("which").arg(command).output().ok()?;
80
81 if !output.status.success() {
82 return None;
83 }
84
85 let stdout = String::from_utf8_lossy(&output.stdout);
86 let trimmed = stdout.trim();
87 if trimmed.is_empty() {
88 return None;
89 }
90
91 Some(trimmed.to_string())
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test_which_sync_existing_command() {
101 let result = which_sync("ls");
103 assert!(result.is_some());
104 }
105
106 #[test]
107 fn test_which_sync_nonexistent_command() {
108 let result = which_sync("nonexistent_command_xyz123");
110 assert!(result.is_none());
111 }
112
113 #[tokio::test]
114 async fn test_which_async_existing_command() {
115 let result = which("ls").await;
116 assert!(result.is_some());
117 }
118
119 #[tokio::test]
120 async fn test_which_async_nonexistent_command() {
121 let result = which("nonexistent_command_xyz123").await;
122 assert!(result.is_none());
123 }
124}