diff --git a/src/lune/builtins/process/mod.rs b/src/lune/builtins/process/mod.rs index 212d325..98309c3 100644 --- a/src/lune/builtins/process/mod.rs +++ b/src/lune/builtins/process/mod.rs @@ -7,6 +7,7 @@ use std::{ use dunce::canonicalize; use mlua::prelude::*; use os_str_bytes::RawOsString; +use tokio::io::AsyncWriteExt; use crate::lune::{scheduler::Scheduler, util::TableBuilder}; @@ -199,17 +200,27 @@ async fn process_spawn( async fn spawn_command( program: String, args: Option>, - options: ProcessSpawnOptions, + mut options: ProcessSpawnOptions, ) -> LuaResult<(ExitStatus, Vec, Vec)> { let inherit_stdio = options.inherit_stdio; + let stdin = options.stdin.take(); - let child = options + let mut child = options .into_command(program, args) - .stdin(Stdio::null()) + .stdin(match stdin.is_some() { + true => Stdio::piped(), + false => Stdio::null(), + }) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; + // If the stdin option was provided, we write that to the child + if let Some(stdin) = stdin { + let mut child_stdin = child.stdin.take().unwrap(); + child_stdin.write_all(&stdin).await.into_lua_err()?; + } + if inherit_stdio { pipe_and_inherit_child_process_stdio(child).await } else { diff --git a/src/lune/builtins/process/options.rs b/src/lune/builtins/process/options.rs index 21a6264..4c4fc73 100644 --- a/src/lune/builtins/process/options.rs +++ b/src/lune/builtins/process/options.rs @@ -14,6 +14,7 @@ pub struct ProcessSpawnOptions { pub(crate) envs: HashMap, pub(crate) shell: Option, pub(crate) inherit_stdio: bool, + pub(crate) stdin: Option>, } impl<'lua> FromLua<'lua> for ProcessSpawnOptions { @@ -133,6 +134,20 @@ impl<'lua> FromLua<'lua> for ProcessSpawnOptions { } } + /* + If we have stdin contents, we need to pass those to the child process + */ + match value.get("stdin")? { + LuaValue::Nil => {} + LuaValue::String(s) => this.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) } } diff --git a/tests/process/spawn.luau b/tests/process/spawn.luau index 68691ae..1eda09d 100644 --- a/tests/process/spawn.luau +++ b/tests/process/spawn.luau @@ -160,3 +160,20 @@ assert( echoResult.stdout == (echoMessage .. trailingAddition), "Inheriting stdio did not return proper output" ) + +-- Passing stdin strings should work + +local stdinChild = process.spawn(not IS_WINDOWS and "xargs" or "powershell", { + "echo", +}, { + stdin = echoMessage .. (IS_WINDOWS and "\n\n" or ""), +}) + +local stdinChildOut = stdinChild.stdout +if IS_WINDOWS then + stdinChildOut = stdinChildOut:sub(#stdinChildOut - #echoMessage - 1, #stdinChildOut) +end +assert( + stdinChildOut == echoMessage .. trailingAddition, + "Stdin passing did not return proper output" +) diff --git a/types/process.luau b/types/process.luau index 3c89e62..131b802 100644 --- a/types/process.luau +++ b/types/process.luau @@ -13,12 +13,14 @@ export type SpawnOptionsStdio = "inherit" | "default" * `env` - Extra environment variables to give to the process * `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell * `stdio` - How to treat output and error streams from the child process - set to "inherit" to pass output and error streams to the current process + * `stdin` - Optional standard input to pass to spawned child process ]=] export type SpawnOptions = { cwd: string?, env: { [string]: string }?, shell: (boolean | string)?, stdio: SpawnOptionsStdio?, + stdin: string?, } --[=[