use std::{ops::DerefMut, time::Duration}; use futures::future::try_join3; use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, Value}; use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select}; use super::Status; use crate::{Error, process::Output}; pub struct Child { inner: tokio::process::Child, stdin: Option>, stdout: Option>, stderr: Option>, } impl Child { pub fn new(mut inner: tokio::process::Child) -> Self { let stdin = inner.stdin.take().map(BufWriter::new); let stdout = inner.stdout.take().map(BufReader::new); let stderr = inner.stderr.take().map(BufReader::new); Self { inner, stdin, stdout, stderr } } } impl UserData for Child { fn add_methods>(methods: &mut M) { #[inline] async fn read_line(me: &mut Child) -> (Option>, u8) { async fn read(r: Option) -> Option> { let mut buf = Vec::new(); match r?.read_until(b'\n', &mut buf).await { Ok(0) | Err(_) => None, Ok(_) => Some(buf), } } select! { r @ Some(_) = read(me.stdout.as_mut()) => (r, 0u8), r @ Some(_) = read(me.stderr.as_mut()) => (r, 1u8), else => (None, 2u8), } } methods.add_async_method_mut("read", |_, mut me, len: usize| async move { async fn read(r: Option, len: usize) -> Option> { let mut r = r?; let mut buf = vec![0; len]; match r.read(&mut buf).await { Ok(0) | Err(_) => return None, Ok(n) => buf.truncate(n), } Some(buf) } let me = me.deref_mut(); Ok(select! { Some(r) = read(me.stdout.as_mut(), len) => (r, 0u8), Some(r) = read(me.stderr.as_mut(), len) => (r, 1u8), else => (vec![], 2u8) }) }); methods.add_async_method_mut("read_line", |lua, mut me, ()| async move { match read_line(&mut me).await { (Some(b), event) => (lua.create_string(b)?, event).into_lua_multi(&lua), (None, event) => (Value::Nil, event).into_lua_multi(&lua), } }); methods.add_async_method_mut("read_line_with", |lua, mut me, options: Table| async move { let timeout = Duration::from_millis(options.raw_get("timeout")?); let Ok(result) = tokio::time::timeout(timeout, read_line(&mut me)).await else { return (Value::Nil, 3u8).into_lua_multi(&lua); }; match result { (Some(b), event) => (lua.create_string(b)?, event).into_lua_multi(&lua), (None, event) => (Value::Nil, event).into_lua_multi(&lua), } }); methods.add_async_method_mut("write_all", |lua, mut me, src: mlua::String| async move { let Some(stdin) = &mut me.stdin else { return Err("stdin is not piped".into_lua_err()); }; match stdin.write_all(&src.as_bytes()).await { Ok(()) => (true, Value::Nil).into_lua_multi(&lua), Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), } }); methods.add_async_method_mut("flush", |lua, mut me, ()| async move { let Some(stdin) = &mut me.stdin else { return Err("stdin is not piped".into_lua_err()); }; match stdin.flush().await { Ok(()) => (true, Value::Nil).into_lua_multi(&lua), Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), } }); methods.add_async_method_mut("wait", |lua, mut me, ()| async move { drop(me.stdin.take()); match me.inner.wait().await { Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), } }); methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move { async fn read_to_end(r: &mut Option) -> io::Result> { let mut vec = Vec::new(); if let Some(r) = r.as_mut() { r.read_to_end(&mut vec).await?; } Ok(vec) } let mut me = ud.take::()?; let mut stdout_pipe = me.stdout.take(); let mut stderr_pipe = me.stderr.take(); let stdout_fut = read_to_end(&mut stdout_pipe); let stderr_fut = read_to_end(&mut stderr_pipe); drop(me.stdin.take()); let result = try_join3(me.inner.wait(), stdout_fut, stderr_fut).await; drop(stdout_pipe); drop(stderr_pipe); match result { Ok((status, stdout, stderr)) => { (Output::new(std::process::Output { status, stdout, stderr }), Value::Nil) .into_lua_multi(&lua) } Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), } }); methods.add_method_mut("start_kill", |lua, me, ()| match me.inner.start_kill() { Ok(_) => (true, Value::Nil).into_lua_multi(lua), Err(e) => (false, Error::Io(e)).into_lua_multi(lua), }); methods.add_method_mut("take_stdin", |lua, me, ()| match me.stdin.take() { Some(stdin) => lua.create_any_userdata(stdin.into_inner())?.into_lua(lua), None => Ok(Value::Nil), }); methods.add_method_mut("take_stdout", |lua, me, ()| match me.stdout.take() { Some(stdout) => lua.create_any_userdata(stdout.into_inner())?.into_lua(lua), None => Ok(Value::Nil), }); methods.add_method_mut("take_stderr", |lua, me, ()| match me.stderr.take() { Some(stderr) => lua.create_any_userdata(stderr.into_inner())?.into_lua(lua), None => Ok(Value::Nil), }); } }