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}