mirror of
https://github.com/lune-org/lune.git
synced 2025-05-04 10:43:57 +01:00
Revamp handling of process args and env with fully featured newtypes
This commit is contained in:
parent
b1fc60023d
commit
2d8e58b028
11 changed files with 637 additions and 112 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1798,7 +1798,6 @@ dependencies = [
|
||||||
"lune-utils",
|
"lune-utils",
|
||||||
"mlua",
|
"mlua",
|
||||||
"mlua-luau-scheduler",
|
"mlua-luau-scheduler",
|
||||||
"os_str_bytes",
|
|
||||||
"pin-project",
|
"pin-project",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1880,6 +1879,7 @@ dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"dunce",
|
"dunce",
|
||||||
"mlua",
|
"mlua",
|
||||||
|
"os_str_bytes",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"path-clean",
|
"path-clean",
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
|
|
|
@ -18,7 +18,6 @@ mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
directories = "6.0"
|
directories = "6.0"
|
||||||
pin-project = "1.0"
|
pin-project = "1.0"
|
||||||
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
|
||||||
|
|
||||||
bstr = "1.9"
|
bstr = "1.9"
|
||||||
bytes = "1.6.0"
|
bytes = "1.6.0"
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
#![allow(clippy::cargo_common_metadata)]
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env::{
|
env::consts::{ARCH, OS},
|
||||||
self,
|
|
||||||
consts::{ARCH, OS},
|
|
||||||
},
|
|
||||||
path::MAIN_SEPARATOR,
|
path::MAIN_SEPARATOR,
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
};
|
};
|
||||||
|
@ -12,9 +9,11 @@ use std::{
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use mlua_luau_scheduler::Functions;
|
use mlua_luau_scheduler::Functions;
|
||||||
|
|
||||||
use os_str_bytes::RawOsString;
|
use lune_utils::{
|
||||||
|
path::get_current_dir,
|
||||||
use lune_utils::{path::get_current_dir, TableBuilder};
|
process::{ProcessArgs, ProcessEnv},
|
||||||
|
TableBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
mod create;
|
mod create;
|
||||||
mod exec;
|
mod exec;
|
||||||
|
@ -58,25 +57,19 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
"little"
|
"little"
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Create readonly args array
|
// Find the readonly args array
|
||||||
let args_vec = lua
|
let args_vec = lua
|
||||||
.app_data_ref::<Vec<String>>()
|
.app_data_ref::<Vec<String>>()
|
||||||
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
|
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
|
||||||
.clone();
|
.clone();
|
||||||
let args_tab = TableBuilder::new(lua.clone())?
|
|
||||||
.with_sequential_values(args_vec)?
|
|
||||||
.build_readonly()?;
|
|
||||||
|
|
||||||
// Create proxied table for env that gets & sets real env vars
|
// Create userdatas for args + env
|
||||||
let env_tab = TableBuilder::new(lua.clone())?
|
// TODO: Move this up into the runtime creation instead,
|
||||||
.with_metatable(
|
// and set it as app data there to later fetch here
|
||||||
TableBuilder::new(lua.clone())?
|
let process_args = ProcessArgs::from_iter(args_vec);
|
||||||
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
|
let process_env = ProcessEnv::current();
|
||||||
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
|
lua.set_app_data(process_args.clone());
|
||||||
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
|
lua.set_app_data(process_env.clone());
|
||||||
.build_readonly()?,
|
|
||||||
)?
|
|
||||||
.build_readonly()?;
|
|
||||||
|
|
||||||
// Create our process exit function, the scheduler crate provides this
|
// Create our process exit function, the scheduler crate provides this
|
||||||
let fns = Functions::new(lua.clone())?;
|
let fns = Functions::new(lua.clone())?;
|
||||||
|
@ -87,73 +80,18 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
.with_value("os", os)?
|
.with_value("os", os)?
|
||||||
.with_value("arch", arch)?
|
.with_value("arch", arch)?
|
||||||
.with_value("endianness", endianness)?
|
.with_value("endianness", endianness)?
|
||||||
.with_value("args", args_tab)?
|
.with_value("args", process_args)?
|
||||||
.with_value("cwd", cwd_str)?
|
.with_value("cwd", cwd_str)?
|
||||||
.with_value("env", env_tab)?
|
.with_value("env", process_env)?
|
||||||
.with_value("exit", process_exit)?
|
.with_value("exit", process_exit)?
|
||||||
.with_async_function("exec", process_exec)?
|
.with_async_function("exec", process_exec)?
|
||||||
.with_function("create", process_create)?
|
.with_function("create", process_create)?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_env_get(lua: &Lua, (_, key): (LuaValue, String)) -> LuaResult<LuaValue> {
|
|
||||||
match env::var_os(key) {
|
|
||||||
Some(value) => {
|
|
||||||
let raw_value = RawOsString::new(value);
|
|
||||||
Ok(LuaValue::String(
|
|
||||||
lua.create_string(raw_value.to_raw_bytes())?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
None => Ok(LuaValue::Nil),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_env_set(_: &Lua, (_, key, value): (LuaValue, String, Option<String>)) -> LuaResult<()> {
|
|
||||||
// Make sure key is valid, otherwise set_var will panic
|
|
||||||
if key.is_empty() {
|
|
||||||
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
|
|
||||||
} else if key.contains('=') {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Key must not contain the equals character '='".to_string(),
|
|
||||||
))
|
|
||||||
} else if key.contains('\0') {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Key must not contain the NUL character".to_string(),
|
|
||||||
))
|
|
||||||
} else if let Some(value) = value {
|
|
||||||
// Make sure value is valid, otherwise set_var will panic
|
|
||||||
if value.contains('\0') {
|
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Value must not contain the NUL character".to_string(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
env::set_var(&key, &value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
env::remove_var(&key);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_env_iter(lua: &Lua, (_, ()): (LuaValue, ())) -> LuaResult<LuaFunction> {
|
|
||||||
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
|
|
||||||
lua.create_function_mut(move |lua, (): ()| match vars.next() {
|
|
||||||
Some((key, value)) => {
|
|
||||||
let raw_key = RawOsString::new(key);
|
|
||||||
let raw_value = RawOsString::new(value);
|
|
||||||
Ok((
|
|
||||||
LuaValue::String(lua.create_string(raw_key.to_raw_bytes())?),
|
|
||||||
LuaValue::String(lua.create_string(raw_value.to_raw_bytes())?),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn process_exec(
|
async fn process_exec(
|
||||||
lua: Lua,
|
lua: Lua,
|
||||||
(program, args, mut options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
(program, args, mut options): (String, ProcessArgs, ProcessSpawnOptions),
|
||||||
) -> LuaResult<LuaTable> {
|
) -> LuaResult<LuaTable> {
|
||||||
let stdin = options.stdio.stdin.take();
|
let stdin = options.stdio.stdin.take();
|
||||||
let stdout = options.stdio.stdout;
|
let stdout = options.stdio.stdout;
|
||||||
|
@ -171,7 +109,7 @@ async fn process_exec(
|
||||||
|
|
||||||
fn process_create(
|
fn process_create(
|
||||||
lua: &Lua,
|
lua: &Lua,
|
||||||
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
(program, args, options): (String, ProcessArgs, ProcessSpawnOptions),
|
||||||
) -> LuaResult<LuaValue> {
|
) -> LuaResult<LuaValue> {
|
||||||
let child = options
|
let child = options
|
||||||
.into_command(program, args)
|
.into_command(program, args)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
env::{self},
|
env::{self},
|
||||||
|
ffi::OsString,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use lune_utils::process::ProcessArgs;
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
use async_process::Command;
|
use async_process::Command;
|
||||||
|
@ -129,31 +131,24 @@ impl FromLua for ProcessSpawnOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProcessSpawnOptions {
|
impl ProcessSpawnOptions {
|
||||||
pub fn into_command(self, program: impl Into<String>, args: Option<Vec<String>>) -> Command {
|
pub fn into_command(self, program: impl Into<OsString>, args: ProcessArgs) -> Command {
|
||||||
let mut program = program.into();
|
let mut program: OsString = program.into();
|
||||||
|
let mut args = args.into_iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
// Run a shell using the command param if wanted
|
// Run a shell using the command param if wanted
|
||||||
let pargs = match self.shell {
|
if let Some(shell) = self.shell {
|
||||||
None => args,
|
let mut shell_command = program.clone();
|
||||||
Some(shell) => {
|
for arg in args {
|
||||||
let shell_args = match args {
|
shell_command.push(" ");
|
||||||
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
|
shell_command.push(arg);
|
||||||
None => vec!["-c".to_string(), program.to_string()],
|
}
|
||||||
};
|
args = vec![OsString::from("-c"), shell_command];
|
||||||
program = shell.to_string();
|
program = shell.into();
|
||||||
Some(shell_args)
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Create command with the wanted options
|
// Create command with the wanted options
|
||||||
let mut cmd = match pargs {
|
|
||||||
None => Command::new(program),
|
|
||||||
Some(args) => {
|
|
||||||
let mut cmd = Command::new(program);
|
let mut cmd = Command::new(program);
|
||||||
cmd.args(args);
|
cmd.args(args);
|
||||||
cmd
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set dir to run in and env variables
|
// Set dir to run in and env variables
|
||||||
if let Some(cwd) = self.cwd {
|
if let Some(cwd) = self.cwd {
|
||||||
|
|
|
@ -17,6 +17,7 @@ mlua = { version = "0.10.3", features = ["luau", "async"] }
|
||||||
|
|
||||||
console = "0.15"
|
console = "0.15"
|
||||||
dunce = "1.0"
|
dunce = "1.0"
|
||||||
|
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
||||||
path-clean = "1.0"
|
path-clean = "1.0"
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
|
|
|
@ -4,8 +4,13 @@ mod table_builder;
|
||||||
mod version_string;
|
mod version_string;
|
||||||
|
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
pub mod jit;
|
|
||||||
pub mod path;
|
pub mod path;
|
||||||
|
pub mod process;
|
||||||
|
|
||||||
pub use self::table_builder::TableBuilder;
|
pub use self::table_builder::TableBuilder;
|
||||||
pub use self::version_string::get_version_string;
|
pub use self::version_string::get_version_string;
|
||||||
|
|
||||||
|
// TODO: Remove this in the next major semver
|
||||||
|
pub mod jit {
|
||||||
|
pub use super::process::ProcessJitEnablement as JitEnablement;
|
||||||
|
}
|
||||||
|
|
252
crates/lune-utils/src/process/args.rs
Normal file
252
crates/lune-utils/src/process/args.rs
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
#![allow(clippy::missing_panics_doc)]
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
env::args_os,
|
||||||
|
ffi::OsString,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use os_str_bytes::OsStringBytes;
|
||||||
|
|
||||||
|
// Inner (shared) struct
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct ProcessArgsInner {
|
||||||
|
values: Vec<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<OsString> for ProcessArgsInner {
|
||||||
|
fn from_iter<T: IntoIterator<Item = OsString>>(iter: T) -> Self {
|
||||||
|
Self {
|
||||||
|
values: iter.into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A struct that can be easily shared, stored in Lua app data,
|
||||||
|
and that also guarantees the values are valid OS strings
|
||||||
|
that can be used for process arguments.
|
||||||
|
|
||||||
|
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
|
||||||
|
|
||||||
|
Also provides convenience methods for working with the arguments
|
||||||
|
as either `OsString` or `Vec<u8>`, where using the latter implicitly
|
||||||
|
converts to an `OsString` and fails if the conversion is not possible.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProcessArgs {
|
||||||
|
inner: Arc<Mutex<ProcessArgsInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessArgs {
|
||||||
|
#[must_use]
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(ProcessArgsInner::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn current() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(args_os().collect())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS strings
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn all(&self) -> Vec<OsString> {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get(&self, index: usize) -> Option<OsString> {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.get(index).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, index: usize, val: impl Into<OsString>) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
if let Some(arg) = inner.values.get_mut(index) {
|
||||||
|
*arg = val.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&self, val: impl Into<OsString>) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.push(val.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn pop(&self) -> Option<OsString> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self, index: usize, val: impl Into<OsString>) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
if index <= inner.values.len() {
|
||||||
|
inner.values.insert(index, val.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn remove(&self, index: usize) -> Option<OsString> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
if index < inner.values.len() {
|
||||||
|
Some(inner.values.remove(index))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes wrappers
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn all_bytes(&self) -> Vec<Vec<u8>> {
|
||||||
|
self.all()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(OsString::into_io_vec)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_bytes(&self, index: usize) -> Option<Vec<u8>> {
|
||||||
|
let val = self.get(index)?;
|
||||||
|
val.into_io_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
|
||||||
|
if let Some(val_os) = OsString::from_io_vec(val.into()) {
|
||||||
|
self.set(index, val_os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_bytes(&self, val: impl Into<Vec<u8>>) {
|
||||||
|
if let Some(val_os) = OsString::from_io_vec(val.into()) {
|
||||||
|
self.push(val_os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn pop_bytes(&self) -> Option<Vec<u8>> {
|
||||||
|
self.pop().and_then(OsString::into_io_vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
|
||||||
|
if let Some(val_os) = OsString::from_io_vec(val.into()) {
|
||||||
|
self.insert(index, val_os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_bytes(&self, index: usize) -> Option<Vec<u8>> {
|
||||||
|
self.remove(index).and_then(OsString::into_io_vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterator implementations
|
||||||
|
|
||||||
|
impl IntoIterator for ProcessArgs {
|
||||||
|
type Item = OsString;
|
||||||
|
type IntoIter = std::vec::IntoIter<OsString>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.clone().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Into<OsString>> FromIterator<S> for ProcessArgs {
|
||||||
|
fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(iter.into_iter().map(Into::into).collect())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Into<OsString>> Extend<S> for ProcessArgs {
|
||||||
|
fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.extend(iter.into_iter().map(Into::into));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua implementations
|
||||||
|
|
||||||
|
impl FromLua for ProcessArgs {
|
||||||
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
|
if let LuaValue::Nil = value {
|
||||||
|
Ok(Self::from_iter([] as [OsString; 0]))
|
||||||
|
} else if let LuaValue::Boolean(true) = value {
|
||||||
|
Ok(Self::current())
|
||||||
|
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
|
||||||
|
Ok(u.clone())
|
||||||
|
} else if let LuaValue::Table(arr) = value {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
for pair in arr.pairs::<LuaValue, LuaValue>() {
|
||||||
|
let val_res = pair.map(|p| p.1.clone());
|
||||||
|
let val = super::lua_value_to_os_string(val_res, "ProcessArgs")?;
|
||||||
|
|
||||||
|
super::validate_os_value(&val)?;
|
||||||
|
|
||||||
|
args.push(val);
|
||||||
|
}
|
||||||
|
Ok(Self::from_iter(args))
|
||||||
|
} else {
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: String::from("ProcessArgs"),
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid type for process args - expected table or nil, got '{}'",
|
||||||
|
value.type_name()
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaUserData for ProcessArgs {
|
||||||
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Index, |_, this, index: usize| {
|
||||||
|
if index == 0 {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(this.get(index - 1))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
methods.add_meta_method(LuaMetaMethod::NewIndex, |_, _, (): ()| {
|
||||||
|
Err::<(), _>(LuaError::runtime("ProcessArgs is read-only"))
|
||||||
|
});
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
|
||||||
|
let mut vars = this
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(OsStringBytes::into_io_vec)
|
||||||
|
.enumerate();
|
||||||
|
lua.create_function_mut(move |lua, (): ()| match vars.next() {
|
||||||
|
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
||||||
|
Some((index, value)) => Ok((
|
||||||
|
LuaValue::Integer(index as i32),
|
||||||
|
LuaValue::String(lua.create_string(value)?),
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
254
crates/lune-utils/src/process/env.rs
Normal file
254
crates/lune-utils/src/process/env.rs
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
#![allow(clippy::missing_panics_doc)]
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
env::vars_os,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use os_str_bytes::{OsStrBytes, OsStringBytes};
|
||||||
|
|
||||||
|
// Inner (shared) struct
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct ProcessEnvInner {
|
||||||
|
values: BTreeMap<OsString, OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(OsString, OsString)> for ProcessEnvInner {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (OsString, OsString)>>(iter: T) -> Self {
|
||||||
|
Self {
|
||||||
|
values: iter.into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A struct that can be easily shared, stored in Lua app data,
|
||||||
|
and that also guarantees the pairs are valid OS strings
|
||||||
|
that can be used for process environment variables.
|
||||||
|
|
||||||
|
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
|
||||||
|
|
||||||
|
Also provides convenience methods for working with the variables
|
||||||
|
as either `OsString` or `Vec<u8>`, where using the latter implicitly
|
||||||
|
converts to an `OsString` and fails if the conversion is not possible.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProcessEnv {
|
||||||
|
inner: Arc<Mutex<ProcessEnvInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessEnv {
|
||||||
|
#[must_use]
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(ProcessEnvInner::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn current() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(vars_os().collect())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS strings
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_all(&self) -> Vec<(OsString, OsString)> {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.clone().into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_value(&self, key: impl AsRef<OsStr>) -> Option<OsString> {
|
||||||
|
let key = key.as_ref();
|
||||||
|
|
||||||
|
super::validate_os_key(key).ok()?;
|
||||||
|
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.get(key).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_value(&self, key: impl Into<OsString>, val: impl Into<OsString>) {
|
||||||
|
let key = key.into();
|
||||||
|
let val = val.into();
|
||||||
|
|
||||||
|
if super::validate_os_pair((&key, &val)).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.insert(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_value(&self, key: impl AsRef<OsStr>) {
|
||||||
|
let key = key.as_ref();
|
||||||
|
|
||||||
|
if super::validate_os_key(key).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes wrappers
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_all_bytes(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||||
|
self.get_all()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, v)| Some((k.into_io_vec()?, v.into_io_vec()?)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_value_bytes(&self, key: impl AsRef<[u8]>) -> Option<Vec<u8>> {
|
||||||
|
let key = OsStr::from_io_bytes(key.as_ref())?;
|
||||||
|
let val = self.get_value(key)?;
|
||||||
|
val.into_io_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_value_bytes(&self, key: impl AsRef<[u8]>, val: impl Into<Vec<u8>>) {
|
||||||
|
let key = OsStr::from_io_bytes(key.as_ref());
|
||||||
|
let val = OsString::from_io_vec(val.into());
|
||||||
|
if let (Some(key), Some(val)) = (key, val) {
|
||||||
|
self.set_value(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_value_bytes(&self, key: impl AsRef<[u8]>) {
|
||||||
|
let key = OsStr::from_io_bytes(key.as_ref());
|
||||||
|
if let Some(key) = key {
|
||||||
|
self.remove_value(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterator implementations
|
||||||
|
|
||||||
|
impl IntoIterator for ProcessEnv {
|
||||||
|
type Item = (OsString, OsString);
|
||||||
|
type IntoIter = std::collections::btree_map::IntoIter<OsString, OsString>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.clone().into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Into<OsString>, V: Into<OsString>> FromIterator<(K, V)> for ProcessEnv {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(
|
||||||
|
iter.into_iter()
|
||||||
|
.map(|(k, v)| (k.into(), v.into()))
|
||||||
|
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok())
|
||||||
|
.collect(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Into<OsString>, V: Into<OsString>> Extend<(K, V)> for ProcessEnv {
|
||||||
|
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.values.extend(
|
||||||
|
iter.into_iter()
|
||||||
|
.map(|(k, v)| (k.into(), v.into()))
|
||||||
|
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lua implementations
|
||||||
|
|
||||||
|
impl FromLua for ProcessEnv {
|
||||||
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
|
if let LuaValue::Nil = value {
|
||||||
|
Ok(Self::from_iter([] as [(OsString, OsString); 0]))
|
||||||
|
} else if let LuaValue::Boolean(true) = value {
|
||||||
|
Ok(Self::current())
|
||||||
|
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
|
||||||
|
Ok(u.clone())
|
||||||
|
} else if let LuaValue::Table(arr) = value {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
for pair in arr.pairs::<LuaValue, LuaValue>() {
|
||||||
|
let (key_res, val_res) = match pair {
|
||||||
|
Ok((key, val)) => (Ok(key), Ok(val)),
|
||||||
|
Err(err) => (Err(err.clone()), Err(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = super::lua_value_to_os_string(key_res, "ProcessEnv")?;
|
||||||
|
let val = super::lua_value_to_os_string(val_res, "ProcessEnv")?;
|
||||||
|
|
||||||
|
super::validate_os_pair((&key, &val))?;
|
||||||
|
|
||||||
|
args.push((key, val));
|
||||||
|
}
|
||||||
|
Ok(Self::from_iter(args))
|
||||||
|
} else {
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: String::from("ProcessEnv"),
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid type for process env - expected table or nil, got '{}'",
|
||||||
|
value.type_name()
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaUserData for ProcessEnv {
|
||||||
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Index, |_, this, key: LuaValue| {
|
||||||
|
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
|
||||||
|
Ok(this.get_value(key))
|
||||||
|
});
|
||||||
|
methods.add_meta_method(
|
||||||
|
LuaMetaMethod::NewIndex,
|
||||||
|
|_, this, (key, val): (LuaValue, Option<LuaValue>)| {
|
||||||
|
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
|
||||||
|
if let Some(val) = val {
|
||||||
|
let val = super::lua_value_to_os_string(Ok(val), "OsString")?;
|
||||||
|
this.set_value(key, val);
|
||||||
|
} else {
|
||||||
|
this.remove_value(key);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
|
||||||
|
let mut vars = this
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(key, val)| Some((key.into_io_vec()?, val.into_io_vec()?)));
|
||||||
|
lua.create_function_mut(move |lua, (): ()| match vars.next() {
|
||||||
|
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
||||||
|
Some((key, val)) => Ok((
|
||||||
|
LuaValue::String(lua.create_string(key)?),
|
||||||
|
LuaValue::String(lua.create_string(val)?),
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,31 @@
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct JitEnablement(bool);
|
pub struct ProcessJitEnablement {
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl JitEnablement {
|
impl ProcessJitEnablement {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(enabled: bool) -> Self {
|
pub fn new(enabled: bool) -> Self {
|
||||||
Self(enabled)
|
Self { enabled }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_status(&mut self, enabled: bool) {
|
pub fn set_status(&mut self, enabled: bool) {
|
||||||
self.0 = enabled;
|
self.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn enabled(self) -> bool {
|
pub fn enabled(self) -> bool {
|
||||||
self.0
|
self.enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<JitEnablement> for bool {
|
impl From<ProcessJitEnablement> for bool {
|
||||||
fn from(val: JitEnablement) -> Self {
|
fn from(val: ProcessJitEnablement) -> Self {
|
||||||
val.enabled()
|
val.enabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<bool> for JitEnablement {
|
impl From<bool> for ProcessJitEnablement {
|
||||||
fn from(val: bool) -> Self {
|
fn from(val: bool) -> Self {
|
||||||
Self::new(val)
|
Self::new(val)
|
||||||
}
|
}
|
78
crates/lune-utils/src/process/mod.rs
Normal file
78
crates/lune-utils/src/process/mod.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use os_str_bytes::{OsStrBytes, OsStringBytes};
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
mod env;
|
||||||
|
mod jit;
|
||||||
|
|
||||||
|
pub use self::args::ProcessArgs;
|
||||||
|
pub use self::env::ProcessEnv;
|
||||||
|
pub use self::jit::ProcessJitEnablement;
|
||||||
|
|
||||||
|
fn lua_value_to_os_string(res: LuaResult<LuaValue>, to: &'static str) -> LuaResult<OsString> {
|
||||||
|
let (btype, bs) = match res {
|
||||||
|
Ok(LuaValue::String(s)) => ("string", s.as_bytes().to_vec()),
|
||||||
|
Ok(LuaValue::Buffer(b)) => ("buffer", b.to_vec()),
|
||||||
|
res => {
|
||||||
|
let vtype = match res {
|
||||||
|
Ok(v) => v.type_name(),
|
||||||
|
Err(_) => "unknown",
|
||||||
|
};
|
||||||
|
return Err(LuaError::FromLuaConversionError {
|
||||||
|
from: vtype,
|
||||||
|
to: String::from(to),
|
||||||
|
message: Some(format!(
|
||||||
|
"Expected value to be a string or buffer, got '{vtype}'",
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(s) = OsString::from_io_vec(bs) else {
|
||||||
|
return Err(LuaError::FromLuaConversionError {
|
||||||
|
from: btype,
|
||||||
|
to: String::from(to),
|
||||||
|
message: Some(String::from("Expected {btype} to contain valid OS bytes")),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_os_key(key: &OsStr) -> LuaResult<()> {
|
||||||
|
let Some(key) = key.to_io_bytes() else {
|
||||||
|
return Err(LuaError::runtime("Key must be IO-safe"));
|
||||||
|
};
|
||||||
|
if key.is_empty() {
|
||||||
|
Err(LuaError::runtime("Key must not be empty"))
|
||||||
|
} else if key.contains(&b'=') {
|
||||||
|
Err(LuaError::runtime(
|
||||||
|
"Key must not contain the equals character '='",
|
||||||
|
))
|
||||||
|
} else if key.contains(&b'\0') {
|
||||||
|
Err(LuaError::runtime("Key must not contain the NUL character"))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_os_value(val: &OsStr) -> LuaResult<()> {
|
||||||
|
let Some(val) = val.to_io_bytes() else {
|
||||||
|
return Err(LuaError::runtime("Value must be IO-safe"));
|
||||||
|
};
|
||||||
|
if val.contains(&b'\0') {
|
||||||
|
Err(LuaError::runtime(
|
||||||
|
"Value must not contain the NUL character",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_os_pair((key, value): (&OsStr, &OsStr)) -> LuaResult<()> {
|
||||||
|
validate_os_key(key)?;
|
||||||
|
validate_os_value(value)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -5,9 +5,10 @@ assert(#process.args > 0, "No process arguments found")
|
||||||
assert(process.args[1] == "Foo", "Invalid first argument to process")
|
assert(process.args[1] == "Foo", "Invalid first argument to process")
|
||||||
assert(process.args[2] == "Bar", "Invalid second argument to process")
|
assert(process.args[2] == "Bar", "Invalid second argument to process")
|
||||||
|
|
||||||
local success, message = pcall(function()
|
local success, err = pcall(function()
|
||||||
process.args[1] = "abc"
|
process.args[1] = "abc"
|
||||||
end)
|
end)
|
||||||
|
local message = if err == nil then nil else tostring(err)
|
||||||
assert(
|
assert(
|
||||||
success == false and type(message) == "string" and #message > 0,
|
success == false and type(message) == "string" and #message > 0,
|
||||||
"Trying to set process arguments should throw an error with a message"
|
"Trying to set process arguments should throw an error with a message"
|
||||||
|
|
Loading…
Add table
Reference in a new issue