use std::{ fmt::Display, io::{BufRead, BufReader, Read, Write}, process::{ChildStderr, ChildStdin, ChildStdout, Command, Stdio}, sync::{Arc, Mutex}, }; use crate::{ data::{self, Data, MersData, MersType, Type}, errors::CheckError, program::{self, run::CheckInfo}, }; use super::Config; impl Config { /// adds utilities to run commands installed on the system and get their output. /// `run_command: fn` runs a command with arguments. /// Args: (cmd, args) where cmd is a string and args is an Iterable over strings /// `RunCommandError` holds the error if the command can't be executed /// returns (int/(), string, string) on success (status code, stdout, stderr) pub fn with_command_running(self) -> Self { // data::object::ObjectT(vec![("run_command_error".to_owned(), Type::new(data::string::StringT))]) // data::object::Object(vec![("run_command_error".to_owned(), Data::new(data::string::String(e.to_string())))]) self .add_type("ChildProcess".to_owned(), Ok(Arc::new(Type::new(ChildProcessT)))) .add_var( "run_command", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.types.iter().all(|t| t.as_any().downcast_ref::().is_some_and(|t| t.0.len() == 2 && t.0[0].is_included_in_single(&data::string::StringT) && t.0[1].iterable().is_some_and(|t| t.is_included_in_single(&data::string::StringT)))) { Ok(Type::newm(vec![ Arc::new(data::tuple::TupleT(vec![ Type::newm(vec![Arc::new(data::int::IntT), Arc::new(data::bool::TrueT), Arc::new(data::bool::FalseT)]), Type::new(data::string::StringT), Type::new(data::string::StringT), ])), Arc::new(data::object::ObjectT(vec![("run_command_error".to_owned(), Type::new(data::string::StringT))])) ])) } else { return Err(format!("run_command called with invalid arguments (must be (String, Iter))").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let cmd = a.as_any().downcast_ref::().unwrap(); let (cmd, args) = (&cmd.0[0], &cmd.0[1]); let cmd = cmd.get(); let (cmd, args) = ( cmd.as_any().downcast_ref::().unwrap(), args.get().iterable().unwrap(), ); let args = args.map(|v| v.map(|v| v.get().to_string())).collect::, _>>()?; match Command::new(&cmd.0) .args(args) .output() { Ok(output) => { let status = if let Some(code) = output.status.code() { Data::new(data::int::Int(code as _)) } else { Data::new(data::bool::Bool(output.status.success())) }; let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); Ok(Data::new(data::tuple::Tuple(vec![ status, Data::new(data::string::String(stdout)), Data::new(data::string::String(stderr)), ]))) } Err(e) => Ok(Data::new(data::object::Object(vec![("run_command_error".to_owned(), Data::new(data::string::String(e.to_string())))]))), } }), inner_statements: None, }, ) .add_var( "spawn_command", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.types.iter().all(|t| t.as_any().downcast_ref::().is_some_and(|t| t.0.len() == 2 && t.0[0].is_included_in_single(&data::string::StringT) && t.0[1].iterable().is_some_and(|t| t.is_included_in_single(&data::string::StringT)))) { Ok(Type::newm(vec![ Arc::new(ChildProcessT), Arc::new(data::object::ObjectT(vec![("run_command_error".to_owned(), Type::new(data::string::StringT))])) ])) } else { return Err(format!("spawn_command called with invalid arguments (must be (String, Iter))").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let cmd = a.as_any().downcast_ref::().unwrap(); let (cmd, args) = (&cmd.0[0], &cmd.0[1]); let cmd = cmd.get(); let (cmd, args) = ( cmd.as_any().downcast_ref::().unwrap(), args.get().iterable().unwrap(), ); let args = args.map(|v| v.map(|v| v.get().to_string())).collect::, _>>()?; match Command::new(&cmd.0) .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() { Ok(mut child) => { let a = Some(child.stdin.take().unwrap()); let b = BufReader::new(child.stdout.take().unwrap()); let c = BufReader::new(child.stderr.take().unwrap()); Ok(Data::new(ChildProcess(Arc::new(Mutex::new((child, a, b, c)))))) } Err(e) => Ok(Data::new(data::object::Object(vec![("run_command_error".to_owned(), Data::new(data::string::String(e.to_string())))]))), } }), inner_statements: None, }, ) .add_var( "childproc_exited", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.is_included_in_single(&ChildProcessT) { Ok(Type::newm(vec![ Arc::new(data::tuple::TupleT(vec![data::bool::bool_type()])), Arc::new(data::tuple::TupleT(vec![])), ])) } else { return Err(format!("childproc_exited called on non-ChildProcess type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let child = a.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); Ok(match child.0.try_wait() { Ok(Some(_)) => Data::one_tuple(Data::new(data::bool::Bool(true))), Ok(None) => Data::one_tuple(Data::new(data::bool::Bool(false))), Err(_) => Data::empty_tuple(), }) }), inner_statements: None, }, ) .add_var( "childproc_await", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.is_included_in_single(&ChildProcessT) { Ok(Type::newm(vec![ Arc::new(data::int::IntT), Arc::new(data::bool::TrueT), Arc::new(data::bool::FalseT), Arc::new(data::tuple::TupleT(vec![])), ])) } else { return Err(format!("childproc_await called on non-ChildProcess type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let child = a.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); drop(child.1.take()); Ok(match child.0.wait() { Ok(s) => if let Some(s) = s.code() { Data::new(data::int::Int(s as _)) } else { Data::new(data::bool::Bool(s.success())) } Err(_) => Data::empty_tuple(), }) }), inner_statements: None, }, ) .add_var( "childproc_write_bytes", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.types.iter().all(|a| a.as_any().downcast_ref::().is_some_and(|t| t.0.len() == 2 && t.0[0].is_included_in_single(&ChildProcessT) && t.0[1].iterable().is_some_and(|i| i.is_included_in_single(&data::byte::ByteT)))) { Ok(data::bool::bool_type()) } else { return Err(format!("childproc_write_bytes called on non-`(ChildProcess, Iter)` type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let tuple = a.as_any().downcast_ref::().unwrap(); let child = tuple.0[0].get(); let bytes = tuple.0[1].get(); let child = child.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); let buf = bytes.iterable().unwrap().map(|v| v.map(|v| v.get().as_any().downcast_ref::().unwrap().0)).collect::, _>>()?; Ok(if child.1.as_mut().is_some_and(|v| v.write_all(&buf).is_ok() && v.flush().is_ok()) { Data::new(data::bool::Bool(true)) } else { Data::new(data::bool::Bool(false)) }) }), inner_statements: None, }, ) .add_var( "childproc_write_string", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.is_included_in_single(&data::tuple::TupleT(vec![Type::new(ChildProcessT), Type::new(data::string::StringT)])) { Ok(data::bool::bool_type()) } else { return Err(format!("childproc_write_string called on non-`(ChildProcess, String)` type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let tuple = a.as_any().downcast_ref::().unwrap(); let child = tuple.0[0].get(); let string = tuple.0[1].get(); let child = child.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); let buf = string.as_any().downcast_ref::().unwrap().0.as_bytes(); Ok(if child.1.as_mut().is_some_and(|v| v.write_all(buf).is_ok() && v.flush().is_ok()) { Data::new(data::bool::Bool(true)) } else { Data::new(data::bool::Bool(false)) }) }), inner_statements: None, }, ) .add_var( "childproc_read_byte", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.is_included_in_single(&ChildProcessT) { Ok(Type::newm(vec![ Arc::new(data::tuple::TupleT(vec![Type::new(data::byte::ByteT)])), Arc::new(data::tuple::TupleT(vec![])), ])) } else { return Err(format!("childproc_read_byte called on non-ChildProcess type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let child = a.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); let mut buf = [0]; Ok(if child.2.read_exact(&mut buf).is_ok() { Data::one_tuple(Data::new(data::byte::Byte(buf[0]))) } else { Data::empty_tuple() }) }), inner_statements: None, }, ) .add_var( "childproc_readerr_byte", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.is_included_in_single(&ChildProcessT) { Ok(Type::newm(vec![ Arc::new(data::tuple::TupleT(vec![Type::new(data::byte::ByteT)])), Arc::new(data::tuple::TupleT(vec![])), ])) } else { return Err(format!("childproc_readerr_byte called on non-ChildProcess type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let child = a.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); let mut buf = [0]; Ok(if child.3.read_exact(&mut buf).is_ok() { Data::one_tuple(Data::new(data::byte::Byte(buf[0]))) } else { Data::empty_tuple() }) }), inner_statements: None, }, ) .add_var( "childproc_read_line", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.is_included_in_single(&ChildProcessT) { Ok(Type::newm(vec![ Arc::new(data::tuple::TupleT(vec![Type::new(data::string::StringT)])), Arc::new(data::tuple::TupleT(vec![])), ])) } else { return Err(format!("childproc_read_line called on non-ChildProcess type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let child = a.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); let mut buf = String::new(); Ok(if child.2.read_line(&mut buf).is_ok() { Data::one_tuple(Data::new(data::string::String(buf))) } else { Data::empty_tuple() }) }), inner_statements: None, }, ) .add_var( "childproc_readerr_line", data::function::Function { info: program::run::Info::neverused(), info_check: Arc::new(Mutex::new( CheckInfo::neverused())), out: Ok(Arc::new(|a, _i| { if a.is_included_in_single(&ChildProcessT) { Ok(Type::newm(vec![ Arc::new(data::tuple::TupleT(vec![Type::new(data::string::StringT)])), Arc::new(data::tuple::TupleT(vec![])), ])) } else { return Err(format!("childproc_read_line called on non-ChildProcess type {a}").into()); } })), run: Arc::new(|a, _i| { let a = a.get(); let child = a.as_any().downcast_ref::().unwrap(); let mut child = child.0.lock().unwrap(); let mut buf = String::new(); Ok(if child.3.read_line(&mut buf).is_ok() { Data::one_tuple(Data::new(data::string::String(buf))) } else { Data::empty_tuple() }) }), inner_statements: None, }, ) } } #[derive(Clone, Debug)] pub struct ChildProcess( Arc< Mutex<( std::process::Child, Option, BufReader, BufReader, )>, >, ); #[derive(Clone, Debug)] pub struct ChildProcessT; impl MersData for ChildProcess { fn iterable(&self) -> Option>>> { None } fn get(&self, _i: usize) -> Option> { None } fn is_eq(&self, other: &dyn MersData) -> bool { other .as_any() .downcast_ref::() .is_some_and(|other| Arc::ptr_eq(&self.0, &other.0)) } fn clone(&self) -> Box { Box::new(Clone::clone(self)) } fn as_type(&self) -> Type { Type::new(ChildProcessT) } fn as_any(&self) -> &dyn std::any::Any { self } fn mut_any(&mut self) -> &mut dyn std::any::Any { self } fn to_any(self) -> Box { Box::new(self) } } impl Display for ChildProcess { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "") } } impl MersType for ChildProcessT { fn iterable(&self) -> Option { None } fn get(&self) -> Option { None } fn is_same_type_as(&self, other: &dyn MersType) -> bool { other.as_any().is::() } fn is_included_in(&self, target: &dyn MersType) -> bool { target.as_any().is::() } fn subtypes(&self, acc: &mut Type) { acc.add(Arc::new(self.clone())); } fn as_any(&self) -> &dyn std::any::Any { self } fn mut_any(&mut self) -> &mut dyn std::any::Any { self } fn to_any(self) -> Box { Box::new(self) } } impl Display for ChildProcessT { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "ChildProcess") } }