use crate::{
Result,
core_lib::io::{File, map_io_err},
derive::*,
prelude::*,
};
use koto_memory::{Ptr, PtrMut};
use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use std::process;
macro_rules! stdio_setter {
($stream:ident, $ctx:expr) => {{
let this = $ctx.instance_mut()?;
let mut command = this.0.borrow_mut();
match $ctx.args {
[KValue::Str(s)] => match s.as_str() {
"inherit" => {
command.$stream(process::Stdio::inherit());
}
"null" => {
command.$stream(process::Stdio::null());
}
"piped" => {
command.$stream(process::Stdio::piped());
}
unexpected => {
return runtime_error!(
"Expected 'inherit', 'null', or 'piped', found '{unexpected}'"
);
}
},
unexpected => return unexpected_args("|String|", unexpected),
}
$ctx.instance_result()
}};
}
#[derive(Clone, Debug, KotoCopy, KotoType)]
pub struct Command(PtrMut<process::Command>);
#[koto_impl(runtime = crate)]
impl Command {
pub fn make_value(command: &str) -> KValue {
let command = make_ptr_mut!(process::Command::new(command));
KObject::from(Self(command)).into()
}
#[koto_method]
fn args(ctx: MethodContext<Self>) -> Result<KValue> {
let this = ctx.instance_mut()?;
let mut command = this.0.borrow_mut();
for arg in ctx.args {
match arg {
KValue::Str(arg) => command.arg(arg.as_str()),
unexpected => return unexpected_type("String as arg", unexpected),
};
}
ctx.instance_result()
}
#[koto_method]
fn current_dir(ctx: MethodContext<Self>) -> Result<KValue> {
let this = ctx.instance_mut()?;
let mut command = this.0.borrow_mut();
match ctx.args {
[KValue::Str(path)] => {
command.current_dir(path.as_str());
}
unexpected => return unexpected_args("|String|", unexpected),
}
ctx.instance_result()
}
#[koto_method]
fn env(ctx: MethodContext<Self>) -> Result<KValue> {
let this = ctx.instance_mut()?;
let mut command = this.0.borrow_mut();
match ctx.args {
[KValue::Str(key), KValue::Str(value)] => {
command.env(key.as_str(), value.as_str());
}
unexpected => return unexpected_args("|String, String|", unexpected),
}
ctx.instance_result()
}
#[koto_method]
fn env_clear(ctx: MethodContext<Self>) -> Result<KValue> {
ctx.instance_mut()?.0.borrow_mut().env_clear();
ctx.instance_result()
}
#[koto_method]
fn env_remove(ctx: MethodContext<Self>) -> Result<KValue> {
let this = ctx.instance_mut()?;
let mut command = this.0.borrow_mut();
match ctx.args {
[KValue::Str(key)] => {
command.env_remove(key.as_str());
}
unexpected => return unexpected_args("|String|", unexpected),
}
ctx.instance_result()
}
#[koto_method]
fn stdin(ctx: MethodContext<Self>) -> Result<KValue> {
stdio_setter!(stdin, ctx)
}
#[koto_method]
fn stdout(ctx: MethodContext<Self>) -> Result<KValue> {
stdio_setter!(stdout, ctx)
}
#[koto_method]
fn stderr(ctx: MethodContext<Self>) -> Result<KValue> {
stdio_setter!(stderr, ctx)
}
#[koto_method]
fn spawn(&mut self) -> Result<KValue> {
match self.0.borrow_mut().spawn() {
Ok(child) => Ok(Child::make_value(child)),
Err(error) => runtime_error!("{error}"),
}
}
#[koto_method]
fn wait_for_output(&mut self) -> Result<KValue> {
match self.0.borrow_mut().output() {
Ok(output) => Ok(CommandOutput::make_value(output)),
Err(error) => runtime_error!("{error}"),
}
}
#[koto_method]
fn wait_for_exit(&mut self) -> Result<KValue> {
match self.0.borrow_mut().status() {
Ok(status) => match status.code() {
Some(code) => Ok(code.into()),
None => Ok(KValue::Null),
},
Err(error) => runtime_error!("{error}"),
}
}
}
impl KotoObject for Command {
fn display(&self, ctx: &mut DisplayContext) -> Result<()> {
ctx.append(format!(
"Command('{}')",
self.0.borrow_mut().get_program().to_string_lossy()
));
Ok(())
}
}
#[derive(Clone, Debug, KotoCopy, KotoType)]
struct CommandOutput(process::Output);
#[koto_impl(runtime = crate)]
impl CommandOutput {
fn make_value(output: process::Output) -> KValue {
KObject::from(Self(output)).into()
}
#[koto_method]
fn exit_code(&self) -> KValue {
self.0.status.code().into()
}
#[koto_method]
fn success(&self) -> KValue {
self.0.status.success().into()
}
#[koto_method]
fn stdout(&self) -> KValue {
let bytes = self.0.stdout.clone();
String::from_utf8(bytes).ok().into()
}
#[koto_method]
fn stderr(&self) -> KValue {
let bytes = self.0.stderr.clone();
String::from_utf8(bytes).ok().into()
}
#[koto_method]
fn stdout_bytes(&self) -> Result<KValue> {
let bytes = self.0.stdout.clone().into();
let iterator = KIterator::with_bytes(bytes)?;
Ok(iterator.into())
}
#[koto_method]
fn stderr_bytes(&self) -> Result<KValue> {
let bytes = self.0.stderr.clone().into();
let iterator = KIterator::with_bytes(bytes)?;
Ok(iterator.into())
}
}
impl KotoObject for CommandOutput {}
#[derive(Clone, KotoCopy, KotoType)]
struct Child {
handle: PtrMut<Option<process::Child>>,
stdin: Option<(ChildStdin, KValue)>,
stdout: Option<(ChildStdout, KValue)>,
stderr: Option<(ChildStderr, KValue)>,
}
macro_rules! child_stream_fn {
($self:expr, $stream:ident, $buffer_wrapper:ident, $child_stream:ident) => {{
let mut this = $self.handle.borrow_mut();
let Some(child) = this.as_mut() else {
return runtime_error!("the process has already finished");
};
match child.$stream.take() {
Some(stream) => {
let stream = $child_stream(make_ptr_mut!(Some($buffer_wrapper::new(stream))));
let result = KValue::from(File::new(make_ptr!(stream.clone())));
$self.$stream = Some((stream, result.clone()));
Ok(result)
}
None => match $self.$stream.as_ref() {
Some((_, stream_value)) => Ok(stream_value.clone()),
None => runtime_error!("{} has already been captured", stringify!($stream)),
},
}
}};
}
#[koto_impl(runtime = crate)]
impl Child {
pub fn make_value(child: process::Child) -> KValue {
KObject::from(Self {
handle: make_ptr_mut!(Some(child)),
stdin: None,
stdout: None,
stderr: None,
})
.into()
}
#[koto_method]
fn id(&self) -> Result<KValue> {
let mut this = self.handle.borrow_mut();
let Some(child) = this.as_mut() else {
return runtime_error!("the process has already finished");
};
Ok(child.id().into())
}
#[koto_method]
fn stdin(&mut self) -> Result<KValue> {
child_stream_fn!(self, stdin, BufWriter, ChildStdin)
}
#[koto_method]
fn stdout(&mut self) -> Result<KValue> {
child_stream_fn!(self, stdout, BufReader, ChildStdout)
}
#[koto_method]
fn stderr(&mut self) -> Result<KValue> {
child_stream_fn!(self, stderr, BufReader, ChildStderr)
}
#[koto_method]
fn has_exited(&mut self) -> Result<KValue> {
let mut this = self.handle.borrow_mut();
let Some(child) = this.as_mut() else {
return Ok(true.into());
};
match child.try_wait() {
Ok(Some(_)) => Ok(true.into()),
Ok(None) => Ok(false.into()),
Err(error) => runtime_error!("{error}"),
}
}
#[koto_method]
fn kill(&mut self) -> Result<KValue> {
let mut this = self.handle.borrow_mut();
let Some(child) = this.as_mut() else {
return runtime_error!("the process has already finished");
};
match child.kill() {
Ok(_) => {
*this = None;
Ok(true.into())
}
Err(_) => Ok(false.into()),
}
}
#[koto_method]
fn wait_for_output(&mut self) -> Result<KValue> {
self.close_streams();
let Some(child) = self.handle.borrow_mut().take() else {
return runtime_error!("the process has already finished");
};
match child.wait_with_output() {
Ok(output) => Ok(CommandOutput::make_value(output)),
Err(error) => runtime_error!("{error}"),
}
}
#[koto_method]
fn wait_for_exit(&mut self) -> Result<KValue> {
self.close_streams();
let mut this = self.handle.borrow_mut();
let Some(child) = this.as_mut() else {
return runtime_error!("the process has already finished");
};
match child.wait() {
Ok(status) => match status.code() {
Some(code) => Ok(code.into()),
None => Ok(KValue::Null),
},
Err(error) => runtime_error!("{error}"),
}
}
fn close_streams(&mut self) {
if let Some((stream, _)) = self.stdin.take() {
*stream.0.borrow_mut() = None;
}
if let Some((stream, _)) = self.stdout.take() {
*stream.0.borrow_mut() = None;
}
if let Some((stream, _)) = self.stderr.take() {
*stream.0.borrow_mut() = None;
}
}
}
impl KotoObject for Child {}
#[derive(Clone)]
struct ChildStdin(PtrMut<Option<BufWriter<process::ChildStdin>>>);
impl KotoFile for ChildStdin {
fn id(&self) -> KString {
"_child_stdin_".into()
}
}
impl KotoRead for ChildStdin {}
impl KotoWrite for ChildStdin {
fn write(&self, bytes: &[u8]) -> Result<()> {
match self.0.borrow_mut().as_mut() {
Some(stream) => stream.write_all(bytes).map_err(map_io_err),
None => runtime_error!("the stream has been closed"),
}
}
fn write_line(&self, output: &str) -> Result<()> {
self.write(output.as_bytes())?;
match self.0.borrow_mut().as_mut() {
Some(stream) => stream.write_all("\n".as_bytes()).map_err(map_io_err),
None => runtime_error!("the stream has been closed"),
}
}
fn flush(&self) -> Result<()> {
match self.0.borrow_mut().as_mut() {
Some(stream) => stream.flush().map_err(map_io_err),
None => runtime_error!("the stream has been closed"),
}
}
}
#[derive(Clone)]
struct ChildStdout(PtrMut<Option<BufReader<process::ChildStdout>>>);
impl KotoFile for ChildStdout {
fn id(&self) -> KString {
"_child_stdout_".into()
}
}
impl KotoRead for ChildStdout {
fn read_line(&self) -> Result<Option<String>> {
let mut result = String::new();
let bytes_read = match self.0.borrow_mut().as_mut() {
Some(stream) => stream.read_line(&mut result).map_err(map_io_err)?,
None => return runtime_error!("the stream has been closed"),
};
if bytes_read > 0 {
Ok(Some(result))
} else {
Ok(None)
}
}
fn read_to_string(&self) -> Result<String> {
let mut result = String::new();
match self.0.borrow_mut().as_mut() {
Some(stream) => stream.read_to_string(&mut result).map_err(map_io_err)?,
None => return runtime_error!("the stream has been closed"),
};
Ok(result)
}
}
impl KotoWrite for ChildStdout {}
#[derive(Clone)]
struct ChildStderr(PtrMut<Option<BufReader<process::ChildStderr>>>);
impl KotoFile for ChildStderr {
fn id(&self) -> KString {
"_child_stderr_".into()
}
}
impl KotoRead for ChildStderr {
fn read_line(&self) -> Result<Option<String>> {
let mut result = String::new();
let bytes_read = match self.0.borrow_mut().as_mut() {
Some(stream) => stream.read_line(&mut result).map_err(map_io_err)?,
None => return runtime_error!("the stream has been closed"),
};
if bytes_read > 0 {
Ok(Some(result))
} else {
Ok(None)
}
}
fn read_to_string(&self) -> Result<String> {
let mut result = String::new();
match self.0.borrow_mut().as_mut() {
Some(stream) => stream.read_to_string(&mut result).map_err(map_io_err)?,
None => return runtime_error!("the stream has been closed"),
};
Ok(result)
}
}
impl KotoWrite for ChildStderr {}