runmat_runtime/builtins/common/
path_state.rs1use once_cell::sync::Lazy;
11use std::sync::RwLock;
12
13use crate::builtins::common::env as runtime_env;
14
15pub const PATH_LIST_SEPARATOR: char = if cfg!(windows) { ';' } else { ':' };
17
18#[derive(Debug, Clone)]
19struct PathState {
20 current: String,
22}
23
24impl PathState {
25 fn initialise() -> Self {
26 Self {
27 current: initial_path_string(),
28 }
29 }
30}
31
32fn initial_path_string() -> String {
33 let mut parts = Vec::<String>::new();
34 for var in ["RUNMAT_PATH", "MATLABPATH"] {
35 if let Ok(value) = runtime_env::var(var) {
36 parts.extend(
37 value
38 .split(PATH_LIST_SEPARATOR)
39 .map(|part| part.trim())
40 .filter(|part| !part.is_empty())
41 .map(|part| part.to_string()),
42 );
43 }
44 }
45 join_parts(&parts)
46}
47
48fn join_parts(parts: &[String]) -> String {
49 let mut joined = String::new();
50 for (idx, part) in parts.iter().enumerate() {
51 if idx > 0 {
52 joined.push(PATH_LIST_SEPARATOR);
53 }
54 joined.push_str(part);
55 }
56 joined
57}
58
59static PATH_STATE: Lazy<RwLock<PathState>> = Lazy::new(|| RwLock::new(PathState::initialise()));
60
61pub fn current_path_string() -> String {
64 PATH_STATE
65 .read()
66 .map(|guard| guard.current.clone())
67 .unwrap_or_else(|poison| poison.into_inner().current.clone())
68}
69
70pub fn append_to_path(segments: &[String]) {
71 if segments.is_empty() {
72 return;
73 }
74 let mut guard = PATH_STATE
75 .write()
76 .unwrap_or_else(|poison| poison.into_inner());
77 let mut parts = split_segments(&guard.current);
78 parts.extend(segments.iter().cloned());
79 guard.current = join_parts(&parts);
80}
81
82pub fn set_path_string(new_path: &str) {
86 if new_path.is_empty() {
87 runtime_env::remove_var("RUNMAT_PATH");
88 } else {
89 runtime_env::set_var("RUNMAT_PATH", new_path);
90 }
91
92 let mut guard = PATH_STATE
93 .write()
94 .unwrap_or_else(|poison| poison.into_inner());
95 guard.current = new_path.to_string();
96}
97
98pub fn current_path_segments() -> Vec<String> {
101 let path = current_path_string();
102 split_segments(&path)
103}
104
105fn split_segments(path: &str) -> Vec<String> {
106 path.split(PATH_LIST_SEPARATOR)
107 .map(|part| part.trim())
108 .filter(|part| !part.is_empty())
109 .map(|part| part.to_string())
110 .collect()
111}
112
113#[cfg(test)]
114pub(crate) mod tests {
115 use super::*;
116
117 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
118 #[test]
119 fn join_and_split_round_trip() {
120 let parts = vec!["/tmp/a".to_string(), "/tmp/b".to_string()];
121 let joined = join_parts(&parts);
122 assert_eq!(split_segments(&joined), parts);
123 }
124}