mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Clean up process spawn options
This commit is contained in:
parent
1baf10fa87
commit
6db01d7b94
2 changed files with 186 additions and 118 deletions
|
@ -1,15 +1,12 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
env::{self, consts},
|
||||
path::{self, PathBuf},
|
||||
path,
|
||||
process::Stdio,
|
||||
};
|
||||
|
||||
use directories::UserDirs;
|
||||
use dunce::canonicalize;
|
||||
use mlua::prelude::*;
|
||||
use os_str_bytes::RawOsString;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::lune::{scheduler::Scheduler, util::TableBuilder};
|
||||
|
||||
|
@ -18,6 +15,9 @@ mod tee_writer;
|
|||
mod pipe_inherit;
|
||||
use pipe_inherit::pipe_and_inherit_child_process_stdio;
|
||||
|
||||
mod options;
|
||||
use options::ProcessSpawnOptions;
|
||||
|
||||
const PROCESS_EXIT_IMPL_LUA: &str = r#"
|
||||
exit(...)
|
||||
yield()
|
||||
|
@ -161,127 +161,18 @@ fn process_env_iter<'lua>(
|
|||
|
||||
async fn process_spawn<'lua>(
|
||||
lua: &'static Lua,
|
||||
(mut program, args, options): (String, Option<Vec<String>>, Option<LuaTable<'lua>>),
|
||||
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
||||
) -> LuaResult<LuaTable<'lua>> {
|
||||
// Parse any given options or create defaults
|
||||
let (child_cwd, child_envs, child_shell, child_stdio_inherit) = match options {
|
||||
Some(options) => {
|
||||
let mut cwd = env::current_dir()?;
|
||||
let mut envs = HashMap::new();
|
||||
let mut shell = None;
|
||||
let mut inherit = false;
|
||||
match options.raw_get("cwd")? {
|
||||
LuaValue::Nil => {}
|
||||
LuaValue::String(s) => {
|
||||
cwd = PathBuf::from(s.to_string_lossy().to_string());
|
||||
// Substitute leading tilde (~) for the actual home dir
|
||||
if cwd.starts_with("~") {
|
||||
if let Some(user_dirs) = UserDirs::new() {
|
||||
cwd = user_dirs.home_dir().join(cwd.strip_prefix("~").unwrap())
|
||||
}
|
||||
};
|
||||
if !cwd.exists() {
|
||||
return Err(LuaError::RuntimeError(
|
||||
"Invalid value for option 'cwd' - path does not exist".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
value => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Invalid type for option 'cwd' - expected 'string', got '{}'",
|
||||
value.type_name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
match options.raw_get("env")? {
|
||||
LuaValue::Nil => {}
|
||||
LuaValue::Table(t) => {
|
||||
for pair in t.pairs::<String, String>() {
|
||||
let (k, v) = pair?;
|
||||
envs.insert(k, v);
|
||||
}
|
||||
}
|
||||
value => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Invalid type for option 'env' - expected 'table', got '{}'",
|
||||
value.type_name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
match options.raw_get("shell")? {
|
||||
LuaValue::Nil => {}
|
||||
LuaValue::String(s) => shell = Some(s.to_string_lossy().to_string()),
|
||||
LuaValue::Boolean(true) => {
|
||||
shell = match env::consts::FAMILY {
|
||||
"unix" => Some("/bin/sh".to_string()),
|
||||
"windows" => Some("/bin/sh".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
value => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Invalid type for option 'shell' - expected 'true' or 'string', got '{}'",
|
||||
value.type_name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
match options.raw_get("stdio")? {
|
||||
LuaValue::Nil => {}
|
||||
LuaValue::String(s) => {
|
||||
match s.to_str()? {
|
||||
"inherit" => {
|
||||
inherit = true;
|
||||
},
|
||||
"default" => {
|
||||
inherit = false;
|
||||
}
|
||||
_ => return Err(LuaError::RuntimeError(
|
||||
format!("Invalid value for option 'stdio' - expected 'inherit' or 'default', got '{}'", s.to_string_lossy()),
|
||||
))
|
||||
}
|
||||
}
|
||||
value => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Invalid type for option 'stdio' - expected 'string', got '{}'",
|
||||
value.type_name()
|
||||
)))
|
||||
}
|
||||
}
|
||||
Ok::<_, LuaError>((cwd, envs, shell, inherit))
|
||||
}
|
||||
None => Ok((env::current_dir()?, HashMap::new(), None, false)),
|
||||
}?;
|
||||
// Run a shell using the command param if wanted
|
||||
let child_args = if let Some(shell) = child_shell {
|
||||
let shell_args = match args {
|
||||
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
|
||||
None => vec!["-c".to_string(), program],
|
||||
};
|
||||
program = shell;
|
||||
Some(shell_args)
|
||||
} else {
|
||||
args
|
||||
};
|
||||
// Create command with the wanted options
|
||||
let mut cmd = match child_args {
|
||||
None => Command::new(program),
|
||||
Some(args) => {
|
||||
let mut cmd = Command::new(program);
|
||||
cmd.args(args);
|
||||
cmd
|
||||
}
|
||||
};
|
||||
// Set dir to run in and env variables
|
||||
cmd.current_dir(child_cwd);
|
||||
cmd.envs(child_envs);
|
||||
let inherit_stdio = options.inherit_stdio;
|
||||
// Spawn the child process
|
||||
let child = cmd
|
||||
let child = options
|
||||
.into_command(program, args)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
// Inherit the output and stderr if wanted
|
||||
let result = if child_stdio_inherit {
|
||||
let result = if inherit_stdio {
|
||||
pipe_and_inherit_child_process_stdio(child).await
|
||||
} else {
|
||||
let output = child.wait_with_output().await?;
|
||||
|
|
177
src/lune/builtins/process/options.rs
Normal file
177
src/lune/builtins/process/options.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
env::{self},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use directories::UserDirs;
|
||||
use mlua::prelude::*;
|
||||
use tokio::process::Command;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProcessSpawnOptions {
|
||||
pub(crate) cwd: Option<PathBuf>,
|
||||
pub(crate) envs: HashMap<String, String>,
|
||||
pub(crate) shell: Option<String>,
|
||||
pub(crate) inherit_stdio: bool,
|
||||
}
|
||||
|
||||
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("/bin/sh".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, make sure its one of the constant values
|
||||
*/
|
||||
match value.get("stdio")? {
|
||||
LuaValue::Nil => {}
|
||||
LuaValue::String(s) => match s.to_str()? {
|
||||
"inherit" => this.inherit_stdio = true,
|
||||
"default" => this.inherit_stdio = false,
|
||||
_ => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Invalid value for option 'stdio' - expected 'inherit' or 'default', got '{}'",
|
||||
s.to_string_lossy()
|
||||
)))
|
||||
}
|
||||
},
|
||||
value => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Invalid type for option 'stdio' - 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue