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