lune_utils/process/
args.rs

1#![allow(clippy::missing_panics_doc)]
2
3use std::{
4    env::args_os,
5    ffi::OsString,
6    sync::{Arc, Mutex},
7};
8
9use mlua::prelude::*;
10use os_str_bytes::OsStringBytes;
11
12// Inner (shared) struct
13
14#[derive(Debug, Default)]
15struct ProcessArgsInner {
16    values: Vec<OsString>,
17}
18
19impl FromIterator<OsString> for ProcessArgsInner {
20    fn from_iter<T: IntoIterator<Item = OsString>>(iter: T) -> Self {
21        Self {
22            values: iter.into_iter().collect(),
23        }
24    }
25}
26
27/**
28    A struct that can be easily shared, stored in Lua app data,
29    and that also guarantees the values are valid OS strings
30    that can be used for process arguments.
31
32    Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
33
34    Also provides convenience methods for working with the arguments
35    as either `OsString` or `Vec<u8>`, where using the latter implicitly
36    converts to an `OsString` and fails if the conversion is not possible.
37*/
38#[derive(Debug, Clone)]
39pub struct ProcessArgs {
40    inner: Arc<Mutex<ProcessArgsInner>>,
41}
42
43impl ProcessArgs {
44    #[must_use]
45    pub fn empty() -> Self {
46        Self {
47            inner: Arc::new(Mutex::new(ProcessArgsInner::default())),
48        }
49    }
50
51    #[must_use]
52    pub fn current() -> Self {
53        Self {
54            inner: Arc::new(Mutex::new(args_os().collect())),
55        }
56    }
57
58    #[must_use]
59    pub fn len(&self) -> usize {
60        let inner = self.inner.lock().unwrap();
61        inner.values.len()
62    }
63
64    #[must_use]
65    pub fn is_empty(&self) -> bool {
66        let inner = self.inner.lock().unwrap();
67        inner.values.is_empty()
68    }
69
70    // OS strings
71
72    #[must_use]
73    pub fn all(&self) -> Vec<OsString> {
74        let inner = self.inner.lock().unwrap();
75        inner.values.clone()
76    }
77
78    #[must_use]
79    pub fn get(&self, index: usize) -> Option<OsString> {
80        let inner = self.inner.lock().unwrap();
81        inner.values.get(index).cloned()
82    }
83
84    pub fn set(&self, index: usize, val: impl Into<OsString>) {
85        let mut inner = self.inner.lock().unwrap();
86        if let Some(arg) = inner.values.get_mut(index) {
87            *arg = val.into();
88        }
89    }
90
91    pub fn push(&self, val: impl Into<OsString>) {
92        let mut inner = self.inner.lock().unwrap();
93        inner.values.push(val.into());
94    }
95
96    #[must_use]
97    pub fn pop(&self) -> Option<OsString> {
98        let mut inner = self.inner.lock().unwrap();
99        inner.values.pop()
100    }
101
102    pub fn insert(&self, index: usize, val: impl Into<OsString>) {
103        let mut inner = self.inner.lock().unwrap();
104        if index <= inner.values.len() {
105            inner.values.insert(index, val.into());
106        }
107    }
108
109    #[must_use]
110    pub fn remove(&self, index: usize) -> Option<OsString> {
111        let mut inner = self.inner.lock().unwrap();
112        if index < inner.values.len() {
113            Some(inner.values.remove(index))
114        } else {
115            None
116        }
117    }
118
119    // Bytes wrappers
120
121    #[must_use]
122    pub fn all_bytes(&self) -> Vec<Vec<u8>> {
123        self.all()
124            .into_iter()
125            .filter_map(OsString::into_io_vec)
126            .collect()
127    }
128
129    #[must_use]
130    pub fn get_bytes(&self, index: usize) -> Option<Vec<u8>> {
131        let val = self.get(index)?;
132        val.into_io_vec()
133    }
134
135    pub fn set_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
136        if let Some(val_os) = OsString::from_io_vec(val.into()) {
137            self.set(index, val_os);
138        }
139    }
140
141    pub fn push_bytes(&self, val: impl Into<Vec<u8>>) {
142        if let Some(val_os) = OsString::from_io_vec(val.into()) {
143            self.push(val_os);
144        }
145    }
146
147    #[must_use]
148    pub fn pop_bytes(&self) -> Option<Vec<u8>> {
149        self.pop().and_then(OsString::into_io_vec)
150    }
151
152    pub fn insert_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
153        if let Some(val_os) = OsString::from_io_vec(val.into()) {
154            self.insert(index, val_os);
155        }
156    }
157
158    pub fn remove_bytes(&self, index: usize) -> Option<Vec<u8>> {
159        self.remove(index).and_then(OsString::into_io_vec)
160    }
161
162    // Plain lua table conversion
163
164    #[doc(hidden)]
165    #[allow(clippy::missing_errors_doc)]
166    pub fn into_plain_lua_table(&self, lua: Lua) -> LuaResult<LuaTable> {
167        let all = self.all_bytes();
168        let tab = lua.create_table_with_capacity(all.len(), 0)?;
169
170        for val in all {
171            let val = lua.create_string(val)?;
172            tab.push(val)?;
173        }
174
175        Ok(tab)
176    }
177}
178
179// Iterator implementations
180
181impl IntoIterator for ProcessArgs {
182    type Item = OsString;
183    type IntoIter = std::vec::IntoIter<OsString>;
184
185    fn into_iter(self) -> Self::IntoIter {
186        let inner = self.inner.lock().unwrap();
187        inner.values.clone().into_iter()
188    }
189}
190
191impl<S: Into<OsString>> FromIterator<S> for ProcessArgs {
192    fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
193        Self {
194            inner: Arc::new(Mutex::new(iter.into_iter().map(Into::into).collect())),
195        }
196    }
197}
198
199impl<S: Into<OsString>> Extend<S> for ProcessArgs {
200    fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {
201        let mut inner = self.inner.lock().unwrap();
202        inner.values.extend(iter.into_iter().map(Into::into));
203    }
204}
205
206// Lua implementations
207
208impl FromLua for ProcessArgs {
209    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
210        if let LuaValue::Nil = value {
211            Ok(Self::from_iter([] as [OsString; 0]))
212        } else if let LuaValue::Boolean(true) = value {
213            Ok(Self::current())
214        } else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
215            Ok(u.clone())
216        } else if let LuaValue::Table(arr) = value {
217            let mut args = Vec::new();
218            for pair in arr.pairs::<LuaValue, LuaValue>() {
219                let val_res = pair.map(|p| p.1.clone());
220                let val = super::lua_value_to_os_string(val_res, "ProcessArgs")?;
221
222                super::validate_os_value(&val)?;
223
224                args.push(val);
225            }
226            Ok(Self::from_iter(args))
227        } else {
228            Err(LuaError::FromLuaConversionError {
229                from: value.type_name(),
230                to: String::from("ProcessArgs"),
231                message: Some(format!(
232                    "Invalid type for process args - expected table or nil, got '{}'",
233                    value.type_name()
234                )),
235            })
236        }
237    }
238}
239
240impl LuaUserData for ProcessArgs {
241    fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
242        methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
243        methods.add_meta_method(LuaMetaMethod::Index, |_, this, index: usize| {
244            if index == 0 {
245                Ok(None)
246            } else {
247                Ok(this.get(index - 1))
248            }
249        });
250        methods.add_meta_method(LuaMetaMethod::NewIndex, |_, _, (): ()| {
251            Err::<(), _>(LuaError::runtime("ProcessArgs is read-only"))
252        });
253        methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
254            let mut vars = this
255                .clone()
256                .into_iter()
257                .filter_map(OsStringBytes::into_io_vec)
258                .enumerate();
259            lua.create_function_mut(move |lua, (): ()| match vars.next() {
260                None => Ok((LuaValue::Nil, LuaValue::Nil)),
261                Some((index, value)) => Ok((
262                    LuaValue::Integer(index as i64),
263                    LuaValue::String(lua.create_string(value)?),
264                )),
265            })
266        });
267    }
268}