py_executer_lib/py_executer_lib.rs
1pub mod macros;
2pub mod path;
3
4use anyhow::anyhow;
5use colored::*;
6use std::collections::HashMap;
7use std::process::Command;
8use std::{env, path::PathBuf};
9
10/// Append the current working directory to the `PYTHONPATH` environment variable.
11///
12/// The function takes a `PathBuf` as its argument, which represents the current working directory.
13/// It returns a `HashMap` where the key is the name of the environment variable and the value is
14/// the value of the environment variable.
15fn append_pwd_to_pythonpath(runtime_path: &PathBuf) -> HashMap<String, String> {
16 let mut path = env::var("PYTHONPATH").unwrap_or_default();
17 if !path.contains(&runtime_path.to_string_lossy().to_string()) {
18 if !path.is_empty() {
19 path.push(':');
20 }
21 path.push_str(runtime_path.to_string_lossy().to_string().as_str());
22 }
23 HashMap::from([("PYTHONPATH".to_string(), path)])
24}
25
26/// Set additional environment variables from command line arguments.
27///
28/// The function takes a list of strings as its first argument, where each string is a key-value pair
29/// separated by an '=' character. The function will parse each string and add the key-value pair to
30/// the `HashMap` returned by this function.
31///
32/// If a key-value pair is malformed, the function will print a warning message and ignore the pair.
33///
34/// The function also adds the current working directory to the `PYTHONPATH` environment variable.
35pub fn set_additional_env_var(
36 additional_env_from_args: Vec<String>,
37 runtime_path: &PathBuf,
38 quiet: bool,
39) -> HashMap<String, String> {
40 let mut additional_env = HashMap::new();
41
42 //add current dir to PYTHONPATH
43 additional_env.extend(append_pwd_to_pythonpath(runtime_path));
44
45 for env_var in additional_env_from_args {
46 if let Some(pos) = env_var.find('=') {
47 let key = env_var[..pos].to_string();
48 let value = env_var[pos + 1..].to_string();
49 additional_env.insert(key.clone(), value.clone());
50 if !quiet {
51 println!("Setting env: {} = {}", key.bold(), value);
52 }
53 } else {
54 if !quiet {
55 warning_println!(
56 "Warning: Ignoring malformed environment variable: {}",
57 env_var.bold()
58 );
59 }
60 }
61 }
62 additional_env
63}
64
65/// Parse and validate a script path.
66///
67/// The function takes a `PathBuf` as argument and checks if the path exists.
68/// If the path exists, it returns a tuple of `(PathBuf, PathBuf)`, where the first element
69/// is the absolute path of the script and the second element is the parent directory of
70/// the script.
71///
72/// # Errors
73///
74/// The function returns an `Err` if the path does not exist or if the parent directory
75/// cannot be obtained.
76pub fn validate_to_absolute_path(script_path: &PathBuf) -> anyhow::Result<PathBuf> {
77 match script_path.canonicalize() {
78 Ok(path) => {
79 if !path.exists() {
80 return Err(anyhow!("{} not exists", path.display().to_string().bold()));
81 }
82 Ok(path)
83 }
84 Err(err) => Err(anyhow!("Failed to get absolute path of script: {}", err)),
85 }
86}
87
88/// Find the path of the `uv` command.
89///
90/// The function returns `Ok<String>` if `uv` is found, where the string is the path of
91/// the `uv` command. If `uv` is not found, the function prints a hint to install it
92/// and returns an `Err`.
93///
94/// The function uses `which` or `where` command to find the path of `uv`. If the command
95/// is not successful, it means `uv` is not installed, so the function prints a hint to
96/// install it and returns an `Err`.
97///
98/// # Errors
99///
100/// The function returns an `Err` if `uv` is not installed.
101pub fn get_uv_path() -> anyhow::Result<String> {
102 // For Unix-like systems (Linux, macOS)
103 #[cfg(not(target_os = "windows"))]
104 let find_executable = "which";
105
106 // For Windows
107 #[cfg(target_os = "windows")]
108 let find_executable = "where";
109
110 let output = Command::new(find_executable).arg("uv").output()?;
111 if output.status.success() {
112 // found uv
113 let path = String::from_utf8(output.stdout)?.trim().to_string();
114 Ok(path)
115 } else {
116 // not found uv, hint to install it
117
118 // for unix, run wget -qO- https://astral.sh/uv/install.sh | sh
119 eprintln!("Please run the following command to install uv:");
120 #[cfg(not(target_os = "windows"))]
121 eprintln!("wget -qO- https://astral.sh/uv/install.sh | sh");
122
123 // for windows, run powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
124 #[cfg(target_os = "windows")]
125 eprintln!(
126 "powershell -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\""
127 );
128 Err(anyhow!("uv not installed"))
129 }
130}
131
132/// Returns the path of the Python executable within the given virtual environment.
133///
134/// For Unix-like systems (Linux, macOS), the Python executable is located in the `bin` directory.
135///
136/// For Windows, the Python executable is located in the `Scripts` directory, and has the `.exe` extension.
137pub fn get_python_exec_path(venv_path: &PathBuf) -> PathBuf {
138 PathBuf::from(if cfg!(target_os = "windows") {
139 venv_path
140 .join("Scripts")
141 .join("python.exe")
142 .to_string_lossy()
143 .to_string()
144 } else {
145 venv_path
146 .join("bin")
147 .join("python")
148 .to_string_lossy()
149 .to_string()
150 })
151}