1#![allow(clippy::missing_panics_doc)]
2
3use std::{
4 collections::BTreeMap,
5 env::vars_os,
6 ffi::{OsStr, OsString},
7 sync::{Arc, Mutex},
8};
9
10use mlua::prelude::*;
11use os_str_bytes::{OsStrBytes, OsStringBytes};
12
13#[derive(Debug, Default)]
16struct ProcessEnvInner {
17 values: BTreeMap<OsString, OsString>,
18}
19
20impl FromIterator<(OsString, OsString)> for ProcessEnvInner {
21 fn from_iter<T: IntoIterator<Item = (OsString, OsString)>>(iter: T) -> Self {
22 Self {
23 values: iter.into_iter().collect(),
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
40pub struct ProcessEnv {
41 inner: Arc<Mutex<ProcessEnvInner>>,
42}
43
44impl ProcessEnv {
45 #[must_use]
46 pub fn empty() -> Self {
47 Self {
48 inner: Arc::new(Mutex::new(ProcessEnvInner::default())),
49 }
50 }
51
52 #[must_use]
53 pub fn current() -> Self {
54 Self {
55 inner: Arc::new(Mutex::new(vars_os().collect())),
56 }
57 }
58
59 #[must_use]
60 pub fn len(&self) -> usize {
61 let inner = self.inner.lock().unwrap();
62 inner.values.len()
63 }
64
65 #[must_use]
66 pub fn is_empty(&self) -> bool {
67 let inner = self.inner.lock().unwrap();
68 inner.values.is_empty()
69 }
70
71 #[must_use]
74 pub fn get_all(&self) -> Vec<(OsString, OsString)> {
75 let inner = self.inner.lock().unwrap();
76 inner.values.clone().into_iter().collect()
77 }
78
79 #[must_use]
80 pub fn get_value(&self, key: impl AsRef<OsStr>) -> Option<OsString> {
81 let key = key.as_ref();
82
83 super::validate_os_key(key).ok()?;
84
85 let inner = self.inner.lock().unwrap();
86 inner.values.get(key).cloned()
87 }
88
89 pub fn set_value(&self, key: impl Into<OsString>, val: impl Into<OsString>) {
90 let key = key.into();
91 let val = val.into();
92
93 if super::validate_os_pair((&key, &val)).is_err() {
94 return;
95 }
96
97 let mut inner = self.inner.lock().unwrap();
98 inner.values.insert(key, val);
99 }
100
101 pub fn remove_value(&self, key: impl AsRef<OsStr>) {
102 let key = key.as_ref();
103
104 if super::validate_os_key(key).is_err() {
105 return;
106 }
107
108 let mut inner = self.inner.lock().unwrap();
109 inner.values.remove(key);
110 }
111
112 #[must_use]
115 pub fn get_all_bytes(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
116 self.get_all()
117 .into_iter()
118 .filter_map(|(k, v)| Some((k.into_io_vec()?, v.into_io_vec()?)))
119 .collect()
120 }
121
122 #[must_use]
123 pub fn get_value_bytes(&self, key: impl AsRef<[u8]>) -> Option<Vec<u8>> {
124 let key = OsStr::from_io_bytes(key.as_ref())?;
125 let val = self.get_value(key)?;
126 val.into_io_vec()
127 }
128
129 pub fn set_value_bytes(&self, key: impl AsRef<[u8]>, val: impl Into<Vec<u8>>) {
130 let key = OsStr::from_io_bytes(key.as_ref());
131 let val = OsString::from_io_vec(val.into());
132 if let (Some(key), Some(val)) = (key, val) {
133 self.set_value(key, val);
134 }
135 }
136
137 pub fn remove_value_bytes(&self, key: impl AsRef<[u8]>) {
138 let key = OsStr::from_io_bytes(key.as_ref());
139 if let Some(key) = key {
140 self.remove_value(key);
141 }
142 }
143
144 #[doc(hidden)]
147 #[allow(clippy::missing_errors_doc)]
148 pub fn into_plain_lua_table(&self, lua: Lua) -> LuaResult<LuaTable> {
149 let all = self.get_all_bytes();
150 let tab = lua.create_table_with_capacity(0, all.len())?;
151
152 for (key, val) in all {
153 let key = lua.create_string(key)?;
154 let val = lua.create_string(val)?;
155 tab.set(key, val)?;
156 }
157
158 Ok(tab)
159 }
160}
161
162impl IntoIterator for ProcessEnv {
165 type Item = (OsString, OsString);
166 type IntoIter = std::collections::btree_map::IntoIter<OsString, OsString>;
167
168 fn into_iter(self) -> Self::IntoIter {
169 let inner = self.inner.lock().unwrap();
170 inner.values.clone().into_iter()
171 }
172}
173
174impl<K: Into<OsString>, V: Into<OsString>> FromIterator<(K, V)> for ProcessEnv {
175 fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
176 Self {
177 inner: Arc::new(Mutex::new(
178 iter.into_iter()
179 .map(|(k, v)| (k.into(), v.into()))
180 .filter(|(k, v)| super::validate_os_pair((k, v)).is_ok())
181 .collect(),
182 )),
183 }
184 }
185}
186
187impl<K: Into<OsString>, V: Into<OsString>> Extend<(K, V)> for ProcessEnv {
188 fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
189 let mut inner = self.inner.lock().unwrap();
190 inner.values.extend(
191 iter.into_iter()
192 .map(|(k, v)| (k.into(), v.into()))
193 .filter(|(k, v)| super::validate_os_pair((k, v)).is_ok()),
194 );
195 }
196}
197
198impl FromLua for ProcessEnv {
201 fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
202 if let LuaValue::Nil = value {
203 Ok(Self::from_iter([] as [(OsString, OsString); 0]))
204 } else if let LuaValue::Boolean(true) = value {
205 Ok(Self::current())
206 } else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
207 Ok(u.clone())
208 } else if let LuaValue::Table(arr) = value {
209 let mut args = Vec::new();
210 for pair in arr.pairs::<LuaValue, LuaValue>() {
211 let (key_res, val_res) = match pair {
212 Ok((key, val)) => (Ok(key), Ok(val)),
213 Err(err) => (Err(err.clone()), Err(err)),
214 };
215
216 let key = super::lua_value_to_os_string(key_res, "ProcessEnv")?;
217 let val = super::lua_value_to_os_string(val_res, "ProcessEnv")?;
218
219 super::validate_os_pair((&key, &val))?;
220
221 args.push((key, val));
222 }
223 Ok(Self::from_iter(args))
224 } else {
225 Err(LuaError::FromLuaConversionError {
226 from: value.type_name(),
227 to: String::from("ProcessEnv"),
228 message: Some(format!(
229 "Invalid type for process env - expected table or nil, got '{}'",
230 value.type_name()
231 )),
232 })
233 }
234 }
235}
236
237impl LuaUserData for ProcessEnv {
238 fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
239 methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
240 methods.add_meta_method(LuaMetaMethod::Index, |_, this, key: LuaValue| {
241 let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
242 Ok(this.get_value(key))
243 });
244 methods.add_meta_method(
245 LuaMetaMethod::NewIndex,
246 |_, this, (key, val): (LuaValue, Option<LuaValue>)| {
247 let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
248 if let Some(val) = val {
249 let val = super::lua_value_to_os_string(Ok(val), "OsString")?;
250 this.set_value(key, val);
251 } else {
252 this.remove_value(key);
253 }
254 Ok(())
255 },
256 );
257 methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
258 let mut vars = this
259 .clone()
260 .into_iter()
261 .filter_map(|(key, val)| Some((key.into_io_vec()?, val.into_io_vec()?)));
262 lua.create_function_mut(move |lua, (): ()| match vars.next() {
263 None => Ok((LuaValue::Nil, LuaValue::Nil)),
264 Some((key, val)) => Ok((
265 LuaValue::String(lua.create_string(key)?),
266 LuaValue::String(lua.create_string(val)?),
267 )),
268 })
269 });
270 }
271}