1use std::env;
2use std::fs;
3use std::path::PathBuf;
4
5use crate::errors::{HuakError, HuakResult};
6
7const PYTHON_BINARY_TARGETS: [PythonBinary; 3] = [
8 PythonBinary::Python3,
9 PythonBinary::Python,
10 PythonBinary::Python2,
11];
12
13enum PythonBinary {
14 Python,
15 Python3,
16 Python2,
17}
18
19impl PythonBinary {
20 fn as_str(&self) -> &'static str {
21 match &self {
22 PythonBinary::Python => "python",
23 PythonBinary::Python3 => "python3",
24 PythonBinary::Python2 => "python2",
25 }
26 }
27}
28
29pub fn find_python_binary_path(
34 from_dir: Option<PathBuf>,
35) -> HuakResult<String> {
36 let paths = match from_dir {
37 Some(path) => vec![path],
38 None => parse_path_var()?,
39 };
40
41 for path in paths {
42 for target in PYTHON_BINARY_TARGETS.iter() {
43 #[cfg(unix)]
44 {
45 if let Ok(Some(python)) = find_binary(target.as_str(), &path) {
46 return Ok(python);
47 }
48 }
49 }
50 }
51
52 Err(HuakError::PythonNotFound)
53}
54
55fn parse_path_var() -> HuakResult<Vec<PathBuf>> {
57 let path_str = match env::var("PATH") {
58 Ok(path) => path,
59 Err(e) => return Err(HuakError::EnvVarError(e)),
60 };
61
62 Ok(path_str.split(':').map(|dir| dir.into()).collect())
63}
64
65fn find_binary(bin_name: &str, dir: &PathBuf) -> HuakResult<Option<String>> {
70 let read_dir = match fs::read_dir(dir) {
71 Ok(read_dir) => read_dir,
72 Err(e) => return Err(HuakError::IOError(e)),
73 };
74
75 for dir_entry in read_dir.flatten() {
76 if let Ok(file_type) = dir_entry.file_type() {
77 if file_type.is_dir() {
78 if let Ok(bin_path) = find_binary(bin_name, &dir_entry.path()) {
79 return Ok(bin_path);
80 }
81 } else if let Some(file_name) = dir_entry.file_name().to_str() {
82 if file_name == bin_name {
83 if let Some(bin_path) = dir_entry.path().to_str() {
84 return Ok(Some(bin_path.to_string()));
85 }
86 }
87 }
88 }
89 }
90
91 Ok(None)
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 use tempfile::tempdir;
99
100 #[cfg(target_os = "windows")]
101 #[test]
102 fn test_python_search_windows() -> Result<(), std::io::Error> {
103 let directory = tempdir().unwrap();
104
105 fs::write(directory.path().join("python.exe"), "")?;
106
107 let expected_python =
108 String::from(directory.path().join("python.exe").to_str().unwrap());
109
110 assert_eq!(
111 find_binary("python.exe", &directory.into_path()).unwrap(),
112 Some(expected_python)
113 );
114
115 Ok(())
116 }
117
118 #[cfg(target_os = "macos")]
119 #[test]
120 fn test_python_search_macos() -> Result<(), std::io::Error> {
121 let directory = tempdir().unwrap();
122
123 fs::write(directory.path().join("python"), "")?;
124
125 let expected_python =
126 String::from(directory.path().join("python").to_str().unwrap());
127
128 assert_eq!(
129 find_binary("python", &directory.into_path()).unwrap(),
130 Some(expected_python)
131 );
132
133 Ok(())
134 }
135
136 #[cfg(target_os = "linux")]
137 #[test]
138 fn test_python_search_linux() -> Result<(), std::io::Error> {
139 let directory = tempdir().unwrap();
140
141 fs::write(directory.path().join("python3"), "")?;
142
143 let expected_python =
144 String::from(directory.path().join("python3").to_str().unwrap());
145
146 assert_eq!(
147 find_binary("python3", &directory.into_path()).unwrap(),
148 Some(expected_python)
149 );
150
151 Ok(())
152 }
153}