use std::{process::{exit, Command, Stdio, Child}, env, rc::Rc, io::{Read, self}, cell::RefCell, fs::{File, self}, os::fd::FromRawFd, sync::OnceLock, path::PathBuf}; use matrix_lang::prelude::*; use matrix_macros::native_func; use os_info::Info; use crate::{VmArgs, error, unpack_args}; #[native_func(1)] fn sys_exit(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [value] = unpack_args!(args); let Value::Int(i) = value else { return error!("exit requires a int exit code") }; exit(i as i32); } #[native_func(0)] fn argv(_: VmArgs, _: Vec<Value>) -> Result<Value> { Ok(Value::List( Gc::new( env::args() .map(|a| Value::String(Rc::from(a.as_str()))) .collect() ))) } #[native_func(1)] fn env(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [value] = unpack_args!(args); let Value::String(value) = value else { return error!("env requires a string name") }; match std::env::var(value.as_ref()) { Ok(v) => Ok(Value::String(v.into())), Err(e) => error!("couldn't read env var: {e}") } } fn exec_impl(cmd: io::Result<Child>) -> Result<Value> { let mut child = match cmd { Ok(c) => c, Err(e) => return error!("error executing command: {e}") }; let status = match child.wait() { Ok(s) => s, Err(e) => return error!("error executing command: {e}") }; let stdout = match child.stdout { Some(ref mut out) => { let mut buf = String::new(); let _ = out.read_to_string(&mut buf); buf }, None => String::new() }; let stderr = match child.stderr { Some(ref mut err) => { let mut buf = String::new(); let _ = err.read_to_string(&mut buf); buf }, None => String::new() }; let mut res = ValueMap::new(); res.insert(Value::from("success"), Value::Bool(status.success()))?; res.insert(Value::from("code"), Value::Int(status.code().unwrap_or(0) as i64))?; res.insert(Value::from("out"), Value::String(stdout.into()))?; res.insert(Value::from("err"), Value::String(stderr.into()))?; Ok(Value::Table(res.into())) } #[native_func(2)] fn exec(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [cmd, args] = unpack_args!(args); let (cmd, args) = match (cmd, args) { (Value::String(s), Value::List(l)) => (s, l.into_inner()), _ => return error!("exec requires a string cmd and string argument list") }; let mut sargs = Vec::new(); for arg in args { let Value::String(arg) = arg else { return error!("exec requires a string cmd and string argument list") }; sargs.push(arg.to_string()); }; let cmd = Command::new(cmd.to_string()) .args(sargs) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn(); exec_impl(cmd) } #[native_func(1)] fn system(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [cmd] = unpack_args!(args); let Value::String(cmd) = cmd else { return error!("system requires a full command argument string") }; let sh = String::from("/bin/sh"); let args = vec!["-c".to_string(), cmd.to_string()]; let cmd = Command::new(sh) .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn(); exec_impl(cmd) } #[native_func(1)] fn systemi(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [cmd] = unpack_args!(args); let Value::String(cmd) = cmd else { return error!("systemi requires a full command argument string") }; let sh = String::from("/bin/sh"); let args = vec!["-c".to_string(), cmd.to_string()]; let cmd = Command::new(sh) .args(args) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .spawn(); exec_impl(cmd) } fn stdin() -> Value { let f = unsafe { File::from_raw_fd(0) }; Value::File(Rc::new(RefCell::new(f))) } fn stdout() -> Value { let f = unsafe { File::from_raw_fd(1) }; Value::File(Rc::new(RefCell::new(f))) } fn stderr() -> Value { let f = unsafe { File::from_raw_fd(2) }; Value::File(Rc::new(RefCell::new(f))) } const OS_INFO: OnceLock<Info> = OnceLock::new(); #[native_func(0)] fn os_type(_: VmArgs, _: Vec<Value>) -> Result<Value> { Ok(Value::String(OS_INFO.get_or_init(|| os_info::get()).os_type().to_string().into())) } #[native_func(0)] fn os_version(_: VmArgs, _: Vec<Value>) -> Result<Value> { Ok(Value::String(OS_INFO.get_or_init(|| os_info::get()).version().to_string().into())) } #[native_func(0)] fn os_edition(_: VmArgs, _: Vec<Value>) -> Result<Value> { Ok(Value::String(OS_INFO.get_or_init(|| os_info::get()).edition().unwrap_or("Unknown").into())) } #[native_func(0)] fn os_bitness(_: VmArgs, _: Vec<Value>) -> Result<Value> { Ok(Value::String(OS_INFO.get_or_init(|| os_info::get()).bitness().to_string().into())) } #[native_func(0)] fn os_arch(_: VmArgs, _: Vec<Value>) -> Result<Value> { Ok(Value::String(OS_INFO.get_or_init(|| os_info::get()).architecture().unwrap_or("Unknown").into())) } #[native_func(0)] fn cwd(_: VmArgs, _: Vec<Value>) -> Result<Value> { match env::current_dir() { Ok(v) => Ok(Value::String(v.into_os_string().into_string().unwrap_or(String::new()).into())), Err(e) => error!("cant get cwd: {e}") } } #[native_func(1)] fn basename(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [value] = unpack_args!(args); let Value::String(value) = value else { return error!("basename requires a string path") }; let path = PathBuf::from(value.to_string()); match path.file_name() { Some(p) => Ok(Value::String(p.to_str().unwrap().into())), None => Ok(Value::String(value.into())) } } #[native_func(1)] fn dirname(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [value] = unpack_args!(args); let Value::String(value) = value else { return error!("basename requires a string path") }; let path = PathBuf::from(value.to_string()); let parent = match path.parent() { Some(p) => p, None => path.as_path() }; let str = parent.as_os_str().to_str().unwrap(); match str { "" => Ok(Value::String(".".into())), s => Ok(Value::String(s.into())) } } #[native_func(1)] fn realpath(_: VmArgs, args: Vec<Value>) -> Result<Value> { let [value] = unpack_args!(args); let Value::String(value) = value else { return error!("basename requires a string path") }; let path = match fs::canonicalize(value.as_ref()) { Ok(p) => p, Err(e) => return error!("could not get realpath: {e}") }; Ok(Value::String(path.to_str().unwrap().into())) } pub fn load(vm: &mut Vm) { vm.load_global_fn(sys_exit(), "exit"); vm.load_global_fn(argv(), "argv"); vm.load_global_fn(exec(), "exec"); vm.load_global_fn(system(), "system"); vm.load_global_fn(systemi(), "systemi"); vm.load_global_fn(env(), "env"); vm.load_global(stdin(), "stdin"); vm.load_global(stdout(), "stdout"); vm.load_global(stderr(), "stderr"); vm.load_global_fn(os_type(), "os_type"); vm.load_global_fn(os_version(), "os_version"); vm.load_global_fn(os_edition(), "os_edition"); vm.load_global_fn(os_bitness(), "os_bitness"); vm.load_global_fn(os_arch(), "os_arch"); vm.load_global_fn(cwd(), "cwd"); vm.load_global_fn(basename(), "basename"); vm.load_global_fn(dirname(), "dirname"); vm.load_global_fn(realpath(), "realpath"); }