1#[cfg(feature = "native-runtime")]
2pub mod navite;
3
4use anyhow::Result;
5use std::{collections::HashMap, env};
6
7pub trait Runtime
8where
9 Self: Copy + Clone,
10{
11 const INTERNAL_SYMBOL: &'static str = "___internal___";
12 fn os(&self) -> String;
13 fn shell_path(&self) -> Result<String>;
14 fn bash_path(&self) -> Option<String>;
15 fn exec_bash_functions(
16 &self,
17 script_file: &str,
18 functions: &[&str],
19 args: &[String],
20 envs: HashMap<String, String>,
21 ) -> Option<Vec<String>>;
22 fn current_exe(&self) -> Option<String>;
23 fn current_dir(&self) -> Option<String>;
24 fn env_vars(&self) -> HashMap<String, String>;
25 fn env_var(&self, name: &str) -> Option<String>;
26 fn which(&self, name: &str) -> Option<String>;
27 fn exist_path(&self, path: &str) -> bool;
28 fn parent_path(&self, path: &str) -> Option<String>;
29 fn join_path(&self, path: &str, parts: &[&str]) -> String;
30 fn chdir(&self, cwd: &str, cd: &str) -> Option<String>;
31 fn metadata(&self, path: &str) -> Option<(bool, bool, bool)>;
32 fn read_dir(&self, path: &str) -> Option<Vec<String>>;
33 fn read_to_string(&self, path: &str) -> Option<String>;
34
35 fn is_windows(&self) -> bool {
36 self.os() == "windows"
37 }
38
39 fn shell_args(&self, shell_path: &str) -> Vec<String> {
40 if let Some(name) = self.basename(shell_path).map(|v| v.to_lowercase()) {
41 match name.as_str() {
42 "bash" => vec!["--noprofile".to_string(), "--norc".to_string()],
43 _ => vec![],
44 }
45 } else {
46 vec![]
47 }
48 }
49
50 fn basename(&self, path: &str) -> Option<String> {
51 let parts: Vec<_> = path.split(['/', '\\']).collect();
52 let last_part = parts.last()?;
53 let name = match last_part.rsplit_once('.') {
54 Some((v, _)) => v.to_string(),
55 None => last_part.to_string(),
56 };
57 Some(name)
58 }
59
60 fn load_dotenv(&self, path: &str) -> Option<HashMap<String, String>> {
61 let contents = self.read_to_string(path)?;
62 let mut output = HashMap::new();
63 for line in contents.lines() {
64 if line.starts_with('#') || line.trim().is_empty() {
65 continue;
66 }
67 if let Some((key, value)) = line.split_once('=') {
68 let env_name = key.trim().to_string();
69 let env_value = value.trim().to_string();
70 let env_value = if (env_value.starts_with('"') && env_value.ends_with('"'))
71 || (env_value.starts_with('\'') && env_value.ends_with('\''))
72 {
73 &env_value[1..env_value.len() - 1]
74 } else {
75 &env_value
76 };
77
78 if env::var(&env_name).is_err() {
79 output.insert(env_name, env_value.to_string());
80 }
81 }
82 }
83 Some(output)
84 }
85
86 fn path_env_with_current_exe(&self) -> String {
87 let mut path_env = self.env_var("PATH").unwrap_or_default();
88 if let Some(exe_dir) = self
89 .current_exe()
90 .and_then(|exe_path| self.parent_path(&exe_path))
91 {
92 if self.is_windows() {
93 path_env = format!("{exe_dir};{path_env}")
94 } else {
95 path_env = format!("{exe_dir}:{path_env}")
96 }
97 }
98 path_env
99 }
100}