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}