mirror of
https://github.com/lune-org/lune.git
synced 2025-04-20 03:43:49 +01:00
177 lines
5.7 KiB
Rust
177 lines
5.7 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
env::{self},
|
|
path::PathBuf,
|
|
};
|
|
|
|
use directories::UserDirs;
|
|
use mlua::prelude::*;
|
|
use tokio::process::Command;
|
|
|
|
mod kind;
|
|
mod stdio;
|
|
|
|
pub(super) use kind::*;
|
|
pub(super) use stdio::*;
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub(super) struct ProcessSpawnOptions {
|
|
pub cwd: Option<PathBuf>,
|
|
pub envs: HashMap<String, String>,
|
|
pub shell: Option<String>,
|
|
pub stdio: ProcessSpawnOptionsStdio,
|
|
}
|
|
|
|
impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
|
|
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
let mut this = Self::default();
|
|
let value = match value {
|
|
LuaValue::Nil => return Ok(this),
|
|
LuaValue::Table(t) => t,
|
|
_ => {
|
|
return Err(LuaError::FromLuaConversionError {
|
|
from: value.type_name(),
|
|
to: "ProcessSpawnOptions",
|
|
message: Some(format!(
|
|
"Invalid spawn options - expected table, got {}",
|
|
value.type_name()
|
|
)),
|
|
})
|
|
}
|
|
};
|
|
|
|
/*
|
|
If we got a working directory to use:
|
|
|
|
1. Substitute leading tilde (~) for the users home dir
|
|
2. Make sure it exists
|
|
*/
|
|
match value.get("cwd")? {
|
|
LuaValue::Nil => {}
|
|
LuaValue::String(s) => {
|
|
let mut cwd = PathBuf::from(s.to_str()?);
|
|
if let Ok(stripped) = cwd.strip_prefix("~") {
|
|
let user_dirs = UserDirs::new().ok_or_else(|| {
|
|
LuaError::runtime(
|
|
"Invalid value for option 'cwd' - failed to get home directory",
|
|
)
|
|
})?;
|
|
cwd = user_dirs.home_dir().join(stripped);
|
|
}
|
|
if !cwd.exists() {
|
|
return Err(LuaError::runtime(
|
|
"Invalid value for option 'cwd' - path does not exist",
|
|
));
|
|
};
|
|
this.cwd = Some(cwd);
|
|
}
|
|
value => {
|
|
return Err(LuaError::RuntimeError(format!(
|
|
"Invalid type for option 'cwd' - expected string, got '{}'",
|
|
value.type_name()
|
|
)))
|
|
}
|
|
}
|
|
|
|
/*
|
|
If we got environment variables, make sure they are strings
|
|
*/
|
|
match value.get("env")? {
|
|
LuaValue::Nil => {}
|
|
LuaValue::Table(e) => {
|
|
for pair in e.pairs::<String, String>() {
|
|
let (k, v) = pair.context("Environment variables must be strings")?;
|
|
this.envs.insert(k, v);
|
|
}
|
|
}
|
|
value => {
|
|
return Err(LuaError::RuntimeError(format!(
|
|
"Invalid type for option 'env' - expected table, got '{}'",
|
|
value.type_name()
|
|
)))
|
|
}
|
|
}
|
|
|
|
/*
|
|
If we got a shell to use:
|
|
|
|
1. When given as a string, use that literally
|
|
2. When set to true, use a default shell for the platform
|
|
*/
|
|
match value.get("shell")? {
|
|
LuaValue::Nil => {}
|
|
LuaValue::String(s) => this.shell = Some(s.to_string_lossy().to_string()),
|
|
LuaValue::Boolean(true) => {
|
|
this.shell = match env::consts::FAMILY {
|
|
"unix" => Some("/bin/sh".to_string()),
|
|
"windows" => Some("powershell".to_string()),
|
|
_ => None,
|
|
};
|
|
}
|
|
value => {
|
|
return Err(LuaError::RuntimeError(format!(
|
|
"Invalid type for option 'shell' - expected 'true' or 'string', got '{}'",
|
|
value.type_name()
|
|
)))
|
|
}
|
|
}
|
|
|
|
/*
|
|
If we got options for stdio handling, parse those as well - note that
|
|
we accept a separate "stdin" value here for compatibility with older
|
|
scripts, but the user should preferrably pass it in the stdio table
|
|
*/
|
|
this.stdio = value.get("stdio")?;
|
|
match value.get("stdin")? {
|
|
LuaValue::Nil => {}
|
|
LuaValue::String(s) => this.stdio.stdin = Some(s.as_bytes().to_vec()),
|
|
value => {
|
|
return Err(LuaError::RuntimeError(format!(
|
|
"Invalid type for option 'stdin' - expected 'string', got '{}'",
|
|
value.type_name()
|
|
)))
|
|
}
|
|
}
|
|
|
|
Ok(this)
|
|
}
|
|
}
|
|
|
|
impl ProcessSpawnOptions {
|
|
pub fn into_command(self, program: impl Into<String>, args: Option<Vec<String>>) -> Command {
|
|
let mut program = program.into();
|
|
|
|
// Run a shell using the command param if wanted
|
|
let pargs = match self.shell {
|
|
None => args,
|
|
Some(shell) => {
|
|
let shell_args = match args {
|
|
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
|
|
None => vec!["-c".to_string(), program.to_string()],
|
|
};
|
|
program = shell.to_string();
|
|
Some(shell_args)
|
|
}
|
|
};
|
|
|
|
// Create command with the wanted options
|
|
let mut cmd = match pargs {
|
|
None => Command::new(program),
|
|
Some(args) => {
|
|
let mut cmd = Command::new(program);
|
|
cmd.args(args);
|
|
cmd
|
|
}
|
|
};
|
|
|
|
// Set dir to run in and env variables
|
|
if let Some(cwd) = self.cwd {
|
|
cmd.current_dir(cwd);
|
|
}
|
|
if !self.envs.is_empty() {
|
|
cmd.envs(self.envs);
|
|
}
|
|
|
|
cmd
|
|
}
|
|
}
|